霍夫变换检测图像直线算法python实现

创作不易,如果对您有帮助,帮忙点赞哦!


一. 霍夫变换理解:

    可参考: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,根据下式进行霍夫变换


霍夫变换,(r_ho,t) 表示极坐标,(x,y) 表示直角坐标 ↑
 

        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()
View Code

 


五. 实验结果:


原图 ↑
 

霍夫变换检测到的直线 ↑
 

六. 参考内容:

  https://www.jianshu.com/p/64c8c696441a


七. 版权声明:

    未经作者允许,请勿随意转载抄袭,抄袭情节严重者,作者将考虑追究其法律责任,创作不易,感谢您的理解和配合!

posted on 2020-03-21 16:19  我坚信阳光灿烂  阅读(5966)  评论(0编辑  收藏  举报

导航