在图像识别中,如果可以将图像感兴趣的物体或区别分割出来,无疑可以增加我们图像识别的准确率,传统的数字图像处理中的分割方法多数基于灰度值的两个基本性质
虽然许多检测算法都被opencv封装成函数可以直接调用,但是理解其背后的理论依据可以更好地帮助我们理解和改进算法
用于计算图像中每个像素位置处的一阶导数和二阶导数可选择方法是使用空间滤波器
R = w 1 z 1 + w 2 z 2 + ⋯ + w 9 z 9 = ∑ k = 1 9 w k z k R = w_1z_1+w_2z_2+\dots +w_9z_9=\sum_{k=1}^9w_kz_k R=w1z1+w2z2+⋯+w9z9=k=1∑9wkzk
点的检测以二阶导数为基础。这意味可以着使用拉普拉斯模板(详情见空间域滤波基础)
如果在某点该处模板的响应的绝对值超过了一个指定的阈值,那么我们` 说在模板中心位置(x,y)处的该点已被检测到了。在输入图像中,这样的点被标注为1,而所有其他点则被标注为0,从而产生一副二值图像。
g ( x , y ) = { 1 ∣ R ( x , y ) ∣ ≥ T 0 其他 g(x,y)=\begin{cases} 1& | R(x,y)| \geq T\\ 0 & \text{ 其他 } \end{cases} g(x,y)={10∣R(x,y)∣≥T 其他
其中,g是输出图像,T是一个非负的阈值,R由上式给出。
复杂度更高的检测是线检测,对于线检测,可以预期二阶导数将导致更强的响应,并产生比一阶导数更细的线。这样对于线检测,我们也可以使用拉普拉斯模板,记住,二阶导数的双线效应必须做适当的处理。
边缘检测是基于灰度突变来分割图像最常用的方法。我们从介绍一些边缘建模的方法开始,然后讨论一些边缘检测的方法。
实际中,数字图像都存在被模糊且带有噪声的边缘,模糊程度主要取决于聚焦机理中的兼职,而噪声水平主要取决于成像系统的电子元件。在这种情况下,边缘被建模为一个更接近灰度斜坡的剖面。
并且我们可以得出结论:一阶导数的幅度可用于检测图像中的某个点是否存在一个边缘,二阶导数可以用于确定一个边缘像素位于该边缘的暗的一侧还是亮的一侧。
那么这是理想情况下的图片边缘,如果图片有噪声的话,其边缘函数则为
微弱的可见噪声对检测边缘所用的两个关键导数的严重影响的这一事实,是我们应记住的一个重要问题。特别地,在类似于我们刚刚讨论的水平的噪声很可能存在的应用中,使用导数之前的图像平滑处理是应该认真考虑的问题。
因此边缘检测的三个基本步骤
Roberts算子
Roberts算子以求对角像素之差为基础,该算子用于识别对角线方向的边缘:
g x = ( z 9 − z 5 ) 和 g y = ( z 8 − z 6 ) g_x=(z_9-z_5)和g_y=(z_8-z_6) gx=(z9−z5)和gy=(z8−z6)
Prewitt算子
Prewitt算子使用以 z 5 z_5 z5为中心的3x3领域对 g x g_x gx和 g y g_y gy的近似如下式所示
g x = ( z 7 + z 8 + z 9 ) − ( z 1 + z 2 + z 3 ) g_x=(z_7+z_8+z_9)-(z_1+z_2+z_3) gx=(z7+z8+z9)−(z1+z2+z3)
g y = ( z 3 + z 6 + z 9 ) − ( z 1 + z 4 + z 7 ) g_y=(z_3+z_6+z_9)-(z_1+z_4+z_7) gy=(z3+z6+z9)−(z1+z4+z7)
模板如下图:
Sobel算子
Sobel算子使用以 z 5 z_5 z5为中心的3x3领域对 g x g_x gx和 g y g_y gy的近似如下式所示:
g x = ( z 7 + 2 z 8 + z 9 ) − ( z 1 + 2 z 2 + z 3 ) g_x=(z_7+2z_8+z_9)-(z_1+2z_2+z_3) gx=(z7+2z8+z9)−(z1+2z2+z3)
g y = ( z 3 + 2 z 6 + z 9 ) − ( z 1 + 2 z 4 + z 7 ) g_y=(z_3+2z_6+z_9)-(z_1+2_z4+z_7) gy=(z3+2z6+z9)−(z1+2z4+z7)
Sobel模板能较好地抑制(平滑)噪声地特性使得它更为可取,因为在处理导数时噪声抑制是一个重要地问题。
检测对角边缘的Prewitt和Sobel模板
① 概念背景:
最早的成功地尝试将更高级的分析结合到边缘检测处理之一应归功与Marr和Hildreth[1980]。
Marr和Hildreth证明了:
这些概念建议,用于边缘检测的算子应有两个显著的特点。
② 高斯拉普拉斯(LoG):
Marr和Hildreth证明了:满足这些最令人满意的算子是滤波器 ▽ 2 G \triangledown^2G ▽2G, ▽ 2 \triangledown^2 ▽2是拉普拉斯算子( ∂ 2 / ∂ x 2 + ∂ 2 / ∂ y 2 \partial^2/\partial x^2+\partial^2/\partial y^2 ∂2/∂x2+∂2/∂y2),而G是标准差为 σ \sigma σ的二维高斯函数
G ( x , y ) = e − x 2 + y 2 2 σ 2 G(x,y)=e^{-\frac{x^2+y^2}{2\sigma^2}} G(x,y)=e−2σ2x2+y2
为求 ▽ 2 G \triangledown^2G ▽2G的表达式,我们执行如下微分:
▽ 2 G ( x , y ) = ∂ 2 G ( x , y ) ∂ x 2 + ∂ 2 G ( x , y ) ∂ y 2 = ∂ ∂ x [ − x σ 2 e − x 2 + y 2 2 σ 2 ] + ∂ ∂ y [ − y σ 2 e − x 2 + y 2 2 σ 2 ] \triangledown^2G(x,y)=\frac{\partial^2G(x,y)}{\partial x^2}+\frac{\partial^2G(x,y)}{\partial y^2}=\frac{\partial}{\partial x}[\frac{-x}{\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}]+\frac{\partial}{\partial y}[\frac{-y}{\sigma^2}e^{-\frac{x^2+y^2}{2\sigma^2}}] ▽2G(x,y)=∂x2∂2G(x,y)+∂y2∂2G(x,y)=∂x∂[σ2−xe−2σ2x2+y2]+∂y∂[σ2−ye−2σ2x2+y2]
整理各项后给出如下最终表达式:
▽ 2 G ( x , y ) = [ x 2 + y 2 − 2 σ 2 σ 4 ] e − x 2 + y 2 2 σ 2 \triangledown^2G(x,y)=[\frac{x^2+y^2-2\sigma^2}{\sigma^4}]e^{-\frac{x^2+y^2}{2\sigma^2}} ▽2G(x,y)=[σ4x2+y2−2σ2]e−2σ2x2+y2
该表达式成为高斯拉普拉斯(LoG)
③ Marr-Hildreth算法
Marr-Hildreth算法由LOG滤波器与一副输入图像f(x,y)卷积组成,即
g ( x , y ) = [ ▽ 2 G ( x , y ) ] ★ f ( x , y ) g(x,y)=[\triangledown^2G(x,y)]\bigstar f(x,y) g(x,y)=[▽2G(x,y)]★f(x,y)
然后找寻g(x,y)的零交叉来确定f(x,y)中边缘的位置。因为这些都是线性操作,故可以写为
KaTeX parse error: Expected 'EOF', got '\bigstarf' at position 30: …ledown^2[G(x,y)\̲b̲i̲g̲s̲t̲a̲r̲f̲(x,y)]
它指出我们可以先使用一个高斯滤波器来平滑图像,然后计算该结果的拉普拉斯。
Marr-Hildreth算法小结
零交叉是Marr-Hildreth边缘检测方法的关键特征,实现简单,并且通常能给出好的结果。
import numpy as np
import matplotlib.pyplot as plt
import cv2
def edgesMarrHildreth(img, sigma):
"""
finds the edges using MarrHildreth edge detection method...
:param im : input image
:param sigma : sigma is the std-deviation and refers to the spread of gaussian
:return:
a binary edge image...
"""
size = int(2 * (np.ceil(3 * sigma)) + 1)
x, y = np.meshgrid(np.arange(-size / 2 + 1, size / 2 + 1), np.arange(-size / 2 + 1, size / 2 + 1))
normal = 1 / (2.0 * np.pi * sigma ** 2)
kernel = ((x ** 2 + y ** 2 - (2.0 * sigma ** 2)) / sigma ** 4) * np.exp(
-(x ** 2 + y ** 2) / (2.0 * sigma ** 2)) / normal # LoG filter
kern_size = kernel.shape[0]
log = np.zeros_like(img, dtype=float)
# applying filter
for i in range(img.shape[0] - (kern_size - 1)):
for j in range(img.shape[1] - (kern_size - 1)):
window = img[i:i + kern_size, j:j + kern_size] * kernel
log[i, j] = np.sum(window)
log = log.astype(np.int64, copy=False)
zero_crossing = np.zeros_like(log)
# computing zero crossing
for i in range(log.shape[0] - (kern_size - 1)):
for j in range(log.shape[1] - (kern_size - 1)):
if log[i][j] == 0:
if (log[i][j - 1] < 0 and log[i][j + 1] > 0) or (log[i][j - 1] < 0 and log[i][j + 1] < 0) or (
log[i - 1][j] < 0 and log[i + 1][j] > 0) or (log[i - 1][j] > 0 and log[i + 1][j] < 0):
zero_crossing[i][j] = 255
if log[i][j] < 0:
if (log[i][j - 1] > 0) or (log[i][j + 1] > 0) or (log[i - 1][j] > 0) or (log[i + 1][j] > 0):
zero_crossing[i][j] = 255
# plotting images
fig = plt.figure()
a = fig.add_subplot(1, 2, 1)
imgplot = plt.imshow(log, cmap='gray')
a.set_title('Laplacian of Gaussian')
a = fig.add_subplot(1, 2, 2)
imgplot = plt.imshow(zero_crossing, cmap='gray')
string = 'Zero Crossing sigma = '
string += (str(sigma))
a.set_title(string)
plt.show()
return zero_crossing
img = cv2.imread('images/17.jpg',0)
img = edgesMarrHildreth(img,4)
(可以看到这个算法检测出了图像的边缘,但是有很多地方都是不连续的,那么之后我们会介绍如何检测边界和边缘连接)
虽然其算法更为复杂,但是Canny边缘检测是迄今为止讨论过的边缘检测器中最为优秀的,Canny基于三个基本目标:
Canny工作的本质是,从数学上表达前面的三个准则,并试图找到这些表达式的最佳解,通常这是很困难的,但是我们可以使用高斯近似得出最优解:首先使用一个环形二维高斯函数平滑图像,计算结果的梯度,然后使用梯度幅度和方向来估计每一点的边缘强度与方向
第一步
令f(x,y)表示输入图像,G(x,y)表示高斯函数:
G ( x , y ) = e − x 2 + y 2 2 σ 2 G(x,y)=e^{-\frac{x^2+y^2}{2\sigma^2}} G(x,y)=e−2σ2x2+y2
我们用G和f卷积形成一幅平滑的图像 f s ( x , y ) f_s(x,y) fs(x,y):
f s ( x , y ) = G ( x , y ) ★ f ( x , y ) f_s(x,y)=G(x,y)\bigstar f(x,y) fs(x,y)=G(x,y)★f(x,y)
第二步
接下来计算结果的梯度幅度和方向:
M ( x , y ) = g x 2 + g y 2 M(x,y)=\sqrt {g_x^2+g_y^2} M(x,y)=gx2+gy2
α ( x , y ) = a r c t a n [ g y g x ] \alpha(x,y)=arctan[\frac{g_y}{g_x}] α(x,y)=arctan[gxgy]
第三步
细化边缘,使用非最大抑制:
第四步
最后操作时对 g N ( x , y ) g_N(x,y) gN(x,y)进行阈值处理,以便减少伪边缘点,Canny算法使用两个阈值:低阈值 T L T_L TL和高阈值 T H T_H TH(Canny建议高低阈值比为2:1或3:1)
g N H = { g N ( x , y ) ≥ T H } g_{NH}=\left \{ g_N(x,y)\geq T_H\right\} gNH={gN(x,y)≥TH}
g N L = { g N ( x , y ) ≥ T L } g_{NL}=\left \{ g_N(x,y)\geq T_L\right\} gNL={gN(x,y)≥TL}
g N H = g N L ( x , y ) − g N H ( x , y ) g_{NH}= g_{NL}(x,y)-g_{NH}(x,y) gNH=gNL(x,y)−gNH(x,y)
g N H ( x , y ) g_{NH}(x,y) gNH(x,y)和 g N L ( x , y ) g_{NL}(x,y) gNL(x,y)的非零像素可分别视为“强”和“弱”边缘像素。其中 g N H ( x , y ) g_{NH}(x,y) gNH(x,y)为边缘点, g N L ( x , y ) g_{NL}(x,y) gNL(x,y)为候选点,对于候选点,如果与边缘点邻近,就标记为边缘点。
具体步骤如下:
那么我们来总结一下Canny算法
Canny算法步骤
OpenCV将这一算法封装成了一个函数
def Canny(image, threshold1, threshold2, edges=None, apertureSize=None, L2gradient=None): # real signature unknown; restored from __doc__
"""
Canny(image, threshold1, threshold2[, edges[, apertureSize[, L2gradient]]]) -> edges
. @brief Finds edges in an image using the Canny algorithm @cite Canny86 .
.
. The function finds edges in the input image and marks them in the output map edges using the
. Canny algorithm. The smallest value between threshold1 and threshold2 is used for edge linking. The
. largest value is used to find initial segments of strong edges. See
.
.
. @param image 8-bit input image.
. @param edges output edge map; single channels 8-bit image, which has the same size as image .
. @param threshold1 first threshold for the hysteresis procedure.
. @param threshold2 second threshold for the hysteresis procedure.
. @param apertureSize aperture size for the Sobel operator.
. @param L2gradient a flag, indicating whether a more accurate \f$L_2\f$ norm
. \f$=\sqrt{(dI/dx)^2 + (dI/dy)^2}\f$ should be used to calculate the image gradient magnitude (
. L2gradient=true ), or whether the default \f$L_1\f$ norm \f$=|dI/dx|+|dI/dy|\f$ is enough (
. L2gradient=false ).
必要参数:
函数返回一副二值图,其中包含检测出的边缘。
使用
Canny函数的使用很简单,只需指定最大和最小阈值即可。如下:
# coding=utf-8
import cv2
import numpy as np
img = cv2.imread("images/17.jpg", 0)
cv2.imshow('img',img)
img = cv2.GaussianBlur(img, (3, 3), 0)
canny = cv2.Canny(img, 50, 150)
cv2.imshow('Canny', canny)
cv2.waitKey(0)
cv2.destroyAllWindows()
可以看到,Canny算法明显提取边缘的效果要优于Marr-Hildreth算法,对边缘的连接也做的更好。