基于opencv的边缘检测方法

Posted weixin_44343355

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了基于opencv的边缘检测方法相关的知识,希望对你有一定的参考价值。

1、梯度运算

用OpenCV的形态变换( 膨胀、腐蚀、开运算和闭运算)函数morphologyEx
梯度运算即膨胀结果-腐蚀结果:

【注意】对于二值图像来说,必须是前景图像为白色,背景为黑色,否则需要进行反二值化处理

import cv2
import matplotlib.pyplot as plt
import numpy as np

##读入图片
im1 = cv2.imread(r"fiction\\xiaozhu.jpg", cv2.IMREAD_GRAYSCALE)
#创建一个5行5列的值全为1 的卷积核
k = np.ones((5,5),np.uint8)
##进行梯度运算
r = cv2.morphologyEx(im1, cv2.MORPH_GRADIENT, k)

##图像展示
plt.subplot(1,2,1)
plt.imshow(im1, cmap="gray")
plt.axis("off")
plt.subplot(1,2,2)
plt.imshow(r, cmap= "gray")
plt.axis("off")
plt.show()

2、sobel算子

可以计算不同方向的梯度,梯度运算如下图所示:

得出x方向的梯度值和y方向的梯度值后,通过G = sqr(GX2 + GY2)或G = |GX|+G|Y|得到整幅图像的梯度。

cv2.Sobel参数:

【注意】
1、如果梯度为负数,会无法显示,所以计算完梯度之后需要进行取绝对值处理。
2、同时计算x方向和y方向的梯度,通常没有分别计算两个方向梯度后,进行后处理效果好,通常用cv2.addWeighted(src1, alpha, src2, beta, gamma)进行修正。
3、卷积核大小只能为奇数。

import cv2
import numpy as np

im1 = cv2.imread(r"fiction\\xiaozhu1.jpg",cv2.IMREAD_GRAYSCALE)
##默认卷积核为3*3,如果写cv2.Sobel(im1, 0, 1, 0)的话,只能取到一侧边界,另一侧边界值为负,会被规整成0
sobelx = cv2.Sobel(im1, cv2.CV_64F, 1, 0,ksize=3)
sobely = cv2.Sobel(im1, cv2.CV_64F, 0, 1,ksize=3)
##取绝对值
cv2.convertScaleAbs(sobelx)

sobelxy = cv2.addWeighted(sobelx, 0.5, sobely, 0.5, 0)
##同时取x、y的sobel算子没有单独计算x、y然后相加的效果好
sobel_tmp = cv2.Sobel(im1, cv2.CV_64F, 1, 1)
cv2.convertScaleAbs(sobel_tmp)


cv2.namedWindow("dx=1", 0)
cv2.namedWindow("dy=1", 0)
cv2.namedWindow("after addWeighted", 0)
cv2.namedWindow("dx=1,dy=1", 0)
cv2.imshow("dx=1", sobelx)
cv2.imshow("dy=1", sobely)
cv2.imshow("after addWeighted", sobelxy)
cv2.imshow("dx=1,dy=1", sobel_tmp)
cv2.waitKey(0)
cv2.destroyAllWindows()


3、scharr算子

类似sobel算子,但是卷积核系数不同,离目标点越近的像素权重越大,边缘提取的效果比sobel算子好

二者效果对比如下:

import cv2

im1 = cv2.imread(r"fiction\\xiaozhu.jpg", cv2.IMREAD_GRAYSCALE)
##Scharr算子提取边缘,scharr相较于sobel算子,靠近核心部分的权值较大,边缘提取的效果更好,x与y不能同时为1
im1x = cv2.Scharr(im1, cv2.CV_64F, 1, 0)
im1y = cv2.Scharr(im1, cv2.CV_64F, 0, 1)
im1x = cv2.convertScaleAbs(im1x)
im1y = cv2.convertScaleAbs(im1y)
im1ScharryXY = cv2.addWeighted(im1x, 0.5, im1y, 0.5, 0)

