openISP学习8-GC — Gamma Correction(Gamma 校正)

文章目录

1.Gamma校正的作用

在图像信号处理器(ISP)中,Gamma校正(Gamma Correction)是图像处理流水线中至关重要的非线性操作。它的核心作用是解决图像传感器、人眼视觉感知以及显示设备之间的非线性差异,从而让最终呈现的图像更加自然、通透。

Gamma校正的核心作用

适配人眼视觉特性:相机传感器(CMOS/CCD)捕获的光信号与入射光强呈线性关系,但人眼对亮度的感知是非线性的(对暗部细节更敏感,对亮部相对迟钝)。Gamma校正通过非线性映射,将更多的数据位分配给人眼敏感的暗部和中间调,使图像观感更符合人类视觉习惯。

2.Gamma校正算法的原理

图像传感器输出与光强成线性关系(线性域),但人眼对亮度的感知是对数/幂次关系,显示器也有自己的伽马特性。Gamma 校正通过幂函数将线性图像映射到感知均匀的空间:

复制代码
output = (input / maxval)^γ × maxval

γ = 0.5(即平方根)是 sRGB 的简化近似,实际 sRGB 使用分段函数。

下面我用desmos画出了常用γ的函数图像,内心感受一下,从函数的形状能看到,当γ值越小. 因为函数是凸函数,所以映射后的像素值会更大,反映在图像上是"图像更亮" .相反,当γ越大,因为图像是一个凹函数,映射后的像素值会更小,反映在图像上是"图像更暗"

如下图

  • 下图中的图像γ为1,就是一条直线,像素值不会被拉伸.
  • 下图左γ=0.5,可以从函数图像就能看出,像素值被拉伸到更大的值
  • 下图右γ=2,中间的像素被压低了,图像偏暗.

查找表(LUT)实现

根据上面的公式

复制代码
output = (input / maxval)^γ × maxval

这里假设图像是10bit,即色彩范围是0-1023. 预先计算全部输入值对应的输出值,存入字典,运行时直接查表:(关于这个构建更详细的解释,再测试代码中也有提到)

python 复制代码
ind = range(0, 1024)
val = [round((i/1024)^0.5 * 1024) for i in ind]
lut = dict(zip(ind, val))

这里我们画出(i/1024)^0.5 * 1024图像, 输入值最大为1024. 因为i∈(0,1024),结合上面的图像,理解一下.

3.算法代码实现

  • 支持 'rgb' 模式(R/G/B 共用同一 LUT)和 'yuv' 模式(Y/UV 分开)
  • RGB 模式输出除以 4,将 10-bit (0~1023) 映射到 8-bit (0~255)
  • 逐像素查表,计算量 O(H×W)
python 复制代码
class GC:
    'Gamma Correction'

    def __init__(self, img, lut, mode):
        self.img = img
        self.lut = lut
        self.mode = mode

    def execute(self):
        img_h = self.img.shape[0]
        img_w = self.img.shape[1]
        img_c = self.img.shape[2]
        gc_img = np.empty((img_h, img_w, img_c), np.uint16)
        for y in range(self.img.shape[0]):
            for x in range(self.img.shape[1]):
                if self.mode == 'rgb':
                    gc_img[y, x, 0] = self.lut[self.img[y, x, 0]]
                    gc_img[y, x, 1] = self.lut[self.img[y, x, 1]]
                    gc_img[y, x, 2] = self.lut[self.img[y, x, 2]]
                    gc_img[y, x, :] = gc_img[y, x, :] / 4 # 映射到0-255
                elif self.mode == 'yuv':
                    gc_img[y, x, 0] = self.lut[0][self.img[y, x, 0]]
                    gc_img[y, x, 1] = self.lut[1][self.img[y, x, 1]]
                    gc_img[y, x, 2] = self.lut[1][self.img[y, x, 2]]
        self.img = gc_img
        return self.img

4.测试代码

测试代码1(限制像素值在(0-255)

构建GC 查找表LUT, 然后在GC算法中,根据像素值索引查找映射后的值.

python 复制代码
    def _build_lut(self, bw=10, gamma=0.5):
        """构建 gamma LUT(与主程序一致)。"""
        maxval = pow(2, bw)
        ind = range(0, int(maxval))
        val = [round(pow(float(i) / maxval, gamma) * maxval) for i in ind]
        return dict(zip(ind, val))

上面最关键的是这句号 val = [round(pow(float(i) / maxval, gamma) * maxval) for i in ind]

  • float(i) / maxval:将当前的整数像素值归一化到 0,1 的浮点数范围内。
  • pow(..., gamma):对归一化后的值进行 Gamma 幂运算。
  • maxval:将运算结果重新映射回 0,1023 的整数范围。
  • round(...):四舍五入取整,确保输出值是合法的像素整数。
python 复制代码
  • 测试效果图
    这里γ等于0.5,按理说图像应该更亮,这是因为GC算法内部,对RGB各分量都做了一个➗4的操作.这是想让各通道都在(0,255)之间,所以看到右边图像没有左边亮.

测试代码2(解除限制验证正确性)

下面我把(r=20, g=80, b=20)各分量都改小一点,去掉GC算法的除4操作,

python 复制代码
    def test_output_shape(self):
        """输出形状应与输入相同。"""
        img = make_rgb(88, 88, r=20, g=80, b=20)
        lut = self._build_lut()
        gc = GC(img.copy(), lut, 'rgb')
        out = gc.execute()
        show_RGB_RGB_images(img, out, "left", "right-shape")

去掉除4操作

-测试效果

如预期一样,图像变的更亮了.

相关推荐
韩师傅1 天前
海天线算法的前世今生
python·计算机视觉
韩师傅1 天前
当你的甲方设备过烂,要如何快速出效果?
python·计算机视觉
韩师傅1 天前
当你的甲方吐槽天空不够蓝,你应该如何应对
python·计算机视觉
兵慌码乱8 天前
基于 MediaPipe 与 PySide2 的手势交互音乐控制系统实现:轻量化视觉交互全流程解析
python·opencv·计算机视觉·人机交互·手势识别·mediapipe·pyside2
小小杨树10 天前
读懂色彩:拍照调色不再难
算法·计算机视觉·配色
H__Rick12 天前
自动对焦学习-3
人工智能·学习·计算机视觉
计算机科研狗@OUC12 天前
(cvpr26) AIMDepth: Asymmetric Image-Event Mamba for Monocular Depth Estimation
人工智能·深度学习·计算机视觉
qq_3665665012 天前
2026最新:5款AI视频口型同步工具实测横评,视频翻译后嘴型对不上的终极解决方案
人工智能·计算机视觉·新媒体运营
梦想三三12 天前
OpenCV银行卡数字识别项目(图像预处理与字符分割)
人工智能·opencv·计算机视觉