ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
## OpenCV 学习(Hough 变换提取直线) 在机器视觉应用中,我们经常要提取图像中的各种特征,最基本的特征就是图像中的线条、拐角等。这篇笔记就来讲讲如何提取图像中的直线。这里使用的方法叫做 Hough 变换。 Hough 变换这个名称最早是在 Richard Duda 和 Peter Hart 两人于 1972 年合写的发表于 Comm. ACM 文章 《Use of the Hough Transformation to Detect Lines and Curves in Pictures》 中提出的。 大家可能会好奇,这俩人没一个叫 Hough,为啥这个变换叫 Hough 变换呢。这还要追溯到更早的年代,1962 年 Paul Hough 申请了一个美国专利,专利的名称叫做 《Method and means for recognizing complex patterns》,这个专利中提出了 Hough 变换基本方法。不过 1962 年那时还没有所谓的机器视觉这个学科,计算机也不是一般人能见到的。所以这个专利并没有受到特别的重视。 Richard Duda 和 Peter Hart 不知是如何翻到这个 10 年前的专利,并敏锐的发现了它的价值,并将其用于机器视觉领域。从此就有了大名鼎鼎的 Hough 变换。 关于 Hough 更详细的历史发展大家可以参考: [https://en.wikipedia.org/wiki/Hough_transform](https://en.wikipedia.org/wiki/Hough_transform) Hough 变换的原理介绍也可以参考上面的 wiki。简单的说 Hough 变换采用的是一种证据收集的方式,遍历一幅图像上所有的直线位置,哪条直线上的特征点(证据)更多,哪条直线就更可能是我们希望找到的直线。 这里不准备详细介绍Hough 变换的原理。但是Hough 变换如何表示图像中的直线还是要介绍的。否则,我们都不知道如何使用获得的结果。 Hough 变换时,我们采用参数方程来表示直线。 ρ=xcosθ+ysinθ ρ 的几何含义是直线到图像原点的距离。 θ 是直线的法向方向与 x 轴的夹角。 θ=0 表示的是垂直的直线,例如下图中直线 1。 θ=π/2 表示的是水平的直线,例如下图中直线 5。 θ 的取值范围是 0 到 π。由于限制了θ的取值范围,ρ 既可以为正也可以为负。比如下图中直线2,θ=0.8π ,ρ 为负。 ![这里写图片描述](https://box.kancloud.cn/2016-04-26_571f1db65bb5e.jpg "") OpenCV 中提供了两个Hough变换提取直线的函数。 1. cv::HoughLines 函数 1. cv::HoughLinesP 函数 下面分别介绍。 ### cv::HoughLines 函数 这个函数采用最原始的Hough 变换来计算直线的位置。 ~~~ void HoughLines( InputArray image, OutputArray lines, double rho, // rho 的步长 double theta, // 角度步长 int threshold, // 阈值 double srn=0, double stn=0 ); ~~~ 输入图像必须是单通道的。输出的直线存在一个 ~~~ std::vector<cv::Vec2f> lines; ~~~ 首先给出一个简单的测试图片。这个图片上有四条直线。没有其他的干扰物体。这属于最基本的情形。 ![这里写图片描述](https://box.kancloud.cn/2016-04-26_571f1db670816.jpg "") 下面是个测试代码。 ~~~ #include <QCoreApplication> #include <math.h> #define PI 3.14159265358979 #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); cv::Mat image = cv::imread("c:\\test.png"); cv::Mat contours; cv::cvtColor(image, contours, cv::COLOR_BGR2GRAY); cv::bitwise_not(contours, contours); //cv::Canny(image, contours, 155, 350); std::vector<cv::Vec2f> lines; cv::HoughLines(contours, lines, 1, PI/180, 180); //cv::imshow("cany",contours ); std::vector<cv::Vec2f>::const_iterator it= lines.begin(); while (it!=lines.end()) { float rho= (*it)[0]; // first element is distance rho float theta= (*it)[1]; // second element is angle theta if (theta < PI/4. || theta > 3.*PI/4.)// ~vertical line { // point of intersection of the line with first row cv::Point pt1(rho/cos(theta), 0); // point of intersection of the line with last row cv::Point pt2((rho - image.rows * sin(theta))/cos(theta), image.rows); // draw a white line cv::line( image, pt1, pt2, cv::Scalar(255), 1); } else { // ~horizontal line // point of intersection of the // line with first column cv::Point pt1(0,rho/sin(theta)); // point of intersection of the line with last column cv::Point pt2(image.cols, (rho - image.cols * cos(theta))/sin(theta)); // draw a white line cv::line(image, pt1, pt2, cv::Scalar(255), 1); } ++it; } cv::imshow("", image); return a.exec(); } ~~~ 输出结果如下: ![这里写图片描述](https://box.kancloud.cn/2016-04-26_571f1db68455e.jpg "") 这几条线找的还是蛮准的。 ### cv::HoughLinesP 函数 与 cv::HoughLines函数不同, cv::HoughLinesP 函数可以提取线段。 输出的直线存在一个 ~~~ std::vector<cv::Vec4i> lines; ~~~ 中。 cv::Vec4i 的四个整数分别是线段的起点和终点坐标。 ~~~ void HoughLinesP( InputArray image, OutputArray lines, double rho, // rho 的步长 double theta, // 角度的步长,单位是度 int threshold, // 阈值 double minLineLength=0, // 线段的最小长度 double maxLineGap=0 ); // 线段之间的最小距离 ~~~ 下面把 HoughLinesP 函数封装到一个类中。 ~~~ class LineFinder { private: cv::Mat img; // original image std::vector<cv::Vec4i> lines; double deltaRho; double deltaTheta; int minVote; double minLength; // min length for a line double maxGap; // max allowed gap along the line public: // Default accumulator resolution is 1 pixel by 1 degree // no gap, no mimimum length LineFinder() : deltaRho(1), deltaTheta(PI/180), minVote(10), minLength(0.), maxGap(0.) {} // Set the resolution of the accumulator void setAccResolution(double dRho, double dTheta) { deltaRho= dRho; deltaTheta= dTheta; } // Set the minimum number of votes void setMinVote(int minv) { minVote= minv; } // Set line length and gap void setLineLengthAndGap(double length, double gap) { minLength= length; maxGap= gap; } // Apply probabilistic Hough Transform std::vector<cv::Vec4i> findLines(cv::Mat& binary) { lines.clear(); cv::HoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap); return lines; } // Draw the detected lines on an image void drawDetectedLines(cv::Mat &image, cv::Scalar color = cv::Scalar(255, 255, 255)) { // Draw the lines 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, 2); ++it2; } } }; ~~~ 用这个类实现图中线段的检测。 ~~~ int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); cv::Mat image = cv::imread("c:\\test.png"); cv::Mat contours; cv::cvtColor(image, contours, cv::COLOR_BGR2GRAY); cv::bitwise_not(contours, contours); //cv::Canny(image, contours, 155, 350); LineFinder finder; // Set probabilistic Hough parameters finder.setLineLengthAndGap(100, 20); finder.setMinVote(80); // Detect lines and draw them std::vector<cv::Vec4i> lines = finder.findLines(contours); finder.drawDetectedLines(image, cv::Scalar(0, 0, 255)); cv::namedWindow("Detected Lines with HoughP"); cv::imshow("Detected Lines with HoughP",image); return a.exec(); } ~~~ ![这里写图片描述](https://box.kancloud.cn/2016-04-26_571f1db69a53f.jpg "")