分享

PS 色阶调整之算法公式原理详解及 Python 实现...

 mediatv 2021-11-19

本文介绍了 PS 中色阶的实现原理及公式,并用 Python 实现,自测与 PS 的色阶调整效果基本完全一样(使用和 PS 中色阶相同的参数对比效果,包括各极限值,本文只实现了 RGB 整体色阶的处理,对各个通道的处理逻辑公式是一样的,实际使用会用 OpenGL 实现)

如下图是 PS 中色阶调整的操作面板,可以对 R、G、B 单独的通道或 RGB 整体通道做色阶调整。每个通道或整体有五个参数(红色箭头所指的五个滑块,调整时也可以直接输入值):
inputShadows: 输入图像的黑场阈值,作用是将输入图像中低于该阈值的全变成 0
inputHighlit: 输入图像的白场阈值,作用是将输入图像中高于该阈值的全变成 255
midtone: 中间调,范围是 [0.01, 9.99],默认值是 1.0 在中间,[9.99 – 1.0 – 0.01],从中间往左调,即[1.0, 9.99] 加灰降对比度,往右调减灰加对比度(RGB通道)
outputShadows: 输出图像的黑场阈值,输出图像的最低值为该阈值
outputHighlight: 输出图像的白场阈值,输出图像的最高值为该阈值
在这里插入图片描述
色阶调整的处理转换公式如下:
分输入色阶映射、中间调调整、输出色阶映射共三步处理,上一步处理的输出做为下一步的输入(以下公式用 MarkDown Latex 语法编辑)。

