序言:
什么是霍夫变换?在图像处理和计算机视觉邻域中,如何从当前的图像中提前所需要的特征信息是图像识别的关键所在。霍夫变换可以快速准确地检测出直线或者圆,在图像处理中识别几何形状的基本方法之一,最基本的霍夫变换是从黑白图像中检测直线(线段)
文章目录
- 1、霍夫变换概述
- 2、OpenCV中的霍夫线变换
- 3、霍夫线变换的原理
- 4、标准霍夫变换:HoughLines()函数
- 5、累计概率霍夫变换:HoughLinesP()函数
- 6、霍夫圆变换
- 6.1 霍夫梯度法的原理
- 6.2 霍夫梯度法的缺点
- 6.3 霍夫圆变化:HoughCircles()函数
1、霍夫变换概述
霍夫变换(Hough Transform)是图像处理技术中的一种特征提前技术,该过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换的结果。霍夫变换运行两个坐标空间之间的变换将在一个空间中具有相同形状的曲线或者直线映射到另一个坐标空间的一个点形成峰值,从而把检测任意形状的问题转化为统计峰值问题。
2、OpenCV中的霍夫线变换
霍夫变换是一种用来寻找直线的方法,在使用霍夫变换之前,首先要对图像进行边缘检测的处理,即霍夫变换的直接输入只能是二值图像。
OpenCV中的霍夫线变换有三种:
- 标准霍夫变换(StandardHough Transform,SHT),由HoughLines函数调用
- 多尺度霍夫变换(MSHT),由HoughLines函数调用
- 累计概率霍夫变换(PPHT),由HoughLinesP函数调用
3、霍夫线变换的原理
说明:书中描述的霍夫变换原理,初学者我感觉看的有点不是很明白。于是乎我就看书之后区百度一下。
(1)一条直线在图像二维空间可由两个变量表示:
- 在直线坐标系:可由参数斜率和截距(m,b)表示
- 在极坐标系:可由参数极径和极角(r,0)表示
对于霍夫变换,采用极坐标方式表示直线:
(2)霍夫空间
高中数学我们就知道两点确定一条直线:如下A=(x1,y1),B=(x2,y2)
将直线y=kx+b写成关于函数表所示:
变换后的空间叫做霍夫空间。直角坐标系(笛卡尔积坐标系)中的一直线对应霍夫空间的一个点,反之也成立。
- 若笛卡尔积坐标系中的点共线,那么这些点在霍夫空间中对应的直线交于一点
- 若笛卡尔积坐标系中有多个直线汇成的点,我们选择尽可能多的点汇聚的直线作为霍夫空间的点
但:笛卡尔坐标系也会出现问题,就是若出现点(2,2),(2,3)这样构成的直线的k是趋近于无穷的,不好判断,此时可以考虑极坐标下的点
以上表明,一条直线能够通过在平面的α-r寻找交于一点的曲线数量来检测。而越多曲线交于一点就意味着这个交点表示的直线由更多的点组成。通常通过设置直线上的点的阈值来定义多少条曲线交于一点,这样才能检测到一条直线。
4、标准霍夫变换:HoughLines()函数
说明:可以用来调用标准霍夫变换SHT和多尺度霍夫变换MSHT
void HoughLines(InputArray image,OutputArray lines,double rho,double theta,int threshold,
double srn=0,double stn=0);
- 第一个参数:输入图像,即源图像。需为二进制图像
- 第二个参数:经过调用HoughLines函数后存储了霍夫线变换检测到线条的输出矢量。每一条线由具有两个元素的矢量(p,α)表示,其中,p是离坐标原点(0,0)【图像的左上角】的距离,α为弧度线条旋转角度(0度表示垂直线,Π/2表示水平线)
- 第三个参数:double类型的rho,以像素为单位的距离精度。另一种表达方式是直线搜索时的进步尺寸的单位角度
- 第四个参数:double类型的theta,以弧度为单位的角度精度。另一种表达方式是直线搜索时的进步尺寸的单位角度
- 第五个参数:int类型的 threshold,累加平面的阈值参数,识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值的threshold的线段才可以被检测通过并返回到结果中
- 第六个参数:doubel类型的srn,默认值0.对于多尺度的霍夫变换,这是第三个参数进步尺寸rho的除数距离。粗略的累加器进步尺寸直接是第三个参数rho,精确的累加器进步尺寸为rho/srn
- 第七个参数:doubel类型的stn,默认值0.对于多尺度的霍夫变换,这是第四个参数进步尺寸theta的除数距离。
示例程序(可以看出标准霍夫变换识别的是直线,不是线段)
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
//载入元素图和Mat变量定义
Mat srcImage = imread("E:\\Pec\\huofu.jpg");
Mat midImage, dstImage;
//进行边缘检测和转化为灰度图
Canny(srcImage, midImage, 50, 200, 3);
cvtColor(midImage, dstImage, CV_GRAY2BGR);
//进行霍夫线变换
vector<Vec2f>lines;//定义一个矢量结构lines用于存放得到的线段矢量集合
HoughLines(midImage, lines, 1, CV_PI / 180, 150, 0, 0);
//依次在图中绘制出每条线段
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(0,255, 0), 1, LINE_AA);
}
imshow("【原始图】", srcImage);
imshow("【边缘检测图】", midImage);
imshow("【效果图图】", dstImage);
waitKey(0);
return 0;
}
5、累计概率霍夫变换:HoughLinesP()函数
void HoughLinesP(InputArray image,OutputArray lines,double rho,double theta,int threshold,
double minLineLength=0,double maxLineGap=0)
- 第一个参数:输入图像
- 第二个参数:OutputArray类型的 lines,经过调用HoughLinesP函数后存储了检测到的线条的输出矢量,每一条线由具有4个元素的矢量(x_1,y_1,x_2,y_2)表示,其中(x_1,y_1)和(x_2,y_2)是每个检测到的线段的结束点
- 第三个参数:double类型的rho,以像素为单位的距离精度。另一种表达方式是直线搜索时的进步尺寸的单位角度
- 第四个参数:double类型的theta,以弧度为单位的角度精度。另一种表达方式是直线搜索时的进步尺寸的单位角度
- 第五个参数:int类型的 threshold,累加平面的阈值参数,识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值的threshold的线段才可以被检测通过并返回到结果中
- 第六个参数:double类型的 minLineLength,默认值0,表达最低线段的长度,比这个设定参数短的线段就不能被显现出来
- 第七个参数:double 类型的maxLineGap,默认值0,允许将同一行点于点之间连接起来的最大距离。
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
//载入元素图和Mat变量定义
Mat srcImage = imread("E:\\Pec\\lunk.jpg");
Mat midImage, dstImage;
//进行边缘检测和转化为灰度图
Canny(srcImage, midImage, 50, 200, 3);
cvtColor(midImage, dstImage, COLOR_GRAY2BGR);
//进行霍夫线变换
vector<Vec4i>lines;//定义一个矢量结构lines用于存放得到的线段矢量集合
HoughLinesP(midImage, lines, 1, CV_PI / 80, 50, 10);
//依次在图中绘制出每条线段
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, LINE_AA);
}
imshow("【原始图】", srcImage);
imshow("【边缘检测图】", midImage);
imshow("【效果图图】", dstImage);
waitKey(0);
return 0;
}
计算单独线段的方向以及范围,从而大大减少计算量,缩短计算时间
6、霍夫圆变换
说明:霍夫圆变换的基本原理和霍夫线变化很类似,把点对点的二维极径极角空间被三维的圆心点x,y和半径r空间取代。需要大量的内存且执行效率低,速度慢。
6.1 霍夫梯度法的原理
(1)首先对图像应用边缘检测,比如用canny边缘检测。
(2)然后,对边缘图像中的每一个非零点,考虑其局部梯度,用Sobel()函数计算x和y方向的Sobel一阶导数得到梯度
(3)利用得到的梯度,由斜率指定的直线上的每一个点都在累加器中被累加,斜率是从一个指定的最小值到指定的最大值的距离
(4)同时,标记边缘图像中的每一个非0像素的位置
(5)然后从二维累加器中这些点中选择候选的中心,这些中心都大于给定阈值并且大于其所有近邻。这些候选的中心按照累加值降序排列,以便于最支持像素的中心首先出现。
(6)对每一个中心,考虑所有非0像素
(7)这些像素按照其与中心的距离排序。从到最大半径的最小距离算起,选择非0像素支持的一条半径
(8)如果一个中心收到边缘图像非0像素最充分的支持,并且到前期被选择的中心有足够的距离,那么它就会被保留下来。
6.2 霍夫梯度法的缺点
(1)在霍夫梯度法中,使用Sobel导数来计算局部梯度,那么随之而来的假设是,它可以作为等同于一条局部切线,这并不是一个数值稳定的做法。大多数情况下,这样做会得到正确的结果,但或许会在输出产生一些噪声
(2)在边缘图像中的整个非0像素集被看作做每个中心的候选部分。如果把累加器的阈值设置偏低,算法将要消耗比较长的时间。
(3)中心是按照关联的累加器值的升序排列的,并且如果新的中心过于接近之前已经接受的中心的话,就不会被保留下来。且当有许多同心圆或者近似同心圆时,霍夫梯度法的倾向是保留最大的一个圆。做法比较极端,因为默认Sobel导数会产生噪声,若针对无穷分辨率的平滑图像。
6.3 霍夫圆变化:HoughCircles()函数
说明:相比较于HoughLines和HoughLinesP而言,HoughCircles函数只需要灰度图中的圆,不需要二值图像。
void HoughCircles(InputArray image,OutputArray circles,int method,double dp,double minDist,
double paraml=100,double param2=100,int minRadius=0,int maxRadius=0)
- 第一个参数:输入图像
- 第二个参数:OutputArray类型circles,经过调用HoughCircles函数后此参数存储了检测到的圆的输出矢量,每个矢量由包含了3个元素的浮点矢量(x,y,radius)表示。
- 第三个参数:int类型的method,即使用的检测方法,目前OpenCV中就霍夫梯度法一种可以使用,它的标识符为HOUGH_GRADIENT
- 第四个参数:doubel类型的dp,用来检测圆心的累加器图像的分辨率于输入图像之比的倒数,且此参数允许创建一个比输入图像分辨率低的累加器。如dp=1时,累加器和输入图像具有相同的分辨率。如果dp=2,累加器便有输入图像一半那么大的宽度和高度
- 第五个参数:为霍夫变换检测到的圆的圆心之间的最小距离,即让算法能明显区分的两个不同圆之间的最小距离。这个参数如果太小的话,多个相邻的圆可能被错误地检测成一个混合的圆,反之,设置太大的话,就不会被检测出圆
- 第六个参数:有默认值100.它是第三个参数method设置的检测方法的对应的参数。对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示传递给canny边缘检测算子的高阈值,而低阈值为高阈值的一半。
- 第七个参数:有默认值100.它是第三个参数method设置的检测方法的对应的参数。对当前唯一的方法霍夫梯度法CV_HOUGH_GRADIENT,它表示在检测阶段圆心的累加器阈值。它越小,就越可以检测到更多根本不存在的圆。越大越检测出接近完美的圆形
- 第八个参数:默认值0,表示圆半径最小值
- 第九个参数:默认值0,表示圆半径最大值
#include<opencv2/opencv.hpp>
#include<opencv2/imgproc/imgproc.hpp>
#include<opencv2/highgui/highgui.hpp>
#include<iostream>
using namespace std;
using namespace cv;
int main()
{
//载入元素图和Mat变量定义
Mat srcImage = imread("E:\\Pec\\霍夫圆.jpg");
Mat midImage, dstImage;
//转化为灰度图并进行图像平滑
cvtColor(srcImage, midImage, COLOR_BGR2GRAY);
GaussianBlur(midImage, midImage, Size(9, 9), 2, 2);
//进行霍夫线变换
vector<Vec3f>circles;//定义一个矢量结构lines用于存放得到的线段矢量集合
HoughCircles(midImage, circles, HOUGH_GRADIENT, 1.5, 10, 200, 100, 0, 0);
//依次在图中绘制出圆
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);
}
//imshow("【原始图】", srcImage);
//imshow("【边缘检测图】", midImage);
imshow("【效果图】", srcImage);
waitKey(0);
return 0;
}