Python implementation of shadow brightening effect in PS shadow/highlight

The Shadows/Highlights command is a way to correct photos that are silhouetted by strong backlighting, or correct focus that is a little whitish due to being too close to the camera flash. In images lit in other ways, this adjustment can also be used to brighten shadowed areas. Instead of simply brightening or darkening an image, the Shadows/Highlights command brightens or darkens based on surrounding pixels (locally adjacent pixels) in the shadows or highlights. Because of this, shadows and highlights have their own controls. The default is set to fix images with backlighting issues, from Adobe's official website .

Shadow :
It is mainly used to brighten the shadow area, and has almost no effect on the non-shadow area
Amount : It is used to control the degree of correction of the shadow area, the larger the value, the brighter the shadow area is lifted
Tone : Control the brightened shadow area A smaller value will limit the brightening to only darker areas, a larger value will affect a wider range of shadows
Radius : Controls the size of local neighboring pixels around each pixel

Highlight:
It is mainly used to darken the highlight area, and has almost no effect on the non-highlight area
Amount : Used to control the degree of correction of the highlight area, the larger the value, the darker the highlight area is suppressed Tone
: Control the darkened highlight area A smaller value will limit the darkening to only brighter areas, a larger value will affect a wider range of highlights Radius
: Controls the size of local neighboring pixels around each pixel

Adjustment:
Color : Positive values ​​increase the saturation of brightened pixels in the shadow area and darkened pixels in the highlight area, negative values ​​decrease saturation
Midtone : used to adjust the contrast of the midtone, negative values ​​reduce contrast, and positive values ​​increase Contrast
Trim Black : Set how much of the shadows in the image are turned into pure black
Trim White : Set what proportion of the highlights in the image are turned into pure white

insert image description here

Python implements shadow area extraction and shadow area brightening

1. Extract the shadow area

Automatically advance the shadow area, for details, see my other blog "Python uses OpenCV to realize PS highlight/shadow selection"

Shadow area extraction:
first convert the original image into a grayscale image, then invert the grayscale image and use it as two input sources for orthogonal overlapping, and then output the result after thresholding

luminance = (1 - luminance) * (1 - luminance)
luminance = np.where(luminance > shadowThreshold, luminance, 0)

insert image description here
insert image description here

Then brighten the shadow area, and make a transition to the non-shadow, otherwise there will be a clear boundary.
The non-linear brightness adjustment used for brightening, the formula is the gamma correction formula, see the midtone adjustment in "PS Color Scale Adjustment Algorithm Formula Principle Detailed Explanation and Python Implementation" .

2. Brighten the shadow area and complete the code

Dependent environment: Python3 numpy OpenCV

# -*- coding: utf-8 -*-
# @Time    : 2021-04-26 19:45
# @Author  : AlanWang4523
# @FileName: ps_shadow_highlight.py

import os
import sys
import cv2
import numpy as np

