1.灰度的线性变换
1.1 概述
原稿图像被数字化后,可使用灰度直方图检查输入图像的质量。如果检查表明规定的灰度分级没有得到充分利用,就可以用线性灰度变换来解决。线性灰度变换将原图像的灰度动态范围按线性关系式扩展到指定范围或整个动态范围。
图像的线性变换是图像处理的基本运算,通常应用在调整图像的画面质量方面,如图像对比度、亮度及反转等操作。灰度的线性变换就是将图像中所有点的灰度按照线性灰度变换函数进行变换。
我们令f和g分别为位置上的像素增强前后的灰度值,a、b为原图像所占用灰度级别的最小值和最大值,c、d为增强后的图像所占用灰度级别的最小值和最大值,令|d - c |总是大于|b - a |。如果把f当作横轴,g当作纵轴,可以得到如图5-3所示的坐标图,原来[a,b]的范围被扩大到[c,d]了。
学过解析几何的读者应该知道直线方程有好多种,比如点斜式、斜截式、截距式、两
点式、一般式等。其中点斜式的形式如下:


除了点斜式之外,我们还可以将其转换为斜截式方程。首先复习一下斜截式方
程:y=kx+b。其中k为直线斜率,b为纵轴上的截距,其值也可以是b=y-kx。如果(a,c)是直
线上的一点,那么有b=c-ka,因此可以将上面的点斜式继续转换为:

这样就转换成斜截式方程。
·当k > 1时,输出图像的对比度将增大。
·当k < 1时,输出图像的对比度将减小。
·当k = 1且b≠0时,所有图像的灰度值上移或下移,其效果是使整个图像更暗或更亮。
·当k = 1、b = 0时,输出图像和输入图像相同。
·当k = -1、b = 255时,输出图像的灰度正好反转。
·当k < 0且b > 0时,暗区域将变亮,亮区域将变暗,点运算完成了图像求补运算。
实际上,k表示图像对比度变化,b表示图像亮度变化。当|k|>1时,图像变换代表对比度增加操作;当|k|<1时,图像变换代表对比度减小操作。当b>0时,表示图像变换操作是亮度增加操作;当b<0时,表示图像变换操作是亮度减小操作。
在曝光不足或过度的情况下,图像灰度可能会局限在一个很小的范围内。这时在显示器上看到的将是一个模糊不清、似乎没有灰度层次的图像。采用线性变换对图像的每一个像素灰度做线性拉伸,可有效地改善图像视觉效果,如图所示的两幅图,右边的图就
是灰度线性变换过的。
1.2 示例
#include <iostream>
#include<opencv2/opencv.hpp>
using namespace cv; //所有opencv类都在命名空间cv下
using namespace std;
// 图像线性变换操作
cv::Mat linearTransform(cv::Mat src, float alpha, int beta)
{
if (src.empty()) {
std::cout << "No data!" << std::endl;
}
const int rows = src.rows;
const int cols = src.cols;
cv::Mat dst = cv::Mat::zeros(src.size(), src.type());
// 图像元素遍历,并像素运用公式
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (src.channels() == 3) { //3通道图像
int b = src.at<Vec3b>(row, col)[0];
int g = src.at<Vec3b>(row, col)[1];
int r = src.at<Vec3b>(row, col)[2];
dst.at<Vec3b>(row, col)[0] = saturate_cast<uchar>((alpha * b + beta));
dst.at<Vec3b>(row, col)[1] = saturate_cast<uchar>((alpha * g + beta));
dst.at<Vec3b>(row, col)[2] = saturate_cast<uchar>((alpha * r + beta));
}
else if (src.channels() == 1) { //灰度图像,灰度图的通道数是1
int v = src.at<uchar>(row, col);
dst.at<uchar>(row, col) = saturate_cast<uchar>(alpha * v + beta);
}
}
}
return dst;
}
int main()
{
// 图像获取及验证
cv::Mat srcImage = cv::imread("lakeWater.jpg");
if (!srcImage.data)
return -1;
cv::imshow("src", srcImage);
//cv::waitKey(0);
// 线性变换
float a = 1.2;
int b = 50;
cv::Mat new_image = linearTransform(srcImage, a, b);
cv::imshow("result", new_image);
cv::waitKey(0);
return 0;
}

线性变换是一种简单的亮度和对比度调整方法,通过对每个像素的灰度级别应用线性变换公式来实现。对每个像素应用公式output_pixel = input_pixel * alpha + beta,其中alpha控制对比度,beta控制亮度。增大alpha值可以增加对比度,增大beta值可以增加亮度。
lakeWater.jpg是工程目录下的图片。在自定义函数linearTransform中,我们根据线性变换表达式进行代码实现,原理就是对每个像素应用公式。由于是针对每个像素,因此用了两个for循环来遍历所有像素,并且在第二个for循环中对传入的图像进行分类,如果是
彩色图像(彩色图片有红、绿、蓝三个通道),那么每个像素有3个值,我们就需要对b、
g、r 3个变量分别套用公式。如果src.channels()等于1,也就是读入的图像是灰度图(灰度
图的通道数为1),那么我们只需要套用一次公式,即可获取原来的像素值,然后运用公
式计算出结果像素值,并赋值给目标图像对应的像素位置。
本例中大量运行了Mat类中的at方法,它的作用是获取图像矩阵某点的值或改变某点的值。比如,对于单通道图像通常这样使用:
image.at(i,j) = 255;
而对于RGB三通道图像,通常这样使用:
image.at(i,j)[0] = 255;
image.at(i,j)[1] = 255;
image.at(i,j)[2] = 255;
不过,相对来讲,Mat::at()取值或改变某点的像素值比较耗时,因此还可以采用Mat的模板子类Mat_来实现对图像矩阵中某个值的获取和改变。此时单通道的使用方法如
下:
Mat_ img = image;
img(i,j) = 255;
而三通道RGB的使用方法如下:
Mat_ img = image;
img(i,j)[0] = 255;
img(i,j)[1] = 255;
img(i,j)[2] = 255;
另外一个要注意的地方是saturate_cast。在OpenCV中,saturate_cast是一个模板函数,用于正确地将一个数值从一种类型转换到另一种类型,同时确保结果在目标类型的有效范围内。这个操作在图像处理中特别有用,比如当像素值在经过计算后可能超出其数据类型允许的范围时,saturate_cast能够保证这样的值被正确地“饱和”,即如果计算结果超出了数据类型的表示范围,结果会被设定为该数据类型能表示的最大或最小值。
比如,对于uchar类型(无符号字符,范围为0到255)的像素值,任何超过255的结果都不能被直接存储在一个uchar变量中。如果有两个uchar类型的像素值,想要将它们相加:
uchar a = 200;
uchar b = 100;
uchar c = a + b; // 直接相加可能导致溢出,因为结果300超出了uchar的范围
使用saturate_cast可以避免溢出:
uchar c = cv::saturate_cast(a + b);
在这个例子中,尽管a + b的结果是300,超出了uchar能表示的范围,但saturate_cast会将结果“饱和”到uchar能表示的最大值,即255。
saturate_cast通过模板特化和函数重载实现了对不同数据类型之间转换的支持。它可以处理从浮点到整数、从长整型到短整型等多种类型的转换,确保在转换过程中值被正确地“饱和”。
转载自 CSDN-专业IT技术社区
原文链接:https://blog.csdn.net/m0_37251750/article/details/164022835




