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
satate_cast <> 应用于最后以避免可能的数值溢出。计算公式基本与对比度和亮度调整公式一致。
调用代码如下:
image.convertTo(new_image, -1, alpha, beta);
3、现有公式的问题分析及改进
增加(/减少) β 值将增加(/减少)一个常量值到每个像素。超过 [0,255] 范围的像素将过于饱和(即大于(或小于)255(/ 0)的像素值将被调整为255(/ 0))。
直方图表示每种颜色级别的像素数量。一个黑暗的图像将有许多低颜色值的像素,因此直方图将在其左侧呈现一个峰值。当添加一个恒定的偏置 β 时,直方图会向右移动,因为我们已经向所有像素添加了恒定的偏置。
α 参数将改变直方图的水平扩展。如果α<1,颜色级别将被压缩,结果将是一个对比度较低的图像。
你会发现,调整β 偏置值将提高亮度,但同时图像会感觉蒙上了一层纱,因为对比度降低,α增益可以用来减小这种影响,但由于饱和,我们会丢失一些原始亮区的细节。
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校正应该倾向于增加较少的饱和效应,因为映射是非线性的,而且不可能像在前一种方法中的数值饱和。
上图比较了三幅图的直方图(其中y的取值范围不同)。您可以注意到,大多数像素值都位于原始图像直方图的下方。经过α,β校正后,由于饱和和右移,我们可以在255处观察到一个大的峰值。经过伽马校正后,直方图向右移动,但黑暗区域的像素比明亮区域的像素移动更大(见伽马曲线图)。
#include
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;
}