霍夫线变换的思想是:霍夫线变换必须应用在二值图像上,它认为图像上每一个点都有可能是某条直线上的一个点,对过每点的所有直线进行投票,根据设定的权重做最终的判断,这个是霍夫线变换的理论基础。
OpenCV 4 提供了检测图像边缘是否存在直线和圆形的检测算法
直线检测
霍夫直线变换
霍夫变换中存在的两个重要的结论
(1)图像空间中的每条直线在参数空间中都对应着单独一个点来表示。
(2)图像空间中的直线上任何像素点在参数空间对应的直线相交于同一个点。上图给出了第(2)个结论的示意图。因此通过霍夫变换寻找图像中的直线就是寻找参数空间中大量直线相交的一点。
注意:上述方法存在问题,即当直线垂直与x轴时,k不存在,如图所示
为了解决垂直直线在参数空间没有交点的问题,一般采用极坐标方式表示图像空间x-y 直角
坐标系中的直线,具体形式如下所示。
其中 r 为坐标原点到直线的距离, 为坐标原点到直线的垂线与x 轴的夹角
通过上述的变换过程, 将图像中的直线检测转换成了在参数空间中寻找某个点(,)通过的正弦曲线最多的问题。由于在参数空间内的曲线是连续的,而在实际情况中图像的像素是离散的, 因此我们需要将参数空间的 轴和 r 轴进行离散化,用离散后的方格表示每一条正弦曲线。首先寻找符合条件的网格, 之后寻找该网格对应的图像空间中所有的点, 这些点共同组成了原图像中的直线.
C++代码实现(标准霍夫变换)HoughLines()
#include <iostream>
#include <opencv2/opencv.hpp>
using namespace cv;
using namespace std;
void drawLine(Mat &img, vector<Vec2f>lines, double rows, double cols, Scalar scalar, int n)
{
Point pt1, pt2;
for (size_t i = 0;i<lines.size();i++)
{
float rho = lines[i][0]; //直线距离坐标原点的距离
float theta = lines[i][1];//夹角
double a = cos(theta);
double b = sin(theta);
double x0 = a * rho, y0 = b * rho; //直线与过坐标原点的垂线的交点
double length = max(rows, cols); //图像高宽的最大值
//计算直线上的一点
pt1.x = cvRound(x0 + length * (-b));
pt1.y = cvRound(y0 + length * (a));
//计算直线上另一点
pt2.x = cvRound(x0 - length * (-b));
pt2.y = cvRound(y0 - length * (a));
//两点绘制一条直线
line(img, pt1, pt2, scalar, n);
}
}
int main()
{
Mat img = imread("HoughLines.jpg",IMREAD_GRAYSCALE);
if (img.empty())
{
cout << "图像载入失败!" << endl;
return -1;
}
Mat edge;
//检测边缘图像并二值化
Canny(img,edge,80,180,3,false);
threshold(edge, edge, 170, 255, THRESH_BINARY);
//用不同的累加器检测直线
vector<Vec2f> lines1, lines2;
HoughLines(edge, lines1, 1, CV_PI / 180, 50, 0, 0);
HoughLines(edge, lines2, 1, CV_PI / 180, 150, 0, 0);
//在原图像中绘制直线
Mat img1, img2;
img.copyTo(img1);
img.copyTo(img2);
drawLine(img1, lines1, edge.rows, edge.cols, Scalar(255), 2);
drawLine(img2, lines2, edge.rows, edge.cols, Scalar(255), 2);
//显示图像
imshow("edge", edge);
imshow("img", img);
imshow("img1", img1);
imshow("img2", img2);
waitKey(0);
return 0;
}
使用标准霍夫变换和多尺度霍夫变换函数 HoughLines() 提取直线时无法准确知道图像中直线或者线段的长度,只能得到图像中是否存在符合要求的直线,以及直线的极坐标解析式。如果需要准确地定位图像中线段的位置,HoughLines()函数便无法满足需求,但是OpenCV 4提供的渐进概率式霍夫变换函数HoughLinesP()可以得到图像中满足条件的直线或者线段两个端点的坐标,进而确定直线或者线段的位置。
C++代码实现(标准霍夫变换) HoughLinesP()
#include <opencv2/opencv.hpp>
#include <iostream>
using namespace cv;
using namespace std;
int main()
{
Mat img = imread("HoughLines.jpg", IMREAD_GRAYSCALE);
if (img.empty())
{
cout << "请确认图像文件名称是否正确" << endl;
return -1;
}
Mat edge;
//检测边缘图像,并二值化
Canny(img, edge, 80, 180, 3, false);
threshold(edge, edge, 170, 255, THRESH_BINARY);
//利用渐进概率式霍夫变换提取直线
vector<Vec4i> linesP1, linesP2;
HoughLinesP(edge, linesP1, 1, CV_PI / 180, 150, 30, 10); //两个点连接最大距离10
HoughLinesP(edge, linesP2, 1, CV_PI / 180, 150, 30, 30); //两个点连接最大距离30
//绘制两个点连接最大距离10直线检测结果
Mat img1;
img.copyTo(img1);
for (size_t i = 0; i < linesP1.size(); i++)
{
line(img1, Point(linesP1[i][0], linesP1[i][1]),
Point(linesP1[i][2], linesP1[i][3]), Scalar(255), 3);
}
//绘制两个点连接最大距离30直线检测结果
Mat img2;
img.copyTo(img2);
for (size_t i = 0; i < linesP2.size(); i++)
{
line(img2, Point(linesP2[i][0], linesP2[i][1]),
Point(linesP2[i][2], linesP2[i][3]), Scalar(255), 3);
}
//显示图像
imshow("img1", img1);
imshow("img2", img2);
waitKey(0);
return 0;
}
前面两个函数都是检测图像中是否存在直线,但在实际工程或者任务需求中,可能得到的是图像中一些点的坐标而不是一幅完整的图像,因此,OpenCV4中提供了能够在含有坐标的众多点中寻找是否存在直线的HoughLinesPointSet()函数。
#include <opencv2/opencv.hpp>
#include <iostream>
#include <vector>
using namespace cv;
using namespace std;
int main()
{
system("color F0"); //更改输出界面颜色
Mat lines; //存放检测直线结果的矩阵
vector<Vec3d> line3d; //换一种结果存放形式
vector<Point2f> point; //待检测是否存在直线的所有点
const static float Points[20][2] = {
{ 0.0f, 369.0f },{ 10.0f, 364.0f },{ 20.0f, 358.0f },{ 30.0f, 352.0f },
{ 40.0f, 346.0f },{ 50.0f, 341.0f },{ 60.0f, 335.0f },{ 70.0f, 329.0f },
{ 80.0f, 323.0f },{ 90.0f, 318.0f },{ 100.0f, 312.0f },{ 110.0f, 306.0f },
{ 120.0f, 300.0f },{ 130.0f, 295.0f },{ 140.0f, 289.0f },{ 150.0f, 284.0f },
{ 160.0f, 277.0f },{ 170.0f, 271.0f },{ 180.0f, 266.0f },{ 190.0f, 260.0f }
};
//将所有点存放在vector中,用于输入函数中
for (int i = 0; i < 20; i++)
{
point.push_back(Point2f(Points[i][0], Points[i][1]));
}
//参数设置
double rhoMin = 0.0f; //最小长度
double rhoMax = 360.0f; //最大长度
double rhoStep = 1; //离散化单位距离长度
double thetaMin = 0.0f; //最小角度
double thetaMax = CV_PI / 2.0f; //最大角度
double thetaStep = CV_PI / 180.0f; 离散化单位角度弧度
HoughLinesPointSet(point, lines, 20, 1, rhoMin, rhoMax, rhoStep,
thetaMin, thetaMax, thetaStep);
lines.copyTo(line3d);
//输出结果
for (int i = 0; i < line3d.size(); i++)
{
cout << "votes:" << (int)line3d.at(i).val[0] << ", "
<< "rho:" << line3d.at(i).val[1] << ", "
<< "theta:" << line3d.at(i).val[2] << endl;
}
return 0;
}