改变图像的对比度和亮度

英文链接:Changing the contrast and brightness of an image!

文章目录

    • 目标
    • 理论
      • 图像处理
      • 像素处理
      • 亮度和对比度调整
    • 源码
    • 解释
    • 结果
    • 实例
      • 亮度和对比度调整
      • 图像灰度校正(Gamma 校正)
      • 校正曝光不足的图像
      • 代码
      • 其它资源

目标

  • 访问像素值
  • 用零初始化一个矩阵
  • 了解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风格的零初始化器

  • 现在我们获取用户需要输入的 α \alpha α β \beta β的值:
    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)。
  • 因为操作 α ⋅ p ( i , j ) + β α⋅p (i, j) +β αp(i,j)+β可能使值超出范围或者非整数(如果α是float),我们使用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

2、得到
改变图像的对比度和亮度_第1张图片

实例

在这一段中,我们将运用所学, 通过 调整亮度和对比度 来 纠正曝光不足的图像 。
我们还将看到另一种校正图像亮度的技术——伽马校正。

亮度和对比度调整

增加(/减少) β \beta β值,像素值将为每个像素增加(/减少)一个常数值。[0 , 255]之外的像素值将被饱和(即高于(小于)255(/ 0)的像素值将被限制为255(/ 0))。
改变图像的对比度和亮度_第2张图片
在浅灰色中,原始图像的直方图,当Gimp中亮度=80时,以深灰色显示

直方图表示每个颜色级别对应的像素数。一个深色的图像会有很多像素颜色值很低,因此直方图会在它的左边出现一个峰值。当添加一个恒定的偏差时,直方图会向右移动,因为我们已经为所有像素添加了一个恒定的偏差。

参数 α \alpha α 将修改灰度级别扩展的方式。。如果 α < 1 \alpha<1 α<1 ,则压缩灰度级别,得到的图像对比度较低。
改变图像的对比度和亮度_第3张图片
浅灰色是原始图像的直方图;在Gimp中,对比度<0时 为深灰色

请注意,这些直方图是使用Gimp软件中的亮度对比工具获得的。亮度工具应该与偏置参数 β \beta β相同,但对比度工具似乎与增益 α \alpha α不同,因为输出范围似乎以Gimp为中心(正如您在前面的直方图中注意到的)。

可能会出现这样的情况:使用偏置 β \beta β会提高亮度,但与此同时,由于对比度降低,图像会出现轻微的遮光。增益 α \alpha α可以用来减少这种效果,但由于饱和,我们将失去一些原始明亮区域的细节。

图像灰度校正(Gamma 校正)

伽马校正可用于校正图像的亮度,使用在输入值和映射输出值之间的 非线性转换:
O = ( I 255 ) γ × 255 O = \left( \frac{I}{255} \right)^{\gamma} \times 255 O=(255I)γ×255
改变图像的对比度和亮度_第4张图片
γ < 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

由于绘图是非线性的,而且不可能象前一种方法那样出现数值饱和,因此伽马校正应倾向于增加较少的饱和效应。
改变图像的对比度和亮度_第5张图片
左: α , β \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监视器
数字曝光技术

你可能感兴趣的:(OpenCV)