0

I was developing an Image processing library in Javascript and was wondering what is the algorithm for achieving the "cross-process" effect

Sort of like this

Sort of like this

Jay Ghosh
  • 674
  • 6
  • 24
  • Why downvote? I have researched but couldn't find any algorithms. That's why I'm asking – Jay Ghosh Aug 17 '16 at 09:46
  • This is just **curves** affecting intensities of red, green and blue channels of each pixel. Find a JS library that allows you to use arrays for curves. Practice your curves on an image editor and recreate the numbers in the arrays (256 slots per array, one array per colour channel) or write code that updates the values according to some calculation. – VC.One Aug 18 '16 at 17:12

1 Answers1

4

I based my script on http://photographypla.net/cross-processed-lightroom/

I did the basic channel correction using the a remapping of the colors according to a segmoid (for the red and green channel) and a double exponential for the blue channel. Those function i took from http://www.flong.com/texts/code/shapers_exp/.

The image after the basic correction looks like this: enter image description here

You can play with this results by the changing the params sFactor1 and sFactor2.

After that i lowered the total contrast and did some local histogram enhancement but i recommend you not to use this part and search for good implementations for highlights shadows and white and black adjustment.

The final result:

enter image description here

The code:

import cv2
import numpy as np
import math

# Define an S shape segmoid that with controlled shape. Based on http://www.flong.com/texts/code/shapers_exp/

# Function for sigmoid creation with s shape facor
def doubleExponentialSigmoid(x, a):

    epsilon = 0.00001
    min_param_a = 0.0 + epsilon
    max_param_a = 1.0 - epsilon
    a = min(max_param_a, max(min_param_a, a))
    a = 1.0 - a # for sensible results
    y = 0
    if x <= 0.5:
        y = (math.pow(2.0 * x, 1.0 / a)) / 2.0
    else:
        y = 1.0 - (pow(2.0 * (1.0-x), 1.0 / a)) / 2.0
    return y

# Function for reverse sigmoid creation with reverse s shape facor
def doubleExponentialSeat(x,a):

    epsilon = 0.00001
    min_param_a = 0.0 + epsilon
    max_param_a = 1.0 - epsilon
    a = min(max_param_a, max(min_param_a, a))
    y = 0
    if x <= 0.5:
        y = (math.pow(2.0*x, 1-a))/2.0;
    else:
        y = 1.0 - (math.pow(2.0*(1.0-x), 1-a))/2.0
    return y

# Function for s shape function creation
def getSigmoidLut(sFactor,reverseShape=False):
    rangeOfValues = np.arange(0, 1+(float(1) / float(255)), float(1) / float(255))
    index = 0
    sigmoidLUT = np.zeros_like(rangeOfValues)
    if reverseShape:
        for v in rangeOfValues:
            sigmoidLUT[index] = doubleExponentialSeat(v, sFactor)
            index = index + 1
    else:
        for v in rangeOfValues:
            sigmoidLUT[index] = doubleExponentialSigmoid(v, sFactor)
            index = index + 1

    return sigmoidLUT

# A function to map one range to another
def RangeMapping(currentMin,currentMax,newMin,newMax):

    newRange = np.zeros((256,1))
    for v in range(256):
        newRange[v] = (((v - currentMin) * (newMax - newMin)) / (currentMax - currentMin)) + newMin

    return newRange

# Function to lower contrast by a factor
def LowerContrast(intensityChannel, factor):

    # Second chane the contrast by the factor
    mappingLUT = RangeMapping(np.min(intensityChannel),np.max(intensityChannel),np.round(np.min(intensityChannel)*factor),np.round(np.max(intensityChannel)/factor))
    newIntensity = cv2.LUT(intensityChannel,mappingLUT)

    return newIntensity

# This cross processing is based on the tutorial in http://photographypla.net/cross-processed-lightroom/

# Params
sFactor1 = 0.7
sFactor2 = 0.3
lowContrastFactor = 1.05

# Read image
I = cv2.imread('im.jpg')

# Step 1: Separate to the three channels
R,G,B = cv2.split(I)

# Step 2: Map to a S curve each channel

# Get a S shaped segmoid
redChannelLUT = np.round(getSigmoidLut(sFactor1,False)*255).astype(np.uint8)
greenChannelLUT = redChannelLUT
blueChannelLUT =np.round(getSigmoidLut(sFactor2,True)*255).astype(np.uint8)

# Apply correction
redChannelCorrection = cv2.LUT(R, redChannelLUT)
greenChannelCorrection = cv2.LUT(G, greenChannelLUT)
blueChannelCorrection = cv2.LUT(B, blueChannelLUT)

# Step 3: Merge corrected channels
ICorrection = cv2.merge((redChannelCorrection,greenChannelCorrection,blueChannelCorrection))

# From here you can do whatever you want to the colors shadows highlights etc...
# Separate color and intensity
Iycr = cv2.cvtColor(ICorrection,cv2.COLOR_RGB2YCR_CB)
intensityCh,C,R = cv2.split(Iycr)

# Step 4: lower contrast
newLowerIntensityContrast = LowerContrast(intensityCh,lowContrastFactor)

# Step 5: Local contrast enhacment
clahe = cv2.createCLAHE(clipLimit=1.0, tileGridSize=(8,8))
ICorrectedShadows = clahe.apply(newLowerIntensityContrast.astype(np.uint8))

# Final step re construct image
IycrLowContrast = cv2.merge((ICorrectedShadows,C,R))
finalImage = cv2.cvtColor(IycrLowContrast,cv2.COLOR_YCrCb2RGB)

cv2.imshow('Original',I)
cv2.imshow('ColorCorrection',ICorrection)
cv2.imshow('LowContrast',newLowerIntensityContrast.astype(np.uint8))
cv2.imshow('Final',finalImage)
Amitay Nachmani
  • 3,259
  • 1
  • 18
  • 21