若该文为原创文章,未经允许不得转载
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105544972
各位读者,知识无穷而人力有穷,要么改需求,要么找专业人士,要么自己研究
目录
2.一般来说,对于点(x0,y0),可以通过这个点的一组直线统一定义
3.对于一个给定点(x0,y0),在极坐标对极径和极角平面会出所有通过他的直线将得到一条正弦曲线
5.从以上得出,一条直线能够通过在平面θ-r寻找交于一点的曲线数量来检测。
OpenCV开发专栏(点击传送门)
OpenCV开发笔记(四十三):红胖子8分钟带你深入了解累计概率霍夫线变换(图文并茂+浅显易懂+程序源码)
前言
红胖子来也!!!
去噪、边缘检测之后,就是特征提取了,识别图形的基本方法之一---霍夫变换,霍夫变换是图像处理中的一种特征提取技术,上一篇讲解了霍夫线变换中的标准霍夫变换,本篇章讲解霍夫线变换中的累计概率霍夫线变换。
此处会再次讲一遍原理,加深各位的理解,因为上一篇的8分钟理解霍夫变换不太够,霍夫变换是重要的检测手段之一,所以本篇再说一次原理,同时对两者的检测效果进行比较。
Demo
霍夫变换
概述
霍夫变换(Hough Transform)是图像处理中的一种特征提取技术,改过程在一个参数空间中通过计算累计结果的局部最大值得到一个符合该特定形状的集合作为霍夫变换结果。
经典的霍夫变换用来检测图像中的直线,后来霍夫变换扩展到任意形状物体的识别,多为圆和椭圆。
霍夫变换运用两个坐标空间之间的变换将在一个空间中具有相同的形状的曲线或直线映射到另一个坐标控件的一个点上形成峰值,从而把检测任何形状的问题转化为统计峰值问题。
OpenCV中的霍夫变换分为两大类型,线变换下又分三种类型,如下图:
累计概率霍夫线变换
概述
霍夫线变换,从名字就可以知道其实针对直线,显而易见就是用来寻找直线的方法,此处特别注意,使用霍夫线变换之前,肯定是需要对图片进行预处理:降噪、边缘检测处理,霍夫线变换只寻找直线,只能识别边缘二值图像,所以输入也就只能是二值化(单通道8位)的图像了。
霍夫线变换分为三种,如下图:
霍夫线变换会找出大量的线,但是有些线其实是无用的。
原理
1.一条直线的图像二维空间可由两个变量表示
- 在笛卡尔坐标系:可有参数斜率和截距(m,b)表示;
- 在极坐标系(霍夫变换采用的方式):可有参数极径和极角(r,θ)表示;
霍夫变换采用极坐标的方式来表示直线。
因此直线的表达式是:
得出r公式为:
2.一般来说,对于点(x0,y0),可以通过这个点的一组直线统一定义
rθ= x0 * cosθ + y0 * sinθ
每一对(rθ,θ)代表一条通过点(x0,y0)的直线。
3.对于一个给定点(x0,y0),在极坐标对极径和极角平面会出所有通过他的直线将得到一条正弦曲线
例如,对于给定点x0=8和y0=6,计算原理如下:
可以会出如下图曲线:
只绘出满足特定条件,如r>0和0<θ<2π,(注意:0表示垂直线,π/2度表示水平线);
4.对图像中所有的点进行上述操作,得到曲线图集合
如果两个不同点进行上述操作后得到的曲线在平面θ-r相交,这就意味着他们通过同一条直线。
例如,接上面的例子继续对点x1=9,y1=4和x2=12,y2=3绘图如下:
这三条曲线在平面相较于点(0.925, 9.6),坐标表示的是参数对θ-r或者是说点(x0, y0)、(x1, y1)和(x2, y2)组成的平面内的直线,所以其实是计算平面内的每个点的每个角度的距离,绘制成曲线后,如果3个点交叉了,那么3个点就是在一条直线上,示意如下图:
(交于一点的曲线的数量超过了阈值(在同一条直线上点的数量),比如上面的示意图,就认为3个点就可以组成一条直线,那么他们可以检测出一跳直线)
5.从以上得出,一条直线能够通过在平面θ-r寻找交于一点的曲线数量来检测。
越多曲线交于一点也就意味着这个点焦点表示的直线有更多的点组成。一般来说我们可以通过设置直线上点的阈值来定义多少条曲线交于一点,这样才认为检测到了一条直线。
以上就是霍夫变换所做的,它追踪图像中每个点对应曲线的交点,如果交于一点的曲线的数量超过了阈值(在同一条直线上点的数量阈值),那么可以认为这个交点所代表的参数对(θ,rθ)在原图像中为一条直线。
累计概率霍夫变换函数原型
void HoughLinesP( InputArray image,
OutputArray lines,
double rho,
double theta,
int threshold,
double minLineLength = 0,
double maxLineGap = 0 );
- 参数一:InputArray类型的image,源图像8位,单通道二进制图像。可以将任意的原图载入进来,并由函数修改成此格式后,再填这里;
- 参数二:OutputArray类型的lines,经过调用HoughLines函数后存储了霍夫变换检测到线条的输出矢量。每一条线由两个元素的矢量(r,θ),r代表离坐标原点的距离,θ是弧度线条旋转角度(0表示垂直线,π/2度表示水平线);
- 参数三:double类型的rho,rho是直线的距离,若为1个像素,那么检测1->2->3长度的直线,若为2则是2->4->6忽略1和3了。
- 参数四:double类型的theta,theta是弧度,哪怕所有的都是线,那么线与线之间的弧度精度,若360度一个点,弧度为π,则表示为从0°开始,每隔π弧度(180°,检测一根线);
- 参数五:int类型的threshold,累加平面的阈值参数,即识别某部分为图中的一条直线时它在累加平面中必须达到的值。大于阈值threshold的线段才可以被检测通过并返回到结果中;
- 参数六:double类型的minLineLength,默认为0,表示最低线段的长度,比这个设定参数短的线段就不能被检测出来;
- 参数七:double类型的maxLineGap,默认为0,允许将同一行点与点之间连接起来的最大距离;
补充数据函数原型
cvRound():返回跟参数最接近的整数值,即四舍五入;
cvFloor():返回不大于参数的最大整数值,即向下取整;
cvCeil():返回不小于参数的最小整数值,即向上取整;
Demo源码
void OpenCVManager::testHoughLinesP()
{
QString fileName1 =
"E:/qtProject/openCVDemo/openCVDemo/modules/openCVManager/images/16.jpg";
cv::Mat srcMat = cv::imread(fileName1.toStdString());
int width = 400;
int height = 300;
cv::resize(srcMat, srcMat, cv::Size(width, height));
cv::Mat colorMat = srcMat.clone();
cv::String windowName = _windowTitle.toStdString();
cvui::init(windowName);
cv::Mat windowMat = cv::Mat(cv::Size(srcMat.cols * 2, srcMat.rows * 3),
srcMat.type());
cv::cvtColor(srcMat, srcMat, CV_BGR2GRAY);
int threshold1 = 200;
int threshold2 = 100;
int apertureSize = 1;
int rh0 = 1; // 默认1像素
int theta = 1; // 默认1°
int threshold = 100; // 默认检测到同一直线的100个点
int minLineLength = 50; // 检测线的最小长度
int maxLineGap = 10; // 同一条线点与点的最大距离
while(true)
{
qDebug() << __FILE__ << __LINE__;
windowMat = cv::Scalar(0, 0, 0);
cv::Mat mat;
cv::Mat dstMat;
cv::Mat grayMat;
// 转换为灰度图像
// 原图先copy到左边
cv::Mat leftMat = windowMat(cv::Range(0, srcMat.rows),
cv::Range(0, srcMat.cols));
cv::cvtColor(srcMat, grayMat, CV_GRAY2BGR);
cv::addWeighted(leftMat, 0.0f, grayMat, 1.0f, 0.0f, leftMat);
{
cvui::printf(windowMat,
width * 1 + 100,
height * 0 + 20,
"threshold1");
cvui::trackbar(windowMat,
width * 1 + 100,
height * 0 + 50,
200,
&threshold1,
0,
255);
cvui::printf(windowMat,
width * 1 + 100,
height * 0 + 100, "threshold2");
cvui::trackbar(windowMat,
width * 1 + 100,
height * 0 + 130,
200,
&threshold2,
0,
255);
cv::Canny(srcMat, dstMat, threshold1, threshold2, apertureSize * 2 + 1);
// copy
mat = windowMat(cv::Range(srcMat.rows * 1, srcMat.rows * 2),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::cvtColor(dstMat, grayMat, CV_GRAY2BGR);
cv::addWeighted(mat, 0.0f, grayMat, 1.0f, 0.0f, mat);
cvui::printf(windowMat,
width * 1 + 100,
height * 1 + 20 - 120,
"rho / 100");
cvui::trackbar(windowMat,
width * 1 + 100,
height * 1 + 50 - 120,
200,
&rh0,
1,
1000);
cvui::printf(windowMat,
width * 1 + 100,
height * 1 + 100 - 120,
"theta = value / 2");
cvui::trackbar(windowMat,
width * 1 + 100,
height * 1 + 130 - 120,
200,
&theta,
1,
720);
cvui::printf(windowMat,
width * 1 + 100,
height * 1 + 180 - 120,
"min points");
cvui::trackbar(windowMat,
width * 1 + 100,
height * 1 + 210 - 120,
200,
&threshold,
2,
300);
cvui::printf(windowMat,
width * 1 + 100,
height * 1 + 260 - 120,
"minLineLength = value / 10");
cvui::trackbar(windowMat,
width * 1 + 100,
height * 1 + 290 - 120,
200,
&minLineLength,
0,
1000);
cvui::printf(windowMat,
width * 1 + 100,
height * 1 + 340 - 120,
"maxLineGap = value / 10");
cvui::trackbar(windowMat,
width * 1 + 100,
height * 1 + 370 - 120,
200,
&maxLineGap,
0,
1000);
// 边缘检测后,进行霍夫线检测
// 使用霍夫线变化检测所有角度范围内的直线
std::vector<cv::Vec2f> lines;
cv::HoughLines(dstMat, // 输入8位
lines, // 输出线 std::vector<std::Vec2f>
rh0 / 100.0f, // 初步像素精度
theta / 720.0 * CV_PI, // 初步偏移角度精度
threshold, // 必须达到的点的数量
0, // 标准霍夫变换,为0
0, // 标准霍夫变换,为0
0, // 检测角度范围最小为0
CV_PI); // 检测角度范围最大为π,即360°
// 在图中绘制出每条线段
dstMat = colorMat.clone();
qDebug() << __FILE__ << __LINE__ << lines.size();
for(int index = 0; index < lines.size(); index++)
{
float rho = lines[index][0];
float theta = lines[index][1];
cv::Point pt1;
cv::Point pt2;
double a = cos(theta);
double b = sin(theta);
double x0 = a * rho;
double 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));
// 画线
cv::line(dstMat, pt1, pt2, cv::Scalar(0, 0, 255), 1, cv::LINE_AA);
}
// copy
mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
cv::Range(srcMat.cols * 0, srcMat.cols * 1));
cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
// 使用概率霍夫线变化检测所有长度范围内的直线
cv::Canny(srcMat, dstMat, threshold1, threshold2, apertureSize * 2 + 1);
std::vector<cv::Vec4i> lines2;
cv::HoughLinesP(dstMat, // 输入8位
lines2, // 输出线 std::vector<std::Vec4i>
rh0/100.0f, // 初步像素精度
theta / 720.0 * CV_PI, // 初步偏移角度精度
threshold, // 必须达到的点的数量
minLineLength / 10.0f, // 检测线的最小长度
maxLineGap / 10.0f); // 检测线的最大距离
// 在图中绘制出每条线段
dstMat = colorMat.clone();
for(int index = 0; index < lines.size(); index++)
{
// 画线
cv::Vec4i line = lines2[index];
cv::line(dstMat,
cv::Point(line[0], line[1]),
cv::Point(line[2], line[3]),
cv::Scalar(0, 0, 255),
1,
cv::LINE_AA);
}
// copy
mat = windowMat(cv::Range(srcMat.rows * 2, srcMat.rows * 3),
cv::Range(srcMat.cols * 1, srcMat.cols * 2));
cv::addWeighted(mat, 0.0f, dstMat, 1.0f, 0.0f, mat);
}
// 更新
cvui::update();
// 显示
cv::imshow(windowName, windowMat);
// esc键退出
if(cv::waitKey(25) == 27)
{
break;
}
}
}
工程模板:对应版本号v1.38.0
对应版本号v1.38.0
参考博文
https://blog.csdn.net/shenziheng1/article/details/75307410
原博主博客地址:https://blog.csdn.net/qq21497936
原博主博客导航:https://blog.csdn.net/qq21497936/article/details/102478062
本文章博客地址:https://blog.csdn.net/qq21497936/article/details/105544972