7.2 霍夫变换
7.2.1 概述
1.特征提取技术,运用两个坐标空间之间的变换将在一个空间中具有相同形状的曲线或直线映射到另一个坐标空间的一个点上形成峰值,从而把检测任意形状的问题转化为统计峰值问题,用于检测直线、曲线、圆、椭圆等。
2.分类:(1)霍夫线变换(2)霍夫圆变换
7.2.2 霍夫线变换
1.原理:
(1)直线在坐标空间的表示:
1)笛卡尔坐标系:斜率和截距(m,b)表示
2)极坐标系:极径和极角(r,θ)
霍夫变换采用极坐标系表示直线:
即:
(2)对于通过点(x0,y0)的一族直线,可表示为:
(3)对于给定点(x0,y0),在极坐标对极径极角平面绘出所有通过它的直线,得到一条正弦曲线,如给定点(8,6):
(4)对图像中所有点绘制正弦曲线,如果两个不同点曲线在平面θ-r相交,意味着它们通过同一条直线,如点(9,4)和点(12,3):
(5)上述说明,一条直线能够通过平面θ-r寻找交于一点的曲线数量来检测,而越多曲线交于一点表示直线由更多的点组成,可以通过设置直线上点的阈值来定义多少条曲线交于一点,才认为检测到了一条直线
(6)所以,霍夫变换追踪图像中每个点对应曲线间的交点,如果交于一点的曲线数量超过阈值,可认为这个交点代表的参数对(θ,r)在原图像中为一条直线
2.分类:
(1)标准霍夫变换(Standard Hough Transform,SHT),HoughLines函数调用
(2)多尺度霍夫变换(Multi-Scale Hough Transform,MSHT):SHT在多尺度下的变种,HoughLines调用
(3)累计概率霍夫变换(Progressive Probabilistic Hough Transform,PPHT):SHT的一个改进,在一定范围内进行霍夫变换,计算单独线段的方向及范围,从而减少计算量,缩短计算时间,HoughLinesP调用
7.2.2.1 标准/多尺度霍夫变换:HoughLines()函数
1.作用:可以找出采用标准霍夫变换的二值图像线条
2.函数原型:
void HoughLines(InputArray image, OuputArray lines, double rho, double theta, int threshold, double srn=0,double stn=0)
3.参数说明:
(1)输入图像,8位单通道二进制图像
(2)检测到线条的输出矢量,一条线由两个元素矢量(ρ,θ)表示,ρ是距坐标原点(0,0)距离,θ是弧度线条旋转角度
(3)以像素为单位的距离精度,直线搜索时的进步尺寸单位半径
(4)以弧度为单位的角度精度,直线搜索时的进步尺寸单位角度
(5)累加平面的阈值参数,识别某部分为图中一条直线时在累加平面中必须达到的值
(6)多尺度霍夫变换中第三个参数rho的除数距离,默认值0,粗略的累加器进步尺寸直接为rho,精确的累加器进步尺寸为rho/srn
(7)多尺度霍夫变换中第四个参数theta的除数距离,默认值0,srn和stn同时为0表示使用SHT,否则两参数都应为正数
4.调用示例:
#include<opencv2/opencv.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
//【1】载入原图
Mat srcImage = imread("1.png");
Mat midImage, dstImage;
//【2】进行边缘检测,将HoughLines函数输入图像处理为边缘二值图
Canny(srcImage, midImage, 50, 200, 3);
//【3】转换边缘检测后的边缘二值图像为灰度图
cvtColor(midImage, dstImage, CV_GRAY2BGR);
//【4】进行霍夫线变换
vector<Vec2f>lines;//定义一个矢量结构lines用于存放得到的线性矢量集合
HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);
//【5】依次在灰度图中绘制出每条线段
for (size_t i = 0; i < lines.size(); i++)
{
float rho = lines[i][0], theta = lines[i][1];
Point pt1, pt2;
double a = cos(theta), b = sin(theta);
double x0 = a * rho, y0 = b * rho;
pt1.x = cvRound(x0 + 1000 * (-b));
pt1.y = cvRound(y0 + 1000 * (a));
pt2.x = cvRound(x0 - 1000 * (-b));
pt2.y = cvRound(y0 - 1000 * (a));
line(dstImage, pt1, pt2, Scalar(55, 100, 195), 1, LINE_AA);
}
//【5】显示原始图
imshow("【原始图】", srcImage);
//【6】边缘检测后的图
imshow("【边缘检测效果图】", midImage);
//【7】显示效果图
imshow("【霍夫线变换效果图】", dstImage);
waitKey(0);
return 0;
}
运行效果:
7.2.2.2 累计概率霍夫变换:HoughLinesP()函数
1.函数原型:
void HoughLinesP(InputArray image, OuputArray lines, double rho, double theta, int threshold, double minLineLength=0, double maxLineGap=0)
2.参数说明:
(1)输入图像,8位单通道二进制图像
(2)检测到线条的输出矢量,一条线由4个元素矢量(x_1,y_1,x_2,y_2)表示
(3)以像素为单位的距离精度,直线搜索时的进步尺寸单位半径
(4)以弧度为单位的角度精度,直线搜索时的进步尺寸单位角度
(5)累加平面的阈值参数,识别某部分为图中一条直线时在累加平面中必须达到的值
(6)表示最低线段的长度,默认值0,比这个设定参数短的线段不被显现
(7)允许将同一行点与点之间连接起来的最大距离,默认值0
3.调用示例:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
//【1】载入原始图和Mat变量定义
Mat srcImage = imread("1.png");
Mat midImage, dstImage;
//【2】进行边缘检测,将HoughLines函数输入图像处理为边缘二值图
Canny(srcImage, midImage, 50, 200, 3);
//【3】转换边缘检测后的边缘二值图像为灰度图
cvtColor(midImage, dstImage, CV_GRAY2BGR);
//【4】进行累计概率霍夫线变换
vector<Vec4i>lines;//定义一个矢量结构lines用于存放得到的线段矢量集合
HoughLinesP(midImage, lines, 1, CV_PI / 180, 80, 50, 10);
//【5】依次在图中绘制出每条线段
for (size_t i = 0; i < lines.size(); i++)
{
Vec4i l = lines[i];
line(dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(186, 88, 255), 1, CV_AA);
}
//【6】显示原始图
imshow("【原始图】", srcImage);
//【7】边缘检测后的图
imshow("【边缘检测效果图】", midImage);
//【8】显示效果图
imshow("【累计概率霍夫变换效果图】", dstImage);
waitKey(0);
return 0;
}
运行效果:
7.2.3 霍夫圆变换
1.与霍夫线变换类似,只是点对应的二维极径极角空间被三维的圆心点x,y,和半径r空间取代,用三个参数表示圆:
2.方法:霍夫梯度法
(1)对图像进行边缘检测
(2)对边缘图像中的每一个非零点,考虑其局部梯度,即用Sobel()函数计算x,y方向的Sobel一阶导数得到的梯度
(3)利用得到的梯度,由斜率(从一个指定的最小值到指定的最大值的距离)指定的直线上的每一个点都在累加器中被累加
(4)标记边缘图像中每一个非0像素位置
(5)从二维累加器中这些点中选择大于给定阈值且大于其所有近邻的候选中心,按累加值降序排列
(6)对每个中心,考虑所有非0像素,按照像素与中心距离排序,从最大半径的最小距离算起,选择非0像素最支持的一条半径
(7)如果一个中心收到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的距离,它就会被保留
3.霍夫梯度法的缺点:
(1)Sobel导数计算局部梯度,假设它可以视作一条局部切线,数值不稳定,可能产生噪声
(2)边缘图像中的整个非0像素集被看作每个中心的候选部分,如果把累加器阈值设置偏低,算法费时
(3)过于相近的中心不会被保留,同心圆或过于相似圆保留最大的圆,极端
4.函数:HoughCircles()函数
5.函数原型:
void HoughCircles(InputArray image,OutputArray circles, int method, double dp, double minDist, double param1=100, double param2=100, int minRadius=0, int maxRadius=0)
6.参数说明:
(1)输入图像,8位灰度单通道图像
(2)检测到的圆输出矢量,每个矢量包含3个元素的浮点矢量(x,y,radius)
(3)使用的检测方法,HOUGH_GRADIENT
(4)用来检测圆心的累加器图像的分辨率与输入图像之比的倒数,允许创建一个比输入图像分辨率低的累加器,如果dp=1累加器和输入图像具有相同的分辨率,如果dp=2累加器具有输入图像一半的宽度和高度
(5)霍夫变换检测到的圆的圆心之间的最小距离,设置太小多个相邻圆可能检测为一个,太大某些圆不能被检测出来
(6)第三个参数设置的检测方法对应的参数,即霍夫梯度法,表示传递给canny边缘检测算子的高阈值,低阈值为高阈值的一半,默认100
(7)第三个参数设置的检测方法对应的参数,即霍夫梯度法,表示在检测阶段圆心的累加器阈值。越小越可以检测到更多不存在的圆,越大能通过检测的圆越接近完美圆形,默认100
(8)圆半径的最小值,默认0
(9)圆半径的最大值,默认0
7.调用示例:
#include <opencv2/opencv.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace cv;
using namespace std;
int main()
{
//【1】载入原始图和Mat变量定义
Mat srcImage = imread("1.jpg");
Mat midImage, dstImage;
//【2】显示原始图
imshow("【原始图】", srcImage);
//【3】转为灰度图,进行图像平滑
cvtColor(srcImage, midImage, CV_BGR2GRAY);
GaussianBlur(midImage, midImage, Size(9, 9), 2, 2);
//【4】进行霍夫圆变换
vector<Vec3f> circles;
HoughCircles(midImage, circles, CV_HOUGH_GRADIENT, 1.5, 10, 200, 100, 0, 0);
//【5】依次在图中绘制出圆
for (size_t i = 0; i < circles.size(); i++)
{
Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
int radius = cvRound(circles[i][2]);
//绘制圆心
circle(srcImage, center, 3, Scalar(0, 255, 0), -1, 8, 0);
//绘制圆轮廓
circle(srcImage, center, radius, Scalar(155, 50, 255), 3, 8, 0);
}
//【6】显示效果图
imshow("【效果图】", srcImage);
waitKey(0);
return 0;
}
运行效果:
7.2.4 霍夫变换综合示例
/*
效果:
滚动条改变阈值threshold,动态控制霍夫线变换检测的线条多少
*/
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
using namespace std;
using namespace cv;
//全局变量声明
Mat g_srcImage, g_dstImage, g_midImage;//原始图、中间图和效果图
vector<Vec4i> g_lines; //定义一个矢量结构g_lines用于存放得到的线段矢量集合
int g_nthreshold = 100; //变量接收的TrackBar位置参数
//全局函数声明
static void on_HoughLinesP(int, void*);
static void ShowHelpText();
int main()
{
//改变console字体颜色
system("color 3F");
ShowHelpText();
//载入原始图和Mat变量定义
Mat g_srcImage = imread("1.jpg");
//显示原始图
imshow("【原始图】", g_srcImage);
//创建滚动条
namedWindow("【效果图】", 1);
createTrackbar("值", "【效果图】", &g_nthreshold, 150, on_HoughLinesP);
//进行边缘检测和转化为灰度图
Canny(g_srcImage, g_midImage, 50, 200, 3); //进行一次canny边缘检测
cvtColor(g_midImage, g_dstImage, CV_GRAY2BGR);//转化边缘检测后的图为灰度图
//调用一次回调函数,调用一次HoughLinesP函数
on_HoughLinesP(g_nthreshold, 0);
waitKey(0);
return 0;
}
//累计概率霍夫变换回调函数
static void on_HoughLinesP(int, void*)
{
//定义局部变量储存全局变量
Mat dstImage = g_dstImage.clone();
Mat midImage = g_midImage.clone();
//调用HoughLinesP函数
vector<Vec4i> mylines;
HoughLinesP(midImage, mylines, 1, CV_PI / 180, g_nthreshold + 1, 50, 10);
//循环遍历绘制每一条线段
for (size_t i = 0; i < mylines.size(); i++)
{
Vec4i l = mylines[i];
line(dstImage, Point(l[0], l[1]), Point(l[2], l[3]), Scalar(23, 180, 55), 1, CV_AA);
}
//显示图像
imshow("【效果图】", dstImage);
}
//ShowHelpText( )函数
static void ShowHelpText()
{
//输出一些帮助信息
printf("\n\n\n\t请调整滚动条观察图像效果~\n\n");
}
运行效果: