线性变换
前面的幂律变换和对数变换都是非线性变换。我们这里讨论一下线性变换。
线性变换是灰度变换的一种,通过建立灰度映射来调整源图像灰度,从而达到图像增强的目的。一般的成像系统只有一定的灰度范围,亮度的最大值与最小值之比称为对比度。由于成像系统亮度有限,图像常常出现对比度不同的问题,使得人眼看图像时获得的视觉效果很差,通过变换法可以大大改善图像的视觉效果。线性变换公式为g(x,y)=k*f(x,y)+b。其中g(x,y)表示变换后的目标像素值,f(x,y)表示原图像素值,k表示斜率,b表示截距。k的取值和变换效果有以下几种情况:
· 当k>1时,可以用于增强图像对比度。图像的像素值在变换后全部增大,整体显示效果被增强。
· 当k=1时,常用于调节图像亮度。
· 当k<1时,效果与k>1相反,图像的对比度和整体效果都被削弱。
· 当k<0时,源图像较亮区域会变暗,而较暗区域会变亮。如果使得k=-1,b=255,即可让图像实现反色效果。
可以用OpenCV中的convertScaleAbs()函数与convertTo()函数对图像进行线性变换。
convertScaleAbs()函数定义如下:
void cv::convertScaleAbs(cv::InputArray src, cv::OutputArray dst, double alpha = (1.0), double beta = (0.0))
该函数的作用是对输入数组进行缩放、取绝对值,并将结果转换为8位无符号整数。在输入数组的每个元素上,convertScaleAbs函数按顺序执行三个操作:缩放、取绝对值,然后将结果转换为无符号8位整数类型。
参数:
src:输入数组。
dst:输出数组。
alpha:可选的缩放因子。
beta:可选的添加到缩放值的增量。
//该函数允许你对输入数组进行线性变换,并将结果转换为8位无符号整数,通常用于调整图像的对比度和亮度。`alpha`参数控制缩放因子,`beta`参数允许你在缩放后添加一个偏移值。
//例如,你可以使用该函数将图像的对比度增加一倍,亮度增加50,如下所示:
```cpp
cv::Mat inputImage = ...; // 你的输入图像
cv::Mat outputImage;
double alpha = 2.0; // 增加对比度的缩放因子
double beta = 50.0; // 增加的亮度值
cv::convertScaleAbs(inputImage, outputImage, alpha, beta);
//这将使outputImage成为经过线性变换后的图像,其中对比度增加了一倍,亮度增加了50。
convertTo 函数是OpenCV中的一个功能强大的函数,用于执行矩阵或图像的数据类型转换和缩放操作。该函数通常用于将一个矩阵或图像的数据类型更改为另一个数据类型,并可以选择对像素值进行缩放或映射。
由于OpenCv主要支持单通道和三通道的图像,并要求其深度为8为或者16位(即CV_16U),而其他数据类型是不支持的,比如float,则当我们的数据深度和通道数不满足上面的要求时可以使用convertTo函数(用于数据格式转换)或者cvtColor函数(图像颜色空间的转换)。
convertTo函数定义如下:
void cv::convertTo(cv::InputArray src, int dstType, double alpha = 1, double beta = 0, cv::OutputArray dst = cv::noArray())
src:输入数组或图像。
dstType:输出的目标数据类型,通常使用OpenCV中定义的常量,如 CV_8U、CV_32F 等。
数据类型 | 内容 |
---|---|
CV_8U | 8位无符号整数,范围为0到255。 |
CV_8S | 8位有符号整数,范围为-128到127。 |
CV_16U | 16位无符号整数,范围为0到65535。 |
CV_16S | 16位有符号整数,范围为-32768到32767。 |
CV_32S | 32位有符号整数,常用于存储像素差值等。 |
CV_32F | 32位浮点数,常用于图像处理中的浮点数计算。 |
CV_64F | 64位双精度浮点数,用于高精度的浮点数计算。 |
alpha:可选的缩放因子,用于线性缩放像素值。
beta:可选的偏移值,用于在缩放后添加到像素值上。
dst:可选的输出数组或图像,用于存储转换后的结果。如果不提供此参数,则函数会自动创建一个输出对象。
convertTo 函数的主要用途包括:
数据类型转换:将一个矩阵或图像从一个数据类型转换为另一个数据类型。例如,将一个 CV_32F 类型的图像转换为 CV_8U 类型,以便在显示或保存时使用更小的数据类型。
像素值缩放:通过使用 alpha 和 beta 参数来线性缩放像素值,以调整图像的对比度和亮度。这对于图像增强和调整非常有用。
像素值映射:可以使用 alpha 和 beta 参数来执行像素值的映射,例如将像素值限制在特定范围内或将浮点图像映射到固定范围的整数像素值。
创建新数据类型的副本:将一个矩阵或图像的数据类型更改为另一个数据类型的副本,并返回新的副本。
总之,convertTo 函数是OpenCV中用于执行图像或矩阵数据类型转换和像素值缩放的强大工具,可以在图像处理和计算机视觉应用中广泛使用。
使用特性:
#include <opencv2/opencv.hpp>
#include <iostream>
int main() {
// 创建一个单通道浮点图像
cv::Mat floatImage(3, 3, CV_32FC1);
floatImage.setTo(cv::Scalar(1.23f)); // 设置所有像素为1.23
// 创建一个目标图像,将数据类型转换为8位整数
cv::Mat intImage;
floatImage.convertTo(intImage, CV_8UC1);
// 输出源图像和目标图像的数据类型
std::cout << "源图像数据类型: " << floatImage.type() << std::endl;
std::cout << "目标图像数据类型: " << intImage.type() << std::endl;
// 输出源图像和目标图像的像素值
std::cout << "源图像像素值: " << floatImage << std::endl;
std::cout << "目标图像像素值: " << intImage << std::endl;
return 0;
}
全域线性变换
全域线性变换就是将图像中的所有点的灰度值按照线性变换函数进行变换。
1、线性变换
假定原图像f(x,y)的灰度范围为[a,b],希望变换后图像g(x,y)的灰度范围扩展至[c,d],则线性表达式如下:
g(x,y)=[(d-c)(b-a)][f(x,y)-a]+c
在此过程中大于0小于a的灰度变换为c,大于b的灰度变换为d。
2、分段线性变换
为了突出感兴趣的目标或者灰度区间,相对抑制那些不感兴趣的目标与灰度空间。常常采用分段线性变换法来对图像进行处理。分段线性变换法可以对图像任意灰度区间进行扩展或放缩。常用的是三段线性变换法,其数学表达式如下:
if : 0 <= x < a_t
g(x, y) = (c / a_t) * x
if : a_t <= x <= b_t
g(x, y) = (((d - c) / (b_t - a_t)) * (x - a_t)) + c
if : b_t < x <= 255
g(x, y) = (((255 - d) / (255 - b_t)) * (x - b_t)) + d
x:表示输入图像中的像素的原始灰度值。
g(x, y):表示输出图像中的像素的新灰度值,经过线性变换后的结果。
x1:表示第一个分段点的灰度值。在这个点之前,灰度值不发生变化。
x2:表示第二个分段点的灰度值。在这个点之前,灰度值也不发生变化。
y1:表示第一个分段点对应的输出灰度值。这是第一段线性变换的起始值。
y2:表示第二个分段点对应的输出灰度值。这是第二段线性变换的结束值,也是第三段线性变换的起始值。
a_t:表示代码中的阈值 a_t,用于划分第一段和第二段线性变换。
b_t:表示代码中的阈值 b_t,用于划分第二段和第三段线性变换。
c:表示第一段和第二段线性变换的输出灰度值的下限。在第一段线性变换中,灰度值小于 a_t 的像素被映射到 c。
d:表示第二段和第三段线性变换的输出灰度值的上限。在第三段线性变换中,灰度值大于 b_t 的像素被映射到 d。
上面的式子对于灰度区间[a,b]进行了线性变换,而灰度区间[0,a][b,Fmax]受到了压缩。通过细心调节折线拐点位置以及控制分段直线的斜率,可以对任意灰度空间进行扩展或压缩。这种变换适用于黑色或者白色区间附近有噪声干扰的情况。例如照片中的划痕,由于变换后0a,以及bFmax范围内的灰度受到了压缩,因此噪声干扰得到减弱,下面是例子说明。
#include <opencv.hpp>
#include <iostream>
using namespace std;
using namespace cv;
int main()
{
//以灰度图的格式打开图片
Mat srcImage = imread("lena.png",IMREAD_GRAYSCALE);
//两个实验,复制副本
Mat exam1, exam2;
exam1 = srcImage.clone();
exam2 = srcImage.clone();
imshow("原始图片", srcImage);
int a = 128;
int b = 128;
//统计原始图片的灰度取值范围
const int Rows = srcImage.rows;
const int Cols = srcImage.cols;
for(int i = 0; i < Rows; i++) {
for (int j = 0; j < Cols; j++) {
//将当前像素的灰度值与最大值和最小值比较
a = a > srcImage.at<uchar>(i,j) ? srcImage.at<uchar>(i,j) : a;
b = b < srcImage.at<uchar>(i,j) ? srcImage.at<uchar>(i,j) : b;
}
}
//输出灰度值取值范围
cout << a << " " << b << endl;
//方法一线性变换
//模拟图片的像素主要分布在[a_t,b_t]中
int a_t = a + 20;
int b_t = b - 20;
//应用公式2-4,重新计算像素值
int d = 255;
int c = 0;
for(int i = 0; i < Rows; i++) {
for (int j = 0; j < Cols; j++) {
if (exam1.at<uchar>(i,j) < a_t) {
exam1.at<uchar>(i,j) = c;
continue;
}
if (exam1.at<uchar>(i,j) > b_t) {
exam1.at<uchar>(i,j) = d;
continue;
}
//模拟的取值范围为[a_t,b_t]
exam1.at<uchar>(i,j) = (((double)(d - c))/(b_t - a_t)) * (exam1.at<uchar>(i,j)-a_t) + c;
}
}
imshow("线性变换", exam1);
//方法二、分段线性变换
int Gmax = 255;
c = 10;
d = 230;
for(int i = 0; i < Rows; i++) {
for (int j = 0; j < Cols; j++) {
if (exam2.at<uchar>(i,j) < a_t) {
exam2.at<uchar>(i,j) = (double(c)/a_t) * (exam2.at<uchar>(i,j) - a);
continue;
}
if (exam2.at<uchar>(i,j) > b_t) {
exam2.at<uchar>(i,j) = (double((Gmax - d))/(b - b_t)) * (exam2.at<uchar>(i,j) - b_t) + d;
continue;
}
exam2.at<uchar>(i,j) = (((double)(d - c))/(b_t - a_t)) * (exam2.at<uchar>(i,j)-a_t) + c;
}
}
imshow("分段线性变换", exam2);
waitKey(0);
return 0;
}