在进行图像识别时,常需要检测图像的边缘信息。即边缘灰度值急剧变化的地方,一般是北京和前景物体的交界处。由于边缘处的灰度值急剧变化特性,可以利用离散数列的差分(相当于连续函数的导数)来识别边缘。目前常用的边缘检测算法大多数是通过梯度方向导数求卷积的方法,常用的卷积算子有
常用卷积算子:Roberts算子、Prewitt算子、Sobel算子、Scharr算子、canny边缘检测算法
非常用卷积算子:Laplacian算子、高斯拉普拉斯(LOG)边缘检测、高斯差分(DOG)边缘检测
Roberts边缘检测算法:图像分别与下面两个卷积核进行卷积运算。第一个为135°方向像素差分,第二个为45°方向像素值差分,卷积后,图像内部相近的像素值为变为0,即黑色北京,二边缘由于像素值相差打,相减的差分值作为新的像素值。因此能够将图像边缘处的像素值识别出来,活得图像边缘轮廓;
通过上述两个卷积会得到145°和45°的卷积结果,将两者合并得到最后的输出结果。有如下四种合并方式:
a.取两个矩阵相对位置的绝对值的和;
b.取两个矩阵对应位置的平方和的开方;
c.取两个矩阵对应位置绝对值的最大值;
d.插值法;
以上4中方法,b方案为效果最优方案,但同时也是最耗时的方案;
Sobel,Scharr算子等边缘检测算法,只是对检测到的边缘进行了超阈值处理(超过255的像素点截断等), Canny边缘算法,是在sobel算法的基础上,对边缘像素进行更细致的后处理,过滤掉部分非边缘点,从而使得到便边缘更加细致准确。
Canny边缘检测可以细分为三步:
采用Sobel卷积核进行卷积运算
基于边缘梯度方向的非极大值抑制
双阈值的滞后阈值处理
(1) Sobel卷积核进行卷积运算
分别采用水平方向和垂直方向的sobel算子,卷积计算出水平方向梯度 和竖直方向梯度 ,然后我们可以计算图像中每个像素的梯度大小为 ,
(2) 基于边缘梯度方向的非极大值抑制
在得到每个像素的梯度大小和方向后,我们遍历每个像素,判断该像素的梯度大小在该像素梯度方向上是否是其邻域中的局部最大值。如下图所示:
在上图中,假设一张5*5的image,左边为sobel算子计算的梯度大小,右边为计算的梯度方向,位于(1, 1)像素位置处的梯度大小为912, 梯度方向为120。上图左边画出了坐标轴和梯度方向,可以发现,沿着梯度方向的梯度值为(0,2)处的292和(2,0)处的276,由于912大于这两个梯度值,所以912为极大值。 按照这样的规则遍历每一个像素点,对于非极大值的像素点,需要将其梯度大小置为0;
(3) 双阈值的滞后阈值处理
prewitt边缘检测算子如下图所示,分别为水平方向和垂直方向的卷积核(锚点为中心点),再进行边缘检测时,也是先分别进行水平和垂直方向的差分计算,最后再进行合并。
prewitt算子的来个卷积核是可分离的,如下图所示。对于水平方向的卷积核,可以拆分为一个垂直方向上的均值平滑卷积核和水平方向的差分卷积核,因此prewitt水平算子,相当于先进行了垂直方向的均值平滑,然后再进行水平方向的差分卷积运算。同样prewitt垂直算子,相当于先进行水平方向的均值平滑,再进行垂直方向的差分卷积。 所以相比于Robert算子,Prewitt算子多了一个平滑操作,所以其受噪声干扰小。
sobel算子的两个卷积核如下,也分为水平方向和垂直方向的卷积核(锚点为中心点),其卷积核也是可以差分,相比于prewitt算子,只是其平滑卷积核由均值平滑变成了高斯平滑,差分卷积核还是一样的。
sobel卷积核除了三阶,还可以是高阶的,通过其两个分离卷积核,进行矩阵乘法可以得到,下面是两个5*5的sobel卷积核:
scharr算子和3阶的Sobel边缘检测算子类似,其对应两个卷积核如下:
opencv提供了cv2.Scharr()函数进行Scharrr边缘检测,其对应参数如下:(注意没有szie参数)
dst= cv2.Scharr(src,ddepth,dx,dy,scale,delta,borderType)
src: 输入图像对象矩阵,单通道或多通道
ddepth:输出图片的数据深度,注意此处最好设置为cv.CV_32F或cv.CV_64F
dx:dx不为0时,img与差分方向为水平方向的Sobel卷积核卷积
dy: dx=0,dy!=0时,img与差分方向为垂直方向的Sobel卷积核卷积
dx=1,dy=0: 与差分方向为水平方向的Sobel卷积核卷积
dx=0,dy=1: 与差分方向为垂直方向的Sobel卷积核卷积
(注意必须满足: dx >= 0 && dy >= 0 && dx+dy == 1)
scale: 放大比例系数
delta: 平移系数
borderType:边界填充类型
下面我用canny边缘提取和Roberts边缘提取做个例子
程序范例:
#include
#include
#include
#include
#include
using namespace cv;
using namespace std;
RNG rng(12345);
Scalar color[7] = {
(Scalar(0,0,255)),//红色
(Scalar(0,255,0)),//绿色
(Scalar(255,0,0)),//蓝色
(Scalar(255,255,0)),//浅蓝色
(Scalar(255,0,255)),//紫色
(Scalar(0,255,255)),//黄色
(Scalar(128,128,192)),//浅粉色
};
int main()
{
Mat image,gray_img,thread_img, dst2_img, dst3_img, dst_img;
image = imread("../basketball.jpg", 1);
if (image.empty()) {
cout << "无此图片" << endl;
return 0;
}
cvtColor(image, gray_img,COLOR_RGB2GRAY,0);//将图像转换为灰度图
threshold(gray_img, thread_img, 100, 255, THRESH_BINARY_INV);//将灰度图转换为二值图
while (1)
{
imshow("原图", image);
waitKey(1);
Mat robert135 = (Mat_(2, 2) << 1,0,
0,-1);
filter2D(thread_img, dst2_img, image.depth(), robert135);
Mat robert45 = (Mat_(2, 2) << 0,1,
-1,0);
filter2D(thread_img, dst3_img, image.depth(), robert45);
addWeighted(dst2_img, 1, dst3_img, 1, 0, dst_img);
imshow("robert", dst_img);
waitKey(1);
Canny(thread_img, dst_img, 80, 160, 3);
imshow("Canny", dst_img);
waitKey(1);
}
return 1;
}