8、OpenCV调整图像对比度和亮度

一、学习目标

  • 理解图像对比度和亮度调整的原理
  • 对比三种不同亮度和对比度调整方法

二、原理理解

1、对比度
对比度指的是一幅图像中明暗区域最亮的白和最暗的黑之间不同亮度层级的测量,差异范围越大代表对比越大,差异范围越小代表对比越小,好的对比率120:1就可容易地显示生动、丰富的色彩,当对比率高达300:1时,便可支持各阶的颜色。但对比率遭受和亮度相同的困境,现今尚无一套有效又公正的标准来衡量对比率,所以最好的辨识方式还是依靠使用者眼睛。

对比度对视觉效果的影响非常关键,一般来说对比度越大,图像越清晰醒目,色彩也越鲜明艳丽;而对比度小,则会让整个画面都灰蒙蒙的。高对比度对于图像的清晰度、细节表现、灰度层次表现都有很大帮助。在一些黑白反差较大的文本显示、CAD显示和黑白照片显示等方面,高对比度产品在黑白反差、清晰度、完整性等方面都具有优势。相对而言,在色彩层次方面,高对比度对图像的影响并不明显。对比度对于动态视频显示效果影响要更大一些,由于动态图像中明暗转换比较快,对比度越高,人的眼睛越容易分辨出这样的转换过程。

2、亮度
图象亮度是指画面的明亮程度,单位是堪德拉每平米(cd/m2)或称nits。图象亮度是从白色表面到黑色表面的感觉连续体,由反射系数决定,亮度侧重物体,重在“反射”。亮度是一副图像给人的一种直观感受,如果是灰度图像,则跟灰度值有关,灰度值越高则图像越亮。在RGB图像中,亮度体现为每个点的像素值的大小,像素值越大,亮度越高。

3、对比度和亮度调整公式
在这里插入图片描述
参数α>0β通常被称为增益参数和偏置参数;有时这些参数被称为分别控制对比度和亮度。
可以认为 f(x) 是源图像像素,g(x) 是输出图像像素。然后,我们可以更方便地将表达式写成:
在这里插入图片描述
其中i和j表示像素位于第i行和第j列。α 作为系数乘以源像素值,会扩大最大像素值和最小像素值之间的差异,从而提升对比度。β 作为加数,直接增大源像素值,能提升亮度。

三、对比度、亮度调整

1、直接套用公式实现

for( int y = 0; y < image.rows; y++ ) {
        for( int x = 0; x < image.cols; x++ ) {
            for( int c = 0; c < image.channels(); c++ ) {
                new_image.at<Vec3b>(y,x)[c] =
                  saturate_cast<uchar>( alpha*image.at<Vec3b>(y,x)[c] + beta );
            }
        }
}

执行操作 g(i,j) = α ⋅ f(i,j) + β ,访问图像中的每个像素。因为我们是在操作BGR图像,所以每个像素有三个值(B, G和R),所以我们需要分别访问它们。

2、使用 cv::Mat::convertTo() 函数
cv::Mat::convertTo 的函数原型为:

void cv::Mat::convertTo	(OutputArray 	m,
						 int 			rtype,
						 double 		alpha = 1,
						 double 		beta = 0)	const
  • 参数 m:输出矩阵;如果在操作之前没有正确的大小或类型,则重新分配。
  • 参数 rtype:所需的输出矩阵类型,或者更确切地说,深度,通道的数量与输入相同;如果rtype为负,则输出矩阵将与输入矩阵具有相同的类型。
  • 参数 alpha:可选的比例因子,用于调整对比度。
  • 参数 beta:可选的缩放因子,用于调整亮度。
    该函数是Mat对象的一个方法,用于将Mat的数组转换为另一种可选伸缩的数据类型。
    该方法将源像素值转换为目标数据类型。其转换公式为:

**m (x, y) = saturate_cast < rType >(α(∗)(x, y) +β)**
satate_cast <> 应用于最后以避免可能的数值溢出。计算公式基本与对比度和亮度调整公式一致。

调用代码如下:

image.convertTo(new_image, -1, alpha, beta);

3、现有公式的问题分析及改进

  • 公式理解

增加(/减少) β 值将增加(/减少)一个常量值到每个像素。超过 [0,255] 范围的像素将过于饱和(即大于(或小于)255(/ 0)的像素值将被调整为255(/ 0))。
在这里插入图片描述

浅灰色为原始图像的像素值分布直方图,Gimp中亮度= 80时为深灰色

直方图表示每种颜色级别的像素数量。一个黑暗的图像将有许多低颜色值的像素,因此直方图将在其左侧呈现一个峰值。当添加一个恒定的偏置 β 时,直方图会向右移动,因为我们已经向所有像素添加了恒定的偏置。

α 参数将改变直方图的水平扩展。如果α<1,颜色级别将被压缩,结果将是一个对比度较低的图像。
在这里插入图片描述

浅灰色为原始图像的直方图,Gimp中对比度< 0时为深灰色

你会发现,调整β 偏置值将提高亮度,但同时图像会感觉蒙上了一层纱,因为对比度降低,α增益可以用来减小这种影响,但由于饱和,我们会丢失一些原始亮区的细节。

  • γ 校正

