lsd 特征点匹配代码_形状识别之直线检测(LSD)

本文介绍了使用LSD算法进行直线检测以实现形状识别的方法,对比了LSD与传统霍夫直线检测的区别。通过直线检测、聚类、筛选和交点计算,最终识别出图像中的四边形轮廓。详细阐述了LSD算法的使用、直线聚类的极坐标方法和直线筛选的特征计算,并提供了关键代码示例。
摘要由CSDN通过智能技术生成

形状识别之直线检测(LSD)

发布时间:2018-05-24 12:17,

浏览次数:885

, 标签:

LSD

转自一篇基于直线检测的形状识别方法,不同于霍夫直线检测。

原文网址:https://blog.csdn.net/liujiabin076/article/details/74917605

LSD官网(源码下载):http://www.ipol.im/pub/art/2012/gjmr-lsd/

LSD官网(在线测试):http://demo.ipol.im/demo/gjmr_line_segment_detector/

作者检测四边形的思路主要为:

* 直线检测

* 直线聚类

* 直线筛选

* 交点计算

* 交点排序启发点:使用区别于霍夫的直线检测LSD算法,通过聚类过滤干扰线段。

=================以下为转发网址原内容====================

形状识别中常见的即是矩形框的识别,识别的主要步骤通常是:图像二值化,查找轮廓,四边形轮廓筛选等。当识别的目标矩形有一条边被部分遮挡,如图1所示,传统的识别方法就不能达到识别的目的。

图1

在这里,提供一种识别的思路,仅供参考。识别的最终目标就是想识别出身份证的四条边,通过计算四条边的交点最后得到四边形的轮廓。主要涉及的问题有如下几点:

* 直线检测

* 直线聚类

* 直线筛选

* 交点计算

* 交点排序

1.直线检测

常规直线检测方法即是Hough。这里推荐使用一种比较新的直线检测算法LSD

算法的具体使用请参考网站提供的源码。

图2和图3分别是Hough直线检测与LSD直线检测的结果示意图。

对于LSD算法得到的结果,可以根据直线的长度进行初步的筛选,得到更好的检测结果,提高后期处理效率。如图4所示。

图2

图3

图4

2.直线聚类

由图4可以看出,身份证的每条边缘被分割成几段短线段,这里给出将每条边上的短线段聚为一类的方法。

在极坐标系下的一点(ρ,θ)(ρ,θ)即定义一条直线,其中ρρ表示极坐标原点到直线的距离,θθ为如图所示夹角。如图5。

图5

此时不难看出,身份证同一边上的线段应该具有相近的极坐标点。

具体做法是,先选取极坐标系的原点O为图像的重点(w/2,h/2)。建立笛卡尔坐标系x=u−w/2,y=h/2−vx=u−w/2,y=h/2−v;其中(u,v)

(u,v)是图像坐标系。极坐标系(ρ,θ)(ρ,θ)与笛卡尔坐标系(x,y)(x,y)的转换关系为ρ=xcos(θ)+ysin(θ)

ρ=xcos(θ)+ysin(θ)。因此,当已知一线段的两个端点(u1,v1),(u2,v2)(u1,v1),(u2,v2),即可求解出对应的(ρ,θ)

(ρ,θ)。具体角度的计算请参考直线检测之极坐标表示

代码如下:

//p[0] u1 p[1] v1 //p[2] u2 p[3] v2 Vec2d getPolarLine(Vec4d p ) { if(fabs(p[0

]-p[2]) < 1e-5 )//垂直直线 { if(p[0] > 0) return Vec2d(p[0],0); else return Vec2d(p[

0],CV_PI); } if(fabs(p[1]-p[3]) < 1e-5 ) //水平直线 { if(p[1] > 0) return Vec2d(p[1

],CV_PI/2); else return Vec2d(p[1],3*CV_PI/2); } float k = (p[1]-p[3])/(p[0]-p[2

]);float y_intercept = p[1] - k*p[0]; float theta; if( k < 0 && y_intercept > 0

) theta =atan(-1/k); else if( k > 0 && y_intercept > 0) theta = CV_PI + atan(-1

/k);else if( k< 0 && y_intercept < 0) theta = CV_PI + atan(-1/k); else if( k> 0

&& y_intercept <0) theta = 2*CV_PI +atan(-1/k); float _cos = cos(theta); float

