PS 色彩平衡之算法公式原理详解及 Python 实现

本文介绍了 PS 中色阶的实现原理及公式,并用 Python 实现,自测与 PS 的色阶调整效果基本完全一样。
PS 中色彩平衡可以对高光、中间调、阴影 三个色调进行调整,每个色调中有可以对 RGB 三个通道调整,每个通道的调整区间范围是 [-100, 100]

图片名称

色彩平衡之高光

高光各色条的现象及规律如下:

  • a. 对于正向的调整,只增加输入图像中该通道的值,其他两个通道不变,比如只调整蓝色 +100,那么原图中蓝色通道值会增加,红色通道和绿色通道不变,如下图;
  • b. 对于负向的调整,该通道值不变,其他两个通道值增加,比如同样只调整蓝色 -100,那么原图中蓝色通道值不变,红色通道和绿色通道值增加,如下图;
  • c. 此外如果将三个调整滑杆都拖动相同的数值,那么原图没有变化。如 RGB 三个滑杆值每个通道都加或减相同数值,效果一样,如 (+10, +20, +30)的效果等价于 (-10, 0, 10) 的效果(RGB 三个滑杆都减 20),也等价于 (20, 30, 40) 的效果(RGB 三个滑杆都加 10)可通过 PS 验证。
  • d. 生成一张 255x255 的图,其从左到右每个像素点 RGB 每个通道的颜色值从 0~255,然后将该图通过 PS 做色彩平衡处理并存处理后的图片,下图中横轴是取的 0~255,纵轴是取的通过 PS 处理后的 RGB 各个通道的值并画线
