OpenCV系列(七)边缘提取

共 3078字,需浏览 7分钟

 ·

2021-02-26 16:02


边缘提取,指数字图像处理中,对于图片轮廓的一个处理。对于边界处,灰度值变化比较剧烈的地方,就定义为边缘。也就是拐点,拐点是指函数发生凹凸性变化的点。二阶导数为零的地方。并不是一阶导数,因为一阶导数为零,表示是极值点。


边缘提取

边缘检测的基本思想首先是利用边缘增强算子,突出图像中的局部边缘,然后定义像素的边缘强度,通过设置阈值的方法提取边缘点集。由于噪声和模糊的存在,监测到的边界可能会变宽或在某点处发生间断。因此,边界检测包括两个基本内容:(1)用边缘算子提取出反映灰度变化的边缘点集。(2)在边缘点集合中剔除某些边界点或填补边界间断点,并将这些边缘连接成完整的线。


边缘定义

图像灰度变化率最大的地方(图像灰度值变化最剧烈的地方)。图像灰度在表面变化的不连续造成的边缘。一般认为边缘提取是要保留图像的灰度变化剧烈的区域,这从数学上看,最直观的方法就是微分(对于数字图像来说就是差分),在信号处理的角度来看,也可以说是用高通滤波器,即保留高频信号。边缘信息包含两个方面:1.像素的坐标 2.边缘的方向



Part 1

Canny 算法

边缘检测是图像处理和计算机视觉中的基本问题,边缘检测的目的是标识数字图像中亮度变化明显的点。图像属性中的显著变化通常反映了属性的重要事件和变化。这些包括


  • 深度上的不连续

  • 表面方向不连续

  • 物质属性变化

  • 场景照明变化。

     

边缘检测是图像处理和计算机视觉中,尤其是特征提取中的一个研究领域。Canny 的目标是找到一个最优的边缘检测算法,最优边缘检测的含义是:

  • 好的检测 - 算法能够尽可能多地标识出图像中的实际边缘。

  • 好的定位 - 标识出的边缘要尽可能与实际图像中的实际边缘尽可能接近。

  • 最小响应 - 图像中的边缘只能标识一次,并且可能存在的图像噪声不应标识为边缘。

 

Canny算法在分为如下几个步骤:

  1. 输入彩色图像,通过高斯模糊去除噪声 GaussianBlur

  2. 灰度转换 cvtColor

  3. 计算梯度 Sobel/Scharr

  4. 非最大信号抑制  

  5. 高低阙值 输出二值图像


接下来让我们来敲代码在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,dst
def 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 img
if __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

同一方向上两条线段判定为一条线段的最大允许间隔(断裂),超过了设定值,则把两条线段当成一条线段,值越大,允许线段上的断裂越大,越有可能检出潜在的直线段



浏览 127
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报