OpenCV4入门教程043:线性变换

索引地址:系列索引

线性变换

线性映射( linear mapping)是从一个向量空间V到另一个向量空间W的映射且保持加法运算和数量乘法运算,而线性变换(linear transformation)是线性空间V到其自身的线性映射。

也就是说会改变原图片每个像素的值,像素值最直观的感受就是图像色彩方面的。也就是调整图像亮度和对比度。

g(i,j)=αf(i,j)+βg(i,j)= \alpha f(i,j) + \beta ,其中 α>0\alpha>0(对比度),β\beta是增益变量(亮度)

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
//-----------------------------------【头文件包含部分】---------------------------------------
// 描述:包含程序所依赖的头文件
//----------------------------------------------------------------------------------------------
#include "opencv2/imgproc/imgproc.hpp"
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>

//-----------------------------------【命名空间声明部分】---------------------------------------
// 描述:包含程序所使用的命名空间
//-----------------------------------------------------------------------------------------------
using namespace std;
using namespace cv;

//-----------------------------------【全局函数声明部分】--------------------------------------
// 描述:全局函数声明
//-----------------------------------------------------------------------------------------------
static void ContrastAndBright(int, void *);

//-----------------------------------【全局变量声明部分】--------------------------------------
// 描述:全局变量声明
//-----------------------------------------------------------------------------------------------
int g_nContrastValue; //对比度值
int g_nBrightValue; //亮度值
Mat g_srcImage, g_dstImage;
//-----------------------------------【main( )函数】--------------------------------------------
// 描述:控制台应用程序的入口函数,我们的程序从这里开始
//-----------------------------------------------------------------------------------------------
int main() {
// 读入用户提供的图像
g_srcImage = imread("1.jpg");
if (!g_srcImage.data) {
printf("读取g_srcImage图片错误~! \n");
return false;
}
g_dstImage = Mat::zeros(g_srcImage.size(), g_srcImage.type());

//设定对比度和亮度的初值
g_nContrastValue = 80;
g_nBrightValue = 80;

//创建窗口
namedWindow("Result", 1);

//创建轨迹条
createTrackbar("对比度:", "Result", &g_nContrastValue, 300, ContrastAndBright);
createTrackbar("亮 度:", "Result", &g_nBrightValue, 200, ContrastAndBright);

//调用回调函数
ContrastAndBright(g_nContrastValue, 0);
ContrastAndBright(g_nBrightValue, 0);

//输出一些帮助信息
cout << endl
<< "\t运行成功,请调整滚动条观察图像效果\n\n"
<< "\t按下“q”键时,程序退出\n";

//按下“q”键时,程序退出
while (char(waitKey(1)) != 'q') {
}
return 0;
}

//-----------------------------【ContrastAndBright( )函数】------------------------------------
// 描述:改变图像对比度和亮度值的回调函数
//-----------------------------------------------------------------------------------------------
static void ContrastAndBright(int, void *) {
// 创建窗口
namedWindow("Original", 1);

// 三个for循环,执行运算 g_dstImage(i,j) = a*g_srcImage(i,j) + b
for (int y = 0; y < g_srcImage.rows; y++) {
for (int x = 0; x < g_srcImage.cols; x++) {
for (int c = 0; c < 3; c++) {
g_dstImage.at<Vec3b>(y, x)[ c ] = saturate_cast<uchar>(
(g_nContrastValue * 0.01) * (g_srcImage.at<Vec3b>(y, x)[ c ]) + g_nBrightValue);
}
}
}

// 显示图像
imshow("Original", g_srcImage);
imshow("Result", g_dstImage);
}

测试结果:

brightness

对比度拉伸变换

下面是典型的对比度拉伸变换。点(r1,s1)和(r2,s2)的位置控制变换函数的形状。

如果r1=r2,s1=s2,则变换为一线性函数;

若r1=r2,s1=0且s2=L-1,则是阈值处理函数,产生一幅二值图像;

处理一幅8bit低对比度图像,

(r1,s1)=(rmin,0),(r2,s2)=(rmax,L1);(r1,s1)=(r_{min},0),(r2,s2)=(r_{max},L-1);

其中rmin,rmaxr_{min},r_{max}是图像中最小和最大灰度级;

因此,变换函数把灰度级由原范围线性的拉伸至整个范围[0,L1][0,L-1]

原理图

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 功能:代码 3-23 对比度拉伸
// 作者:朱伟 zhu1988wei@163.com
// 来源:《OpenCV图像处理编程实例》
// 博客:http://blog.csdn.net/zhuwei1988
// 更新:2016-8-1
// 说明:版权所有,引用或摘录请联系作者,并按照上面格式注明出处,谢谢。//
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;

cv::Mat contrastStretch(cv::Mat srcImage) {
cv::Mat resultImage = srcImage.clone();
int nRows = resultImage.rows;
int nCols = resultImage.cols;
// 图像连续性判断
if (resultImage.isContinuous()) {
nCols = nCols * nRows;
nRows = 1;
}
// 图像指针操作
uchar *pDataMat;
int pixMax = 0, pixMin = 255;
// 计算图像的最大最小值
for (int j = 0; j < nRows; j++) {
pDataMat = resultImage.ptr<uchar>(j);
for (int i = 0; i < nCols; i++) {
if (pDataMat[ i ] > pixMax)
pixMax = pDataMat[ i ];
if (pDataMat[ i ] < pixMin)
pixMin = pDataMat[ i ];
}
}
// 对比度拉伸映射
for (int j = 0; j < nRows; j++) {
pDataMat = resultImage.ptr<uchar>(j);
for (int i = 0; i < nCols; i++) {
pDataMat[ i ] = (pDataMat[ i ] - pixMin) * 255 / (pixMax - pixMin);// 255/(pixMax - pixMin)是斜率 y=k(x-a)
}
}
return resultImage;
}

void contrastStretch2(cv::Mat &srcImage)
{
if( srcImage.empty() ){
std::cerr << "image empty" << std::endl;
return;
}
// 计算图像的最大最小值
double pixMin,pixMax;
cv::minMaxLoc(srcImage,&pixMin,&pixMax);
std::cout << "min_a=" << pixMin << " max_b=" << pixMax << std::endl;

//create lut table
cv::Mat lut( 1, 256, CV_8U);
for( int i = 0; i < 256; i++ ){
if (i < pixMin) lut.at<uchar>(i)= 0;
else if (i > pixMax) lut.at<uchar>(i)= 255;
else lut.at<uchar>(i)= static_cast<uchar>(255.0*(i-pixMin)/(pixMax-pixMin)+0.5);
}
//apply lut
LUT( srcImage, lut, srcImage );
}

int main() {
cv::Mat srcImage = cv::imread("lena.jpg");
if (!srcImage.data)
return 0;
cv::Mat srcGray;
cvtColor(srcImage, srcGray, COLOR_BGR2GRAY);

//std::vector<cv::Mat> bgr;
//split( srcImage, bgr );
//contrastStretch2(bgr[0]);
//contrastStretch2(bgr[1]);
//contrastStretch2(bgr[2]);
//merge(bgr, srcGray);

imshow("srcGray", srcGray);
cv::Mat resultImage = contrastStretch(srcGray);
cv::imshow("resultImage", resultImage);
cv::waitKey(0);
return 0;
}

测试效果:

strencth


OpenCV4入门教程043:线性变换
https://feater.top/opencv/opencv-linear-transform/
作者
JackeyLea
发布于
2020年9月10日
许可协议