class PSShadowHighlight:
    """
    色阶调整
    """
    def __init__(self, image):
        self.shadows_light = 50

        img = image.astype(np.float)/255.0

        srcR = img[:, :, 2]
        srcG = img[:, :, 1]
        srcB = img[:, :, 0]
        srcGray = 0.299 * srcR + 0.587 * srcG + 0.114 * srcB

        # 高光选区
        # luminance = luminance * luminance
        # luminance = np.where(luminance > 0.64, luminance, 0)

        # 阴影选区
        luminance = (1 - srcGray) * (1 - srcGray)

        self.maskThreshold = np.mean(luminance)
        mask = luminance > self.maskThreshold

        imgRow=np.size(img, 0)
        imgCol=np.size(img, 1)
        print("imgRow:%d, imgCol:%d, maskThreshold:%f" % (imgRow, imgCol, self.maskThreshold))
        print("shape:", img.shape)

        self.rgbMask = np.zeros([imgRow, imgCol, 3], dtype = bool)
        self.rgbMask[:, :, 0] = self.rgbMask[:, :, 1] = self.rgbMask[:, :, 2] = mask

        self.rgbLuminance = np.zeros([imgRow, imgCol, 3], dtype = float)
        self.rgbLuminance[:, :, 0] = self.rgbLuminance[:, :, 1] = self.rgbLuminance[:, :, 2] = luminance

        self.midtonesRate = np.zeros([imgRow, imgCol, 3], dtype = float)
        self.brightnessRate = np.zeros([imgRow, imgCol, 3], dtype = float)


    def adjust_image(self, img):
        maxRate = 4
        brightness = (self.shadows_light / 100.0 - 0.0001) / maxRate
        midtones = 1 + maxRate * brightness

        self.midtonesRate[self.rgbMask] = midtones
        self.midtonesRate[~self.rgbMask] = (midtones - 1.0) / self.maskThreshold * self.rgbLuminance[~self.rgbMask] + 1.0

        self.brightnessRate[self.rgbMask] = brightness
        self.brightnessRate[~self.rgbMask] = (1 / self.maskThreshold * self.rgbLuminance[~self.rgbMask]) * brightness

        outImg = 255 * np.power(img / 255.0, 1.0 / self.midtonesRate) * (1.0 / (1 - self.brightnessRate))

        img = outImg
        img[img < 0] = 0
        img[img > 255] = 255

        img = img.astype(np.uint8)
        return img
                          
def ps_shadow_highlight_adjust_and_save_img(psSH, origin_image):
    psSH.shadows_light = 50
    image = psSH.adjust_image(origin_image)
    cv2.imwrite('py_sh_out_01.png', image)


def ps_shadow_highlight_adjust(path):
    """
    阴影提亮调整
    """
    origin_image = cv2.imread(path)

    psSH = PSShadowHighlight(origin_image)
    # ps_shadow_highlight_adjust_and_save_img(psSH, origin_image)

    def update_shadows_light(x):
        psSH.shadows_light = x
            
    # 创建图片显示窗口
    title = "ShadowHighlight"
    cv2.namedWindow(title, cv2.WINDOW_NORMAL)   
    cv2.resizeWindow(title, 800, 600)
    cv2.moveWindow(title, 0, 0)

    # 创建阴影提亮操作窗口
    option_title = "Option"
    cv2.namedWindow(option_title, cv2.WINDOW_NORMAL)   
    cv2.resizeWindow(option_title, 400, 20)
    cv2.moveWindow(option_title, 0, 630)
    cv2.createTrackbar('shadows_light', option_title, psSH.shadows_light, 100, update_shadows_light)

    while True:
        image = psSH.adjust_image(origin_image)
        cv2.imshow(title, image)
        if cv2.waitKey(1) == ord('q'):
            break
    cv2.destroyAllWindows()        

if __name__ == '__main__':
    '''
        运行环境:Python 3
        执行:python3 ps_shadow_hightlight.py <图片路径>
        如:python3 ps_shadow_hightlight.py test.jpg
    '''
    if len(sys.argv) == 1:
        print("参数错误:未传入图片路径!")
        sys.exit(-1)
    img_path = sys.argv[1]
    print("img_path Params:", img_path)
    ps_shadow_highlight_adjust(img_path)

3. The final effect

The comparison between the default effect (degree of 50%) and the default effect of the shadow in PS:
the picture below is the comparison with the default effect of PS, the left is the default effect of PS (amount 30, hue 50%, radius 30, saturation +20), and the right is the above The default 50% effect achieved by Python is basically the same.
insert image description here
Effect pictures of other backlit pictures:
The following is the effect of other pictures implemented by Python. The left side is the original picture, and the right side is the processed picture. The default parameters are 50%. The graph is automatically calculated without manual modification):
insert image description here
insert image description here
insert image description here
insert image description here
insert image description here
insert image description here

Guess you like

Origin blog.csdn.net/u011520181/article/details/116308449