用霍夫变换检测直线 - sumpig/OpenCV GitHub Wiki


霍夫变换 的目的是在 二值图像 中找出 全部直线,同时这些直线必须穿过 足够多的像素点

它的 处理方法 是,检查输入的二值分布图中 每个独立的像素点,识别出穿过该像素点的 所有可能直线。如果 同一条直线 穿过很多像素点,就说明这条直线明显到足以被认定。

为了统计某条直线被标识的次数,霍夫变换使用了一个 二维累加器。累加器的 大小 依据 (ρ, θ)的步长 确定,其中(ρ, θ)参数用来表示一条直线。

在霍夫变换中,用这个方程式表示直线:

参数 ρ 是直线与图像原点(左上角)的距离,θ 是直线与垂直线间的角度。θ 的范围为 [0, π],而半径 ρ 的最大值是图像对角线的长度,半径值可以用负数表示。


OpenCV 中通过 cv::HoughLinesP 函数实现 概率霍夫变换 (霍夫变换的改进版)。

// 创建LineFinder 类的实例
LineFinder finder;

// 设置概率霍夫变换的参数
finder.setLineLengthAndGap(100, 20);
finder.setMinVote(60);

// 检测直线并画线
std::vector<cv::Vec4i> lines = finder.findLines(contours);
finder.drawDetectedLines(image);
class LineFinder {

    private:

        // 原始图像
        cv::Mat img;

        // 包含被检测直线的端点的向量
        std::vector<cv::Vec4i> lines;

        // 累加器分辨率参数
        double deltaRho;    // ρ
        double deltaTheta;  // θ

        // 确认直线之前必须收到的最小投票数
        int minVote;

        // 直线的最小长度
        double minLength;

        // 直线上允许的最大空隙
        double maxGap;

    public:

        // 默认累加器分辨率是 1 像素,1 度,没有空隙,没有最小长度
        LineFinder() : deltaRho(1), deltaTheta(PI/180),
                       minVote(10), minLength(0.), maxGap(0.) {}

        // 设置累加器的分辨率
        void setAccResolution(double dRho, double dTheta) {

            deltaRho = dRho;
            deltaTheta = dTheta;
        }

        // 设置最小投票数
        void setMinVote(int minv) {

            minVote = minv;
        }

        // 设置直线长度和空隙
        void setLineLengthAndGap(double length, double gap) {

            minLength = length;
            maxGap = gap;
        }

        // 应用概率霍夫变换
        std::vector<cv::Vec4i> findLines(cv::Mat& binary) {

            lines.clear();
            cv::HoughLinesP(binary, lines,
                            deltaRho, deltaTheta, minVote,
                            minLength, maxGap);
            return lines;
        }

        // 在图像上绘制检测到的直线
        void drawDetectedLines(cv::Mat &image,
                               cv::Scalar color=cv::Scalar(255,255,255)) {

            // 画直线
            std::vector<cv::Vec4i>::const_iterator it2 = lines.begin();

            while (it2!=lines.end()) {

                cv::Point pt1((*it2)[0], (*it2)[1]);
                cv::Point pt2((*it2)[2], (*it2)[3]);
                cv::line(image, pt1, pt2, color);
                ++it2;
        }
}

概率霍夫变换对基本算法做了一些修正。

首先,概率霍夫变换在二值分布图上随机选择像素点,而不是系统性地逐行扫描图像。一旦累加器的某个入口达到了预设的最小值,就沿着对应的直线扫描图像,并移除在这条直线上的所有像素点(包括还没投票的像素点)。

这个扫描过程还检测可以接受的线段长度。为此,算法定义了两个额外的参数:一个是允许的线段最小长度,另一个是组成连续线段时允许的最大像素间距。这个额外的步骤增加了算法的复杂度,但也得到了一定的补偿——由于在扫描直线的过程中已经清除了部分像素点,因此减少了投票过程中用到的像素点。


检测圆

霍夫变换也能用来检测其他几何物体。事实上,任何可以用一个参数方程来表示的物体,都很适合用霍夫变换来检测。还有一种泛化霍夫变换,可以检测任何形状的物体。

圆的参数方程为:

这个方程包含三个参数(圆半径和圆心坐标),这表明需要使用三维的累加器。但一般来说,累加器的维数越多,霍夫变换就越复杂,可靠性也越低。

OpenCV 采用的策略是在用霍夫变换检测圆的实现中使用两轮筛选。

第一轮筛选使用一个二维累加器,找出可能是圆的位置。因为圆周上像素点的梯度方向与半径的方向是一致的,所以对每个像素点来说,累加器只对沿着梯度方向的入口增加计数(根据预先定义的最小和最大半径值)。

一旦检测到可能的圆心(即收到了预定数量的投票),就在第二轮筛选中建立半径值范围的一维直方图。这个直方图的尖峰值就是被检测圆的半径。

实现上述策略的 cv::HoughCircles 函数将 Canny 检测 与 霍夫变换 结合,它的调用方法是:

// 图像平滑
cv::GaussianBlur(image, image, cv::Size(5,5), 1.5);

std::vector<cv::Vec3f> circles;
cv::HoughCircles(image, circles, cv::HOUGH_GRADIENT,
                 2, // 累加器分辨率(图像尺寸/2)
                 50, // 两个圆之间的最小距离
                 200, // Canny 算子的高阈值
                 100, // 最少投票数
                 25,
                 100); // 最小和最大半径

在调用 cv::HoughCircles 函数之前,要对图像进行平滑化,以减少图像中可能导致误判的噪声。

检测的结果存放在 cv::Vec3f 实例的向量中。前面两个数值是圆心坐标,第三个数值是半径。

第四个参数定义了累加器的分辨率,它是一个分割比例。例如,数值2 表示累加器是图像尺寸的一半。

第六个参数是 Canny 边缘检测器的高阈值,低阈值通常设置为高阈值的一半。

第七个参数是圆心位置必须收到的最少投票数。

最后两个参数是被检测圆的最小和最大半径值。

得到存放圆的向量后,就可以在图像上画出这些圆:

std::vector<cv::Vec3f>::const_iterator itc= circles.begin();

while (itc!=circles.end()) {

    cv::circle(image,
               cv::Point((*itc)[0], (*itc)[1]), // 圆心
               (*itc)[2], // 半径
               cv::Scalar(255), // 颜色
               2); // 厚度

    ++itc;
}
⚠️ **GitHub.com Fallback** ⚠️