前言

Canny边缘检测是一种非常流行的边缘检测算法，是John Canny在1986年提出的。它是一个多阶段的算法，即由多个步骤构成。本文主要讲解了Canny算子的原理及实现过程。

一、Canny的实现步骤

1. 应用高斯滤波来平滑图像，目的是去除噪声
3. 应用非最大抑制（non-maximum suppression）技术来消除边误检（本来不是但检测出来是）
4. 应用双阈值的方法来决定可能的（潜在的）边界
5. 利用滞后技术来跟踪边界

二、具体实现

1.高斯平滑滤波

def gaussian_kernel(size, sigma):
""" Implementation of Gaussian Kernel.

This function follows the gaussian kernel formula,
and creates a kernel matrix.

Hints:
- Use np.pi and np.exp to compute pi and exp.

Args:
size: int of the size of output matrix.
sigma: float of sigma to calculate kernel.

Returns:
kernel: numpy array of shape (size, size).
"""

kernel = np.zeros((size, size))

for x in range(size):
for y in range(size):
kernel[x, y] = 1 / (2*np.pi*sigma*sigma) * np.exp(-(x*x+y*y)/(2*sigma*sigma))

return kernel


def conv(image, kernel):
""" An implementation of convolution filter.

This function uses element-wise multiplication and np.sum()
to efficiently compute weighted sum of neighborhood at each
pixel.

Args:
image: numpy array of shape (Hi, Wi).
kernel: numpy array of shape (Hk, Wk).

Returns:
out: numpy array of shape (Hi, Wi).
"""
Hi, Wi = image.shape
Hk, Wk = kernel.shape
out = np.zeros((Hi, Wi))

# For this assignment, we will use edge values to pad the images.
# Zero padding will make derivatives at the image boundary very big,
# whereas we want to ignore the edges at the boundary.

x = Hk // 2
y = Wk // 2
# 横向遍历卷积后的图像
# 纵向遍历卷积后的图像
# 对应元素相乘
out[i, j] = np.sum(np.multiply(split_img, kernel))
# out = (out-out.min()) * (1/(out.max()-out.min()) * 255).astype('uint8')

return out


# Test with different kernel_size and sigma
kernel_size = 5
sigma = 1.4

# Define 5x5 Gaussian kernel with std = sigma
kernel = gaussian_kernel(kernel_size, sigma)

# Convolve image with kernel to achieve smoothed effect
smoothed = conv(img, kernel)

plt.subplot(1,2,1)
plt.imshow(img)
plt.title('Original image')
plt.axis('off')

plt.subplot(1,2,2)
plt.imshow(smoothed)
plt.title('Smoothed image')
plt.axis('off')

plt.show()


2.计算梯度大小和方向

def partial_x(img):
""" Computes partial x-derivative of input img.

Hints:
- You may use the conv function in defined in this file.

Args:
img: numpy array of shape (H, W).
Returns:
out: x-derivative image.
"""

out = None

# 对x求偏导
kernel = np.array(([-1, 0, 1], [-1, 0, 1], [-1, 0, 1]))# np.random.randint(10, size=(3, 3))
out = conv(img, kernel) / 2

return out

def partial_y(img):
""" Computes partial y-derivative of input img.

Hints:
- You may use the conv function in defined in this file.

Args:
img: numpy array of shape (H, W).
Returns:
out: y-derivative image.
"""

out = None

# 对y求偏导
kernel = np.array(([1, 1, 1], [0, 0, 0], [-1, -1, -1]))# np.random.randint(10, size=(3, 3))
out = conv(img, kernel) / 2

return out

""" Returns gradient magnitude and direction of input img.

Args:
img: Grayscale image. Numpy array of shape (H, W).

Returns:
G: Magnitude of gradient at each pixel in img.
Numpy array of shape (H, W).
theta: Direction(in degrees, 0 <= theta < 360) of gradient
at each pixel in img. Numpy array of shape (H, W).

Hints:
- Use np.sqrt and np.arctan2 to calculate square root and arctan
"""
G = np.zeros(img.shape)
theta = np.zeros(img.shape)

Gx = partial_x(img)
Gy = partial_y(img)
# 求梯度的大小(平方和的根号)
G = np.sqrt(np.power(Gx, 2) + np.power(Gy, 2))
theta = np.arctan2(Gy, Gx) * 180 / np.pi # 转换成角度制

