創作不易,如果對您有幫助,幫忙點贊哦!
一. 霍夫變換理解:
可參考: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
七. 版權聲明:
未經作者允許,請勿隨意轉載抄襲,抄襲情節嚴重者,作者將考慮追究其法律責任,創作不易,感謝您的理解和配合!
