在检测边缘了解后,我们介绍一种用来检测直线的方法,那就是霍夫变换,这是一个检测直线的经典算法。
霍夫变换的数学原理较为复杂,我们先在应用层面介绍霍夫变换:
void HoughLines( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double srn = 0, double stn = 0,
double min_theta = 0, double max_theta = CV_PI );
我们可以看到,霍夫变换有四个初始化参数,那么我们就只需要确定前几个就可以了
霍夫变换的输入图像是包含一组点的二值图像,我们一般通过Canny算子来得到这幅图像
cv::Canny(image, contours, 125, 350);
这里的contours就是HoughLines的输入图像
函数的输出是一组向量,每个向量都储存着检测到的直线的信息,也就是一个n*2的数组
第三个和第四个参数代表着搜索时的步进尺寸,我们一般定义:
rho为1,theta为CV_PI / 180
第五个参数为阈值,推荐使用大一些的数,比如超过200,因为如果小了opencv就直接报错了。
但是这里需要根据图像进行自我调整,比如我们的例子中100就是比较合适的数据
这样,我们就得到了目标图像上的所有直线,下一步就是把他们画在图像上
-----------------------下面是画直线--------------------------
我们通过使用迭代器遍历向量Lines_Hough的方法来进行可视化直线操作
由于返回的数据是一个n*2数组,我们采用如下的迭代器:
vector<cv::Vec2f>::const_iterator it = lines_Hough.begin();
通过it的自加一操作,就可以得到所有的向量数据
在这里,我们需要了解一下霍夫变换中蕴含的数学原理,这样,我们才能正确的编写代码
---------------------原理-----------------------
这是电脑中图像的坐标系
A是目标直线
B是目标直线的垂线
rho是垂直距离
theta是偏角,范围是0~2*PI
在极坐标系下,所有的直线都可以表示为
这样,我们就可以知道霍夫变换中得到的就是所有可能情况下的rho和theta
依据这两个参数,我们就可以算出所有可能情况的直线
----------------------原理-------------------------
在迭代器遍历时,我们将theta分为[0, PI/4]并[3*PI/4, PI]和[ PI/4,3*PI/4]两部分
这样,在theta为[0, PI/4]并[3*PI/4, PI]时,直线和图像第一行的交点为
直线和图像最后一行交点为
这样,我们使用cv::line就可以做出直线
对于theta在另外的取值范围内的情况,基本同理
这种简单情况下的霍夫变换得到的结果为
我们不难发现,这种结果产生了错误的直线检测,并且由于cv::line方法绘出的是直线,造成了直线的失真,因此,我们采取改进的概率霍夫变换来解决上述出现的问题。
我们先来了解一下概率霍夫变换 HoughLineP以及他的数学原理:
数学原理:
概率霍夫变换将会检测图像上所有的点,并识别出经过每个点的所有直线,并标记这条直线,记为每条直线的投票数。如果一条直线被标记了多次,也就是这条直线的投票数超过我们规定的值,那么我们就认为这是一条可以选定的直线,这就是概率霍夫变换的作用原理。
opencv函数实现:
void HoughLinesP( InputArray image, OutputArray lines,
double rho, double theta, int threshold,
double minLineLength = 0, double maxLineGap = 0 );
概率霍夫变换和普通霍夫变换的输入是一样的,都是由Canny算子得到的二值图像
返回的是一个Vec4i类的向量,这是一个n*4的数组,数组的第一个和第二个数据是直线一个端点的x、y;第三个和第四个数据是直线另一个端点的x、y数据
rho和theta的定义与普通霍夫变换一样
threshold就是投票数阈值
minLineLength是直线的最小长度
maxLineGap是沿直线方向的最大缺口
为了更好的使用概率霍夫变换,我们将这个方法封装到一个LineFinder类中
class LineFinder
{
private:
//原图
cv::Mat img;
//检测到的直线的端点
vector<cv::Vec4i>lines;
//投票累加器的分辨率
double deltaRho;
double deltaTheta;
//直线合法的最小投票数
int minVote;
//直线的最小长度
double minLength;
//沿直线的最大缺口
double maxGap;
public:
//重定义
LineFinder()
{
deltaRho = 1;
deltaTheta = CV_PI / 180;
minVote = 10;
minLength = 0.0;
maxGap = 0.0;
}
//数据重定义
void setAccResolution(double dRho, double dTheta);
void setMinVote(int minv);
void setLineLengthAndGap(double length, double gap);
//寻找合法直线
std::vector<cv::Vec4i> findLines(cv::Mat& binary);
//画出直线
void drawDetectedLines(cv::Mat &image, cv::Scalar color = cv::Scalar(0, 255, 0));
};
这个类中初始化了所有需要的数值,并且定义了几种需要的方法
在霍夫变换给出的结果中(Lines),我们就得到了直线段的所有数据,这样,我们就可以在原图中画出确定的直线。
普通霍夫变换HoughLines:
int main()
{
cv::Mat image = cv::imread("F:\\Image\\road.jpg");
if (!image.data)
{
exit(1);
}
cv::Mat contours;
cv::Canny(image, contours, 125, 350);
vector<cv::Vec2f>lines_Hough;
cv::HoughLines(contours, lines_Hough, 1, CV_PI / 180, 100);
vector<cv::Vec2f>::const_iterator it = lines_Hough.begin();
while (it != lines_Hough.end())
{
float rho = (*it)[0];
float theta = (*it)[1];
if (theta < CV_PI / 4. || theta > 3.*CV_PI / 4.)
{
//直线和第一行的交点
cv::Point pt1(rho / cos(theta), 0);
//直线和最后一行的交点
cv::Point pt2((rho - contours.rows*sin(theta)) / cos(theta), contours.rows);
cv::line(image, pt1, pt2, cv::Scalar(255), 1);
}
else
{
//直线和第一列的交点
cv::Point pt1(0, rho / sin(theta));
//直线和最后一列交点
cv::Point pt2(contours.cols, (rho - contours.cols*cos(theta)) / sin(theta));
cv::line(image, pt1, pt2, cv::Scalar(255), 1);
}
std::cout << "line: (" << rho << "," << theta << ")\n";
++it;
}
cv::imshow("11", image);
cv::waitKey(0);
return 0;
}
概率霍夫变换:
LineFinder类
#pragma once
#include "stdafx.h"
#include <opencv2\opencv.hpp>
using namespace std;
class LineFinder
{
private:
//原图
cv::Mat img;
//检测到的直线的端点
vector<cv::Vec4i>lines;
//投票累加器的分辨率
double deltaRho;
double deltaTheta;
//直线合法的最小投票数
int minVote;
//直线的最小长度
double minLength;
//沿直线的最大缺口
double maxGap;
public:
//重定义
LineFinder()
{
deltaRho = 1;
deltaTheta = CV_PI / 180;
minVote = 10;
minLength = 0.0;
maxGap = 0.0;
}
//数据重定义
void setAccResolution(double dRho, double dTheta);
void setMinVote(int minv);
void setLineLengthAndGap(double length, double gap);
//寻找合法直线
std::vector<cv::Vec4i> findLines(cv::Mat& binary);
//画出直线
void drawDetectedLines(cv::Mat &image, cv::Scalar color = cv::Scalar(0, 255, 0));
};
#include "stdafx.h"
#include "LineFinder.h"
void LineFinder::setAccResolution(double dRho, double dTheta)
{
deltaRho = dRho;
deltaTheta = dTheta;
}
void LineFinder::setMinVote(int minv)
{
minVote = minv;
}
void LineFinder::setLineLengthAndGap(double length, double gap)
{
minLength = length;
maxGap = gap;
}
std::vector<cv::Vec4i> LineFinder::findLines(cv::Mat& binary)
{
lines.clear();
cv::HoughLinesP(binary, lines, deltaRho, deltaTheta, minVote, minLength, maxGap);
return lines;
}
void LineFinder::drawDetectedLines(cv::Mat &image, cv::Scalar color)
{
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;
}
}
cv::Mat image = cv::imread("F:\\Image\\road.jpg");
if (!image.data)
{
exit(1);
}
cv::Mat contours;
cv::Canny(image, contours, 125, 350);
LineFinder finder;
finder.setLineLengthAndGap(100, 20);
finder.setMinVote(80);
vector<cv::Vec4i>lines = finder.findLines(contours);
finder.drawDetectedLines(image, cv::Scalar(255, 0, 0));
cv::imshow("123", image);
cv::waitKey(0);
return 0;
这样,我们就得到了改进的概率霍夫变换图像