_sin =sin(theta); float r = p[0]*_cos + p[1]*_sin; return Vec2d(r,theta); }

* 将图4中检测到的所有直线线段利用极坐标表示,然后进行分类,同类的直线分配相同的标签号。然后对相同标签号的线段对应的极坐标进行加权平均,即为对应直线。

算法如下:

// vector polarLines 是检测出的所有线段对应的极坐标表示 bool getIndexWithPolarLine(vector

& _index) { int polar_num = polarLines.size(); if(polar_num == 0) { return

false; } _index.clear(); _index.resize(polar_num); //初始化标签号 for (int i=0; i <

polar_num;i++) _index[i] = i;for (int i=0; i < polar_num-1 ;i++) { float

minTheta = CV_PI;float minR = 50; Vec2d polar1 = polarLines[i]; for (int j = i+1

; j < polar_num; j++) { Vec2d polar2 = polarLines[j];float dTheta = fabs(polar2[

1] - polar1[1]); float dR = fabs(polar2[0] - polar1[0]); if(dTheta < minTheta )

minTheta = dTheta;if(dR < minR) minR = dR; //同类直线角度误差不超过1.8°,距离误差不超过8% if

(dTheta <1.8*CV_PI/180 && dR < polar1[0]*0.08) _index[j] = _index[i]; } } return

true; }

由于身份证边缘长度是大于一定阈值的,此时,如果同类线段的长度和小于某阈值,则可以剔除掉该线段。

如图6红色线段为LSD检测结果,红色直线为线段对应极坐标表示的直线。

图6

3.直线筛选

由图6可以看出,图中不仅有身份证边缘的直线,同样存在其他干扰直线,并且背景环境越复杂,干扰的直线会越多。此时就需要对直线进行筛选。这里进行筛选的思路是,采集图6中所示红色线段两侧的图像数据,计算颜色特征H,S,V。针对图6,手上的颜色特征明显区别于身份证边缘的特征,很容易去除。数据获取如图7所示,图中红色和蓝色区域即是对应线段的采集样本区域。

图7

具体代码如下,输入是一条线段,输出是布尔类型,表示该线段是否符合要求。

// 直线两侧采样,计算特征:亮度与对比度,色彩等 // Vec6d data: // data[0] u1, data[1] v1 first line

point // data[2] u2, data[3] v2 second line point // data[4] line width //

data[5] line length // ksize 采样参数 bool lineSidesFeature(Mat _input,Vec6d data,

int ksize ) { //_input 输入的图像数据,为彩色图 Mat gray; if(_input.channels() == 3)

cvtColor(_input,gray,CV_BGR2GRAY);else _input.copyTo(gray); // Mat drawIm =

_input.clone(); float x1 = data[0]; float y1 = data[1]; float x2 = data[2];

float y2 = data[3]; //

line(_drawIm,Point2f(x1,y1),Point2f(x2,y2),Scalar(0,255,0)); // imshow("Sample

line",drawIm); // waitKey(10); //直线左右两侧的灰度值 vector left_side; vector

right_side;//灰度值和 int left_sum = 0; int right_sum = 0; //色调与饱和度 int left_H = 0;

float left_S = 0.0; int right_H = 0; float right_S = 0.0; //彩色像素个数 int

color_pix_num =0; //采样总数 int sample_num = 0; //垂直直线? int vertical = 0; //直线斜率 截距

float _k = 0; float _b = 0; //计算直线表达式 if(fabs(x1-x2) < 1e-2) // x = b; {

vertical =1; _b = x1; } else { _k = (y1-y2)/(x1-x2); //直线方程 kx + b = y; _b= y1

- _k*x1; }// cout<

1,采样的直线是水平的,对应图7中红色区域 // fabs(_k) < 1, 采样的直线是垂直的,对应图7中蓝色区域 int sample_line_type

=fabs(_k) > 1 ? 1 : 0; // cout<

//设定采样点的起点,取线段端点,并且是x值最小或者y值最小,依据sample_line_type 的值, //这样循环取下一个点时可以不断递增+1 float

u1,u2;int step = 1; // 这里step = 1,如果step = -1;那么起点又得是最大值。 if(vertical == 1) {

if(y1 < y2) { u1 = y1; u2 = y2; } else { u1 = y2; u2 = y1; } } else { if

(sample_line_type ==1) { if(y1 < y2) { u1 = y1; u2 = y2; } else { u1 = y2; u2 =

y1; } }else { if(x1 < x2) { u1 = x1; u2 = x2; } else { u1 = x2; u2 = x1; } } }

// cout <

从直线的一个端点开始进行采样,该端点要么离图像坐标u最近,要么离图像坐标v最近,步长为step //

得到采样点后,计算过该点垂直于直线的法线,法线上的点作为样本点。 // 在法线上采集的样本点个数为 2*ksize+ 1,

步进的方向依据直线的斜率决定是沿x方向还是y方向,步长为1。 for (float u = u1; u<= u2; u += step) { float v0

;float v1,v2; //采样直线的斜率与截距 float sk,sb; if(vertical == 1) { v0 = x1; } else { if

(sample_line_type ==1) { v0 = (u - _b)/_k; sk = -1/(1e-6 +_k); sb = u - sk*v0; }

else { v0 = _k*u + _b; sk = -1/(1e-6 +_k); sb = v0 - sk*u; } } v1 = v0 - ksize;

v2 = v0 + ksize;// cout<

line(_drawIm,Point2f(v1,u),Point2f(v2,u),Scalar(0,0,255)); } else { if

(sample_line_type ==1) { line(_drawIm,Point2f(v1,sk*v1 + sb),Point2f(v2,sk*v2 +

sb),Scalar(0,0,255)); } else

line(_drawIm,Point2f((v1-sb)/sk,v1),Point2f((v2-sb)/sk,v2),Scalar(255,0,0)); }

// sample_line_type = 1 ,点P0(v0,u)在直线上,垂直于该直线并过点P0,进行采样,按x方向步长为1进行步进,起点x =

v1,终点x =v2; // sample_line_tpye = 0,

点P0(u,v0)在直线上,垂直于该直线并过点P0,进行采样,按y方向步长为1进行步进, 起点y = v1, 终点y = v2。 for (float v =

v1; v <= v2; v +=1) { sample_num++; int x , y; if(vertical == 1) //垂直线段 { x = (

int)v; y = (int)u; } else { if(sample_line_type == 1) //“水平”采样,v按照x方向递增 { x = (

int)v; y = (int)(sk*v + sb); } else //“垂直”采样, v按照y方向递增 { x = (int)((v-sb)/sk);

y = (int)v; } } //这一句很重要。 if(x < 0 || x > gray.cols-1 || y < 0 || y > gray.rows-

1) continue; int nx = MAX(0,x); nx = min(nx,gray.cols-1); int ny = MAX(0,y); ny

= min(ny,gray.rows-1); int val = gray.at(ny,nx); Vec3b pixel =

_input.at(ny,nx);int b = pixel[0]; int g = pixel[1]; int r = pixel[2];

int _max = MAX(b,MAX(g,r)); int _min = MIN(b,MIN(g,r)); int C = _max - _min ;

float S = 0; int H = 0; if(C > 10) { S = (float)C/_max; int vr = _max == r ? -1

:0; int vg = _max == g ? -1 : 0; H = (vr & (g - b)) + (~vr & ((vg & (b - r + 2

* C)) + ((~vg) & (r - g +4 * C)))); H = (H * hdiv_table180[C] + (1 <<

(hsv_shift-1))) >> hsv_shift; H += H < 0 ? 180 : 0; //色度判断直线两边相似的颜色 if( S > 0.1

&& H >10 ) color_pix_num++; } if(vertical == 1) { if(nx > _b) {

right_side.push_back(val); right_sum += val; right_H += H; right_S += S; }else

{ left_side.push_back(val); left_sum += val; left_H += H; left_S += S; } }else {

float d = _k*nx + _b - ny; if(d > 0 ) { right_side.push_back(val); right_sum +=

val; right_H += H; right_S += S; }else { left_side.push_back(val); left_sum +=

val; left_H += H; left_S += S; } } }//v }//u int l_num = left_side.size(); int

r_num = right_side.size();// cout << l_num <

left_mean = (float) (left_sum)/l_num; float right_mean = (float) (right_sum)/

r_num;float left_H_mean = (float)(left_H)/l_num; float left_S_mean = (float

)(left_S)/l_num;float right_H_mean = (float)(right_H)/r_num; float right_S_mean

= (float)(right_S)/r_num; float left_var = 0, right_var = 0; for (int m = 0; m

< l_num; m++) { left_var += (left_side[m] - left_mean)*(left_side[m] -

left_mean); }if(l_num > 2) left_var = sqrtf(left_var)/(l_num-1); for (int n = 0

; n < r_num; n++) { right_var += (right_side[n] - right_mean)*(right_side[n] -

right_mean); }if(r_num > 2) right_var = sqrtf(right_var)/(r_num-1); cout<

<

<

+MAX(left_S_mean,right_S_mean))<

" "<

+MAX(left_H_mean,right_H_mean))<

<

imshow("Sample line",drawIm); // waitKey(0); return false; }

由于待测身份证的边缘邻域颜色特征是稳定的,可以作为初始经验值,当识别线段的颜色特征不符合经验值要求即可剔除掉,最后得到想要的边缘线段以及对应的极坐标表示直线。然而,有时候可能得到满足条件的直线比较多,此时可以考虑为每一类直线进行评分,然后根据得分排序,取出前4条得分最高的直线,大部分情况下都是所求边缘直线。具体情况可具体对待,此处不再展开。

4.交点计算

这里给出极坐标系下直线的求交点方法,这里主要注意两点:首先,两条直线不是平行的,其次,直线的交点在图像范围内。

Point2f polarLinesCorss(Vec2d l0, Vec2d l1,Size sz) { int w = sz.width; int h

= sz.height;float r0 = l0[0]; float theta0 = l0[1]; float _cos0 = cos(theta0);

float _sin0 = sin(theta0); float r1 = l1[0]; float theta1 = l1[1]; float _cos1 =

cos(theta1); float _sin1 = sin(theta1); if(fabs(_cos0*_sin1 - _sin0*_cos1) <

1e-5) //两条平行的直线 return Point2f(0,0); float y = (r0*_cos1 - r1*_cos0) /

(_sin0*_cos1 - _cos0*_sin1);float x = (r0*_sin1 - r1*_sin0) / (_cos0*_sin1 -

_cos1*_sin0);if(x > - w/2 && x < w/2 && y > -h/2 && y < h/2) return Point2f(x+w/

2,h/2-y); else return Point2f(0,0); }

*

5.交点排序

得到四个交点,此时点的顺序可能是错乱的,需要对点进行排序,起点选择为左上角的点,并按逆时针方向对点排序。方法如下:

// 以左上角点为起点逆时针排序 static void sortPoints(vector & points ) { vector

minXpoints; vector maxXpoints; minXpoints.push_back(points[0

]); minXpoints.push_back(points[1]); maxXpoints.push_back(points[2]);

maxXpoints.push_back(points[3]); for(int i =0; i< 2; i++) { float x =

minXpoints[i].x;if(x > maxXpoints[0].x) { if(x >= maxXpoints[1].x) {

(maxXpoints[0].x > maxXpoints[1].x ) ? swap(maxXpoints[1],minXpoints[i]) :

swap(maxXpoints[0],minXpoints[i]); continue; } if(x < maxXpoints[1].x) {

swap(maxXpoints[0],minXpoints[i]); continue; } } if(x <= maxXpoints[0].x) if(x

> maxXpoints[1].x ) { swap(minXpoints[i], maxXpoints[1]); } } if(minXpoints[0

].y > minXpoints[1].y) { points[0] = minXpoints[1]; points[1] = minXpoints[0]; }

else { points[0] = minXpoints[0]; points[1] = minXpoints[1]; } if(maxXpoints[0

].y > maxXpoints[1].y) { points[2] = maxXpoints[0]; points[3] = maxXpoints[1]; }

else { points[2] = maxXpoints[1]; points[3] = maxXpoints[0]; } }

*

最后,检测结果如图8所示。

图8

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值