Gamma校正可以使用输入值和映射输出值之间的非线性转换来校正图像的亮度:
在这里插入图片描述
由于这种关系是非线性的,所有像素的效果将不相同,并将取决于它们的原始值。
在这里插入图片描述

绘制不同的伽玛值

γ<1 时,原始的暗区会变亮,直方图会向右移动,而γ>1 与之相反。

  • 使用查找表实现 γ校正
 Mat lookUpTable(1, 256, CV_8U);
 uchar* p = lookUpTable.ptr();
 for( int i = 0; i < 256; ++i)
     p[i] = saturate_cast<uchar>(pow(i / 255.0, gamma_) * 255.0);
 Mat res = img.clone();
 LUT(img, lookUpTable, res);
  • 效果对比
    在这里插入图片描述

上图是直接套用公式调整对比度和亮度的效果,其中α=1.3 ,β=40。整体的亮度得到了提高,但是你可以注意到,由于存在数值饱和度(saturate_cast),云层已经非常饱和了。
在这里插入图片描述
上图是γ校正的效果,其中γ=0.4。gamma校正应该倾向于增加较少的饱和效应,因为映射是非线性的,而且不可能像在前一种方法中的数值饱和。
在这里插入图片描述

左:alpha, beta修正后直方图;中心:原始图像的直方图;右:gamma校正后的直方图

上图比较了三幅图的直方图(其中y的取值范围不同)。您可以注意到,大多数像素值都位于原始图像直方图的下方。经过α,β校正后,由于饱和和右移,我们可以在255处观察到一个大的峰值。经过伽马校正后,直方图向右移动,但黑暗区域的像素比明亮区域的像素移动更大(见伽马曲线图)。

四、完整代码示例

#include<opencv2/opencv.hpp>
using namespace cv;
using namespace std;

int main(int argc, char** argv)
{
    // 读入图像,判断读入是否成功
	string fileName = samples::findFile("O:\\CSDN\\7.jpg");
	Mat src = imread(fileName, IMREAD_COLOR);
	if (src.empty())
	{
		fprintf(stderr, "failed to load image: %s\n", fileName);
		system("pause");
		return EXIT_FAILURE;
	}

	Mat dst1, dst2, dst3;
	dst1 = Mat::zeros(src.size(), src.type());
	double alpha = 1.0;
	double beta = 0.0;
	double gama = 1.0;

    // 提示并输入 α  β  γ  的值
	cout << " Basic Linear Transforms " << endl;
	cout << "-------------------------" << endl;
	cout << "* Enter the alpha value [1.0-3.0]: "; cin >> alpha;
	cout << "* Enter the beta value [0-100]: ";    cin >> beta;
	cout << "* Enter the gama value [-1,1]: ";    cin >> gama;
    
    // 直接使用循环遍历每一个像素,应用公式
	double t1 = (double)getTickCount();
	for (int row=0;row<src.rows;++row)
		for(int col=0;col<src.cols;++col)
			for (int channel = 0; channel < src.channels(); ++channel)
			{
				dst1.at<Vec3b>(row, col)[channel] = saturate_cast<uchar>(alpha * src.at<Vec3b>(row, col)[channel] + beta);
			}
	double time1 = ((double)getTickCount() - t1) / getTickFrequency();
	cout << "Method by pixel use time:" << time1 << "(ms)" << endl;

    // 调用 convertTo() 函数调整对比度和亮度
	double t2 = (double)getTickCount();
	src.convertTo(dst2, -1, alpha, beta);
	double time2 = ((double)getTickCount() - t2) / getTickFrequency();
	cout << "Method by pixel use time:" << time2 << "(ms)" << endl;

    // 构建查找表
	Mat lookUpTable(1, 256, CV_8U);
	uchar* p = lookUpTable.ptr();
	for (int i = 0; i < 256; ++i)
		p[i] = saturate_cast<uchar>(pow(i / 255.0, gama) * 255.0);

   // 使用查找表进行对比度亮度调整
	double t3 = (double)getTickCount();
	LUT(src, lookUpTable, dst3);
	double time3 = ((double)getTickCount() - t3) / getTickFrequency();
	cout << "Method by γ correct use time:" << time3 << "(ms)" << endl;

    // 调整窗体大小,显示调整效果
	namedWindow("original", WINDOW_NORMAL);
	resizeWindow("original", Size(src.cols / 2, src.rows / 2));
	imshow("original", src);
	namedWindow("pixel set", WINDOW_NORMAL);
	resizeWindow("pixel set", Size(src.cols / 2, src.rows / 2));
	imshow("pixel set", dst1);
	namedWindow("convertTo", WINDOW_NORMAL);
	resizeWindow("convertTo", Size(src.cols / 2, src.rows / 2));
	imshow("convertTo", dst2);
	namedWindow("γ correct", WINDOW_NORMAL);
	resizeWindow("γ correct", Size(src.cols / 2, src.rows / 2));
	imshow("γ correct", dst3);
	waitKey(0);
	system("pause");
	return EXIT_SUCCESS;
}

在这里插入图片描述

  • 23
    点赞
  • 125
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值