快乐学习
前程无忧、中华英才非你莫属!

9-OpenCV-梯度、边缘检测、金字塔

一、梯度


本章节你需要学习以下内容:

*查找图像渐变,边缘等

*我们将看到以下函数:cv.Sobel(),cv.Scharr(),cv.Laplacian()等



1、理论

OpenCV提供三种类型的梯度滤波器或高通滤波器,Sobel,Scharr和Laplacian。我们会一一介绍他们。

Sobel,Scharr 其实就是求一阶或二阶导数。Scharr是对Sobel(使用小的卷积核求解求解梯度角度时)的优化。Laplacian 是求二阶导数。

(1)Sobel算子和Scharr算子

Sobel算子是高斯联合平滑加微分运算,因此它更能抵抗噪声。你可以指定要采用的导数的方向,垂直或水平(分别通过参数,yorder和xorder),你还可以通过参数ksize指定卷积核的大小。如果ksize = -1,则使用3x3的Scharr滤波器,其结果优于3x3的Sobel滤波器。请参阅所用卷积核的文档。

(2)Laplacian算子

它计算由关系给出的图像的拉普拉斯算子,$Delta src= frac{partial ^{2}src}{partial x^{2}}+ frac{partial ^{2}src}{partial y^{2}}$,其中使用Sobel导数找到每个导数。 如果ksize = 1,则使用以下卷积核进行过滤:

$$kernel=begin{bmatrix}  0    1    0  1 -4  1  0    1    0 end{bmatrix}$$

2、代码实现

下面的代码显示了单个图表中的所有运算符,所有卷积核都是5x5大小。

输出图像的深度为-1,以获得np.uint8类型的结果。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('dave.jpg',0)
laplacian = cv.Laplacian(img,cv.CV_64F)
sobelx = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
sobely = cv.Sobel(img,cv.CV_64F,0,1,ksize=5)
plt.subplot(2,2,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,2),plt.imshow(laplacian,cmap = 'gray')
plt.title('Laplacian'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,3),plt.imshow(sobelx,cmap = 'gray')
plt.title('Sobel X'), plt.xticks([]), plt.yticks([])
plt.subplot(2,2,4),plt.imshow(sobely,cmap = 'gray')
plt.title('Sobel Y'), plt.xticks([]), plt.yticks([])
plt.show()



3、一个重要的事情

在我们的上一个示例中,输出数据类型为cv.CV_8U或np.uint8,但是这有一个小问题,将黑到白转换视为正斜率(它具有正值),而将白到黑转换视为负斜率(它具有负值)。

因此,当你将数据转换为np.uint8时,所有负斜率都为零。简单来说,你丢掉了所有的边界。

如果要检测两个边,更好的选择是将输出数据类型保持为某些更高的形式,如cv.CV_16S,cv.CV_64F等,取其绝对值,然后转换回cv.CV_8U。

下面的代码演示了水平Sobel滤波器的这个过程以及结果的差异。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('box.png',0)
# Output dtype = cv.CV_8U
sobelx8u = cv.Sobel(img,cv.CV_8U,1,0,ksize=5)
# Output dtype = cv.CV_64F. Then take its absolute and convert to cv.CV_8U
sobelx64f = cv.Sobel(img,cv.CV_64F,1,0,ksize=5)
abs_sobel64f = np.absolute(sobelx64f)
sobel_8u = np.uint8(abs_sobel64f)
plt.subplot(1,3,1),plt.imshow(img,cmap = 'gray')
plt.title('Original'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,2),plt.imshow(sobelx8u,cmap = 'gray')
plt.title('Sobel CV_8U'), plt.xticks([]), plt.yticks([])
plt.subplot(1,3,3),plt.imshow(sobel_8u,cmap = 'gray')
plt.title('Sobel abs(CV_64F)'), plt.xticks([]), plt.yticks([])
plt.show()

窗口如下图显示:



二、Canny边缘检测


目标:

本章节你需要学习以下内容:

*Canny边缘检测的概念

*OpenCV的功能:cv.Canny()

1、理论

Canny边缘检测是一种流行的边缘检测算法,它是由John F. Canny开发的

这是一个多阶段算法,我们将了解其中的每个阶段。

(1)降噪

