在图像处理中,霍夫变换是经典算法之一,常被用来检测图像中的直线,圆等特征图形。本节将讨论霍夫变换的理论基础。
假设有一个样本集合
{ ( 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)收入到目标集合中。
当完成样本集合的遍历后,统计目标集合中元素的个数,如果目标集合中元素个数超过一定数量(减小干扰)则可以判定为样本集合中存在要提取的目标集合。
上文的方法只能提取集合中符合 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。这样一来就能满足提取集合特征值的要求了。
从上文论述中可以总结出霍夫变换检测直线的主要步骤。
下面将展示一个简单的霍夫变换实现直线检测的算法,图片读取和显示使用了openCV3的接口,算法中使用了openCV的Mat结构,如有移植需要可自行更改。
程序中需要注意的地方:
//直线参数
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 (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);
}