Opencv之霍夫直线检测

学习资料参考:

张平.《OpenCV算法精解:基于Python与C++》.[Z].北京.电子工业出版社.2017.


原理

  1. 直线在计算机图形中一般表示

    与我们在数学教材中有所不同,此处的y轴方向是向下的。那么象限的位置是顺时针计数的。
    上图中标注了四个参数,分别是直线与x轴正向的夹角 φ \varphi φ,直线到原点的距离 ϱ \varrho ϱ, ϱ \varrho ϱ所在直线与x轴的夹角 ϑ \vartheta ϑ,直线的截距 b b b.
    类似的第二象限的直线为下图所示,三四象限类似,此处不列举。

  2. 直线的数学表达式
    如第一象限的直线,斜率为 t a n φ tan\varphi tanφ,截距为 b b b, b = ϱ s i n ϑ b=\frac{\varrho}{sin\vartheta} b=sinϑϱ, φ = ϑ + 90 \varphi=\vartheta + 90 φ=ϑ+90那么直线的表达式为 y = t a n ( ϑ + 90 ) ∗ x + ϱ s i n ϑ = − cos ⁡ ϑ s i n ϑ ∗ x + ϱ s i n ϑ y=tan(\vartheta + 90)*x + \frac{\varrho}{sin\vartheta}=-\frac{\cos{\vartheta}}{sin\vartheta}*x + \frac{\varrho}{sin\vartheta} y=tan(ϑ+90)x+sinϑϱ=sinϑcosϑx+sinϑϱ,将此式化改写为 y sin ⁡ ϑ + x cos ⁡ ϑ = ϱ y\sin{\vartheta} + x\cos{\vartheta}={\varrho} ysinϑ+xcosϑ=ϱ.

    再进行二三四象限的直线表示,最终可以得到这四个象限的直线表示是一样的,都是 y sin ⁡ ϑ + x cos ⁡ ϑ = ϱ y\sin{\vartheta} + x\cos{\vartheta}={\varrho} ysinϑ+xcosϑ=ϱ

    那么 y sin ⁡ ϑ + x cos ⁡ ϑ = ϱ y\sin{\vartheta} + x\cos{\vartheta}={\varrho} ysinϑ+xcosϑ=ϱ具体含义就是,在笛卡尔坐标系中,确定唯一的 ϱ {\varrho} ϱ ϑ \vartheta ϑ可以确定唯一一条的直线;而在极坐标系下,如下图所示:

    在极坐标中,该条直线就是一个点,该点的坐标是 ( ϑ , ρ ) (\vartheta,\rho) (ϑ,ρ)

    而在笛卡尔坐标系中,经过固定一点有无穷条直线,那么这些直线在极坐标中组成一条曲线,类似下图所示:

    那么这条曲线就是笛卡尔坐标系中固定某一点的全部直线。这又一个很好的应用,如下图所示:

    上图左侧中,出现三个点,那么经过这三个点的全部直线在极坐标中的就会形成三条曲线。上图右侧的极坐标中,会发现这三条曲线有一个交点(图中黄色部分),从数学表达式来看, y sin ⁡ ϑ + x cos ⁡ ϑ = ϱ y\sin{\vartheta} + x\cos{\vartheta}={\varrho} ysinϑ+xcosϑ=ϱ,这三条曲线在该点 ϑ \vartheta ϑ ρ \rho ρ相同,那么很明显,这两个参数在笛卡尔坐标系中会确定一条直线,继而可以推出这三个点在笛卡尔坐标系中是共线的。

    那么这也就是霍夫检测的基础,根据极坐标中的曲线有无交点,确定直线笛卡尔坐标系中的点是否是共线,从而进行边缘构造。


图像处理

如下面这张二维矩阵(当作图像,每一方格表示一个像素点)
在这里插入图片描述
我们要判断图中空白的三点是否是共线,根据前面的原理,我们需要转换到极坐标中的曲线是否有交点。但是,绘制曲线的点的个数是连续无限的。前人采取离散化处理,即将曲线看作有限个点组成。如下图所示:

θ \theta θ轴0到180进行按照一度一个刻度,分为180份,即可实现点的有限化。

扫描二维码关注公众号,回复: 14220738 查看本文章

解决了曲线问题,只剩下最后一个问题,如何判断交点?
很直观的感受,交点就是在每条曲线中都有出现,频次最高。那么我们可以统计点的频次,即可得到交点所在的位置。统计的工具被称为"二维直方图"。类似于下图所示:

具体的构造方法是,按照极坐标的方式进行构造,x轴是角度,分为180份。y轴是笛卡尔坐标系中的点到原点的距离,最后构造的二维直方图如下所示:

在这里插入图片描述


python实现

import math
import cv2
import numpy as np


def HTLine(image, stepTheta=1, stepRho=1):
    rows, cols = image.shape
    # 图像中可能出现的最大垂线长度
    L = round(math.sqrt(pow(rows - 1, 2.0) + pow(cols - 1, 2.0))) + 1
    # 初始化二维直方图
    numtheta = int(180.0 / stepTheta)
    numRho = int(2 * L / stepRho + 1)
    accumulator = np.zeros((numRho, numtheta), np.int32)
    accuDict = {
    
    }
    for k1 in range(numRho):
        for k2 in range(numtheta):
            accuDict[(k1, k2)] = []
    # 投票计数
    for y in range(rows):
        for x in range(cols):
            # 只对边缘点做霍夫变换
            if image[y][x] == 255:
                for m in range(numtheta):
                    rho = x * math.cos(stepTheta * m / 180 * math.pi) + y * math.sin(stepTheta * m / 180.0 * math.pi)
                    n = int(round(rho + L) / stepRho)
                    accumulator[n, m] += 1
                    accuDict[(n, m)].append((x, y))
    return accumulator, accuDict


if __name__ == "__main__":
    image = cv2.imread(r"C:\Users\1\Pictures\test2.jpg")
    edge = cv2.Canny(cv2.cvtColor(image, cv2.COLOR_BGR2GRAY), 50, 200)
    cv2.imshow("edge", edge)
    accumulator, accuDict = HTLine(edge, 1, 1)
    rows, cols = accumulator.shape
    grayAccu = accumulator / float(np.max(accumulator))
    grayAccu = 255 * grayAccu
    grayAccu = grayAccu.astype(np.uint8)
    voteTresh = 100
    for r in range(rows):
        for c in range(cols):
            if accumulator[r][c] > voteTresh:
                points = accuDict[(r, c)]
                cv2.line(image, points[0], points[len(points) - 1], (238, 34, 17), 2)
    cv2.imshow("acculator", grayAccu)
    cv2.imshow("image", image)
    cv2.waitKey(0)
    cv2.destroyAllWindows()

运行结果
在这里插入图片描述
上图中蓝色部分就是识别后画出的部分。

猜你喜欢

转载自blog.csdn.net/qq_44116998/article/details/124725068