## 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π ,ρ 為負。

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;
~~~
首先給出一個簡單的測試圖片。這個圖片上有四條直線。沒有其他的干擾物體。這屬于最基本的情形。

下面是個測試代碼。
~~~
#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();
}
~~~
輸出結果如下:

這幾條線找的還是蠻準的。
### 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();
}
~~~
