霍夫变换手工实现 检测图像中的直线

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


一. 霍夫变换理解:

    可参考: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条直线,输出图像

霍夫逆变换公式 ↑

四. 纯手工实现 ——> 利用霍夫变换检测图像中的直线

import cv2

import numpy as np

import matplotlib.pyplot as plt

# Canny算法:提取图像边缘

def Canny(img):

    # Gray scale

    def BGR2GRAY(img):

        b = img[:, :, 0].copy()

        g = img[:, :, 1].copy()

        r = img[:, :, 2].copy()

        # Gray scale

        out = 0.2126 * r + 0.7152 * g + 0.0722 * b

        out = out.astype(np.uint8)

        return out

    # Gaussian filter for grayscale

    def gaussian_filter(img, K_size=3, sigma=1.3):

        if len(img.shape) == 3:

            H, W, C = img.shape

            gray = False

        else:

            img = np.expand_dims(img, axis=-1)

            H, W, C = img.shape

            gray = True

        ## Zero padding

        pad = K_size // 2

        out = np.zeros([H + pad * 2, W + pad * 2, C], dtype=np.float)

        out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float)

        ## prepare Kernel

        K = np.zeros((K_size, K_size), dtype=np.float)

        for x in range(-pad, -pad + K_size):

            for y in range(-pad, -pad + K_size):

                K[y + pad, x + pad] = np.exp( - (x ** 2 + y ** 2) / (2 * sigma * sigma))

        #K /= (sigma * np.sqrt(2 * np.pi))

        K /= (2 * np.pi * sigma * sigma)

        K /= K.sum()

        tmp = out.copy()

        # filtering

        for y in range(H):

            for x in range(W):

                for c in range(C):

                    out[pad + y, pad + x, c] = np.sum(K * tmp[y : y + K_size, x : x + K_size, c])

        out = np.clip(out, 0, 255)

        out = out[pad : pad + H, pad : pad + W]

        out = out.astype(np.uint8)

        if gray:

            out = out[..., 0]

        return out

    # sobel filter

    def sobel_filter(img, K_size=3):

        if len(img.shape) == 3:

            H, W, C = img.shape

        else:

            H, W = img.shape

        # Zero padding

        pad = K_size // 2

        out = np.zeros((H + pad * 2, W + pad * 2), dtype=np.float)

        out[pad : pad + H, pad : pad + W] = img.copy().astype(np.float)

        tmp = out.copy()

        out_v = out.copy()

        out_h = out.copy()

        ## Sobel vertical

        Kv = [[1., 2., 1.],[0., 0., 0.], [-1., -2., -1.]]

        ## Sobel horizontal

        Kh = [[1., 0., -1.],[2., 0., -2.],[1., 0., -1.]]

        # filtering

        for y in range(H):

            for x in range(W):

                out_v[pad + y, pad + x] = np.sum(Kv * (tmp[y : y + K_size, x : x + K_size]))

                out_h[pad + y, pad + x] = np.sum(Kh * (tmp[y : y + K_size, x : x + K_size]))

        out_v = np.clip(out_v, 0, 255)

        out_h = np.clip(out_h, 0, 255)

        out_v = out_v[pad : pad + H, pad : pad + W]

        out_v = out_v.astype(np.uint8)

        out_h = out_h[pad : pad + H, pad : pad + W]

        out_h = out_h.astype(np.uint8)

        return out_v, out_h

    def get_edge_angle(fx, fy):

        # get edge strength

        edge = np.sqrt(np.power(fx.astype(np.float32), 2) + np.power(fy.astype(np.float32), 2))

        edge = np.clip(edge, 0, 255)

        fx = np.maximum(fx, 1e-10)

        #fx[np.abs(fx) <= 1e-5] = 1e-5

        # get edge angle

        angle = np.arctan(fy / fx)

        return edge, angle

    def angle_quantization(angle):

        angle = angle / np.pi * 180

        angle[angle < -22.5] = 180 + angle[angle < -22.5]

        _angle = np.zeros_like(angle, dtype=np.uint8)

        _angle[np.where(angle <= 22.5)] = 0

        _angle[np.where((angle > 22.5) & (angle <= 67.5))] = 45

        _angle[np.where((angle > 67.5) & (angle <= 112.5))] = 90

        _angle[np.where((angle > 112.5) & (angle <= 157.5))] = 135

        return _angle

    def non_maximum_suppression(angle, edge):

        H, W = angle.shape

        _edge = edge.copy()


        for y in range(H):

            for x in range(W):

                    if angle[y, x] == 0:

                            dx1, dy1, dx2, dy2 = -1, 0, 1, 0

                    elif angle[y, x] == 45:

                            dx1, dy1, dx2, dy2 = -1, 1, 1, -1

                    elif angle[y, x] == 90:

                            dx1, dy1, dx2, dy2 = 0, -1, 0, 1

                    elif angle[y, x] == 135:

                            dx1, dy1, dx2, dy2 = -1, -1, 1, 1

                    if x == 0:

                            dx1 = max(dx1, 0)

                            dx2 = max(dx2, 0)

                    if x == W-1:

                            dx1 = min(dx1, 0)

                            dx2 = min(dx2, 0)

                    if y == 0:

                            dy1 = max(dy1, 0)

                            dy2 = max(dy2, 0)

                    if y == H-1:

                            dy1 = min(dy1, 0)

                            dy2 = min(dy2, 0)

                    if max(max(edge[y, x], edge[y + dy1, x + dx1]), edge[y + dy2, x + dx2]) != edge[y, x]:

                            _edge[y, x] = 0

        return _edge

    def hysterisis(edge, HT=100, LT=30):

        H, W = edge.shape

        # Histeresis threshold

        edge[edge >= HT] = 255

        edge[edge <= LT] = 0

        _edge = np.zeros((H + 2, W + 2), dtype=np.float32)

        _edge[1 : H + 1, 1 : W + 1] = edge

        ## 8 - Nearest neighbor

        nn = np.array(((1., 1., 1.), (1., 0., 1.), (1., 1., 1.)), dtype=np.float32)

        for y in range(1, H+2):

                for x in range(1, W+2):

                        if _edge[y, x] < LT or _edge[y, x] > HT:

                                continue

                        if np.max(_edge[y-1:y+2, x-1:x+2] * nn) >= HT:

                                _edge[y, x] = 255

                        else:

                                _edge[y, x] = 0

        edge = _edge[1:H+1, 1:W+1]


        return edge

    # grayscale

    gray = BGR2GRAY(img)

    # gaussian filtering

    gaussian = gaussian_filter(gray, K_size=5, sigma=1.4)

    # sobel filtering

    fy, fx = sobel_filter(gaussian, K_size=3)

    # get edge strength, angle

    edge, angle = get_edge_angle(fx, fy)

    # angle quantization

    angle = angle_quantization(angle)

    # non maximum suppression

    edge = non_maximum_suppression(angle, edge)

    # hysterisis threshold

    out = hysterisis(edge, 100, 30)

    return out

