实现Canny算法原理 python
创作很累,如果您觉得对您有帮助,请点赞支持,感谢!
一. 总的算法流程:
① 使用高斯滤波器滤波
② 使用 Sobel 滤波器滤波获得在 x 和 y 方向上的输出,在此基础上求出边缘的强度和边缘的角度
edge 为边缘强度,tan 为边缘角度 ↑
③ 对边缘角度进行量化处理
对边缘角度进行量化处理算法 ↑
④ 根据边缘角度对边缘强度进行非极大值抑制(Non-maximum suppression),使图像边缘变得更细
非极大值抑制算法:0°时取(x,y)、(x+1,y)、(x-1,y) 中的最大值,其它角度类似 ↑
⑤ 使用滞后阈值对图像进行二值化处理,优化图像显示效果
算法如上所示 ↑
⑥ 输出图像边缘提取效果
二. 使用python手动实现 Canny 算法,完成图像边缘提取
# writer: # date :2020.3.20 import cv2 import numpy as np import matplotlib.pyplot as plt def Canny(img): # Gray scale def BGR2GRAY(img): b = img[:, :, 0].copy() g = img[:, :, 1].copy() r = img[:, :, 2].copy() # Gray scale out = 0.2126 * r + 0.7152 * g + 0.0722 * b out = out.astype(np.uint8) return out # Gaussian filter for grayscale def gaussian_filter(img, K_size=3, sigma=1.4): if len(img.shape) == 3: H, W, C = img.shape gray = False else: img = np.expand_dims(img, axis=-1) H, W, C = img.shape gray = True ## Zero padding pad = K_size // 2 out = np.zeros([H + pad * 2, W + pad * 2, C], dtype=np.float) out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float) ## prepare Kernel K = np.zeros((K_size, K_size), dtype=np.float) for x in range(-pad, -pad + K_size): for y in range(-pad, -pad + K_size): K[y + pad, x + pad] = np.exp( - (x ** 2 + y ** 2) / (2 * sigma * sigma)) #K /= (sigma * np.sqrt(2 * np.pi)) K /= (2 * np.pi * sigma * sigma) K /= K.sum() tmp = out.copy() # filtering for y in range(H): for x in range(W): for c in range(C): out[pad + y, pad + x, c] = np.sum(K * tmp[y : y + K_size, x : x + K_size, c]) out = np.clip(out, 0, 255) out = out[pad : pad + H, pad : pad + W] out = out.astype(np.uint8) if gray: out = out[..., 0] return out # sobel filter def sobel_filter(img, K_size=3): if len(img.shape) == 3: H, W, C = img.shape else: H, W = img.shape # Zero padding pad = K_size // 2 out = np.zeros((H + pad * 2, W + pad * 2), dtype=np.float) out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float) tmp = out.copy() out_v = out.copy() out_h = out.copy() ## Sobel vertical Kv = [[1., 2., 1.],[0., 0., 0.], [-1., -2., -1.]] ## Sobel horizontal Kh = [[1., 0., -1.],[2., 0., -2.],[1., 0., -1.]] # filtering for y in range(H): for x in range(W): out_v[pad + y, pad + x] = np.sum(Kv * (tmp[y : y + K_size, x : x + K_size])) out_h[pad + y, pad + x] = np.sum(Kh * (tmp[y : y + K_size, x : x + K_size])) out_v = np.clip(out_v, 0, 255) out_h = np.clip(out_h, 0, 255) out_v = out_v[pad : pad + H, pad : pad + W] out_v = out_v.astype(np.uint8) out_h = out_h[pad : pad + H, pad : pad + W] out_h = out_h.astype(np.uint8) return out_v, out_h # get edge strength and edge angle def get_edge_angle(fx, fy): # get edge strength edge = np.sqrt(np.power(fx.astype(np.float32), 2) + np.power(fy.astype(np.float32), 2)) edge = np.clip(edge, 0, 255) # make sure the denominator is not 0 fx = np.maximum(fx, 1e-10) #fx[np.abs(fx) <= 1e-5] = 1e-5 # get edge angle angle = np.arctan(fy / fx) return edge, angle # 将角度量化为0°、45°、90°、135° def angle_quantization(angle): angle = angle / np.pi * 180 angle[angle < -22.5] = 180 + angle[angle < -22.5] _angle = np.zeros_like(angle, dtype=np.uint8) _angle[np.where(angle <= 22.5)] = 0 _angle[np.where((angle > 22.5) & (angle <= 67.5))] = 45 _angle[np.where((angle > 67.5) & (angle <= 112.5))] = 90 _angle[np.where((angle > 112.5) & (angle <= 157.5))] = 135 return _angle def non_maximum_suppression(angle, edge): H, W = angle.shape _edge = edge.copy() for y in range(H): for x in range(W): if angle[y, x] == 0: dx1, dy1, dx2, dy2 = -1, 0, 1, 0 elif angle[y, x] == 45: dx1, dy1, dx2, dy2 = -1, 1, 1, -1 elif angle[y, x] == 90: dx1, dy1, dx2, dy2 = 0, -1, 0, 1 elif angle[y, x] == 135: dx1, dy1, dx2, dy2 = -1, -1, 1, 1 # 边界处理 if x == 0: dx1 = max(dx1, 0) dx2 = max(dx2, 0) if x == W-1: dx1 = min(dx1, 0) dx2 = min(dx2, 0) if y == 0: dy1 = max(dy1, 0) dy2 = max(dy2, 0) if y == H-1: dy1 = min(dy1, 0) dy2 = min(dy2, 0) # 如果不是最大值,则将这个位置像素值置为0 if max(max(edge[y, x], edge[y + dy1, x + dx1]), edge[y + dy2, x + dx2]) != edge[y, x]: _edge[y, x] = 0 return _edge # 滞后阈值处理二值化图像 # > HT 的设为255,< LT 的设置0,介于它们两个中间的值,使用8邻域判断法 def hysterisis(edge, HT=100, LT=30): H, W = edge.shape # Histeresis threshold edge[edge >= HT] = 255 edge[edge <= LT] = 0 _edge = np.zeros((H + 2, W + 2), dtype=np.float32) _edge[1 : H + 1, 1 : W + 1] = edge ## 8 - Nearest neighbor nn = np.array(((1., 1., 1.), (1., 0., 1.), (1., 1., 1.)), dtype=np.float32) for y in range(1, H+2): for x in range(1, W+2): if _edge[y, x] < LT or _edge[y, x] > HT: continue if np.max(_edge[y-1:y+2, x-1:x+2] * nn) >= HT: _edge[y, x] = 255 else: _edge[y, x] = 0 edge = _edge[1:H+1, 1:W+1] return edge # grayscale gray = BGR2GRAY(img) # gaussian filtering gaussian = gaussian_filter(gray, K_size=5, sigma=1.4) # sobel filtering fy, fx = sobel_filter(gaussian, K_size=3) # get edge strength, angle edge, angle = get_edge_angle(fx, fy) # angle quantization angle = angle_quantization(angle) # non maximum suppression edge = non_maximum_suppression(angle, edge) # hysterisis threshold out = hysterisis(edge, 80, 20) return out if __name__ == ‘__main__‘: # Read image img = cv2.imread("../paojie.jpg").astype(np.float32) # Canny edge = Canny(img) out = edge.astype(np.uint8) # Save result cv2.imwrite("out.jpg", out) cv2.imshow("result", out) cv2.waitKey(0) cv2.destroyAllWindows()
三. 实验结果:
原图 ↑
Canny 算法 提取图像边缘结果 ↑
可以看到,我的代码如愿以偿地提取了图像边缘,而且效果很好!
四. 参考内容:
https://www.jianshu.com/p/ff4c1a6a68d8
五. 版权声明:
未经作者允许,请勿随意转载抄袭,抄袭情节严重者,作者将考虑追究其法律责任,创作不易,感谢您的理解和配合!