return G, theta


# Compute partial derivatives of smoothed image
Gx = partial_x(smoothed)
Gy = partial_y(smoothed)

plt.subplot(1,2,1)
plt.imshow(Gx)
plt.title('Derivative in x direction')
plt.axis('off')

plt.subplot(1,2,2)
plt.imshow(Gy)
plt.title('Derivative in y direction')
plt.axis('off')

plt.show()


3.非极大抑制

a) 将其梯度方向近似为以下值中的一个[0,45,90,135,180,225,270,315]（即上下左右和45度方向）这一步是为了方便使用梯度；
b) 比较该像素点，和其梯度方向正负方向的像素点的梯度强度，这里比较的范围一般为像素点的八邻域
c) 如果该像素点梯度强度最大则保留，否则抑制(删除，即置为0)；

def non_maximum_suppression(G, theta):
""" Performs non-maximum suppression.

This function performs non-maximum suppression along the direction

Args:
G: gradient magnitude image with shape of (H, W).
theta: direction of gradients with shape of (H, W).

Returns:
out: non-maxima suppressed image.
"""
H, W = G.shape
out = np.zeros((H, W))

# 将角度近似到45°的对角线, 正负无所谓, 只看对角线跟水平垂直
# Round the gradient direction to the nearest 45 degrees
theta = np.floor((theta + 22.5) / 45) * 45

anchor = np.stack(np.where(G!=0)).T  # 获取非零梯度的位置
for x, y in anchor:
center_point = G[x, y]
current_theta = theta[x, y]
dTmp1 = 0
dTmp2 = 0
W = 0
if current_theta >= 0 and current_theta < 45:
g1, g2, g3, g4 = G[x + 1, y - 1], G[x + 1, y], G[x - 1, y + 1], G[x - 1, y]
W = abs(np.tan(current_theta*np.pi/180))  # tan0-45范围为0-1
dTmp1 = W * g1 + (1-W) * g2
dTmp2 = W * g3 + (1-W) * g4
elif current_theta >= 45 and current_theta < 90:
g1, g2, g3, g4 = G[x+1, y-1], G[x, y-1], G[x-1, y+1], G[x, y+1]
W = abs(np.tan((current_theta-90)*np.pi/180))
dTmp1 = W * g1 + (1-W) * g2
dTmp2 = W * g3 + (1-W) * g4
elif current_theta >= -90 and current_theta < -45:
g1, g2, g3, g4 = G[x-1, y-1], G[x, y-1], G[x+1, y+1], G[x, y+1]
W = abs(np.tan((current_theta-90)*np.pi/180))
dTmp1 = W * g1 + (1-W) * g2
dTmp2 = W * g3 + (1-W) * g4
elif current_theta>=-45 and current_theta<0:
g1, g2, g3, g4 = G[x+1, y+1], G[x+1, y], G[x-1, y-1], G[x-1, y]
W = abs(np.tan(current_theta * np.pi / 180))
dTmp1 = W * g1 + (1-W) * g2
dTmp2 = W * g3 + (1-W) * g4
if dTmp1 < center_point and dTmp2 < center_point:
out[x,y]=center_point
return out


4.双阈值(Double Thresholding)和滞后边界跟踪

def double_thresholding(img, high, low):
"""
Args:
img: numpy array of shape (H, W) representing NMS edge response.
high: high threshold(float) for strong edges.
low: low threshold(float) for weak edges.

Returns:
strong_edges: Boolean array representing strong edges.
Strong edeges are the pixels with the values greater than
the higher threshold.
weak_edges: Boolean array representing weak edges.
Weak edges are the pixels with the values smaller or equal to the
higher threshold and greater than the lower threshold.
"""

strong_edges = np.zeros(img.shape, dtype=np.bool)
weak_edges = np.zeros(img.shape, dtype=np.bool)

H, W = img.shape
for i in range(0, H):
for j in range(0, W):
if img[i, j] > high:
strong_edges[i, j] = True
elif img[i, j] >= low:
weak_edges[i, j] = True

return strong_edges, weak_edges


