一、要求
首先要明确一下本文到底是要干什么。本文要完成基于视觉的交通标识牌检测与识别,说白了,就两个事:1)在一张图中找到交通标识牌在哪里(检测);2)认清楚这个标识牌是啥,表达的什么意思(识别)。那么最后得到的结果预览如下:
二、使用数据
交通标识牌种类数不胜数,我国的交通标志一共有一百余种,按类别可分为黄底黑边的警告标志、白底红圈的禁令标志、蓝底白字的指示标志,形状上以三角形、圆形和矩形为主。本文主要是为了介绍一下交通标识牌的识别流程和一些主要方法的实现,为了简化工作,本文挑选了以下五类交通标识牌。
可以看出来,博主用心良苦,选择的交通标识牌具有很清楚的特征:1)颜色上,这五类交通标识牌的外边框都是红色的;2)形状上,标识牌都是标准的圆形。这事实上也表明了,交通标识牌具有着鲜明的特征,故无论是人眼还是机器,都较易识别。(其他种类的交通标志牌也是一样,利用形状和颜色特征来处理)
三、使用方法
在我看来,目前处理交通标牌识别的主要有两种方法,1)传统的图像处理+机器学习办法;2)最近很火的深度学习。那么本文采用的是前者,后者后续再进行介绍。
1>检测:颜色和形状。
交通标志牌为了起到其警示作用,在颜色和形状上都有着易区分性,如本文所讨论的五类标志牌,颜色特征为外框均为鲜艳的红色;形状特征为均为圆形。于是,检测的思路如下,最终得到了圆形部分的交通标牌:
接下来,主要分为颜色分割和形状检测两部分进行讨论:
基于颜色分割的图像二值化处理:
最直观、简单的是利用rgb颜色空间来描述图像的色彩情况,但是,rgb色彩空间极易受到光线情况的影响,鲁棒性并不是很好,所以在相关论文中,你会发现,很少有人直接使用rgb色彩空间进行色彩分割。而实际上,本人拿有限的样本和测试集进行测试,rgb色彩分割效果在图像成像质量较理想的时候效果极佳,但是的确容易受到干扰。本文此处选择了hsi色彩空间模型进行色彩分割。先来点理论知识:
色调h(hue):与光波的波长有关,它表示人的感官对不同颜色的感受,如红色、绿色、蓝色等,它也可表示一定范围的颜色,如暖色、冷色等。
饱和度s(saturation):表示颜色的纯度,纯光谱色是完全饱和的,加入白光会稀释饱和度。饱和度越大,颜色看起来就会越鲜艳,反之亦然。
亮度i(intensity):对应成像亮度和图像灰度,是颜色的明亮程度。
从理论上看,hsi色彩空间将饱和度和亮度信息独立了出来,这样一定程度上就降低了光线带来的影响。听上去很有道理,但是,实际上,这也仅仅是一定程度上降低了亮度和色彩的耦合关系,并不是完全地进行了解耦,所以,效果会有提升,但是很难带来质的改变(这是笔者自己的体验,也许是笔者能力不足,实现得不是很理想)
那么从rgb色彩空间转换到hsi空间的转换公式如下:
函数rgb2hsi是将rgb色彩空间转换到hsi色彩空间,其转换的过程参照式(2.2),最后将饱和度s和强度i均放大100倍,便于操作。 最后得到的h、 s、 i的取值范围分别为[0,360]、 [0,100]、 [0,100]。
cpp代码
voidrgb2hsv(doublered,doublegreen,doubleblue,double&hue,double&saturation,
double&intensity)
{
doubler,g,b;
doubleh,s,i;
doublesum;
doubleminrgb,maxrgb;
doubletheta;
r=red/255.0;
g=green/255.0;
b=blue/255.0;
minrgb=((r
minrgb=(minrgb
maxrgb=((r>g)?(r):(g));
maxrgb=(maxrgb>b)?(maxrgb):(b);
sum=r+g+b;
i=sum/3.0;
if(i<0.001||maxrgb-minrgb<0.001)
{
h=0.0;
s=0.0;
}
else
{
s=1.0-3.0*minrgb/sum;
theta=sqrt((r-g)*(r-g)+(r-b)*(g-b));
theta=acos((r-g+r-b)*0.5/theta);
if(b<=g)
h=theta;
else
h=2*pi-theta;
if(s=337&&h=0&&h=12&&s20&&vcontours;
vectorhierarchy;
findcontours(mat_rgb,contours,hierarchy,cv_retr_external,
cv_chain_approx_simple,point(0,0));
///多边形逼近轮廓+获取矩形和圆形边界框
vector>contours_poly(contours.size());
vectorboundrect(contours.size());
vectorcenter(contours.size());
vectorradius(contours.size());
//得到轮廓矩形框
for(inti=0;i
{
approxpolydp(mat(contours[i]),contours_poly[i],3,true);
boundrect[i]=boundingrect(mat(contours_poly[i]));
minenclosingcircle(contours_poly[i],center[i],radius[i]);
}
///画多边形轮廓+包围的矩形框
matdrawing=mat::zeros(mat_rgb.size(),cv_8uc3);
for(inti=0;i
{
rectrect=boundrect[i];
//首先进行一定的限制,筛选出区域
//高宽比限制
floatratio=(float)rect.width/(float)rect.height;
//轮廓面积
floatarea=(float)rect.width*(float)rect.height;
floatdconarea=(float)contourarea(contours[i]);
floatdconlen=(float)arclength(contours[i],1);
if(dconarea2||ratio<0.5)//roi区域宽高比限制
continue;
//检测到了!
scalarcolor=scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));
//绘制轮廓和检测到的轮廓外接矩形
drawcontours(drawing,contours_poly,i,color,1,8,vector(),0,point());
rectangle(drawing,boundrect[i].tl(),boundrect[i].br(),color,2,8,0);
rectangle(src,boundrect[i].tl(),boundrect[i].br(),color,2,8,0);
}
圆度算法检测,实际上这是利用了非常简单的数学约束,来对检测到的区域进行圆形验证。圆度定义如下:
其中,s为圆的面积,l为圆的周长,c为圆度。圆度值越接近1,则表示该图形与圆形的契合程度越高。经过大量的实验,可以得出圆度大于0.5时,即 4 . 0 c 时,可以筛选出巨大部分的圆形。
roi区域无效像素面积约束,这是进一步确定筛选后的roi区域是目标圆形区域。该约束条件是基于roi区域中圆形的缺失面积而得到的。前文中得到的roi区域是包含圆形交通标志牌的矩形区域,如下图所示,可以将整个roi区域分成1、2、3、4四块,其中红色部分为交通标志牌,灰色部分为roi区域中的无效像素。可以直观地看到,1、2、3、4四块的无效像素满足一定的数学关系,
有如下约束
圆度代码即为一个约束条件,对面个轮廓检测得到的roi进行验证,无效像素面积约束则代码如下:
cpp代码
booliscircle(constmatsrcbw,mat&mytemp)//(待改进)
{//输入的是一个灰度图像
mattemp=mat::zeros(srcbw.size(),cv_8uc1);;
booliscircle=false;
//获得srcbw信息
intw=srcbw.cols;
inth=srcbw.rows;
intcount1=0;//各部分的缺失像素计数器
intcount2=0;
intcount3=0;
intcount4=0;
//将srcbw平均分成四份,进行访问缺失的像素个数、所占比重
//先访问左上
for(inti=0;i
{
for(intj=0;j
{
if(srcbw.at(i,j)==0)
{
temp.at(i,j)=255;
mytemp.at(i,j)=255;
count1++;
}
}
}
//右上
for(inti=0;i
{
for(intj=w/2-1;j
{
if(srcbw.at(i,j)==0)
{
temp.at(i,j)=255;
mytemp.at(i,j)=255;
count2++;
}
}
}
//左下
for(inti=h/2-1;i
{
for(intj=0;j
{
if(srcbw.at(i,j)==0)
{
temp.at(i,j)=255;
mytemp.at(i,j)=255;
count3++;
}
}
}
//右下
for(inti=h/2-1;i
{
for(intj=w/2-1;j
{
if(srcbw.at(i,j)==0)
{
temp.at(i,j)=255;
mytemp.at(i,j)=255;
count4++;
}
}
}
floatc1=(float)count1/(float)(w*h);//左上
floatc2=(float)count2/(float)(w*h);//右上
floatc3=(float)count3/(float)(w*h);//左下
floatc4=(float)count4/(float)(w*h);//右下
cout<
<<,<
&&c2<0.12))
{
//限制差值,差值比较容错,相邻块之间差值相近,如左上=右上&&左下=右下或左上=左下&&右上=右下
if((abs(c1-c2)<0.04&&abs(c3-c4)<0.04)||(abs(c1-c3)<0.04&&abs(c2-c4)识别:svm分类。
有了上文提取roi的基础,分类过程实际上和我之前写过的箭头分类如出一辙。
图像预处理,首先将无效像素全部去除,只留下圆形roi有效区域
然后进行二值化处理,二值化后的图像特征更为清晰
可以选择所有像素作为特征,当然更科学的是hu不变矩、zernike不变矩、二者混合矩等特征。关于hu、zernike特征的代码网上比比皆是,这里仅推荐一个作为参考。本文为了简单实现框架,拿全部像素特征进行训练。准备好样本和测试集,并给这五类交通标牌设置标签“stop”,“20t”,“car forbidden”,“5”,“stop2”
svm代码框架如下。
svm训练
cpp代码
//*********************svm训练部分***********************
//准备开始训练
cvsvmclassifier;
cvsvmparamssvm_params;
svm_params.kernel_type=cvsvm::linear;//使用rbf分类非线性问题
svm_params.svm_type=cvsvm::c_svc;
svm_params.degree=0;
svm_params.gamma=0.01;
svm_params.term_crit=cvtermcriteria(cv_termcrit_iter,1000,flt_epsilon);
svm_params.c=100;
svm_params.coef0=0;
svm_params.nu=0;
svm_params.p=0.005;
classifier.train(train,labels,mat(),mat(),svm_params);//svm训练,线性核上述参数c起作用
svm保存
cpp代码
classifier.save(model180.txt);
svm读取
cpp代码
//这里载入分类器,方便直接训练
cvsvmclassifier;
classifier.load(model180.txt);
svm预测
cpp代码
for(inti=0;i
{
intresult=(int)classifier.predict(testdata[i]);
std::cout<<测试样本<的测试结果为:
<<
}
最后得到了文中开始展示的效果。
本文完整代码和数据,已托管在github上https://github.com/lps683/trafficssigndetection。这些东西也许在高手看来不值一提,但是,若能给一部分人带来一些哪怕一点点收获,那么花这么多功夫写这篇文章也不算白费。
锂电设备领域4点明显变化
区块链技术可能会推动自美国独立战争以来最重大的社会变革
电力线宽带技术(BPL)工作原理
希玛红外线测温仪使用说明书及注意事项
利用数字货币洗钱?保证数字货币交易可追踪
基于视觉的交通标识牌检测与识别
杜比音效到底是什么
锌锰电池型号命名与标识
叠层压电振动片:看上去很平,摸起来凹凸不平?
大模型开源开放评测体系司南正式发布
PCB
小米宣布一键关闭MIUI所有系统工具广告的开关
FTDI 再宣布利用Indiegogo , 对全球工程师再推出一套新的 Arduino 相关产品块
最强科普!深度解析华为云盘古大模型
区块链与监管怎样合体
我国电网柔性直流能量可用率达到97.9%!已是国际领先水平!
隆基为全球客户提供全生命周期的专业化和数字化解决方案
未来两年LED光引擎快速增长 频闪耐高压难题仍待解决
【工程师笔记】利用滤波电容与电感抑制辐射EMI --- 特性分析与设计方法
苹果13.3寸笔记本尺寸