由于边缘检测易受图像中的噪声影响,因此第一步是使用5x5高斯滤波器去除图像中的噪声。我们在之前的章节中已经看到了这一点。

(2)计算图像的强度梯度

然后在水平和垂直方向上用Sobel核对平滑后的图像进行滤波,以获得水平方向($G_{x}$)和垂直方向($G_{y}$)的一阶导数。从这两个图像中,我们可以找到每个像素的边缘梯度和方向,如下所示:

$$Edge_Gradientleft ( G right )= sqrt{G_{x}^{2}+G_{y}^{2}}$$

$$Angleleft ( theta right )= tan^{-1}left ( frac{G_{y}}{G_{x}} right )$$

渐变方向始终垂直于边缘。梯度方向被归为四类:垂直,水平,和两个对角线。

(3)非极大值抑制

在获得梯度的大小和方向之后,完成图像的全扫描以去除可能不构成边缘的任何不需要的像素。为此,在每个像素处,检查像素是否是其在梯度方向上的邻域中的局部最大值。检查下图:

A点位于边缘(垂直方向)。渐变方向与边缘垂直。B点和C点处于梯度方向。

因此,用点B和C检查点A,看它是否形成局部最大值。如果是这样,则考虑下一阶段,否则,它被抑制(置零)。

简而言之,你得到的结果是具有“细边”的二进制图像。

(4)滞后阈值

这个阶段决定哪些边缘都是边缘,哪些边缘不是边缘。为此,我们需要两个阈值,minVal和maxVal。强度梯度大于maxVal的任何边缘肯定是边缘,而minVal以下的边缘肯定是非边缘的,因此被丢弃。位于这两个阈值之间的人是基于其连通性的分类边缘或非边缘。如果它们连接到“可靠边缘”像素,则它们被视为边缘的一部分。否则,他们也被丢弃。见下图:

边缘A高于maxVal,因此被视为“确定边缘”。虽然边C低于maxVal,但它连接到边A,因此也被视为有效边,我们得到完整的曲线。但边缘B虽然高于minVal并且与边缘C的区域相同,但它没有连接到任何“可靠边缘”,因此被丢弃。所以我们必须相应地选择minVal和maxVal才能获得正确的结果。

假设边是长线,这个阶段也会消除小像素噪声。

所以我们最终得到的是图像中的强边缘。


2、OpenCV中的Canny边缘检测

OpenCV将以上所有步骤放在单个函数cv.Canny()中。我们将看到如何使用它。第一个参数是我们的输入图像。第二个和第三个参数分别是我们的minVal和maxVal。第三个参数是aperture_size,它是用于查找图像渐变的Sobel卷积核的大小。默认情况下它是3。最后一个参数是L2gradient,它指定用于查找梯度幅度的等式。如果它是True,它使用上面提到的更准确的等式,否则它使用这个函数:$Edge_Gradientleft ( G right )= left | G_{x} right |+left | G_{y} right |$。默认情况下,它为False。

import numpy as np
import cv2 as cv
from matplotlib import pyplot as plt
img = cv.imread('messi5.jpg',0)
edges = cv.Canny(img,100,200)
plt.subplot(121),plt.imshow(img,cmap = 'gray')
plt.title('Original Image'), plt.xticks([]), plt.yticks([])
plt.subplot(122),plt.imshow(edges,cmap = 'gray')
plt.title('Edge Image'), plt.xticks([]), plt.yticks([])

plt.show()

窗口将如下图显示:



三、图像金字塔


目标:

本章节你需要学习以下内容:

*我们将了解Image Pyramids

*我们将使用Image金字塔创建一个新的水果,“Orapple”

*我们将看到这些函数:cv.pyrUp(),cv.pyrDown()

1、理论

通常,我们曾经使用恒定大小的图像。但在某些情况下,我们需要使用不同分辨率的(相同)图像。

例如,在搜索图像中的某些内容时,如脸部,我们不确定该对象在所述图像中的大小。

在这种情况下,我们需要创建一组具有不同分辨率的相同图像,并在所有图像中搜索对象。

这些具有不同分辨率的图像被称为图像金字塔

(因为当它们保持在堆叠中,底部具有最高分辨率图像而顶部具有最低分辨率图像时,它看起来像金字塔)。

图像金字塔有两种:高斯金字塔和拉普拉斯金字塔


