OpenCV系列(七)边缘提取
边缘提取,指数字图像处理中,对于图片轮廓的一个处理。对于边界处,灰度值变化比较剧烈的地方,就定义为边缘。也就是拐点,拐点是指函数发生凹凸性变化的点。二阶导数为零的地方。并不是一阶导数,因为一阶导数为零,表示是极值点。
边缘提取
边缘检测的基本思想首先是利用边缘增强算子,突出图像中的局部边缘,然后定义像素的“边缘强度”,通过设置阈值的方法提取边缘点集。由于噪声和模糊的存在,监测到的边界可能会变宽或在某点处发生间断。因此,边界检测包括两个基本内容:(1)用边缘算子提取出反映灰度变化的边缘点集。(2)在边缘点集合中剔除某些边界点或填补边界间断点,并将这些边缘连接成完整的线。
边缘定义
图像灰度变化率最大的地方(图像灰度值变化最剧烈的地方)。图像灰度在表面变化的不连续造成的边缘。一般认为边缘提取是要保留图像的灰度变化剧烈的区域,这从数学上看,最直观的方法就是微分(对于数字图像来说就是差分),在信号处理的角度来看,也可以说是用高通滤波器,即保留高频信号。边缘信息包含两个方面:1.像素的坐标 2.边缘的方向
Part 1
Canny 算法
边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。这些包括
深度上的不连续
表面方向不连续
物质属性变化
场景照明变化。
边缘检测是图像处理和计算机视觉中,尤其是特征提取中的一个研究领域。Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:
好的检测 - 算法能够尽可能多地标识出图像中的实际边缘。
好的定位 - 标识出的边缘要尽可能与实际图像中的实际边缘尽可能接近。
最小响应 - 图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘。
Canny算法在分为如下几个步骤:
输入彩色图像,通过高斯模糊去除噪声 GaussianBlur
灰度转换 cvtColor
计算梯度 Sobel/Scharr
非最大信号抑制
高低阙值 输出二值图像
接下来让我们来敲代码在OpenCV中实现Canny算法吧:
import cv2import numpy as npdef Canny_demo(img):blur=cv2.GaussianBlur(img,(5,5),0) #除去噪声gray=cv2.cvtColor(blur,cv2.COLOR_BGR2GRAY) #图像灰度xgrad=cv2.Scharr(gray,cv2.CV_16SC1,1,0) #求梯度Scharr算子ygrad=cv2.Scharr(gray,cv2.CV_16SC1,0,1)edge=cv2.Canny(xgrad,ygrad,0,255)dst=cv2.bitwise_and(img,img,mask=edge)return edge,dst
首先我们定义了一个Canny_demo函数,函数的主要功能是: 使用高斯模糊除去图片的噪声,接着使用cv2.cvtColor 把图像灰度化,然后求梯度Schaar算子,然后使用Canny算法进行边缘检测,最后返回掩模和图片,函数写完了,我们来进行调用:
if __name__ == '__main__':img=cv2.imread('opencv_image/morph02.png')cv2.imshow('img',img)_,image=Canny_demo(img)cv2.imshow('demo',image)cv2.waitKey(0)
运行结果如下:

Tips:像上面的运行结果,会发现噪点挺多的,我们可以通过加大高斯模糊的算子来除去更多的噪声。如图,这是把GaussianBlur中(5,5)加到(9,9)的结果。可见噪声变得少了。

Part 2
直线检测
直线检测的前提条件是:边缘检测已经完成!所以这时候我们可以再写一个函数来调用我们刚刚写好的Canny函数,这样子我们就可以不需要重复地去造轮子。所有代码如下:
import cv2import numpy as npdef Canny_demo(img):blur=cv2.GaussianBlur(img,(9,9),0) #除去噪声gray=cv2.cvtColor(blur,cv2.COLOR_BGR2GRAY) #图像灰度xgrad=cv2.Scharr(gray,cv2.CV_16SC1,1,0) #求梯度Scharr算子ygrad=cv2.Scharr(gray,cv2.CV_16SC1,0,1)edge=cv2.Canny(xgrad,ygrad,0,255)dst=cv2.bitwise_and(img,img,mask=edge)return edge,dstdef find_lines(img):edge,_=Canny_demo(img)lines=cv2.HoughLinesP(edge,1,np.pi/180,100,minLineLength=50,maxLineGap=10)for line in lines:x1,y1,x2,y2=line[0]cv2.line(img,(x1,y1),(x2,y2),(255,0,0),1)return imgif __name__ == '__main__':img=cv2.imread('opencv_image/morph02.png')cv2.imshow('img',img)image=find_lines(img)cv2.imshow('demo',image)cv2.waitKey(0)
程序运行结果:

函数解析:
HoughLinesP(image,rho, theta, threshold, lines=None, minLineLength=None, maxLineGap=None
作用:获得霍夫直线
| image | 必须是二值图像,推荐使用canny边缘检测的结果图像 |
| rho | 线段以像素为单位的距离精度,double类型的,推荐用1.0 |
| theta | 线段以弧度为单位的角度精度,推荐用numpy.pi/180 |
| threshold | 累加平面的阈值参数,int类型,超过设定阈值才被检测出线段,值越大,基本上意味着检出的线段越长,检出的线段个数越少。根据情况推荐先用100试试 |
| lines | 这个参数的意义未知,发现不同的lines对结果没影响,但是不要忽略了它的存在 |
| minLineLength | 线段以像素为单位的最小长度,根据应用场景设置 |
| maxLineGap | 同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段 |
