javascriptimage-processingimage-manipulationcross-process

Cross Processing Algorithm (Image manipulation)


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


Solution

  • 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)