图片名称
图片名称
  • e. 通过设置不同的参数值导出多张图,然后拟合出调整系数(上图中变化直线斜率)与滑杆调整值[0, 100]的关系(见上图高光下加不同蓝色值对应的输出曲线),只测正值即可,负值相当于其另外两个通道做正向调整(公式如下,Vin 为输入图像的 RGB,a 为固定系数,X 为各通道的正向调节值 [0, 100];

色彩平衡-高光映射公式:
V o u t = 255 ∗ V i n 255 − X Vout = \frac{255 * Vin}{255 - X} Vout=255X255Vin

V o u t = { 0 if  V o u t < 0 255 if  V o u t > 255 Vout = \left\{ \begin{array}{ll} 0 & \textrm{if $Vout<0$}\\ 255 & \textrm{if $Vout>255$}\\ \end{array} \right. Vout={ 0255if Vout<0if Vout>255

色彩平衡之阴影调整

阴影各色条的现象及规律如下:

  • a. 对于正向的调整,该通道值不变,其他两个通道值减小,比如同样只调整蓝色 +100,那么原图中蓝色通道值不变,红色通道和绿色通道值减小,如下图;
  • b. 对于负向的调整,只减少输入图像中该通道的值,其他两个通道不变,比如只调整蓝色 -100,那么原图中蓝色通道值会减小,红色通道和绿色通道不变,如下图;
  • c. 此外如果将三个调整滑杆都拖动相同的数值,那么原图没有变化。如果 RGB 三个滑杆值每个通道都加或减相同数值,效果一样。
  • d. 生成一张 255x255 的图,其从左到右每个像素点 RGB 每个通道的颜色值从 0~255,然后将该图通过 PS 做色彩平衡处理并存处理后的图片,下图中横轴是取的 0~255,纵轴是取的通过 PS 处理后的 RGB 各个通道的值并画线
图片名称
图片名称
  • e. 通过设置不同的参数值导出多张图,然后拟合出直线斜率、截距与滑杆调整值[0, 100]的关系(见上图阴影下加不同蓝色值对应的输出曲线),只测负值即可,正值相当于其另外两个通道做负向调整(公式如下,Vin 为输入图像的 RGB,X 为各通道的正向调节值 [0, 100])

色彩平衡-阴影映射公式:
V o u t = 255 ∗ ( V i n − X ) 255 − X Vout = \frac{255 * (Vin - X)}{255 - X} Vout=255X255(VinX)

V o u t = { 0 if  V o u t < 0 255 if  V o u t > 255 Vout = \left\{ \begin{array}{ll} 0 & \textrm{if $Vout<0$}\\ 255 & \textrm{if $Vout>255$}\\ \end{array} \right. Vout={ 0255if Vout<0if Vout>255

代码如下:

# -*- coding: utf-8 -*-
# @Time    : 2021-03-09 11:05
# @Author  : AlanWang4523
# @FileName: ps_color_balance_test.py

import os
import sys
import cv2
import numpy as np

TONE_SHADOWS   = 0
TONE_MIDTONE   = 1
TONE_HIGHLIGHT = 2
SH_COEFFICIENTS = 0.0039216 # 1.0/255
MID_COEFFICIENTS = 0.0033944


class ColorBalance:
    """
    色彩平衡调整
    """
    def __init__(self):
        self.tone = TONE_SHADOWS
        self.tone_arguments = np.zeros([3,3], dtype=np.int)
        self.tone_coefficients = np.zeros([3,3])
        self.sh_coef_self = np.array([
                [1, 0, 0], 
                [0, 1, 0], 
                [0, 0, 1]
        ])
        self.sh_coef_other = np.array([
                [0, 1, 1], 
                [1, 0, 1], 
                [1, 1, 0]
        ])
        self.mid_coef_self = np.array([
                [1, -1, -1], 
                [-1, 1, -1], 
                [-1, -1, 1]
        ])


    def update_tones(self, x):
        self.tone = x;

    def update_cyan_red(self, x):
        self.tone_arguments[self.tone, 0] = x

    def update_magenta_green(self, x):
        self.tone_arguments[self.tone, 1] = x
        
    def update_yellow_blue(self, x):
        self.tone_arguments[self.tone, 2] = x

    def adjust_image(self, img):
        print("ColorBalance Params:")
        print("   Shadows: ", self.tone_arguments[TONE_SHADOWS][:])
        print("  Midtones: ", self.tone_arguments[TONE_MIDTONE][:])
        print("Highlights: ", self.tone_arguments[TONE_HIGHLIGHT][:])
        # print("   Shadows: %d, %d, %d" % (self.tone_arguments[TONE_SHADOWS, 0], self.tone_arguments[TONE_SHADOWS, 1], self.tone_arguments[TONE_SHADOWS, 2]))
        # print("  Midtones: %d, %d, %d" % (self.tone_arguments[TONE_MIDTONE, 0], self.tone_arguments[TONE_MIDTONE, 1], self.tone_arguments[TONE_MIDTONE, 2]))
        # print("Highlights: %d, %d, %d" % (self.tone_arguments[TONE_HIGHLIGHT, 0], self.tone_arguments[TONE_HIGHLIGHT, 1], self.tone_arguments[TONE_HIGHLIGHT, 2]))

        
        temp = np.sort(self.tone_arguments[TONE_SHADOWS][:])
        self.tone_coefficients[TONE_SHADOWS][:] = (self.tone_arguments[TONE_SHADOWS][:] - temp[1]) * SH_COEFFICIENTS

        temp = np.sort(self.tone_arguments[TONE_MIDTONE][:])
        self.tone_coefficients[TONE_MIDTONE][:] = (self.tone_arguments[TONE_MIDTONE][:] - temp[1]) * MID_COEFFICIENTS

        temp = np.sort(self.tone_arguments[TONE_HIGHLIGHT][:])
        self.tone_coefficients[TONE_HIGHLIGHT][:] = (self.tone_arguments[TONE_HIGHLIGHT][:] - temp[1]) * SH_COEFFICIENTS

        print("tone_coefficients:")
        print("   Shadows: ", self.tone_coefficients[TONE_SHADOWS][:])
        print("  Midtones: ", self.tone_coefficients[TONE_MIDTONE][:])
        print("Highlights: ", self.tone_coefficients[TONE_HIGHLIGHT][:])

        test_array = self.tone_coefficients[TONE_HIGHLIGHT][:]
        self.tone_coefficients[TONE_HIGHLIGHT][:] = np.dot(np.where(test_array > 0, test_array, 0), self.sh_coef_self) +\
            np.dot((-1) * np.where(test_array < 0, test_array, 0), self.sh_coef_other)

        test_array = self.tone_coefficients[TONE_SHADOWS][:]
        self.tone_coefficients[TONE_SHADOWS][:] = np.dot(np.where(test_array > 0, test_array, 0), self.sh_coef_other) +\
            np.dot((-1) * np.where(test_array < 0, test_array, 0), self.sh_coef_self)  

        # test_array = self.tone_coefficients[TONE_MIDTONE][:]
        # self.tone_coefficients[TONE_MIDTONE][:] = np.dot(test_array, self.mid_coef_self)
        # self.tone_coefficients[TONE_MIDTONE][:] = np.power(e, np.dot(test_array, self.mid_coef_self))


        print("after_tone_coefficients:")
        print("   Shadows: ", self.tone_coefficients[TONE_SHADOWS][:])
        print("  Midtones: ", self.tone_coefficients[TONE_MIDTONE][:])
        print("Highlights: ", self.tone_coefficients[TONE_HIGHLIGHT][:])



        # r, g, b = 
        img = img.astype(np.float)

        b, g ,r =cv2.split(img)
        r = r / (1.0 - self.tone_coefficients[TONE_HIGHLIGHT, 0])
        g = g / (1.0 - self.tone_coefficients[TONE_HIGHLIGHT, 1])
        b = b / (1.0 - self.tone_coefficients[TONE_HIGHLIGHT, 2])
        r[r > 255] = 255
        g[g > 255] = 255
        b[b > 255] = 255


        r = (r - 255.0 * self.tone_coefficients[TONE_SHADOWS, 0]) / (1.0 - self.tone_coefficients[TONE_SHADOWS, 0])
        g = (g - 255.0 * self.tone_coefficients[TONE_SHADOWS, 1]) / (1.0 - self.tone_coefficients[TONE_SHADOWS, 1])
        b = (b - 255.0 * self.tone_coefficients[TONE_SHADOWS, 2]) / (1.0 - self.tone_coefficients[TONE_SHADOWS, 2])
        r[r < 0] = 0
        g[g < 0] = 0
        b[b < 0] = 0

        img = cv2.merge([b, g, r])
        img = img.astype(np.uint8)
        return img    


def color_balance_adjust(path):
    """
    色彩平衡调整
    """
    origin_image = cv2.imread(path)

    cloorBalance = ColorBalance()

    title = "ColorBalance"

    # 阴影
    cloorBalance.update_tones(0)
    cloorBalance.update_cyan_red(0)
    cloorBalance.update_magenta_green(-100)
    cloorBalance.update_yellow_blue(0)

    # 中间调
    cloorBalance.update_tones(1)
    cloorBalance.update_cyan_red(-30)
    cloorBalance.update_magenta_green(-50)
    cloorBalance.update_yellow_blue(-50)

    # 高光
    cloorBalance.update_tones(2)
    cloorBalance.update_cyan_red(100)
    cloorBalance.update_magenta_green(0)
    cloorBalance.update_yellow_blue(0)

    image = cloorBalance.adjust_image(origin_image)    



    cv2.namedWindow(title, cv2.WINDOW_NORMAL)   
    cv2.resizeWindow(title, 800, 600)
    cv2.moveWindow(title, 0, 0)

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


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

感谢:
https://zhuanlan.zhihu.com/p/59450298

猜你喜欢

转载自blog.csdn.net/u011520181/article/details/118530067