英文链接:Changing the contrast and brightness of an image!
cv::saturate_cast
做什么以及它为什么有用两种常用的点处理是:带一个常数的 乘法和加法:
g ( x ) = α f ( x ) + β g(x) = \alpha f(x) + \beta g(x)=αf(x)+β
其中的参数 α > 0 \alpha>0 α>0和 β \beta β常被称为增益和偏置参数; 这些参数分别控制对比度和亮度。
你可以把 f ( x ) f(x) f(x)看作是源图像像素, g ( x ) g(x) g(x)看作是输出图像像素。那么,我们可以更方便地将表达式写成:
g ( i , j ) = α ⋅ f ( i , j ) + β g(i,j) = \alpha \cdot f(i,j) + \beta g(i,j)=α⋅f(i,j)+β
其中i和j表示像素位于第i行和第j列。
#include "opencv2/imgcodecs.hpp"
#include "opencv2/highgui.hpp"
#include
// we're NOT "using namespace std;" here, to avoid collisions between the beta variable and std::beta in c++17
using std::cin;
using std::cout;
using std::endl;
using namespace cv;
int main( int argc, char** argv )
{
CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
Mat image = imread( samples::findFile( parser.get<String>( "@input" ) ) );
if( image.empty() )
{
cout << "Could not open or find the image!\n" << endl;
cout << "Usage: " << argv[0] << " " << endl;
return -1;
}
Mat new_image = Mat::zeros( image.size(), image.type() );
double alpha = 1.0; /*< Simple contrast control */
int beta = 0; /*< Simple brightness control */
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;
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 );
}
}
}
imshow("Original Image", image);
imshow("New Image", new_image);
waitKey();
return 0;
}
1、我们使用cv::imread
加载图像,并保存在一个Mat对象:
CommandLineParser parser( argc, argv, "{@input | lena.jpg | input image}" );
Mat image = imread( samples::findFile( parser.get<String>( "@input" ) ) );
if( image.empty() )
{
cout << "Could not open or find the image!\n" << endl;
cout << "Usage: " << argv[0] << " " << endl;
return -1;
}
2、现在,由于我们将对这个图像进行一些转换,我们需要一个新的Mat对象来存储它。此外,我们希望这有以下特点:
Mat new_image = Mat::zeros( image.size(), image.type() );
3、我们观察到,cv::Mat::zeros
返回一个基于image.size()
和image.type()
的matlab风格的零初始化器
double alpha = 1.0; /*< Simple contrast control */
int beta = 0; /*< Simple brightness control */
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;
4、现在,执行操作 g ( i , j ) = α ⋅ f ( i , j ) + β g (i, j) =α⋅f (i, j) +β g(i,j)=α⋅f(i,j)+β我们将获得每个像素。由于我们使用的是BGR图像,因此每个像素将有三个值(B、G和R),因此我们也将分别访问它们。下面是代码:
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 );
}
}
}
请注意以下(仅限c++代码):
image.at(y,x)[c]
,其中y是行,x是列,c是B, G或R(0,1或2)。cv::saturate_cast
确保值是有效的。4、 最后,我们创建窗口并按照通常的方式显示图像。
imshow("Original Image", image);
imshow("New Image", new_image);
waitKey();
可以不使用for循环来访问每个像素,我们可以简单地使用这个命令:
image.convertTo(new_image, -1, alpha, beta);
其中cv::Mat::convertTo
将有效执行 n e w _ i m a g e = α ∗ i m a g e + β {new\_image} = \alpha*image + \beta new_image=α∗image+β 。但是,我们想向您展示如何访问每个像素。在任何情况下,这两种方法都能得到相同的结果,但是convertTo更优化,工作速度更快。
1、运行代码,并使用 α = 2.2 和 β = 50 \alpha =2.2和\beta =50 α=2.2和β=50
./BasicLinearTransforms lena.jpg
Basic Linear Transforms
-------------------------
* Enter the alpha value [1.0-3.0]: 2.2
* Enter the beta value [0-100]: 50
在这一段中,我们将运用所学, 通过 调整亮度和对比度 来 纠正曝光不足的图像 。
我们还将看到另一种校正图像亮度的技术——伽马校正。
增加(/减少) β \beta β值,像素值将为每个像素增加(/减少)一个常数值。[0 , 255]之外的像素值将被饱和(即高于(小于)255(/ 0)的像素值将被限制为255(/ 0))。
在浅灰色中,原始图像的直方图,当Gimp中亮度=80时,以深灰色显示
直方图表示每个颜色级别对应的像素数。一个深色的图像会有很多像素颜色值很低,因此直方图会在它的左边出现一个峰值。当添加一个恒定的偏差时,直方图会向右移动,因为我们已经为所有像素添加了一个恒定的偏差。
参数 α \alpha α 将修改灰度级别扩展的方式。。如果 α < 1 \alpha<1 α<1 ,则压缩灰度级别,得到的图像对比度较低。
浅灰色是原始图像的直方图;在Gimp中,对比度<0时 为深灰色
请注意,这些直方图是使用Gimp软件中的亮度对比工具获得的。亮度工具应该与偏置参数 β \beta β相同,但对比度工具似乎与增益 α \alpha α不同,因为输出范围似乎以Gimp为中心(正如您在前面的直方图中注意到的)。
可能会出现这样的情况:使用偏置 β \beta β会提高亮度,但与此同时,由于对比度降低,图像会出现轻微的遮光。增益 α \alpha α可以用来减少这种效果,但由于饱和,我们将失去一些原始明亮区域的细节。
伽马校正可用于校正图像的亮度,使用在输入值和映射输出值之间的 非线性转换:
O = ( I 255 ) γ × 255 O = \left( \frac{I}{255} \right)^{\gamma} \times 255 O=(255I)γ×255
当 γ < 1 \gamma<1 γ<1,原始的暗区会变亮,直方图会向右偏移。当 γ > 1 \gamma>1 γ>1时刚好相反。
下面的图片已经被修正为: α = 1.3 ; β = 40 \alpha =1.3;\beta =40 α=1.3;β=40
整体亮度得到了改善,但你可以注意到,由于使用的数值饱和度(摄影中的高亮剪裁),云现在过饱和。
下面的图像已经被修正为: γ = 0.4 \gamma =0.4 γ=0.4
由于绘图是非线性的,而且不可能象前一种方法那样出现数值饱和,因此伽马校正应倾向于增加较少的饱和效应。
左: α , β \alpha, \beta α,β校正后的直方图; 中心:原始图像的直方图; 右:伽玛校正后的直方图
前面的图比较了三幅图像的直方图(三幅图之间的y范围不相同)。您可以注意到,原始图像直方图的大多数像素值都位于较低部分。在经 α , β \alpha, \beta α,β校正后,我们可以观察到255有一个较大的峰值,由于饱和以及右移造成的。在伽玛校正后,直方图向右偏移,但黑暗区域的像素比明亮区域的像素偏移更多(见伽玛曲线图)。
在本教程中,您看到了调整图像对比度和亮度的两种简单方法。它们是基本的技术,不打算被用来作为一个光栅图形编辑器的替代!
伽马校正代码:
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);
由于只需要计算256个值一次,因此使用了一个查找表来提高计算性能。
图形渲染中的伽马校正
伽玛校正和图像显示在CRT监视器
数字曝光技术