前言
耐心看完一定会有收获的,大部分内容也会在代码中体现,结合理论知识和代码进行理解会更有效。代码用opencv4.5.1(c++)版实现
一、边缘检测算法
边缘检测算法是指利用灰度值的不连续性质,以灰度突变为基础分割出目标区域。对铝铸件表面进行成像后会产生一些带缺陷的区域,这些区域的灰度值比较低,与背景图像相比在灰度上会有突变,这是由于这些区域对光线产生散射所引起的。因此边缘检测算子可以用来对特征的提取。
1、一阶算子
一种是基于一阶微分的算子,也称基于搜索的算子,首先通过一阶导数计算边缘强度,然后采用梯度的方向来对边缘的局部方向进行寻找,同时根据该方向来寻找出局部梯度模的最大值,由此定位边缘,如Roberts Cross算子,Prewitt算子Sobel算子,Kirsch算子,Canny算子,罗盘算子等;
图像中的边缘区域,像素值会发生“跳跃”,对这些像素求导,在其一阶导数在边缘位置为极值,这就是Sobel算子使用的原理——极值处就是边缘。
2、二阶算子
另一种是基于二阶微分的算子,也称基于零交叉的算子,通过寻找由图像得到的二阶导数的过零点来定位检测边缘,如Marr-Hildreth算子,Laplacian算子,LOG算子等。如果对像素值求二阶导数,会发现边缘处的导数值为0。
二、一阶算子分析
一阶微分算子进行边缘检测的思路大致就是通过指定大小的核(kernal)(也称为算子)与图像进行卷积,将得到的梯度进行平方和或者最大值作为新的梯度赋值给对应的像素点,不同的一阶微分算子主要的不同在于其算子即核的元素不同以及核的大小不一样
以下是连续函数的一阶导数求导公式:因为图像是一个面,就相当于是灰度值关于x,y两个方向的函数,要求某一点的导数,则是各个方向的偏导数的平方和再进行开方运算。
离散函数的一阶导数公式:
y'=[y(x0+h)-y(x0-h)]/(2h);这是一维函数的一阶求导,h是步长,在图像处理中一般为1
首先复习一下什么是卷积?
卷积就是对应的元素相乘再进行累加的过程
实例图片:
1、Roberts算子
Robert算子是用于求解对角线方向的梯度,因为根据算子GX和GY的元素设置可以看到,只有对角线上的元素非零,其本质就是以对角线作为差分的方向来检测。
检测垂直边缘的效果好于斜向边缘,定位精度高,对噪声敏感,无法抑制噪声的影响。
不同方向的算子模板: 梯度的计算:
代码示例:
#include<opencv2/opencv.hpp>
#include<opencv2/core/core.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
Mat roberts(Mat srcImage);
int main(int argc, char** argv)
{
Mat src,src_binary,src_gray;
src = imread("D:/opencv练习图片/薛之谦.jpg");
imshow("原图", src);
cvtColor(src, src_gray, COLOR_BGR2GRAY);
GaussianBlur(src_gray, src_binary, Size(3, 3),0, 0, BORDER_DEFAULT);
Mat dstImage = roberts(src_binary);
imshow("dstImage", dstImage);
waitKey(0);
return 0;
}
//roberts 边缘检测
Mat roberts(Mat srcImage)
{
Mat dstImage = srcImage.clone();
int nRows = dstImage.rows;
int nCols = dstImage.cols;
for (int i = 0; i < nRows - 1; i++) {
for (int j = 0; j < nCols - 1; j++) {
//根据公式计算
int t1 = (srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1))*
(srcImage.at<uchar>(i, j) -
srcImage.at<uchar>(i + 1, j + 1));
int t2 = (srcImage.at<uchar>(i + 1, j) -
srcImage.at<uchar>(i, j + 1))*
(srcImage.at<uchar>(i + 1, j) -
srcImage.at<uchar>(i, j + 1));
//计算g(x,y)
dstImage.at<uchar>(i, j) = (uchar)sqrt(t1 + t2);
}
}
return dstImage;
}
效果展示:
2、Sobel算子
Sobel算子是主要用于边缘检测的离散微分算子,它结合了高斯平滑和微分求导,用于计算图像灰度函数的近似梯度。
因为Sobel算子结合了高斯平滑和分化,因此结果会具有更多的抗噪性。
不同方向的算子模板: 梯度的计算:
opencv中sobel函数的参数如下:
void cv::Sobel (
InputArray src, // 输入图像
OutputArray dst, // 输出图像
int ddepth, // 输出图像深度,-1 表示等于 src.depth()
int dx, // 水平方向的阶数
int dy, // 垂直方向的阶数
int ksize = 3, // 卷积核的大小,常取 1, 3, 5, 7 等奇数
double scale = 1, // 缩放因子,应用于计算结果
double delta = 0, // 增量数值,应用于计算结果
int borderType = BORDER_DEFAULT // 边界模式
)
3、Prewitt算子
Prewitt算子利用像素点上下、左右邻点的灰度差,在边缘处达到极值检测边缘,去掉部分伪边缘,对噪声具有平滑作用 。其原理是在图像空间利用两个方向模板与图像进行邻域卷积来完成的。
这两个方向模板一个检测水平边缘,一个检测垂直边缘。
不同方向的算子模板:
Prewitt算子定位精度不如Sobel算子,在真正的使用中,一般不会用到这个算子,效果较差,因此不多做分析。
4、Canny算子
可以说,Canny 边缘检测算法是被业界公认的性能最为优良的边缘检测算法之一。Canny算法不是像Roberts、Prewitt、Sobel等这样简单梯度算子或锐化模板,它是在梯度算子基础上,引入了一种能获得抗噪性能好、定位精度高的单像素边缘的计算策略。
Canny 算子,在一阶微分的基础上,增加了非最大值抑制和双阈值检测,是边缘检测算子中最常用的一种,常被其它算子作为标准算子来进行优劣比较。
4.1算法步骤
1) 用高斯滤波器对输入图像做平滑处理 (大小为 5x5 的高斯核)
2) 计算图像的梯度强度和角度方向 ( x 和 y 方向上的卷积核)
角度方向近似为四个可能值,即 0, 45, 90, 135
3) 对图像的梯度强度进行非极大抑制
可看做边缘细化:只有候选边缘点被保留,其余的点被移除
4) 利用双阈值检测和连接边缘
若候选边缘点大于上阈值,则被保留;小于下阈值,则被舍弃;处于二者之间,须视其所连接的像素点,大于上阈值则被保留,反之舍弃
opencv中canny函数的参数如下:
void cv::Canny (
InputArray image, // 输入图像 (8位)
OutputArray edges, // 输出图像 (单通道,8位)
double threshold1, // 下阈值
double threshold2, // 上阈值
int apertureSize = 3,
bool L2gradient = false
)
一般 上阈值 / 下阈值 = 2 ~ 3
L2gradient 默认 flase,表示图像梯度强度的计算采用近似形式;若为 true,则表示采用更精确的形式
5、Laplace(拉普拉斯算子)
索贝尔算子 (Sobel) 和拉普拉斯算子 (Laplace) 都是用来对图像进行边缘检测的,不同之处在于,前者是求一阶导,后者是求二阶导。
常用算子模块:
opencv中Laplace函数的参数如下:
void Laplacian (
InputArray src,
OutputArray dst,
int ddepth,
int ksize = 1,
double scale = 1,
double delta = 0,
int borderType = BORDER_DEFAULT
)
简单对比
在进行 Sobel,Laplacian 和 Canny 边缘检测之前,统一调用 GaussianBlur 来降低图像噪声
using namespace std;
using namespace cv;
int main(int argc, char** argv)
{
Mat src,src_binary,src_gray;
Mat grad_x, grad_y;
Mat abs_grad_x, abs_grad_y, dst;
src = imread("D:/opencv练习图片/薛之谦.jpg");
imshow("原图", src);
GaussianBlur(src, src, Size(3, 3), 0);
cvtColor(src, src_gray, COLOR_BGR2GRAY);
Sobel(src_gray, grad_x, CV_16S, 1, 0, 3, 1, 1, BORDER_DEFAULT);//求x方向梯度
convertScaleAbs(grad_x, abs_grad_x);//转换格式 8u
Sobel(src_gray, grad_y, CV_16S, 0, 1, 3, 1, 1, BORDER_DEFAULT);//求y方向梯度
convertScaleAbs(grad_y, abs_grad_y);
//合并梯度
addWeighted(abs_grad_x, 0.5, abs_grad_y, 0.5, 0, dst);
imshow("solbel算子", dst);
Canny(src_gray, dst, 100, 300);
imshow("Canny算子", dst);
Laplacian(src_gray, dst, -1, 3);
imshow("Laplace算子", dst);
waitKey(0);
return 0;
}
效果展示: