文章目录
-
- 1.Gamma校正的作用
- 2.Gamma校正算法的原理
- 3.算法代码实现
- 4.测试代码
-
- [测试代码1(限制像素值在(0-255)](#测试代码1(限制像素值在(0-255))
- 测试代码2(解除限制验证正确性)
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操作

-测试效果
如预期一样,图像变的更亮了.
