OpenCV学习 day9 自定义线性滤波 图像边缘补偿 边缘算子
一、自定义滤波器
代码演示:
//Sboel算子 Mat kernel_x = (Mat_<int>(3, 3) << -1, 0, 1, -2, 0, 2, -1 ,0, 1); //X方向 Mat kernel_y = (Mat_<int>(3, 3) << -1, -2, -1, 0, 0, 0, 1, 2, 1); //Y方向 filter2D(src, dst, -1, kernel_x, Point(-1, -1), 0.0);
自定义高斯滤波
// 5*5大小的高斯核 Mat kernel = Mat::ones(Size(5, 5), CV_32F) / (float)(5* 5); filter2D(src, dst, -1, kernel, Point(-1, -1));
二、边缘补偿
在卷积开始之前增加边缘像素,填充的像素值为0或者RGB黑色,比如3x3在四周各填充1个像素的边缘,这样就确保图像的边缘被处理,在卷积处理之后再去掉这些边缘。
openCV中默认的处理方法是: BORDER_DEFAULT,
此外常用的还有如下几种:
- BORDER_CONSTANT – 填充边缘用指定像素值
- BORDER_REPLICATE – 填充边缘像素用已知的边缘像素值。
- BORDER_WRAP – 用另外一边的像素来补偿填充
API:
copyMakeBorder( - Mat src, // 输入图像 - Mat dst, // 添加边缘图像 - int top, // 边缘填充长度,一般上下左右都取相同值, - int bottom, - int left, - int right, - int borderType // 边缘类型 - Scalar value )
几种填充类型:
enum BorderTypes { BORDER_CONSTANT = 0, //常值填充 BORDER_REPLICATE = 1, //原图边缘复制 BORDER_REFLECT = 2, // BORDER_WRAP = 3, //原图的另一边复制到这边 BORDER_REFLECT_101 = 4, // BORDER_TRANSPARENT = 5, // BORDER_REFLECT101 = BORDER_REFLECT_101, // BORDER_DEFAULT = BORDER_REFLECT_101, // BORDER_ISOLATED = 16 // };
三、边缘算子
如下图所示,相邻像素之间发生较大改变时,它它们方向上的导数值也会很大
1,Sobel算子
是离散微分算子(discrete differentiation operator),用来计算图像灰度的近似梯度
Soble算子功能集合高斯平滑和微分求导
又被称为一阶微分算子,求导算子,在水平和垂直两个方向上求导,得到图像X方向与Y方向梯度图像
opencv中使用改进版Scharr的算子如下:
API:
cv::Sobel ( InputArray Src // 输入图像 OutputArray dst// 输出图像,大小与输入图像一致 int depth // 输出图像深度. 通常深度比输入图像要高 防止像素值被截断 int dx. // X方向,几阶导数 int dy // Y方向,几阶导数. int ksize // SOBEL算子kernel大小,必须是1、3、5、7、 double scale = 1 double delta = 0 int borderType = BORDER_DEFAULT )
Scharr:
cv::Scharr ( InputArray Src // 输入图像 OutputArray dst// 输出图像,大小与输入图像一致 int depth // 输出图像深度. int dx. // X方向,几阶导数 int dy // Y方向,几阶导数. double scale = 1 double delta = 0 int borderType = BORDER_DEFAULT )
代码演示:
思路:
- 高斯平滑
- 转灰度
- 求X、Y方向梯度
- 梯度图融合
int sobel_boder(Mat src) { Mat blur_src, gray_src, dst; Mat xgrad, ygrad; GaussianBlur(src, blur_src, Size(3, 3), 0, 0); //高斯模糊 cvtColor(blur_src, gray_src, COLOR_BGR2GRAY); //转灰度 Sobel(gray_src, xgrad, CV_16S, 1, 0, 3); //X方向求梯度 Sobel(gray_src, ygrad, CV_16S, 0, 1, 3); //Y方向求梯度 //Scharr(gray_src, xgrad, CV_16S, 1, 0, 3); //Y方向求梯度 //Scharr(gray_src, ygrad, CV_16S, 0, 1, 3); //Y方向求梯度 convertScaleAbs(xgrad, xgrad); //计算图像的像素绝对值,输出到图像B convertScaleAbs(ygrad, ygrad); imshow("x grad", xgrad); imshow("y grad", ygrad); addWeighted(xgrad, 0.5, ygrad, 0.5,0,dst); // X Y方向梯度融合 imshow("x y add result", dst); //标准的Sobel最后梯度计算 Mat xygrad = Mat(xgrad.size(), xgrad.type()); printf("type: %d\n", xgrad.type()); for (int row = 0; row < xgrad.rows; row++) { for (int col = 0; col < xgrad.cols; col++) { int xg = xgrad.at<uchar>(row, col); int yg = ygrad.at<uchar>(row, col); int xyg = xg + yg; xygrad.at<uchar>(row, col) = saturate_cast<uchar>(xyg); } } imshow("final image", xygrad); waitKey(0); return 0; }
2,Laplance算子
在二阶导数的时候,最大变化处的值为零即边缘是零值。通过二阶导数计算,依据此理论我们可以计算图像二阶导数,提取边缘。
x,y方向的二阶导数之和
代码演示:
思路:
- 高斯平滑(模糊)
- 转灰度
- 拉普拉斯-二阶导数计算
- 取绝对值
- 后续对梯度图的处理
int Laplance_boder(Mat src) { Mat gray_src, blur_src, dst; GaussianBlur(src, blur_src, Size(3, 3), 0, 0); cvtColor(blur_src, gray_src, COLOR_BGR2GRAY); Laplacian(gray_src, dst, CV_16S, 3); convertScaleAbs(dst, dst); //取绝对值 threshold(dst, dst, 0, 255, THRESH_OTSU | THRESH_BINARY); //通过阈值减少噪声 只能是灰度图 namedWindow("laplance result", WINDOW_AUTOSIZE); imshow("laplance result", dst); waitKey(0); return 0; }
Canny边缘检测
Canny是边缘检测算法,在1986年提出的。
是一个很好的边缘检测器
很常用也很实用的图像处理方法
算法步骤:
- 首先,图像降噪。我们知道梯度算子可以用于增强图像,本质上是通过增强边缘轮廓来实现的,也就是说是可以检测到边缘的。但是,它们受噪声的影响都很大。那么,我们第一步就是想到要先去除噪声,因为噪声就是灰度变化很大的地方,所以容易被识别为伪边缘。
- 第二步,计算图像梯度,得到可能边缘。我们在前面的关于《图像梯度》文章中有所介绍,计算图像梯度能够得到图像的边缘,因为梯度是灰度变化明显的地方,而边缘也是灰度变化明显的地方。当然这一步只能得到可能的边缘。因为灰度变化的地方可能是边缘,也可能不是边缘。这一步就有了所有可能是边缘的集合。
- 第三步,非极大值抑制。通常灰度变化的地方都比较集中,将局部范围内的梯度方向上,灰度变化最大的保留下来,其它的不保留,这样可以剔除掉一大部分的点。将有多个像素宽的边缘变成一个单像素宽的边缘。即“胖边缘”变成“瘦边缘”。
- 第四步,双阈值筛选。通过非极大值抑制后,仍然有很多的可能边缘点,进一步的设置一个双阈值,即低阈值(low),高阈值(high)。灰度变化大于high的,设置为强边缘像素,低于low的,剔除。在low和high之间的设置为弱边缘。进一步判断,如果其领域内有强边缘像素,保留,如果没有,剔除。
具体地:
- 高斯模糊 - GaussianBlur
- 灰度转换 - cvtColor
- 计算梯度 – Sobel/Scharr
- 非最大信号抑制
- 高低阈值输出二值图像
非最大信号抑制
θ是梯度的方向,0-180°之间,表示哪个方向上的变化率最大。找出梯度方向所对应的值,与其周围的值进比较,进行抑制,如下图所示。
高低阈值输出:
T1, T2为阈值,凡是高于T2的都保留,凡是小于T1都丢弃。凡是大于T1而且与高于T2像素相互连接的,都保留。最终得到一个输出二值图像。
推荐的高低阈值比值为 T2: T1 = 3:1或者2:1 其中T2为高阈值,T1为低阈值
API:
CV::Canny
必须是八位的灰度图像
Canny( InputArray src, // 8-bit的输入图像 OutputArray edges,// 输出边缘图像, 一般都是二值图像,背景是黑色 double threshold1,// 低阈值,常取高阈值的1/2或者1/3 double threshold2,// 高阈值 int aptertureSize,// Soble算子的size,通常3x3,取值3 bool L2gradient // 选择 true表示是L2来归一化,否则用L1归一化 )
代码演示:
void Canny_Demo(int pos, void* usrdata); int Canny_boder(Mat src) { int t1_value = 50; int max_value = 255; namedWindow("Canny Result", WINDOW_AUTOSIZE); createTrackbar("Treshold Value", "Canny Result", &t1_value, max_value, Canny_Demo, &src); Canny_Demo(t1_value, &src); waitKey(0); return 0; } void Canny_Demo(int pos, void* usrdata) { Mat gray_src, edge_output, dst; Mat src = *(Mat*)(usrdata); //强转Mat类型 cvtColor(src, gray_src, COLOR_BGR2GRAY); blur(gray_src, gray_src, Size(3, 3), Point(-1,-1), BORDER_DEFAULT); Canny(gray_src, edge_output, pos, pos*2, 3, false); //dst = Scalar::all(0); dst.create(src.size(), src.type()); //初始化dst src.copyTo(dst, edge_output); //将非0的像素值copy过去 imshow("Canny Result", dst); }
关于回调函数,可以参考例子:https://www.cnblogs.com/Fsiswo/p/8030649.html