创作不易,如果对您有帮助,帮忙点赞哦!
一. 霍夫变换理解:
可参考:https://www.cnblogs.com/hellcat/p/9896426.html
二. 霍夫变换简介:
霍夫变换,是将坐标由直角坐标系变换到极坐标系,然后再根据数学表达式检测某些形状(如直线和圆)的方法。当 l1直线 上的某些点变换到极坐标系下时,表现为某些线(和前面点数量一致),这些线交于一点,通过该点的坐标就能表示原先的 l1直线。
三. 霍夫变换用于检测图像直线算法实现:
① 提取图像边缘(可使用Canny算法等)[我也实现了它,前面Canny算法有问题可以参考我的另一篇文章:https://www.cnblogs.com/wojianxin/p/12533526.html]
② 实现二值图像霍夫变换
1. 求出图像对角线长:r_max
2. 在边缘点 (x,y) 处,t 取[0,180),步长设为1,根据下式进行霍夫变换
3. 做一个大小为 r_max * 180 的表,变换后一个值落在表内某坐标,就将该坐标表内值 + 1,简言之,就是在进行投票,统计通过哪个点的直线的数量最多(即在原图像上越趋近于一条直线)。
③ 进行非极大值抑制(NMS)操作,使找出的直线落在不同的地点
NMS 的算法如下:
1. 遍历该表,如果遍历到的像素的投票数大于其8近邻的像素投票值,则它不变。
2. 如果遍历到的像素的投票数小于其8近邻的像素投票值,则将其设置为0。
④ 找到20个投票数最多的点(即:直角坐标系下20条直线)准备进行输出
1. np.ravel 将多维数组降为1维
2. np.argsort 将数组元素从小到大排序,返回索引值
3. [::-1] 数组反序 -> 得到从大到小索引值
4. [:20] 前20个最大投票值的索引
5. 根据索引得到坐标(r,t)
⑤ 霍夫反变换后,画出原图中的20条直线,输出图像
四. 纯手工实现 ——> 利用霍夫变换检测图像中的直线
1 import cv2 2 import numpy as np 3 import matplotlib.pyplot as plt 4 5 # Canny算法:提取图像边缘 6 def Canny(img): 7 8 # Gray scale 9 def BGR2GRAY(img): 10 b = img[:, :, 0].copy() 11 g = img[:, :, 1].copy() 12 r = img[:, :, 2].copy() 13 14 # Gray scale 15 out = 0.2126 * r + 0.7152 * g + 0.0722 * b 16 out = out.astype(np.uint8) 17 18 return out 19 20 21 # Gaussian filter for grayscale 22 def gaussian_filter(img, K_size=3, sigma=1.3): 23 24 if len(img.shape) == 3: 25 H, W, C = img.shape 26 gray = False 27 else: 28 img = np.expand_dims(img, axis=-1) 29 H, W, C = img.shape 30 gray = True 31 32 ## Zero padding 33 pad = K_size // 2 34 out = np.zeros([H + pad * 2, W + pad * 2, C], dtype=np.float) 35 out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float) 36 37 ## prepare Kernel 38 K = np.zeros((K_size, K_size), dtype=np.float) 39 for x in range(-pad, -pad + K_size): 40 for y in range(-pad, -pad + K_size): 41 K[y + pad, x + pad] = np.exp( - (x ** 2 + y ** 2) / (2 * sigma * sigma)) 42 #K /= (sigma * np.sqrt(2 * np.pi)) 43 K /= (2 * np.pi * sigma * sigma) 44 K /= K.sum() 45 46 tmp = out.copy() 47 48 # filtering 49 for y in range(H): 50 for x in range(W): 51 for c in range(C): 52 out[pad + y, pad + x, c] = np.sum(K * tmp[y : y + K_size, x : x + K_size, c]) 53 54 out = np.clip(out, 0, 255) 55 out = out[pad : pad + H, pad : pad + W] 56 out = out.astype(np.uint8) 57 58 if gray: 59 out = out[..., 0] 60 61 return out 62 63 64 # sobel filter 65 def sobel_filter(img, K_size=3): 66 if len(img.shape) == 3: 67 H, W, C = img.shape 68 else: 69 H, W = img.shape 70 71 # Zero padding 72 pad = K_size // 2 73 out = np.zeros((H + pad * 2, W + pad * 2), dtype=np.float) 74 out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float) 75 tmp = out.copy() 76 77 out_v = out.copy() 78 out_h = out.copy() 79 80 ## Sobel vertical 81 Kv = [[1., 2., 1.],[0., 0., 0.], [-1., -2., -1.]] 82 ## Sobel horizontal 83 Kh = [[1., 0., -1.],[2., 0., -2.],[1., 0., -1.]] 84 85 # filtering 86 for y in range(H): 87 for x in range(W): 88 out_v[pad + y, pad + x] = np.sum(Kv * (tmp[y : y + K_size, x : x + K_size])) 89 out_h[pad + y, pad + x] = np.sum(Kh * (tmp[y : y + K_size, x : x + K_size])) 90 91 out_v = np.clip(out_v, 0, 255) 92 out_h = np.clip(out_h, 0, 255) 93 94 out_v = out_v[pad : pad + H, pad : pad + W] 95 out_v = out_v.astype(np.uint8) 96 out_h = out_h[pad : pad + H, pad : pad + W] 97 out_h = out_h.astype(np.uint8) 98 99 return out_v, out_h 100 101 102 def get_edge_angle(fx, fy): 103 # get edge strength 104 edge = np.sqrt(np.power(fx.astype(np.float32), 2) + np.power(fy.astype(np.float32), 2)) 105 edge = np.clip(edge, 0, 255) 106 107 fx = np.maximum(fx, 1e-10) 108 #fx[np.abs(fx) <= 1e-5] = 1e-5 109 110 # get edge angle 111 angle = np.arctan(fy / fx) 112 113 return edge, angle 114 115 116 def angle_quantization(angle): 117 angle = angle / np.pi * 180 118 angle[angle < -22.5] = 180 + angle[angle < -22.5] 119 _angle = np.zeros_like(angle, dtype=np.uint8) 120 _angle[np.where(angle <= 22.5)] = 0 121 _angle[np.where((angle > 22.5) & (angle <= 67.5))] = 45 122 _angle[np.where((angle > 67.5) & (angle <= 112.5))] = 90 123 _angle[np.where((angle > 112.5) & (angle <= 157.5))] = 135 124 125 return _angle 126 127 128 def non_maximum_suppression(angle, edge): 129 H, W = angle.shape 130 _edge = edge.copy() 131 132 for y in range(H): 133 for x in range(W): 134 if angle[y, x] == 0: 135 dx1, dy1, dx2, dy2 = -1, 0, 1, 0 136 elif angle[y, x] == 45: 137 dx1, dy1, dx2, dy2 = -1, 1, 1, -1 138 elif angle[y, x] == 90: 139 dx1, dy1, dx2, dy2 = 0, -1, 0, 1 140 elif angle[y, x] == 135: 141 dx1, dy1, dx2, dy2 = -1, -1, 1, 1 142 if x == 0: 143 dx1 = max(dx1, 0) 144 dx2 = max(dx2, 0) 145 if x == W-1: 146 dx1 = min(dx1, 0) 147 dx2 = min(dx2, 0) 148 if y == 0: 149 dy1 = max(dy1, 0) 150 dy2 = max(dy2, 0) 151 if y == H-1: 152 dy1 = min(dy1, 0) 153 dy2 = min(dy2, 0) 154 if max(max(edge[y, x], edge[y + dy1, x + dx1]), edge[y + dy2, x + dx2]) != edge[y, x]: 155 _edge[y, x] = 0 156 157 return _edge 158 159 def hysterisis(edge, HT=100, LT=30): 160 H, W = edge.shape 161 162 # Histeresis threshold 163 edge[edge >= HT] = 255 164 edge[edge <= LT] = 0 165 166 _edge = np.zeros((H + 2, W + 2), dtype=np.float32) 167 _edge[1 : H + 1, 1 : W + 1] = edge 168 169 ## 8 - Nearest neighbor 170 nn = np.array(((1., 1., 1.), (1., 0., 1.), (1., 1., 1.)), dtype=np.float32) 171 172 for y in range(1, H+2): 173 for x in range(1, W+2): 174 if _edge[y, x] < LT or _edge[y, x] > HT: 175 continue 176 if np.max(_edge[y-1:y+2, x-1:x+2] * nn) >= HT: 177 _edge[y, x] = 255 178 else: 179 _edge[y, x] = 0 180 181 edge = _edge[1:H+1, 1:W+1] 182 183 return edge 184 185 # grayscale 186 gray = BGR2GRAY(img) 187 188 # gaussian filtering 189 gaussian = gaussian_filter(gray, K_size=5, sigma=1.4) 190 191 # sobel filtering 192 fy, fx = sobel_filter(gaussian, K_size=3) 193 194 # get edge strength, angle 195 edge, angle = get_edge_angle(fx, fy) 196 197 # angle quantization 198 angle = angle_quantization(angle) 199 200 # non maximum suppression 201 edge = non_maximum_suppression(angle, edge) 202 203 # hysterisis threshold 204 out = hysterisis(edge, 100, 30) 205 206 return out 207 208 # 霍夫变换实现检测图像中的20条直线 209 def Hough_Line(edge, img): 210 ## Voting 211 def voting(edge): 212 H, W = edge.shape 213 214 drho = 1 215 dtheta = 1 216 217 # get rho max length 218 rho_max = np.ceil(np.sqrt(H ** 2 + W ** 2)).astype(np.int) 219 220 # hough table 221 hough = np.zeros((rho_max, 180), dtype=np.int) 222 223 # get index of edge 224 # ind[0] 是 符合条件的纵坐标,ind[1]是符合条件的横坐标 225 ind = np.where(edge == 255) 226 227 ## hough transformation 228 # zip函数返回元组 229 for y, x in zip(ind[0], ind[1]): 230 for theta in range(0, 180, dtheta): 231 # get polar coordinat4s 232 t = np.pi / 180 * theta 233 rho = int(x * np.cos(t) + y * np.sin(t)) 234 235 # vote 236 hough[rho, theta] += 1 237 238 out = hough.astype(np.uint8) 239 240 return out 241 242 # non maximum suppression 243 def non_maximum_suppression(hough): 244 rho_max, _ = hough.shape 245 246 ## non maximum suppression 247 for y in range(rho_max): 248 for x in range(180): 249 # get 8 nearest neighbor 250 x1 = max(x-1, 0) 251 x2 = min(x+2, 180) 252 y1 = max(y-1, 0) 253 y2 = min(y+2, rho_max-1) 254 if np.max(hough[y1:y2, x1:x2]) == hough[y,x] and hough[y, x] != 0: 255 pass 256 #hough[y,x] = 255 257 else: 258 hough[y,x] = 0 259 260 return hough 261 262 def inverse_hough(hough, img): 263 H, W, _= img.shape 264 rho_max, _ = hough.shape 265 266 out = img.copy() 267 268 # get x, y index of hough table 269 # np.ravel 将多维数组降为1维 270 # argsort 将数组元素从小到大排序,返回索引 271 # [::-1] 反序->从大到小 272 # [:20] 前20个 273 ind_x = np.argsort(hough.ravel())[::-1][:20] 274 ind_y = ind_x.copy() 275 thetas = ind_x % 180 276 rhos = ind_y // 180 277 278 # each theta and rho 279 for theta, rho in zip(thetas, rhos): 280 # theta[radian] -> angle[degree] 281 t = np.pi / 180. * theta 282 283 # hough -> (x,y) 284 for x in range(W): 285 if np.sin(t) != 0: 286 y = - (np.cos(t) / np.sin(t)) * x + (rho) / np.sin(t) 287 y = int(y) 288 if y >= H or y < 0: 289 continue 290 out[y, x] = [0,255,255] 291 for y in range(H): 292 if np.cos(t) != 0: 293 x = - (np.sin(t) / np.cos(t)) * y + (rho) / np.cos(t) 294 x = int(x) 295 if x >= W or x < 0: 296 continue 297 out[y, x] = [0,0,255] 298 299 out = out.astype(np.uint8) 300 301 return out 302 303 304 # voting 305 hough = voting(edge) 306 307 # non maximum suppression 308 hough = non_maximum_suppression(hough) 309 310 # inverse hough 311 out = inverse_hough(hough, img) 312 313 return out 314 315 316 # Read image 317 img = cv2.imread("../paojie.jpg").astype(np.float32) 318 319 # Canny 320 edge = Canny(img) 321 322 # Hough 323 out = Hough_Line(edge, img) 324 325 out = out.astype(np.uint8) 326 327 # Save result 328 cv2.imwrite("out.jpg", out) 329 cv2.imshow("result", out) 330 cv2.waitKey(0) 331 cv2.destroyAllWindows()
五. 实验结果:
六. 参考内容:
https://www.jianshu.com/p/64c8c696441a
七. 版权声明:
未经作者允许,请勿随意转载抄袭,抄袭情节严重者,作者将考虑追究其法律责任,创作不易,感谢您的理解和配合!