# 霍夫变换实现检测图像中的20条直线

def Hough_Line(edge, img):

    ## Voting

    def voting(edge):

        H, W = edge.shape


        drho = 1

        dtheta = 1

        # get rho max length

        rho_max = np.ceil(np.sqrt(H ** 2 + W ** 2)).astype(np.int)

        # hough table

        hough = np.zeros((rho_max, 180), dtype=np.int)

        # get index of edge

        # ind[0] 是 符合条件的纵坐标,ind[1]是符合条件的横坐标

        ind = np.where(edge == 255)

        ## hough transformation

        # zip函数返回元组

        for y, x in zip(ind[0], ind[1]):

                for theta in range(0, 180, dtheta):

                        # get polar coordinat4s

                        t = np.pi / 180 * theta

                        rho = int(x * np.cos(t) + y * np.sin(t))

                        # vote

                        hough[rho, theta] += 1


        out = hough.astype(np.uint8)

        return out

    # non maximum suppression

    def non_maximum_suppression(hough):

        rho_max, _ = hough.shape

        ## non maximum suppression

        for y in range(rho_max):

            for x in range(180):

                # get 8 nearest neighbor

                x1 = max(x-1, 0)

                x2 = min(x+2, 180)

                y1 = max(y-1, 0)

                y2 = min(y+2, rho_max-1)

                if np.max(hough[y1:y2, x1:x2]) == hough[y,x] and hough[y, x] != 0:

                    pass

                    #hough[y,x] = 255

                else:

                    hough[y,x] = 0

        return hough

    def inverse_hough(hough, img):

        H, W, _= img.shape

        rho_max, _ = hough.shape

        out = img.copy()

        # get x, y index of hough table

        # np.ravel 将多维数组降为1维

        # argsort  将数组元素从小到大排序,返回索引

        # [::-1]  反序->从大到小

        # [:20]    前20个

        ind_x = np.argsort(hough.ravel())[::-1][:20]

        ind_y = ind_x.copy()

        thetas = ind_x % 180

        rhos = ind_y // 180

        # each theta and rho

        for theta, rho in zip(thetas, rhos):

            # theta[radian] -> angle[degree]

            t = np.pi / 180. * theta

            # hough -> (x,y)

            for x in range(W):

                if np.sin(t) != 0:

                    y = - (np.cos(t) / np.sin(t)) * x + (rho) / np.sin(t)

                    y = int(y)

                    if y >= H or y < 0:

                        continue

                    out[y, x] = [0,255,255]

            for y in range(H):

                if np.cos(t) != 0:

                    x = - (np.sin(t) / np.cos(t)) * y + (rho) / np.cos(t)

                    x = int(x)

                    if x >= W or x < 0:

                        continue

                    out[y, x] = [0,0,255]


        out = out.astype(np.uint8)

        return out

    # voting

    hough = voting(edge)

    # non maximum suppression

    hough = non_maximum_suppression(hough)

    # inverse hough

    out = inverse_hough(hough, img)

    return out

# Read image

img = cv2.imread("../paojie.jpg").astype(np.float32)

# Canny

edge = Canny(img)

# Hough

out = Hough_Line(edge, img)

out = out.astype(np.uint8)

# Save result

cv2.imwrite("out.jpg", out)

cv2.imshow("result", out)

cv2.waitKey(0)

cv2.destroyAllWindows()


五. 实验结果:

原图 ↑
霍夫变换检测到的直线 ↑

六. 参考内容:

    ① https://www.cnblogs.com/wojianxin/p/12539886.html

    ② https://blog.csdn.net/Ibelievesunshine/article/details/105011867


七. 版权声明:

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,513评论 4 369
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,312评论 1 305
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,124评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,529评论 0 217
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,937评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,913评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,084评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,816评论 0 205
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,593评论 1 249
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,788评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,267评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,601评论 3 261
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,265评论 3 241
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,158评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,953评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,066评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,852评论 2 277