zoukankan      html  css  js  c++  java
  • 霍夫变换检测图像直线算法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


    七. 版权声明:

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

  • 相关阅读:
    【Rust】文件操作
    【Rust】转义字符
    【Rust】原始标识符
    【Rust】字节数组
    【Rust】文档测试
    【Rust】外部函数接口
    【Rust】不安全操作
    【Rust】单元测试
    【Rust】集成测试
    WPF之ComboBox 安静点
  • 原文地址:https://www.cnblogs.com/wojianxin/p/12539886.html
Copyright © 2011-2022 走看看