##拉普拉斯算子
imLap = cv2.Laplacian(im1,cv2.CV_64F)
imLap = cv2.convertScaleAbs(imLap)


##对比sobel算子提取边缘的效果
im1SobelX = cv2.Sobel(im1, cv2.CV_64F, 1, 0)
im1SobelY = cv2.Sobel(im1, cv2.CV_64F, 0, 1)
im1SobelX = cv2.convertScaleAbs(im1SobelX)
im1SobelY = cv2.convertScaleAbs(im1SobelY)
im1SobelXY = cv2.addWeighted(im1SobelX, 0.5, im1SobelY, 0.5, 0)

##sobel算子模拟scharr算子进行边缘提取
im1So_SC_X = cv2.Sobel(im1, cv2.CV_64F, 1, 0, -1)
im1So_SC_Y = cv2.Sobel(im1, cv2.CV_64F, 0, 1, -1)
im1So_SC_X = cv2.convertScaleAbs(im1So_SC_X)
im1So_SC_Y = cv2.convertScaleAbs(im1So_SC_Y)
im1So_SC_XY = cv2.addWeighted(im1So_SC_X, 0.5, im1So_SC_Y, 0.5, 0)


cv2.namedWindow("im1ScharryXY", 0)
cv2.namedWindow("im1SobelXY", 0)
cv2.namedWindow("im1So_SC_XY", 0)
cv2.namedWindow("imLap", 0)
cv2.imshow("im1ScharryXY", im1ScharryXY)
cv2.imshow("im1SobelXY", im1SobelXY)
cv2.imshow("im1So_SC_XY", im1So_SC_XY)
cv2.imshow("imLap", imLap)
cv2.waitKey(0)
cv2.destroyAllWindows()

依次为scharry算子、sobel算子、sobel仿scharry、拉普拉斯算子结果

4、canny边缘检测

原理:
1、高斯滤波去噪声
2、平滑后的图像用sobel算子计算梯度,梯度方向如下图所示:

梯度方向一般都与边界垂直,
一般归类为四个方向:垂直、水平,两个对角线

3、去除所有非边界点


4、滞后阈值
选取两个阈值,maxVal和minVal

5、用法,其实就一个函数,cv2.Canny()

import cv2

im1 = cv2.imread(r"fiction\\pig2.jpg", cv2.IMREAD_UNCHANGED)
im1 = cv2.Canny(im1, 128, 256)
im2 = cv2.Canny(im1, 0, 256)
im3 = cv2.Canny(im1, 0, 128)
im4 = cv2.Canny(im1, 0, 10)

cv2.namedWindow("128_256",0)
cv2.namedWindow("0_256",0)
cv2.namedWindow("0_128",0)
cv2.namedWindow("0_10",0)
cv2.imshow("128_256",im1)
cv2.imshow("0_256",im2)
cv2.imshow("0_128",im3)
cv2.imshow("0_10",im4)
cv2.waitKey(0)


5、拉普拉斯金字塔

原图像减去(原图->向下采样->向上采样)
两次采样后图像会被平滑

import cv2

im1 = cv2.imread(r"fiction\\xiaozhu.jpg", cv2.IMREAD_GRAYSCALE)

##向下取样,长、宽各变为1/2
im1Low = cv2.pyrDown(im1)

##向上取样,长、宽各变为原来2倍,会变模糊
im1High = cv2.pyrUp(im1Low)

##拉普拉斯金字塔结果
lapPyr = im1-im1High

cv2.namedWindow("im1Low",0)
cv2.namedWindow("im1High",0)
cv2.namedWindow("im1",0)
cv2.namedWindow("lapPyr",0)

cv2.imshow("im1",im1)
cv2.imshow("im1Low",im1Low)
cv2.imshow("lapPyr",lapPyr)
cv2.imshow("im1High",im1High)

cv2.waitKey(0)
cv2.destroyAllWindows()

依次为原图、向下取样一次、向上取样、原图-向上取样结果
可多层构造拉普拉斯金字塔

5、findContours

这是个轮廓检测的方法,注意,边缘和轮廓是不一样的,边缘不一定连续,但是轮廓是连续的。


