OpenCV4入门系列教程40:Gamma变换

索引地址:系列教程索引地址

上一篇:OpenCV4入门系列教程40:DFT(离散傅利叶变换)

gamma变换说明

Gamma变换是对输入图像灰度值进行的非线性操作,使输出图像灰度值与输入图像灰度值呈指数关系:

Vout=AVinγV_{out}=AV_{in}^{\gamma}

这个指数即为Gamma。

注意这个 VinV_{in} 的取值范围为0~1,因此需要先进行归一化,然后取指数。

Gamma变换就是用来图像增强,其提升了暗部细节,简单来说就是通过非线性变换,让图像从暴光强度的线性响应变得更接近人眼感受的响应,即将漂白(相机曝光)或过暗(曝光不足)的图片,进行矫正。

经过Gamma变换后的输入和输出图像灰度值关系如图1所示:横坐标是输入灰度值,纵坐标是输出灰度值,蓝色曲线是gamma值小于1时的输入输出关系,红色曲线是gamma值大于1时的输入输出关系。可以观察到,当gamma值小于1时(蓝色曲线),图像的整体亮度值得到提升,同时低灰度处的对比度得到增加,更利于分辩低灰度值时的图像细节。

correction

即可以总结如下:
gamma>1gamma>1, 较亮的区域灰度被拉伸,较暗的区域灰度被压缩的更暗,图像整体变暗;
gamma<1gamma<1, 较亮的区域灰度被压缩,较暗的区域灰度被拉伸的较亮,图像整体变亮;

用处

1、人眼对外界光源的感光值与输入光强不是呈线性关系的,而是呈指数型关系的。在低照度下,人眼更容易分辨出亮度的变化,随着照度的增加,人眼不易分辨出亮度的变化。而摄像机感光与输入光强呈线性关系。如下图所示:

2

2、为能更有效的保存图像亮度信息,需进行Gamma变换。未经Gamma变换和经过Gamma变换保存图像信息如下图所示:可以观察到,未经Gamma变换的情况下,低灰度时,有较大范围的灰度值被保存成同一个值,造成信息丢失;同时高灰度值时,很多比较接近的灰度值却被保存成不同的值,造成空间浪费。经过Gamma变换后,改善了存储的有效性和效率。

3

3、Gamma矫正用在8位RGB图中,用来解决在有限的存储空间中保存尽可能多的人类感受敏感的色彩内容。显示器显示8位图片时,我们已知现在主流显示器的色彩要大于8位,多数都是32位真彩色。显示器显示的时候也经过了一次Gamma校正,它把0.5又变成了0.2。如果不经过这一步,直接输出0.5,因为显示器是32位的,那么你在屏幕上得到的将是位于图2中间位置的那个颜色,你会说这个显示器有问题,颜色不准确,亮瞎了。

8位图与32位图对比如下,可以看出,在8位图,黑白分解线在0.2附近,而由于32位可以表现的颜色多,而人的眼睛对暗色比较敏感,因此感觉分界线在0.2附近:

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// 功能:代码 3-20 Gamma 校正
// 作者:朱伟 zhu1988wei@163.com
// 来源:《OpenCV图像处理编程实例》
// 博客:http://blog.csdn.net/zhuwei1988
// 更新:2016-8-1
// 说明:版权所有,引用或摘录请联系作者,并按照上面格式注明出处,谢谢。//
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
// Gamma变换函数实现
cv::Mat gammaTransform(cv::Mat &srcImage, float kFactor) {
// 建立查表文件LUT
unsigned char LUT[ 256 ];
for (int i = 0; i < 256; i++) {
// Gamma变换表达式
LUT[ i ] = saturate_cast<uchar>(pow((float)(i / 255.0), kFactor) * 255.0f);
}
cv::Mat resultImage = srcImage.clone();
// 输入通道为单通道时 直接进行变换
if (srcImage.channels() == 1) {
cv::MatIterator_<uchar> iterator = resultImage.begin<uchar>();
cv::MatIterator_<uchar> iteratorEnd = resultImage.end<uchar>();
for (; iterator != iteratorEnd; iterator++)
*iterator = LUT[ (*iterator) ];
} else {
// 输入通道为三通道时 需对每个通道分别进行变换
cv::MatIterator_<cv::Vec3b> iterator = resultImage.begin<Vec3b>();
cv::MatIterator_<cv::Vec3b> iteratorEnd = resultImage.end<Vec3b>();
// 通过查找表进行转换
for (; iterator != iteratorEnd; iterator++) {
(*iterator)[ 0 ] = LUT[ ((*iterator)[ 0 ]) ];
(*iterator)[ 1 ] = LUT[ ((*iterator)[ 1 ]) ];
(*iterator)[ 2 ] = LUT[ ((*iterator)[ 2 ]) ];
}
}
return resultImage;
}
int main() {
cv::Mat srcImage = imread("lena.jpg");
if (!srcImage.data)
return -1;
// 初始化两种不同参数 对比分析效果
float kFactor1 = 0.3;
float kFactor2 = 3.0;
cv::Mat result1 = gammaTransform(srcImage, kFactor1);
cv::Mat result2 = gammaTransform(srcImage, kFactor2);
cv::imshow("srcImage", srcImage);
cv::imshow("result1", result1);
cv::imshow("result2", result2);
cv::waitKey(0);
return 0;
}

测试结果:

result

下一篇:OpenCV4入门系列教程41:极坐标变换