OpenCV学习笔记08_对比度增强

1.拉普拉斯锐化

拉普拉斯变换是工程数学中常用的一种积分变换;
拉普拉斯算子是n维欧几里得空间的一个二阶微分算子;
在这里插入图片描述

具有各向同性,对数字图像的一阶导数为:

在这里插入图片描述
二阶导数为:
在这里插入图片描述

所以拉普拉斯算子为:
在这里插入图片描述

拉普拉斯算子四邻域模板如下所示:
在这里插入图片描述

八邻域:
在这里插入图片描述

卷积的图示:
在这里插入图片描述

然后通过滑动卷积核,就可以得到整张图片的卷积结果。

OpenCV中拉普拉斯边缘算子的函数为:

CV_EXPORTS_W void Laplacian( 
InputArray src, 
OutputArray dst, 
int ddepth,
int ksize = 1, 
double scale = 1, 
double delta = 0,
int borderType = BORDER_DEFAULT );

参数解释:
1、InputArray src:输入图像

2、OutputArray dst:输出图像

3、int ddepth:表示输出图像的深度

4、depth 图像元素的位深度,可以是下面的其中之一:

位深度------------------------取值范围

CV_8U - 无符号8位整型…0–255

CV_8S - 有符号8位整型… -128–127

CV_16U - 无符号16位整型 0–65535

CV_16S - 有符号16位整型 -32768–32767

CV_32S - 有符号32位整型 -65535–65535

CV_32F - 单精度浮点数

CV_64F - 双精度浮点数
5、int ksize=1:表示拉普拉斯核的大小,1表示核的大小是三:
6、double scale =1:表示是否对图像进行放大或者缩小
7、double delta=0:表示是否在输出的像素中加上一个量
8、int borderType=BORDER_DEFAULT:表示处理边界的方式,一般默认

拉普拉斯锐化步骤:
方法一:
步骤1,使用拉普拉斯边缘算子检测边缘(如果是UCHAR类型需要保留负值):

//内核为 0, 1, 0, 1, -4, 1, 0, 1, 0		所以检测的边缘是负值
cv::Laplacian(src, lapl_1, CV_8U,1,-1);
//加负号的原因是保留负数,不然Uchar类型直接省略负数

步骤2,使用原图像加上边缘检测之后的图像:

srcaddlapl_1_add = src + lapl_1;//类型必须一至
//也可以转为浮点型(注意归一化)运算,最后再转成UCHAR型

方法二:使用图像卷积运算函数filter2D()

CV_EXPORTS_W void filter2D( 
InputArray src, 
OutputArray dst, 
int ddepth,
InputArray kernel, 
Point anchor = Point(-1,-1),
double delta = 0, 
int borderType = BORDER_DEFAULT );

参数解释:
1、InputArray src: 输入图像

2、OutputArray dst: 输出图像,和输入图像具有相同的尺寸和通道数量

3、int ddepth: 目标图像深度,如果没写将生成与原图像深度相同的图像。

支持深度如下(-1为原图像深度):
src.depth() = CV_8U,--------------------ddepth = -1/CV_16S/CV_32F/CV_64F
src.depth() = CV_16U/CV_16S, -----ddepth = -1/CV_32F/CV_64F
src.depth() = CV_32F, ------------------ddepth = -1/CV_32F/CV_64F
src.depth() = CV_64F, ------------------ddepth = -1/CV_64F

4、InputArray kernel: 卷积核(或者是相关核),一个单通道浮点型矩阵。如果想在图像不同的通道使用不同的kernel,可以先使用split()函数将图像通道事先分开。

5、Point anchor: 内核的基准点(anchor),其默认值为(-1,-1)说明位于kernel的中心位置。基准点即kernel中与进行处理的像素点重合的点。

6、double delta: 在储存目标图像前可选的添加到像素的值,默认值为0

7、int borderType: 像素向外逼近的方法,默认值是BORDER_DEFAULT,即对全部边界进行计算。

卷积核kernel:

cv::Mat  kernel = (cv::Mat_<float>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
//Mat_类型是用来简化代码的

拉普拉斯边缘检测算子:

拉普拉斯锐化算子:

2.对数变换

公式:
在这里插入图片描述

对数变换可以将图像的低灰度值部分扩展,显示出低灰度部分更多的细节,将其高灰度值部分压缩,减少高灰度值部分的细节,从而达到强调图像低灰度部分的目的。

步骤:

//log 对比度增强
	cv::Mat   src_log(src.size(),CV_64FC3);
	for (int i = 0; i< src.rows;i++)
	{
		for (int j = 0; j< src.cols;j++)
		{
			src_log.at<cv::Vec3d>(i, j)[0] = log(1 + src.at<cv::Vec3b>(i, j)[0]);
			src_log.at<cv::Vec3d>(i, j)[1] = log(1 + src.at<cv::Vec3b>(i, j)[1]);
			src_log.at<cv::Vec3d>(i, j)[2] = log(1 + src.at<cv::Vec3b>(i, j)[2]);

		}

	}
	//此处归一化是因为src未归一化,得出的log值大于1,显示时候乘以255值会非常大
	normalize(src_log, src_log, 0, 255,cv::NORM_MINMAX,CV_8UC3);
	cv::imshow("src_log", src_log);

对数变换结果:
OpenCV学习笔记08_对比度增强_第1张图片

3.直方图均衡化

直方图均衡化公式:
在这里插入图片描述

其中f(x)为均衡化之后的函数值,

L为max像素级,
在这里插入图片描述
为概率密度函数的分布函数,

为像素级为x的像素个数/总像素个数。

理论依据:

如果一个图像占有全部可能的灰度级,并且均匀分布。那么这个图像具有较高的对比度,并且具有多变的灰度色调。反映到图像上面,就是细节特别丰富、图像看起来质量很高。

步骤:
1、统计直方图每个灰度级出现的次数;
2、累计归一化的直方图, 在累计直方图中,概率相近的原始值,会被处理为相同的值。
3、计算新的像素值。

有两个问题比较难懂,一是为什么要选用累积分布函数,二是为什么使用累积分布函数处理后像素值会均匀分布。

第一个问题。均衡化过程中,必须要保证两个条件:

①像素无论怎么映射,一定要保证原来的大小关系不变,较亮的区域,依旧是较亮的,较暗依旧暗,只是对比度增大,绝对不能明暗颠倒;
②如果是八位图像,那么像素映射函数的值域应在0和255之间的,不能越界。综合以上两个条件,累积分布函数是个好的选择,因为累积分布函数是单调增函数(控制大小关系),并且值域是0到1(控制越界问题),所以直方图均衡化中使用的是累积分布函数。

第二个问题。累积分布函数具有一些好的性质,那么如何运用累积分布函数使得直方图均衡化?

比较概率分布函数和累积分布函数,前者的二维图像是参差不齐的,后者是单调递增的。

在OpenCV中使用封装好的函数equalizeHist();

CV_EXPORTS_W void equalizeHist( InputArray src, OutputArray dst );

参数解释:
1、InputArray src 输入图像;
2、OutputArray dst 输出图像;
原始灰度图:
OpenCV学习笔记08_对比度增强_第2张图片

直方图均衡化结果(灰度单通道):
OpenCV学习笔记08_对比度增强_第3张图片

4.伽马校正

伽马变换的公式为:
s = C r γ s=C r^{\gamma} s=Crγ
s为变换之后图像的像素值,C为灰度缩放系数,通常取1,r 为原始图像的像素值, γ {\gamma } γ为伽马因子,控制的整个算法的缩放程度,伽马变换也被称作幂变换。

注意:其中r的取值范围为[0,1],所以需要将uchar型的数据转为float型,且需要归一化。

cv::Mat::convertTo(dst, CV_32FC3, 1 / 255.0);
//其中dst为目标图, CV_32FC3为要转化的类型

在整数表示的颜色空间中,数值范围是0-255,但在浮点数表示的颜色空间中,数值范围是0-1.0,所以要把0-255归一化。
CV_8UC3的灰度或BGR图像的颜色分量都在0~255之间。直接imshow可以显示图像。
CV_32FC3取值范围为0~1.0,imshow的时候会把图像x255后再显示。
imwrite不能保存浮点数类型的图片。

#include 


void main()
{
	cv::Mat src = cv::imread("C:/Users/Administrator/Desktop/test.jpg");

	cv::Mat dst,gamma;
	src.convertTo(dst, CV_32FC3,(double) 1 / 255);
	

	float ga = 0.5;
	cv::pow(dst,ga, gamma);

	
	cv::imshow("src", src);
	cv::imshow("dst",dst);
	cv::imshow("gamma", gamma);
	cv::waitKey(0);
}

如图所示:

代码附录:

#include 



void main()
{
	cv::Mat src = cv::imread("C:/Users/Administrator/Desktop/test.jpg");
	cv::Mat dst,dst_lap, gray,gamma, equal, 
	lapl,lapl_1,src_lap, srcaddlapl_1_add,srcaddlapl_add, srcaddlapl_f_add;

	/*
	dst:CV_32F类型,伽马变换源图;
	dst_lap:CV_32F类型,拉普拉斯变换源图;
	gamma:CV_32F类型,伽马变换输出图像;
	gray:CV_8UC1类型,灰度图;
	equal:CV_8UC1类型,直方图均衡化图像;
	lapl:Laplacian函数卷积之后的图像;
	src_lap:直接使用拉普拉斯锐化算子对原始图像操作得出的图像;
	srcaddlapl_add:Laplacian函数得到的图像与原图像运算;
	srcaddlapl_1_add:CV_8U类型,scale乘了-1转为不省略负数的UCHAR类型;
	srcaddlapl_f_add:拉普拉斯边缘算子卷积之后与原图像相加的图像;
	*/

	cv::cvtColor(src, gray, cv::COLOR_BGR2GRAY);
	src.convertTo(dst, CV_32F, (double)1 / 255);
	src.convertTo(dst_lap, CV_32F, (double)1 / 255);
	/*lapl.convertTo(lapl, CV_64F);*/

	//gamma变换
	double gm = 27 / 40.0;
	cv::pow(dst, gm, gamma);

	//直方图均衡
	cv::equalizeHist(gray, equal);

	//拉普拉斯变换 (内核为 0, 1, 0, 1, -4, 1, 0, 1, 0)
	cv::Laplacian(src, lapl_1, CV_8U,1,-1);//加负号的原因是保留负数,
	//不然Uchar类型直接省略负数

	srcaddlapl_1_add = src + lapl_1;

	cv::Laplacian(dst_lap, lapl, CV_32F);
	cv::imshow("dst_lap", dst_lap);
	cv::imshow("lapl", lapl);
	cv::imshow("lapl_1", lapl_1);
	srcaddlapl_add = dst_lap - lapl;



	//拉普拉斯边缘检测算子与原图相加与拉普拉斯锐化算子的区别
	cv::Mat test_lap,test_src;
	cv::Mat  kernel_1 = (cv::Mat_<float>(3, 3) << 0, -1, 0, -1, 4, -1, 0, -1, 0);
	cv::filter2D(src, test_lap, CV_8U, kernel_1);
	cv::Mat  kernel_2 = (cv::Mat_<float>(3, 3) << 0,0,0,0,1,0,0,0,0);
	cv::filter2D(src, test_src, CV_8U, kernel_2);
	srcaddlapl_f_add = test_src + test_lap  ;
	
	

	//拉普拉斯锐化
	cv::Mat  kernel = (cv::Mat_<float>(3, 3) << 0, -1, 0, -1, 5, -1, 0, -1, 0);
	cv::filter2D(src, src_lap, CV_8UC3, kernel);
	
	
	
	//log 对比度增强
	cv::Mat   src_log(src.size(),CV_64FC3);
	for (int i = 0; i< src.rows;i++)
	{
		for (int j = 0; j< src.cols;j++)
		{
			src_log.at<cv::Vec3d>(i, j)[0] = log(1 + src.at<cv::Vec3b>(i, j)[0]);
			src_log.at<cv::Vec3d>(i, j)[1] = log(1 + src.at<cv::Vec3b>(i, j)[1]);
			src_log.at<cv::Vec3d>(i, j)[2] = log(1 + src.at<cv::Vec3b>(i, j)[2]);

		}

	}
	//此处归一化是因为src未归一化,得出的log值大于1,显示时候乘以255值会非常大
	normalize(src_log, src_log, 0, 255,cv::NORM_MINMAX,CV_8UC3);
	cv::imshow("src_log", src_log);
	


	cv::imshow("src", src);
	cv::imshow("lapl", lapl);	
	cv::imshow("gamma", gamma);
	cv::imshow("equal", equal);
	cv::imshow("src_lap", src_lap);
	cv::imshow("test_src", test_src);
	cv::imshow("srcaddlapl_add", srcaddlapl_add);
	cv::imshow("srcaddlapl_f_add", srcaddlapl_f_add);
	cv::imshow("srcaddlapl_1_add", srcaddlapl_1_add);
	cv::waitKey(0);
}

你可能感兴趣的:(笔记,图像处理,编程原理,opencv,人工智能,计算机视觉)