import cv2

o = cv2.imread(r"fiction\\xiaozhu.jpg", cv2.IMREAD_GRAYSCALE)
co = cv2.imread(r"fiction\\xiaozhu.jpg", cv2.IMREAD_UNCHANGED)
cco = co.copy()
max_threshold,img = cv2.threshold(o,127,255,cv2.THRESH_BINARY)
##findcounters:原始图像、轮廓检测方式(只检测外轮廓、等级树形式等)、轮廓近似方式
##查找、绘制过程中会改变原图像
counters, hieraichy = cv2.findContours(img, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
r = cv2.drawContours(co, counters, -1, (255,0,0),6)

cv2.namedWindow("o", 0)
cv2.namedWindow("r", 0)
cv2.imshow("o", cco)
cv2.imshow("r", r)
cv2.waitKey(0)
cv2.destroyAllWindows()

6、高通滤波

利用傅里叶变换,将o转化为频域
低频为内部信息,高频为边缘信息
通过高通滤波器得到边缘信息

#低频为内部信息,高频为边缘信息

import cv2
import matplotlib.pyplot as plt
import numpy as np

o = cv2.imread(r"fiction\\xiaozhu.jpg", cv2.IMREAD_GRAYSCALE)
##利用傅里叶变换,将o转化为频域,cv2.DFT_COMPLEX_OUTPUT返回双通道的结果,包含幅度和频率,第一个通道是实数部分,第二个通道为虚数部分
#dft = cv2.dft(np.float32(o), flags=cv2.DFT_COMPLEX_OUTPUT)


dft = np.fft.fft2(o)

#将频域0点移动到中心
dft1 = np.fft.fftshift(dft)

# result = 20*np.log(cv2.magnitude(dft1[:,:,0], dft1[:,:,1]))
#
# plt.subplot(2,2,1)
# plt.imshow(o, cmap="gray")
#
# plt.subplot(2,2,2)
# plt.imshow(result,cmap="gray")
#
# plt.show()

##通过高通滤波器得到边缘信息
rows,cols = o.shape
crow,ccols = int(rows/2),int(cols/2)

dft1[crow-3:crow+30,ccols-30:ccols+30] = 0

ishift = np.fft.ifftshift(dft1)

iimg = np.fft.ifft2(ishift)

iimg = np.abs(iimg)

plt.subplot(1,2,1)
plt.imshow(o,cmap="gray")

plt.subplot(1,2,2)
plt.imshow(iimg,cmap="gray")

plt.show()

OpenCV边缘检测


上一章节,我们在使用图像轮廓发现的时候使用了图像边缘检测,一次来提高图像轮廓发现的准确率。事实上在计算机的各个领域都有图像边缘检测的身影。边缘检测一大优点就在于可以大幅度减少数据量,并且提出可以认为不相关的信息,保留了图像的结构属性。边缘检测的方法有很多,但是绝大部分都可以分为两大类,第一类是基于搜索,也就是通过寻找图像一阶导数中的最大值和最小值来检测边界,通常是定位在梯度最大的方向。其次是基于零穿越的方法,其通过寻找图像二阶导数零穿越来寻找便捷,通常是Laplacian过零点或者非线性差分表示的过零点。(以下内容引用自百度百科)




边缘可能与视角有关—— 也就是说边缘可能随着视角不同而变化,典型地反映在场景、物体的几何形状一个将另一个遮挡起来,也可能与视角无关——这通常反映被观察物体的属性如表面纹理和表面形状。在二维乃至更高维空间中,需要考虑透视投影的影响。


在我们需要检测表面纹理和表面形状时,我们往往需要更细致的检测,比如基于二阶导数的Canny,但是很多时候简单的基于一阶导数的算子想过可能更好。不同的算子由于其具体算法不同,实际效果也存在比较大的差距。具体情况还需要具体处理。

OpenCV(四)边缘检测
Sobel 边缘检测


基本原理:

Sobel算子是几种边缘检测算子中最简单的,其由两组3*3矩阵组成(这里用水平矩阵sobelx,垂直矩阵sobely表示)。将两组算子与图像(这里用A表示)做平面卷积就可以的得到垂直和水平的亮度差分近似值。然后Gx=sobel*A,Gy=sobely*A,最终得到的结果就是:

OpenCV(四)边缘检测

Sobel相比于其他算子的优势在于比较简单和快速,只需要三次简单运算就可以得到(Sobel需要灰度图,所以更准确的说想要使用Sobel还需要灰度图转换的步骤)。同时Sobel也可以只检测垂直方向或者只检测水平方向。因此当我们不需要注意细纹理或者只需要单方向检测的时候不妨使用一下Sobel。

下面是Sobel在OpenCV中的实现,这里我们还是使用之前的OpenCV中自带的水果分尸图,下面是核心代码,经过了这么多次的我们就不再多写数据加载,灰度转换,窗口等待这些基本内容了:

代码实现:

# 使用sobel算子并进行边缘检测sobelx = cv2.Sobel(gray, cv2.CV_64F, 1, 0)sobely = cv2.Sobel(gray, cv2.CV_64F, 0, 1)
sobelx = numpy.uint8(numpy.absolute(sobelx))sobely = numpy.uint8(numpy.absolute(sobely))
# 得带最终结果sobelcombine = cv2.bitwise_or(sobelx,sobely)
# 展示效果并保存cv2.imshow("Edge detection by Sobel", numpy.hstack([gray,sobelx,sobely, sobelcombine]))cv2.imwrite("1_edge_by_sobel.jpg", numpy.hstack([gray,sobelx,sobely, sobelcombine]))

OpenCV(四)边缘检测

OpenCV(四)边缘检测

Canny边缘检测

基本原理:

以下内容引用自百度百科:

Canny边缘检测算子是John F. Canny于1986年开发出来的一个多级边缘检测算法。更为重要的是Canny创立了“边缘检测计算理论”(computational theory of edge detection)解释这项技术如何工作。

Canny的目标是找到一个最优的边缘检测算法,而这个最优边缘检测算法的含义是指三个方面:

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

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

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

为了满足这些要求Canny使用了变分法,这是一种寻找满足特定功能的函数的方法。最优检测使用四个指数函数项的和表示,但是它非常近似于高斯函数的一阶导数。

为了得到最好的结果,Canny算子需要多个步骤,第一步是降噪,任何一个边缘检测算法都不可能在一个没有经过降噪的图片上得到好的结果。Canny的第一步是进行高斯平滑操作,这样可以有效避免少量噪音像素对最终结果造成不好的影响。

第二步是寻找梯度,为了在各个方向更好地寻找梯度,Canny算法使用4个mask寻找水平,垂直和对角线方向的边缘(可以和Sobel的两种作对比),这样和Sobel类似,可以得到图像中每个像素的亮度梯度图以及亮度梯度的方向图。

第三步是跟踪边缘,较亮的亮度梯度更有可能是边缘,但是较亮的亮度梯度并不一定都是边缘,有些亮度梯度比较明亮可能是真正的边缘,有些则可能不是。所以Canny使用了滞后阈值,滞后阈值由两个阈值——高阈值和低阈值共同组成,我们需要同时使用他们来确定真正的边缘。

首先假设图像中的重要边缘都是连续的曲线。那么我们首先根据求导得到的方向信息,用一个较大的阈值标识出我们比较确信的真实边缘,然后使用一个较小的阈值来扩展这些已经定了的真实边缘。由于我们假设重要边缘都是连续的曲线,那么也就意味着只要我们不断地沿着最可能是真实边缘的方向(最亮梯度方向)不断延长我们的真实边缘,直到到达下一个最亮梯度达到最小阈值,这样能够最终获得一个完美的曲线来表示我们想要寻找的重要边缘。当整个过程完成,我们就得到了一个二值图像,每点表示是否是一个边缘点。

除此之外,还有一个获得亚像素精度边缘的改进实现是在梯度方向检测二阶方向导数的过零点。这里我们不再进行详细描述。

但是通过对Canny算法的具体了解,我们得到了两个重要的内容就是有两个核心的参数可以有效的影响Canny边缘检测的效果。一个是第一步的高斯平滑,第二是第三步的阈值设置。我们上一篇文章末尾的例子(opencv自带的例子)就是使用的Canny算子进行的边缘检测的展示。其中也包含了这两个核心的设置,模糊和阈值。

代码实现:

为了方便演示我们来写一个更简单的例子:

canny = cv2.Canny(gray, 30, 150)
canny = numpy.uint8(numpy.absolute(canny))#display two images in a figurecv2.imshow("Edge detection by Canny", numpy.hstack([gray,canny]))

OpenCV(四)边缘检测

写到这你可能很奇怪,为什么这里Canny也是使用的灰度图,我们上一次演示不是在彩色图片上绘制出的边缘吗?这是因为之前的demo思路和之前的图像轮廓获取一致的。都是在获取要检测的内容之后,将结果绘制在了原来的彩色图片上。而且要注意的是这些demo中的阈值都是可以调节的哦,具体请见下图:


OpenCV(四)边缘检测

OpenCV(四)边缘检测

其中第一幅图片的高低阈值比例是固定的,而第二幅图片没有固定高低阈值,我们可以手动调节查看具体效果。这两个更详细的demo都是opencv自带的例子,尤其第二个是可以实时从摄像头获取数据并检测绘制的。我们之前还没有接触过有关的内容,但是之后的章节将会不断接触。

OpenCV(四)边缘检测

Laplacian边缘检测


Laplacian 算子是n维欧几里德空间中的一个二阶微分算子,可以用于图像增强或者边缘提取。效果和之前的差别不大,和Canny一样属于二阶算法,但是由于要计算梯度,OpeCV的Laplacian算子内部也使用了Sobel,同时他又和Canny一样进行了多个方向上的梯度检测,因为复杂度更好,细纹理的发现效果更好。由于篇幅原因这里不再多讲,直接看效果就好。

ap = cv2.Laplacian(gray, cv2.CV_64F,ksize=3)
Laplacian = cv2.convertScaleAbs(lap)# 等价于上面的代码# Laplacian = numpy.uint8(numpy.absolute(lap))
#display two images in a figurecv2.imshow("Edge detection by Laplacaian", numpy.hstack([gray,Laplacian]))

OpenCV(四)边缘检测

OpenCV(四)边缘检测

Scharr

OpenCV还有一个边缘检测Scharr,其主要是为了配合Sobel,下面代码是等价的。

Scharr(src,dst,ddepth,dx,dy,scale,delta,borderType)sobel(src,dst,ddepth,dx,dy,CV_SCHARR,scale,delta,borderType)
写在最后


总结:

边缘检测是OpenCV中比较多的内容,完全可以铺开讲很多,但是考虑到该部分在当前的计算机视觉领域已经不算重点,所以这里没有讲太多。但是万变不离其宗,所以我们重点讲述了一个一阶算法Sobal,一个二阶算法Canny,同时也代码展示了OpenCV中的其他元素。希望这些能够有所帮助。

而除了边缘检测,OpenCV中还有一类名字有点接近边缘检测的检测器,那就是角点检测。角点检测使用范围将会更广。下次我们将从讲点检测谈起,说说更复杂的内容。

下载:

参考:

1、百度百科——边缘检测https://baike.baidu.com/item/%E8%BE%B9%E7%BC%98%E6%A3%80%E6%B5%8B

2、OpenCV—github官方项目

https://github.com/opencv/opencv)


往期回顾:








以上是关于基于opencv的边缘检测方法的主要内容,如果未能解决你的问题,请参考以下文章

基于OpenCV的图像梯度与边缘检测!

基于opencv的边缘检测方法

youcans 的 OpenCV 例程200篇151. 边缘检测中的平滑处理

OpenCV4中调用HED边缘检测算法

OpenCV--边缘检测

OpenCV3入门角点检测