拉普拉斯变换是工程数学中常用的一种积分变换;
拉普拉斯算子是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_类型是用来简化代码的
拉普拉斯边缘检测算子:
拉普拉斯锐化算子:
对数变换可以将图像的低灰度值部分扩展,显示出低灰度部分更多的细节,将其高灰度值部分压缩,减少高灰度值部分的细节,从而达到强调图像低灰度部分的目的。
步骤:
//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);
其中f(x)为均衡化之后的函数值,
为像素级为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 输出图像;
原始灰度图:
伽马变换的公式为:
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);
}