def get_neighbors(y, x, H, W):
""" Return indices of valid neighbors of (y, x).

Return indices of all the valid neighbors of (y, x) in an array of
shape (H, W). An index (i, j) of a valid neighbor should satisfy
the following:
1. i >= 0 and i < H
2. j >= 0 and j < W
3. (i, j) != (y, x)

Args:
y, x: location of the pixel.
H, W: size of the image.
Returns:
neighbors: list of indices of neighboring pixels [(i, j)].
"""
neighbors = []

for i in (y-1, y, y+1):
for j in (x-1, x, x+1):
if i >= 0 and i < H and j >= 0 and j < W:
if (i == y and j == x):
continue
neighbors.append((i, j))

return neighbors

""" Find weak edges connected to strong edges and link them.

Iterate over each pixel in strong_edges and perform breadth first
search across the connected pixels in weak_edges to link them.
Here we consider a pixel (a, b) is connected to a pixel (c, d)
if (a, b) is one of the eight neighboring pixels of (c, d).

Args:
strong_edges: binary image of shape (H, W).
weak_edges: binary image of shape (H, W).

Returns:
edges: numpy boolean array of shape(H, W).
"""

H, W = strong_edges.shape
# 获取强边界的非零元素坐标，一行为一组坐标
indices = np.stack(np.nonzero(strong_edges)).T
print(indices)
edges = np.zeros((H, W), dtype=np.bool)

# Make new instances of arguments to leave the original
# references intact
weak_edges = np.copy(weak_edges)
edges = np.copy(strong_edges)

for (x, y) in indices:
neighbors = get_neighbors(x, y, H, W)
edges[x, y] = True
for neighbor in neighbors:
if weak_edges[neighbor]:
edges[neighbor] = True

return edges


'''

'''

test_strong = np.array(
[[1, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 0],
[0, 0, 0, 1]],
dtype=np.bool
)

test_weak = np.array(
[[0, 0, 0, 1],
[0, 1, 0, 0],
[1, 0, 0, 0],
[0, 0, 1, 0]],
dtype=np.bool
)

plt.subplot(1, 3, 1)
plt.imshow(test_strong)
plt.title('Strong edges')

plt.subplot(1, 3, 2)
plt.imshow(test_weak)
plt.title('Weak edges')

plt.subplot(1, 3, 3)
plt.show()


def canny(img, kernel_size=5, sigma=1.4, high=20, low=15):
""" Implement canny edge detector by calling functions above.

Args:
img: binary image of shape (H, W).
kernel_size: int of size for kernel matrix.
sigma: float for calculating kernel.
high: high threshold for strong edges.
low: low threashold for weak edges.
Returns:
edge: numpy array of shape(H, W).
"""
# 高斯模糊
kernel = gaussian_kernel(kernel_size, sigma)
img = conv(img, kernel)
#io.imshow(color.gray2rgb(img))
# 计算梯度Prewitt算子
#io.imshow(color.gray2rgb(G))
# 非极大值抑制
G = non_maximum_suppression(G, theta)
#io.imshow(color.gray2rgb(G))

# 双阈值抑制
strong_edges, weak_edges = double_thresholding(G, high, low)
# 连通边

return edge

from edge import canny

# Run Canny edge detector
edges = canny(img, kernel_size=5, sigma=1.4, high=0.03, low=0.02)
print (edges.shape)

plt.subplot(1, 3, 1)
plt.imshow(edges)
plt.axis('off')

plt.subplot(1, 3, 2)
plt.imshow(reference)
plt.axis('off')
plt.title('Reference')

plt.subplot(1, 3, 3)
plt.imshow(edges ^ reference)
plt.title('Difference')
plt.axis('off')
plt.show()


总结

• 27
点赞
• 123
收藏
• 打赏
• 6
评论
05-12 9903
11-01 2918
06-08
11-01 1499
10-13 2万+
01-28 9万+
08-08 1万+
12-04 686
07-18 3747
07-10 791
03-24 1781
10-06 440
08-30 678
07-04 2万+
04-27 8070

“相关推荐”对你有帮助么？

• 非常没帮助
• 没帮助
• 一般
• 有帮助
• 非常有帮助

¥2 ¥4 ¥6 ¥10 ¥20

1.余额是钱包充值的虚拟货币，按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载，可以购买VIP、C币套餐、付费专栏及课程。