通过去除较低级别(较高分辨率)图像中的连续行和列来形成高斯金字塔中的较高级别(低分辨率)。

然后,较高级别中的每个像素由来自基础级别中的5个像素的贡献形成,具有高斯权重。

通过这样做,M×N图像变为M/2 × N/2图像。因此面积减少到原始面积的四分之一。

它被称为Octave。当我们在金字塔中上升时(即分辨率降低)将以相同的模式继续。

同样,在扩展时,每个级别的区域变为4次。我们可以使用cv.pyrDown()和cv.pyrUp()函数找到高斯金字塔。

img = cv.imread('messi5.jpg')

lower_reso = cv.pyrDown(higher_reso)

窗口将如下图显示:

现在,你可以使用cv.pyrUp()函数沿着图像金字塔向下移动。

higher_reso2 = cv.pyrUp(lower_reso)

请记住,higher_reso2不等于higher_reso,因为一旦降低了分辨率,就会丢失信息。

下图是从前一种情况下的最小图像创建的金字塔下3级。将其与原始图像进行比较:

拉普拉斯金字塔由高斯金字塔形成,没有特别的功能。

拉普拉斯金字塔图像仅与边缘图像相似。它的大部分元素都是零。

它们用于图像压缩。拉普拉斯金字塔中的一个层次由高斯金字塔中的该层次与高斯金字塔中的上层的扩展版本之间的差异形成。

拉普拉斯级别的三个级别如下所示(调整对比度以增强内容):


2、使用金字塔的图像混合

金字塔的一个应用是图像混合。例如,在图像拼接中,你需要将两个图像堆叠在一起,但由于图像之间的不连续性,它可能看起来不太好。

在这种情况下,与金字塔混合的图像可以让你无缝混合,而不会在图像中留下太多数据。其中一个典型的例子是混合了两种水果,橙子和苹果。

 现在查看结果以了解我在说什么:

请在附加资源中查看第一个参考资料,它有关于图像混合,拉普拉斯金字塔等的完整图表细节。简单地完成如下:

  1. 加载苹果和橙色的两个图像

  2. 找到苹果和橙色的高斯金字塔(在这个特殊的例子中,级别数是6)

  3. 从高斯金字塔,找到他们的拉普拉斯金字塔

  4. 现在加入左半部分的苹果和右半部分的拉普拉斯金字塔

  5. 最后,从这个联合图像金字塔,重建原始图像。

以下是完整的代码。(为简单起见,每个步骤都是单独完成的,可能会占用更多内存。如果需要,可以对其进行优化)。

import cv2 as cv
import numpy as np,sys
A = cv.imread('apple.jpg')
B = cv.imread('orange.jpg')
# generate Gaussian pyramid for AG = A.copy()
gpA = [G]
for i in xrange(6):
G = cv.pyrDown(G)
gpA.append(G)
# generate Gaussian pyramid for BG = B.copy()
gpB = [G]
for i in xrange(6):
G = cv.pyrDown(G)
gpB.append(G)
# generate Laplacian Pyramid for AlpA = [gpA[5]]
for i in xrange(5,0,-1):
GE = cv.pyrUp(gpA[i])
L = cv.subtract(gpA[i-1],GE)
lpA.append(L)
# generate Laplacian Pyramid for BlpB = [gpB[5]]
for i in xrange(5,0,-1):
GE = cv.pyrUp(gpB[i])
L = cv.subtract(gpB[i-1],GE)
lpB.append(L)
# Now add left and right halves of images in each levelLS = []
for la,lb in zip(lpA,lpB):
rows,cols,dpt = la.shape
ls = np.hstack((la[:,0:cols/2], lb[:,cols/2:]))
LS.append(ls)
# now reconstructls_ = LS[0]
for i in xrange(1,6):
ls_ = cv.pyrUp(ls_)
ls_ = cv.add(ls_, LS[i])
# image with direct connecting each halfreal = np.hstack((A[:,:cols/2],B[:,cols/2:]))
cv.imwrite('Pyramid_blending2.jpg',ls_)
cv.imwrite('Direct_blending.jpg',real)

打赏
赞(0) 打赏
未经允许不得转载:同乐学堂 » 9-OpenCV-梯度、边缘检测、金字塔

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

error: Sorry,暂时内容不可复制!