基于Canny算子的边缘检测主要有5个步骤,依次是:
def smooth(image, sigma, length ):
# define and compute kernel
k = length // 2
gaussian = np.zeros([length, length]) # init kernel
coef = 2 * np.pi * sigma ** 2
for i in range(length):
for j in range(length):
gaussian[i, j] = np.exp(-((i-k) ** 2 + (j-k) ** 2) / (2 * sigma ** 2)) / coef
gaussian = gaussian / np.sum(gaussian) # normalize kernel
# convolution # !!!attention to the subscript !!!
W, H = image.shape
new_image = np.zeros([W - k * 2, H - k * 2]) # no padding (add padding ,to be continued...)
for i in range(W - 2 * k):
for j in range(H - 2 * k):
new_image[i, j] = np.sum(image[i:i+length, j:j+length] * gaussian)
new_image = np.uint8(new_image) # greyscale image
return new_image
代码):
def get_G_Theta(image):
Sx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) # can detect lines in vertical(along axis y) direction
Sy = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]]) # can detect lines in horizontal(along axis x) direction
W, H = image.shape
gradients = np.zeros([W - 2, H - 2]) # -2 ? -> no padding, so there is 2 pixels cannot be calculated gradients
theta = np.zeros([W - 2, H - 2])
for i in range(W - 2):
for j in range(H - 2):
dx = np.sum(image[i:i+3, j:j+3] * Sx)
dy = np.sum(image[i:i+3, j:j+3] * Sy)
gradients[i, j] = np.sqrt(dx ** 2 + dy ** 2)
if dx == 0: # attention !!when denominator is 0
theta[i, j] = np.pi / 2
else:
theta[i, j] = np.arctan(dy / dx)
gradients = np.uint8(gradients)
return gradients, theta
def NMS(gradients,direction):
W, H = gradients.shape
nms = np.copy(gradients[1:-1, 1:-1]) # without first/last columns/rows (gradients on four edges) (W-2)X(H-2)
for i in range(1, W - 1): # original img's [1,W-2] without margin
for j in range(1, H - 1):
theta = direction[i, j]
k = np.tan(theta) # k = dy/dx
# 图例2 dx*dy >= 0 and |dy|>|dx|
if theta > np.pi / 4:
k = 1/ k # = dx/dy
d1 = gradients[i-1,j] * (1-k) + gradients[i-1,j+1]*k
d2 = gradients[i+1,j] * (1-k) + gradients[i+1,j-1]*k
# 图例3 dx*dy >= 0 and |dy|<|dx|
elif theta >= 0:
d1 = gradients[i,j-1] * (1-k) + gradients[i+1,j-1]*k
d2 = gradients[i,j+1] * (1-k) + gradients[i-1,j+1]*k
# 图例4 dx*dy <= 0 and |dy|<|dx|
elif theta >= - np.pi / 4:
k *= -1
d1 = gradients[i,j-1] * (1-k) + gradients[i-1,j-1]*k
d2 = gradients[i,j+1] * (1-k) + gradients[i+1,j+1]*k
# 图例1 dx*dy <= 0 and |dy|>|dx|
else:
k = -1/ k
d1 = gradients[i-1,j] * (1-k) + gradients[i-1,j-1]*k
d2 = gradients[i+1,j] * (1-k) + gradients[i+1,j+1]*k
if d1 > gradients[i, j] or d2 > gradients[i, j]:
nms[i - 1, j - 1] = 0
return nms
This stage also removes small pixels noises on the assumption that edges are long lines.
def thresholding(nms, minVal, maxVal):
#return an binary img
vis = np.zeros_like(nms) # record the pixels visited
edge = np.zeros_like(nms)
W, H = edge.shape
def check_N8(i,j):
if (i >= W or i < 0 or j >= H or j < 0 or vis[i, j] == 1):
return
vis[i,j] = 1
if nms[i,j] >= minVal :
edge[i,j] = 255
for w in range(W):
for h in range(H):
if vis[w, h] == 1:
continue
elif nms[w, h] <= minVal:
vis[w, h] = 1
elif nms[w, h] >= maxVal:
vis[w,h] = 1
edge[w,h] = 255 # sure-edge
check_N8(w-1, h-1)
check_N8(w-1, h )
check_N8(w-1, h+1)
check_N8(w , h-1)
check_N8(w , h+1)
check_N8(w+1, h-1)
check_N8(w+1, h )
check_N8(w+1, h+1)
return edge
image = cv.imread("CUC.jpg", 0) # 0指的是读为灰度图
smoothed_image = smooth(image,sigma=3,length=5)
gradients, direction = get_G_Theta(smoothed_image)
nms = NMS(gradients, direction)
edge = thresholding(nms,20, 80)
cv.imshow("edge",edge)
cv.imwrite('MyCannyEdge.jpg', edge, [cv.IMWRITE_PNG_COMPRESSION, 0])
cv.waitKey(0)
cv.destroyAllWindows()
pip uninstall opencv-contrib-python
直接通过命令可能无法卸载干净,如果它提示某些需要manually,则自己按照提示删除干净。确保卸载干净后,再重新安装
pip install opencv-contrib-python
import cv2 as cv
import numpy as np
# 1. Noise Reduction
def smooth(image, sigma, length ):
# define and compute kernel
k = length // 2
gaussian = np.zeros([length, length]) # init kernel
coef = 2 * np.pi * sigma ** 2
for i in range(length):
for j in range(length):
gaussian[i, j] = np.exp(-((i-k) ** 2 + (j-k) ** 2) / (2 * sigma ** 2)) / coef
gaussian = gaussian / np.sum(gaussian) # normalize kernel
# convolution # !!!attention to the subscript !!!
W, H = image.shape
new_image = np.zeros([W - k * 2, H - k * 2]) # no padding (add padding ,to be continued...)
for i in range(W - 2 * k):
for j in range(H - 2 * k):
new_image[i, j] = np.sum(image[i:i+length, j:j+length] * gaussian)
new_image = np.uint8(new_image) # greyscale image
return new_image
# 2. Sobel Kernel
def get_G_Theta(image):
Sx = np.array([[-1, 0, 1], [-2, 0, 2], [-1, 0, 1]]) # can detect lines in vertical(along axis y) direction
Sy = np.array([[-1, -2, -1], [0, 0, 0], [1, 2, 1]]) # can detect lines in horizontal(along axis x) direction
W, H = image.shape
gradients = np.zeros([W - 2, H - 2]) # -2 ? -> no padding, so there is 2 pixels cannot be calculated gradients
theta = np.zeros([W - 2, H - 2])
for i in range(W - 2):
for j in range(H - 2):
dx = np.sum(image[i:i+3, j:j+3] * Sx)
dy = np.sum(image[i:i+3, j:j+3] * Sy)
gradients[i, j] = np.sqrt(dx ** 2 + dy ** 2)
if dx == 0: # attention !!when denominator is 0
theta[i, j] = np.pi / 2
else:
theta[i, j] = np.arctan(dy / dx)
gradients = np.uint8(gradients)
return gradients, theta
# 3. Non-Maximum Suppression(NMS)
def NMS(gradients,direction):
W, H = gradients.shape
nms = np.copy(gradients[1:-1, 1:-1]) # without first/last columns/rows (gradients on four edges) (W-2)X(H-2)
for i in range(1, W - 1): # original img's [1,W-2] without margin
for j in range(1, H - 1):
theta = direction[i, j]
k = np.tan(theta) # k = dy/dx
# 图例2 dx*dy >= 0 and |dy|>|dx|
if theta > np.pi / 4:
k = 1/ k # = dx/dy
d1 = gradients[i-1,j] * (1-k) + gradients[i-1,j+1]*k
d2 = gradients[i+1,j] * (1-k) + gradients[i+1,j-1]*k
# 图例3 dx*dy >= 0 and |dy|<|dx|
elif theta >= 0:
d1 = gradients[i,j-1] * (1-k) + gradients[i+1,j-1]*k
d2 = gradients[i,j+1] * (1-k) + gradients[i-1,j+1]*k
# 图例4 dx*dy <= 0 and |dy|<|dx|
elif theta >= - np.pi / 4:
k *= -1
d1 = gradients[i,j-1] * (1-k) + gradients[i-1,j-1]*k
d2 = gradients[i,j+1] * (1-k) + gradients[i+1,j+1]*k
# 图例1 dx*dy <= 0 and |dy|>|dx|
else:
k = -1/ k
d1 = gradients[i-1,j] * (1-k) + gradients[i-1,j-1]*k
d2 = gradients[i+1,j] * (1-k) + gradients[i+1,j+1]*k
if d1 > gradients[i, j] or d2 > gradients[i, j]:
nms[i - 1, j - 1] = 0
return nms
# 4.Hysteresis Thresholding
def thresholding(nms, minVal, maxVal):
#return an binary img
vis = np.zeros_like(nms) # record the pixels visited
edge = np.zeros_like(nms)
W, H = edge.shape
def check_N8(i,j):
if (i >= W or i < 0 or j >= H or j < 0 or vis[i, j] == 1):
return
vis[i,j] = 1
if nms[i,j] >= minVal :
edge[i,j] = 255
for w in range(W):
for h in range(H):
if vis[w, h] == 1:
continue
elif nms[w, h] <= minVal:
vis[w, h] = 1
elif nms[w, h] >= maxVal:
vis[w,h] = 1
edge[w,h] = 255 # sure-edge
check_N8(w-1, h-1)
check_N8(w-1, h )
check_N8(w-1, h+1)
check_N8(w , h-1)
check_N8(w , h+1)
check_N8(w+1, h-1)
check_N8(w+1, h )
check_N8(w+1, h+1)
return edge
# main
image = cv.imread("CUC.jpg", 0)
smoothed_image = smooth(image,sigma=3,length=5)
gradients, direction = get_G_Theta(smoothed_image)
nms = NMS(gradients, direction)
edge = thresholding(nms,20, 80)
cv.imshow("edge",edge)
cv.imwrite('MyCannyEdge.jpg', edge, [cv.IMWRITE_PNG_COMPRESSION, 0])
cv.waitKey(0)
cv.destroyAllWindows()