霍夫变换检测直线的原理及C/C++实现
- 霍夫变换原理概述
- 霍夫直线检测原理
- C/C++代码及细节提要
霍夫变换原理概述
在图像处理中,霍夫变换是经典算法之一,常被用来检测图像中的直线,圆等特征图形。本节将讨论霍夫变换的理论基础。
1、基础理论
假设有一个样本集合
{
(
x
i
,
y
j
)
}
\{(x_i,y_j)\}
{(xi,yj)}
我们想从这个样本集合中提取出符合一定条件的子集(称为目标集合)
{
(
x
i
,
y
j
)
∣
f
(
x
,
y
)
=
s
}
\{(x_i,y_j)|f(x,y)=s\}
{(xi,yj)∣f(x,y)=s}
这个子集中的任意一个元素均满足
f
(
x
,
y
)
=
s
f(x,y)=s
f(x,y)=s的条件,即集合中所有元素经
f
(
)
f()
f()运算后都能得到相同的结果
s
s
s,可以理解为这个集合中所有的元素都拥有相同的特征
s
s
s。
有了特征
s
s
s和转换函数
f
(
)
f()
f()后,从样本集合中提取出目标集合就变得简单了。
首先遍历样本集合,将样本元素代入转换函数,得到一个中间值
p
p
p
p
=
f
(
x
i
,
y
j
)
p=f(x_i,y_j)
p=f(xi,yj)
然后对比
p
p
p和
s
s
s,如果有
p
=
s
p=s
p=s
则将
(
x
i
,
y
j
)
(x_i,y_j)
(xi,yj)收入到目标集合中。
当完成样本集合的遍历后,统计目标集合中元素的个数,如果目标集合中元素个数超过一定数量(减小干扰)则可以判定为样本集合中存在要提取的目标集合。
2、拓展
上文的方法只能提取集合中符合
f
(
x
,
y
)
=
s
f(x,y)=s
f(x,y)=s这一个目标集合,但在实际应用中我们往往知道样本集合中一定有某(几)个特征集合,也知道它们的特征函数
f
(
)
f()
f()
{
(
x
i
,
y
j
)
∣
f
(
x
,
y
)
=
X
}
\{(x_i,y_j)|f(x,y)=X\}
{(xi,yj)∣f(x,y)=X}
我们希望的是能得到这些集合的特征参数
X
X
X,例如下面这张图片,已经知道它里面一定有一条直线,现在希望的是找到能表征这条直线的参数。
那么如何寻找特征参数
X
X
X?
可以建立一个所有
X
X
X可能取值的集合
{
X
1
,
X
2
.
.
.
X
n
}
\{X_1,X_2...X_n\}
{X1,X2...Xn}
再为这个集合中的每一个元素建立一个计分板
{
N
X
1
,
N
X
2
.
.
.
N
X
n
}
\{N_{X1},N_{X2}...N_{Xn}\}
{NX1,NX2...NXn}
然后遍历样本集合,将样本集合中的每一个元素代入特征函数得到一个特征值
f
(
x
i
,
y
j
)
=
X
m
f(x_i,y_j)=X_m
f(xi,yj)=Xm
X
m
∈
{
X
1
,
X
2
.
.
.
X
n
}
X_m\in\{X_1,X_2...X_n\}
Xm∈{X1,X2...Xn}
并让
X
m
X_m
Xm的计分加一,当结束了样本集合的遍历后,统计计分板,假如
N
X
k
N_{Xk}
NXk大于某个阈值,即可认为样本集合中存在满足特征函数
f
(
)
f()
f()的子集,且其特征值为
X
k
X_k
Xk。这样一来就能满足提取集合特征值的要求了。
霍夫直线检测原理
- 本节以下图为测试图片
1、原理描述
从上文论述中可以总结出霍夫变换检测直线的主要步骤。
- 获取样本集合,在图像中就是像素点的坐标集合,测试图片中线段颜色为黑色,所以第一步要得到测试图片中黑色像素的坐标集合。
- 寻找直线的特征函数,直线的方程形式有很多,例如常见的点斜式,但点斜式存在斜率无穷大的情况,所以一般选择直线的极坐标方程
p = x ∗ c o s ( θ ) + y ∗ s i n ( θ ) p=x*cos(\theta)+y*sin(\theta) p=x∗cos(θ)+y∗sin(θ)
θ 是 弧 度 \theta是弧度 θ是弧度
一组极坐标参数 ( p , θ ) (p,\theta) (p,θ)可以唯一的表示一条直线,如下图
- 建立计分板并初始化
- 遍历像素集合,将像素坐标 ( x , y ) (x,y) (x,y)代入极坐标方程中得到参数 ( p , θ ) (p,\theta) (p,θ)并投票计分
- 遍历计分板,找出分数大于某个阈值的 ( p , θ ) (p,\theta) (p,θ)对,结果即检测到的直线的参数,可使用这些参数去在数学概念上还原这些直线。
2、C/C++代码及细节提要
下面将展示一个简单的霍夫变换实现直线检测的算法,图片读取和显示使用了openCV3的接口,算法中使用了openCV的Mat结构,如有移植需要可自行更改。
程序中需要注意的地方:
- 为了方便遍历,使用角度 a n g l e angle angle而不是弧度 θ \theta θ,在做运算时需要转化
- 使用二维数组作为计分板,
p
p
p和
a
n
g
l
e
angle
angle作为索引,由于极角和极径有正有负,所以需要进行范围调整和对
p
p
p取绝对值
极 角 : [ 0 , 180 ] 极角:[0,180] 极角:[0,180]
极 径 : [ 0 , 2 ∗ l ] 极径:[0,2*l] 极径:[0,2∗l]
l 为 图 片 对 角 线 长 度 l为图片对角线长度 l为图片对角线长度
//直线参数
typedef struct hline_t
{
int p;//极径
int theta;//极角,角度
}hline;
void hough_line_v(Mat &img,int threshold, hline* lines,int *num)
{
int row, col;
int i,k;
//参数空间的参数极角angle(角度),极径p;
int angle,p;
//累加器
int **socboard;
int *buf;
int w, h;
w = img.cols;
h = img.rows;
int Size;
int offset;
//申请累加器空间并初始化
Size= w*w + h*h;
Size = 2*sqrt(Size)+100;
offset = Size / 2;
socboard = (int **)malloc(Size * sizeof(int*));
if (!socboard)
{
printf("mem err\n");
return;
}
for (i = 0; i < Size; i++)
{
socboard[i] = (int *)malloc(181 * sizeof(int));
if (socboard[i] == NULL)
{
printf("buf err\n");
return;
}
memset(socboard[i], 0, 181 * sizeof(int));
}
//遍历图像并投票
Vec3b src_data;
p = 0;
for (row = 0; row< img.rows; row++)
{
for (col = 0; col< img.cols; col++)
{
//获取像素点
src_data = img.at <Vec3b>(row, col);
//检测黑线
if (src_data[0] == 0 && src_data[1] == 0 && src_data[2] == 0)
{
for (angle = 0; angle < 181; angle++)
{
p = col * cos(angle * PI / 180.0) + row * sin(angle * PI / 180.0)+offset;
//错误处理
if (p <0)
{
printf("at (%d,%d),angle:%d,p:%d\n", col, row, angle, p);
printf("warrning!");
printf("size:%d\n", Size/2);
continue;
}
//投票计分
socboard[p][angle]++;
}
}
}
}
//遍历计分板,选出符合阈值条件的直线
int count = 0;
int Max = 0;
int kp, kt;
kp = 0;
kt = 0;
for (i = 0; i < Size; i++)//p
{
for (k = 0; k < 181; k++)//angle
{
if (socboard[i][k] > Max)
{
Max = socboard[i][k];
kp = i-offset;
kt = k;
}
if (socboard[i][k] >= threshold)
{
printf("count:%d\n",count);
lines[count].p = i-Size/2;
lines[count].theta = k;
count++;
}
}
}
*num = count;
//释放资源
for (int e = 0; e < Size; e++)
{
free(socboard[e]);
}
free(socboard);
}
- 运行结果
- 如有错误之处,欢迎指出修正