输入色阶映射公式:
V o u t = 255 ∗ V i n − i n S h a d o w s i n H i g h l i g h t s − i n S h a d o w s Vout = 255 * \frac{Vin - inShadows}{inHighlights - inShadows} Vout=255inHighlightsinShadowsVininShadows
V o u t = { 0 if  V o u t < 0 255 if  V o u t > 255 Vout = \left\{

0255if Vout<0if Vout>2550if Vout<0255if Vout>255
\right. Vout={0255if Vout<0if Vout>255
图片名称

中间调调整:
V o u t = 255 ∗ ( V i n 255.0 ) 1 m i d t o n e s Vout = 255 * (\frac{Vin}{255.0})^\frac{1}{midtones} Vout=255(255.0Vin)midtones1

图片名称

输出色阶映射:
V o u t = V i n 255.0 ∗ ( o u t L i g h h i g h t s − o u t S h a d o w s ) + o u t S h a d o w s Vout = \frac{Vin}{255.0} * (outLighhights - outShadows) + outShadows Vout=255.0Vin(outLighhightsoutShadows)+outShadows

V o u t = { 0 if  V o u t < 0 255 if  V o u t > 255 Vout = \left\{

0255if Vout<0if Vout>2550if Vout<0255if Vout>255
\right. Vout={0255if Vout<0if Vout>255
图片名称
完整 python 代码如下:
# -*- coding: utf-8 -*-
# @Time    : 2021-02-24 16:45
# @Author  : AlanWang4523
# @FileName: ps_levels.py

import os
import sys
import cv2
import numpy as np

class Levels:
    """
    @Author  : AlanWang4523
    色阶调整类,根据输入参数调整图片色阶,并输出处理后的图片
    """
    def __init__(self):
        self.channel = 0
        self.input_shadows = 0
        self.input_highlights = 255
        self.midtones = 1.0
        self.output_shadows = 0
        self.output_highlights = 255

    def adjust_image(self, img):
        print("Levels Params:")
        print("          channel:", self.channel)
        print("    input_shadows:", self.input_shadows)
        print(" input_highlights:", self.input_highlights)
        print("         midtones:", self.midtones)
        print("   output_shadows:", self.output_shadows)
        print("output_highlights:", self.output_highlights)
        print("")

        img = img.astype(np.float)

        # 输入色阶映射
        img = 255 * ((img - self.input_shadows) / (self.input_highlights - self.input_shadows))
        img[img < 0] = 0
        img[img > 255] = 255

        # 中间调处理
        img = 255 * np.power(img / 255.0, 1.0 / self.midtones)

        # 输出色阶映射
        img = (img / 255) * (self.output_highlights - self.output_shadows) + self.output_shadows
        img[img < 0] = 0
        img[img > 255] = 255

        img = img.astype(np.uint8)
        return img
                          
def level_adjust_and_save_img(origin_image):
    levels.input_shadows = 40
    levels.input_highlights = 240
    levels.midtones = 0.60
    levels.output_shadows = 30
    levels.output_highlights = 220

    image = levels.adjust_image(origin_image)

    cv2.imwrite('py_test_out.png', image)


def level_adjust(path):
    """
    色阶调整
    """
    origin_image = cv2.imread(path)

    levels = Levels()

    def update_input_shadows(x):
        if (x < levels.input_highlights):
            levels.input_shadows = x

    def update_input_highlights(x):
        if (x > levels.input_shadows):
            levels.input_highlights = x

    def update_midtones(x):
        # 由于 midtones 的调整范围是 [9.99, 0.01],Python 滑杆无法自定义显示小数,因此将滑杆的 [0, 100] 映射到 [9.99, 0.01]
        midtones = 1.0
        if (x < 50):
            midtones = 1 + 9 * ((50.0 - x) / 50.0)
        elif (x > 50):
            midtones = 1 - (x - 50) / 50.0
        
        levels.midtones = np.clip(midtones, 0.01, 9.99)
        # levels.midtones = 0.6 # 直接测试某个参数值

    def update_output_shadows(x):
        if (x < levels.output_highlights):
            levels.output_shadows = x

    def update_output_highlights(x):
        if (x > levels.output_shadows):
            levels.output_highlights = x
        
    # 创建图片显示窗口
    title = "Levels"
    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, 200)
    cv2.moveWindow(option_title, 800, 0)

    cv2.createTrackbar('    input_shadows', option_title, levels.input_shadows, 255, update_input_shadows)
    cv2.createTrackbar(' input_highlights', option_title, levels.input_highlights, 255, update_input_highlights)
    cv2.createTrackbar('         midtones', option_title, 50, 100, update_midtones)
    cv2.createTrackbar('   output_shadows', option_title, levels.output_shadows, 255, update_output_shadows)
    cv2.createTrackbar('output_highlights', option_title, levels.output_highlights, 255, update_output_highlights)

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


if __name__ == '__main__':
    '''
    	Author: AlanWang4523
        运行环境:Python 3
        执行:python3 ps_levels.py <图片路径>
        如:python3 ps_levels.py test.jpg
    '''
    if len(sys.argv) == 1:
        print("参数错误:未传入图片路径!")
        sys.exit(-1)
    img_path = sys.argv[1]
    print("img_path Params:", img_path)
    level_adjust(img_path)
    def update_midtones(x):
        # 由于 midtones 的调整范围是 [9.99, 0.01],Python 滑杆无法自定义显示小数,因此将滑杆的 [0, 100] 映射到 [9.99, 0.01]
        midtones = 1.0
        if (x < 50):
            midtones = 1 + 9 * ((50.0 - x) / 50.0)
        elif (x > 50):
            midtones = 1 - (x - 50) / 50.0
        
        levels.midtones = np.clip(midtones, 0.01, 9.99)
        # levels.midtones = 0.6 # 直接测试某个参数值

使用和 PS 相同的参数,效果和 PS 上的完全一致,各参数下和 PS 对比效果如下:
(截图上半部部分为用 Python 使用与 PS 相同参数的效果,下半部分为 PS 的效果)
① Python 和 PS 都使用如下参数:
inputShadows: 50
inputHighlit: 52
midtone: 0.50 (根据上面的映射公式,python 实现的 midtones 的滑块在 75 的位置)
outputShadows: 50
outputHighlight: 200
效果如下:
在这里插入图片描述

② Python 和 PS 都使用如下参数:
inputShadows: 0
inputHighlit: 255
midtone: 0.10
outputShadows: 0
outputHighlight: 255
效果如下:
在这里插入图片描述
参考文档:
Adobe 官网的 Levels Adjustment
Algorithm for adjustment of image levels

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多