参考博客:
https://www.jianshu.com/p/effb2371ea12
https://www.cnblogs.com/nowgood/p/cannyedge.html
简单的目标检测用边缘检测算法就可以识别出来,先展示一下结果:
这里采用Canny边缘检测,首先回顾以下Canny边缘检测算法的原理,一般分为6个部分,下面逐步介绍每一步过程:
1、图像灰度化
首先将图像转换成灰度图像,可以理解为是一种降维操作,可以减少计算量。
2、使用高斯滤波器,平滑图像,滤除噪声
在图像中一般都会包含噪声,噪声会造成图像中边缘信息的丢失,因此可以使用高斯平滑来减少噪声。
二维高斯函数的定义如下:
高斯滤波其实就是:对高斯函数进行离散化,以离散点上的高斯函数值为权值,对我们采集到的灰度矩阵的每个像素点做一定范围邻域内的加权平均,下面是 (2k+1)x(2k+1) 大小的离散高斯函数(高斯卷积核)的计算公式 :
比如k=1,sigma=1,得到3*3大小的高斯卷积核为:
归一化后得到:
然后进行图像卷积操作,如果图像中一个3x3的窗口为A,要滤波的像素点为e,则经过高斯滤波之后,像素点e的像素值的计算具体如下:
3、计算图像中每个像素点的梯度强度和梯度方向
边缘检测算子(如Roberts,Prewitt,Sobel等)返回像素点在水平Gx和垂直Gy方向的一阶导数值(梯度),这里选择Sobel算子(也可以用其他差分卷积模板)来计算梯度,因为相对于其他边缘算子,Sobel算子得到的边缘粗大明亮。Sobel算子模板在X和Y方向的分别为:
通过将其与图像进行卷积操作,即可得到像素点在X和Y方向的梯度:
再通过以下计算公式可得出每个像素点的梯度幅值和梯度方向:
4、对梯度幅值应用非极大值抑制,消除边缘检测带来的杂散响应
仅仅得到全局的梯度并不足以确定边缘,为了确定边缘,必须保留的是局部梯度最大的点,而非极大值抑制,即将非局部极大值点的像素值置零以得到细化的边缘。
(1)将当前像素的梯度幅值与沿着梯度方向线上的两个像素进行比较;但沿着梯度方向线两侧的像素点不一定是存在的,或者说是亚像素点,而不存在的点, 以及其梯度幅值就必须通过对其两侧的点进行线性插值来得到。
比如当前像素P与沿着梯度方向线上的两个像素P1和P2进行比较,P1和P2的梯度幅值可以通过E和NE处的梯度幅值线性插值得到,具体计算如下:
(2)如果当前像素的梯度幅值与另外两个像素相比都要大,则该像素点保留为边缘点,否则该像素点将被抑制,即该像素点的灰度值设为0。
5、应用双阈值来检测真实的和候选的边缘,去除假阳性(双阈值的选取取决于给定输入图像的内容)
(1)如果边缘像素的梯度幅值高于高阈值,则将其标记为强边缘像素;
(2)如果边缘像素的梯度幅值小于高阈值并且大于低阈值,则将其标记为弱边缘像素;
(3)如果边缘像素的梯度幅值小于低阈值,则该边缘像素被抑制。
6、抑制孤立的弱边缘
通过查看弱边缘像素及其8个邻域像素,只要其中一个为强边缘像素,则该弱边缘像素就可以保留为真实的边缘。
代码实现:
import cv2 as cv
image = cv.imread(r'C:\Users\jwsun\Desktop\a.jpg')
gray_image = cv.cvtColor(image, cv.COLOR_BGR2GRAY)
canny_image = cv.Canny(gray_image, 80, 160)#Canny的高低阈值设置一般为2:1
cv.imshow("canny_image", canny_image)
kernel = cv.getStructuringElement(cv.MORPH_RECT, (30, 30))
dilated = cv.dilate(canny_image, kernel)
eroded = cv.erode(dilated, kernel)
contours, hierarchy = cv.findContours(eroded, cv.RETR_TREE, cv.CHAIN_APPROX_SIMPLE)
# print(len(contours))#打印轮廓的数量
for i in range(0, len(contours)):
x, y, w, h = cv.boundingRect(contours[i])#得出轮廓最小外接矩形左上角坐标及长、宽信息
cv.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)
cv.imwrite(r'C:\Users\jwsun\Desktop\b.jpg',image)
cv.imshow('detected_image', image)
cv.waitKey(0)
cv.destroyAllWindows()