前言
极坐标系介绍
比如上面的圆方程是 x2+y2=r2
想表示一点A(x0,y0) 就是直接把横纵坐标带入方程,但是方程有两个需要计算的平方,我想简化一下,看下面坐标系
点A的位置可以被两个参数确定 首先是半径r 其次是角度θ
于是半径为r的圆上任意一点A,就被r和θ 唯一定义了:A(r,θ)。
即是在极坐标系中
o是极点O,x是极轴,对于坐标平面内的任一点A,第一个变量表示A到O的距离,记作ρ;
第二个变量表示夹角∠AOx,记作θ;所以,A点的坐标表示为:A(ρ,θ)
1.霍夫变换原理
首先是笛卡尔坐标系到霍夫空间的转换
比如笛卡尔坐标系中有一条直线 y=ax+b
笛卡尔坐标系中一条直线,对应霍夫空间的一个点。
反过来同样成立(霍夫空间的一条直线,对应笛卡尔坐标系的一个点)
原理其实很简单
比如
(1)笛卡尔坐标系内y=ax+b 一条直线确定时 它的斜率和截距是确定的 即是a b是确定的,因此到了霍夫空间内就对应一个(a,b) 即是笛卡尔中一条直线对应霍夫空间一个点
(2)笛卡尔坐标系内一个点 比如x1,y1 相当于x1 y1是确定的 在霍夫空间中
b=-xa+y 即是 b=-x1a+y1 代表的是一条直线 即是笛卡尔中一个点对应霍夫空间一条直线
(3)笛卡尔坐标系多个点
这些点如果共线 就相当于回到了(1) 笛卡尔坐标系中一条直线对应霍夫空间一个点
(4)笛卡尔坐标系多个点 不共线
(5)但是 如果直线斜率不存在的时候 霍夫空间那就不容易表示
因此我们换成极坐标 一样的转换原理
先求极坐标方程 其中参数从斜率a和截距b变成 极径p和极角θ
比如下面的变换对比
具体计算过程举例:
2.OpenCV C++实现
/*
*参数说明:
*src:待检测的原图像
*rho:以像素为单位的距离分辨率,即距离r离散时的单位长度
*theat:以角度为单位的距离分辨率,即角度Θ离散时的单位长度(取值的步长)
*Threshold:累加器阈值,参数空间中离散化后每个方格被通过的
累计次数大于该阈值,则该方格代表的直线被视为在
原图像中存在
*lines:检测到的直线极坐标描述的系数数组,每条直线由两个参
数表示,分别为直线到原点的距离r和原点到直线的垂线与
x轴的夹角
*/
void myHoughLines(Mat src, double rho, double theat, int Threshold, vector<Vec2f>& lines)
{
if (src.empty() || rho < 0.1 || theat>360 || theat < 0)
return;
int row = src.rows;
int col = src.cols;
Mat gray;
if (src.channels() > 1)
{
cvtColor(src, gray, COLOR_BGR2GRAY);
}
else
src.copyTo(gray);
int maxDistance = sqrt(src.cols * src.cols + src.rows * src.rows); // 图像任意两点最大距离
int houghMat_cols = 360 / theat; // theat是角度取值的步长 霍夫变换后距离夹角坐标下对应的Mat的宽(一共多少个θ)
int houghMat_rows = maxDistance / rho; // 霍夫坐标距离夹角下对应的Mat的高 就是p的取值个数
Mat houghMat = Mat::zeros(houghMat_rows, houghMat_cols, CV_32FC1); // 存储p和 θ的矩阵
//边缘检测
Canny(gray, gray, 100, 200, 3);
//二值化
threshold(gray, gray, 160, 255, THRESH_BINARY);
//遍历二值化后的图像
for (int i = 0; i < row; i++)
{
for (int j = 0; j < col; j++)
{
if (gray.ptr<uchar>(i)[j] != 0)
{
/*从0到360度遍历角度,得到一组关于距离夹角的离散点,即得到
一组关于经过当前点(i,j)按单位角度theat旋转得到的直线*/
for (int k = 0; k < 360 / theat; k += theat)
{
// k * CV_PI / 180 是极角 θ
double r = i * sin(k * CV_PI / 180) + j * cos(k * CV_PI / 180);
// 找哪个(θ,r) 最多
if (r >= 0)
{ // 直线到原点的距离必须大于0 获得在霍夫变换距离夹角坐标系下对应的Mat的行的下标
int r_subscript = r / rho;
// 经过该直线的点数加1
houghMat.at<float>(r_subscript, k) = houghMat.at<float>(r_subscript, k) + 1;
}
}
}
}
}
/*现在 houghMat 矩阵里面的数值N 是经过这点的线个数(极坐标下)
比如说 (行,列)对应 (p, θ) 对应的值就是 经过(p, θ)的线一共N条
经过直线的点数N 大于阈值, 则视为在原图中存在该直线 就要这条直线了 */
for (int i = 0; i < houghMat_rows; i++)
{
for (int j = 0; j < houghMat_cols; j++)
{
if (houghMat.ptr<float>(i)[j] > Threshold)
{
// line保存直线到原点的距离和直线到坐标原点的垂线和x轴的夹角 求p和θ
Vec2f line(i * rho, j * theat * CV_PI / 180);
lines.push_back(line);
}
}
}
}
本部分学习自https://zhuanlan.zhihu.com/p/203292567 (理论知识来自这位大神)
https://zhuanlan.zhihu.com/p/55877700 (极坐标)
http://t.csdn.cn/4PBMG (复现代码)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。