一、要求
首先要明確一下本文到底是要干什么。本文要完成基于視覺的交通標(biāo)識(shí)牌檢測(cè)與識(shí)別,說白了,就兩個(gè)事:1)在一張圖中找到交通標(biāo)識(shí)牌在哪里(檢測(cè));2)認(rèn)清楚這個(gè)標(biāo)識(shí)牌是啥,表達(dá)的什么意思(識(shí)別)。那么最后得到的結(jié)果預(yù)覽如下:
二、使用數(shù)據(jù)
交通標(biāo)識(shí)牌種類數(shù)不勝數(shù),我國的交通標(biāo)志一共有一百余種,按類別可分為黃底黑邊的警告標(biāo)志、白底紅圈的禁令標(biāo)志、藍(lán)底白字的指示標(biāo)志,形狀上以三角形、圓形和矩形為主。本文主要是為了介紹一下交通標(biāo)識(shí)牌的識(shí)別流程和一些主要方法的實(shí)現(xiàn),為了簡(jiǎn)化工作,本文挑選了以下五類交通標(biāo)識(shí)牌。
可以看出來,博主用心良苦,選擇的交通標(biāo)識(shí)牌具有很清楚的特征:1)顏色上,這五類交通標(biāo)識(shí)牌的外邊框都是紅色的;2)形狀上,標(biāo)識(shí)牌都是標(biāo)準(zhǔn)的圓形。這事實(shí)上也表明了,交通標(biāo)識(shí)牌具有著鮮明的特征,故無論是人眼還是機(jī)器,都較易識(shí)別。(其他種類的交通標(biāo)志牌也是一樣,利用形狀和顏色特征來處理)
三、使用方法
在我看來,目前處理交通標(biāo)牌識(shí)別的主要有兩種方法,1)傳統(tǒng)的圖像處理+機(jī)器學(xué)習(xí)辦法;2)最近很火的深度學(xué)習(xí)。那么本文采用的是前者,后者后續(xù)再進(jìn)行介紹。
1>檢測(cè):顏色和形狀。
交通標(biāo)志牌為了起到其警示作用,在顏色和形狀上都有著易區(qū)分性,如本文所討論的五類標(biāo)志牌,顏色特征為外框均為鮮艷的紅色;形狀特征為均為圓形。于是,檢測(cè)的思路如下,最終得到了圓形部分的交通標(biāo)牌:
接下來,主要分為顏色分割和形狀檢測(cè)兩部分進(jìn)行討論:
基于顏色分割的圖像二值化處理:
最直觀、簡(jiǎn)單的是利用RGB顏色空間來描述圖像的色彩情況,但是,RGB色彩空間極易受到光線情況的影響,魯棒性并不是很好,所以在相關(guān)論文中,你會(huì)發(fā)現(xiàn),很少有人直接使用RGB色彩空間進(jìn)行色彩分割。而實(shí)際上,本人拿有限的樣本和測(cè)試集進(jìn)行測(cè)試,RGB色彩分割效果在圖像成像質(zhì)量較理想的時(shí)候效果極佳,但是的確容易受到干擾。本文此處選擇了HSI色彩空間模型進(jìn)行色彩分割。先來點(diǎn)理論知識(shí):
色調(diào)H(Hue):與光波的波長(zhǎng)有關(guān),它表示人的感官對(duì)不同顏色的感受,如紅色、綠色、藍(lán)色等,它也可表示一定范圍的顏色,如暖色、冷色等。
飽和度S(Saturation):表示顏色的純度,純光譜色是完全飽和的,加入白光會(huì)稀釋飽和度。飽和度越大,顏色看起來就會(huì)越鮮艷,反之亦然。
亮度I(Intensity):對(duì)應(yīng)成像亮度和圖像灰度,是顏色的明亮程度。
從理論上看,HSI色彩空間將飽和度和亮度信息獨(dú)立了出來,這樣一定程度上就降低了光線帶來的影響。聽上去很有道理,但是,實(shí)際上,這也僅僅是一定程度上降低了亮度和色彩的耦合關(guān)系,并不是完全地進(jìn)行了解耦,所以,效果會(huì)有提升,但是很難帶來質(zhì)的改變(這是筆者自己的體驗(yàn),也許是筆者能力不足,實(shí)現(xiàn)得不是很理想)
那么從RGB色彩空間轉(zhuǎn)換到HSI空間的轉(zhuǎn)換公式如下:
函數(shù)RGB2HSI是將RGB色彩空間轉(zhuǎn)換到HSI色彩空間,其轉(zhuǎn)換的過程參照式(2.2),最后將飽和度S和強(qiáng)度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<=0.01)??
h=0;
}
hue=(int)(h*180/PI);
saturation=(int)(s*100);
intensity=(int)(i*100);
}
在得到HSI空間的基礎(chǔ)上,分割出紅色像素,事實(shí)上這個(gè)閾值最好時(shí)自己調(diào)出來,無論是基于哪個(gè)色彩空間,網(wǎng)上的代碼或者論文中的數(shù)值都是個(gè)參考,自己調(diào)出來的才靠譜嘛,代碼如下:
Cpp代碼
//得到圖像參數(shù)
intwidth=src.cols;//圖像寬度
intheight=src.rows;//圖像高度
//色彩分割
doubleB=0.0,G=0.0,R=0.0,H=0.0,S=0.0,I=0.0;
MatMat_rgb=Mat::zeros(src.size(),CV_8UC1);
intx,y,px,py;//循環(huán)
for(y=0;y
{
for(x=0;x
{
//獲取BGR值
B=src.at(y,x)[0];
G=src.at(y,x)[1];
R=src.at(y,x)[2];
RGB2HSV(R,G,B,H,S,I);
//紅色:337-360
if((H>=337&&H<=360||H>=0&&H<=10)&&??
S>=12&&S<=100&&V>20&&V<99)??
{
Mat_rgb.at(y,x)=255;//分割出紅色
}
}
}
分割效果可見如下組圖,由近至遠(yuǎn):
注意:有一個(gè)很嚴(yán)肅的問題我這里沒有提,那就是圖像預(yù)處理!做圖像處理的很重要的一個(gè)步驟就是圖像預(yù)處理,預(yù)處理做好了,后面的問題復(fù)雜度也就降低了許多。實(shí)際上,用顏色分割來二值化圖像也可以看作一種預(yù)處理。那么顏色分割之前有不有必要做圖像預(yù)處理呢?是有的。舉個(gè)例子,我那我的MATE8在學(xué)校里拍了一張照,然后使用手機(jī)相機(jī)自帶的功能,調(diào)整其色彩飽和度,亮度等,得到以下兩種圖片:
相機(jī)拍的原圖
手機(jī)調(diào)整飽和度、亮度后
這兩種圖片,顯然右邊的將更有利于顏色分割!(不信可以試試哦)。本文主要以介紹交通標(biāo)牌的主要流程為主,預(yù)處理的方法包括直方圖均衡化、白平衡、亮度調(diào)節(jié)等等這些就不仔細(xì)糾結(jié)了,但是,不代表這部分不重要,圖像預(yù)處理往往一定程度上決定了最后的效果。
基于形狀(圓形)檢測(cè)的ROI提取
在進(jìn)行顏色分割之后,得到的只是一個(gè)粗略的交通標(biāo)志牌ROI區(qū)域, 還會(huì)留下一些噪聲以及一些和目標(biāo)區(qū)域面積相當(dāng)或者比目標(biāo)面積略大的區(qū)域,這時(shí)候就還需要進(jìn)行一些圖像預(yù)處理,為準(zhǔn)確檢測(cè)交通標(biāo)志牌打下堅(jiān)實(shí)基礎(chǔ)。由于交通標(biāo)志最明顯的特征是其顏色和形狀,在用顏色分割之后,我們可以通過形狀特征來去除其余的干擾。對(duì)于本文的研究對(duì)象而言,交通標(biāo)志牌的形狀為圓形,可以采用經(jīng)典的Hough變換進(jìn)行圓檢測(cè),該方法準(zhǔn)確性高,但是計(jì)算量大,耗時(shí)且占用較大內(nèi)存;也可以采用圓度的方法來提取圓形,該方法原理簡(jiǎn)單,計(jì)算量小,準(zhǔn)確率高。綜合考慮,本文使用基于圓度的圓檢測(cè)算法。大概流程如下,后文還會(huì)詳細(xì)介紹:
圖有點(diǎn)不太清楚,下文中對(duì)于關(guān)鍵的部分會(huì)再次給出效果圖。
中值濾波,這個(gè)沒啥好說的,圖上效果不是很明顯,但是實(shí)際上可以一定程度上濾掉單個(gè)噪點(diǎn),對(duì)得到準(zhǔn)確的結(jié)果會(huì)有一定的幫助;
形態(tài)學(xué)處理,最后我們的目的是要得到一個(gè)封閉的區(qū)域,所以,顏色分割后的結(jié)果很可能不會(huì)是比較理想封閉的圓形,那么選用的3×3腐蝕模板,7×7膨脹模板,這樣檢測(cè)到的圓形將基本不會(huì)產(chǎn)生缺口,保證是一個(gè)封閉的形狀。
圖像填充,有了上述步驟得到的封閉圓形,我們接下來就可以填充封閉圖形了(這里你可能會(huì)問,為啥要這樣做。實(shí)際上直接進(jìn)行Hough圓檢測(cè)可以得到ROI結(jié)果,但是本文是換了一個(gè)思路,使用圓度來判斷圓形,所以算法需要一個(gè)實(shí)心區(qū)域),代碼如下:
Cpp代碼
voidfillHole(constMatsrcBw,Mat&dstBw)
{
Sizem_Size=srcBw.size();
MatTemp=Mat::zeros(m_Size.height+2,m_Size.width+2,srcBw.type());//延展圖像
srcBw.copyTo(Temp(Range(1,m_Size.height+1),Range(1,m_Size.width+1)));
cv::floodFill(Temp,Point(0,0),Scalar(255));//填充區(qū)域
MatcutImg;//裁剪延展的圖像
Temp(Range(1,m_Size.height+1),Range(1,m_Size.width+1)).copyTo(cutImg);
dstBw=srcBw|(~cutImg);
}
輪廓檢測(cè),初步篩選ROI,要想使用基于圓度的圓檢測(cè)算法,則需要從圖像中提取初步的ROI來進(jìn)行篩選。這里使用輪廓檢測(cè)法來檢測(cè)圖片中的ROI區(qū)域。可以看到,一些細(xì)小的噪聲也被檢測(cè)進(jìn)來。
所以,本文先通過對(duì)檢測(cè)區(qū)域的寬高比、面積大小進(jìn)行限制,篩選出有效的檢測(cè)區(qū)域,經(jīng)過實(shí)驗(yàn),可以確定寬高比限制在0.5-2之間,面積最小值設(shè)定為400,可以進(jìn)一步得到下圖的檢測(cè)效果,可以看到,此時(shí)小面積的噪聲已經(jīng)被排除。
代碼如下:
Cpp代碼
//找輪廓
vector>contours;
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());
vector
//得到輪廓矩形框
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];
//首先進(jìn)行一定的限制,篩選出區(qū)域
//高寬比限制
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(dConArea<400)//ROI?區(qū)域面積限制??
continue;
if(ratio>2||ratio<0.5)//ROI?區(qū)域?qū)捀弑认拗??
continue;
//檢測(cè)到了!
Scalarcolor=Scalar(rng.uniform(0,255),rng.uniform(0,255),rng.uniform(0,255));
//繪制輪廓和檢測(cè)到的輪廓外接矩形
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);
}
圓度算法檢測(cè),實(shí)際上這是利用了非常簡(jiǎn)單的數(shù)學(xué)約束,來對(duì)檢測(cè)到的區(qū)域進(jìn)行圓形驗(yàn)證。圓度定義如下:
其中,S為圓的面積,L為圓的周長(zhǎng),C為圓度。圓度值越接近1,則表示該圖形與圓形的契合程度越高。經(jīng)過大量的實(shí)驗(yàn),可以得出圓度大于0.5時(shí),即 4 . 0 ? C 時(shí),可以篩選出巨大部分的圓形。
ROI區(qū)域無效像素面積約束,這是進(jìn)一步確定篩選后的ROI區(qū)域是目標(biāo)圓形區(qū)域。該約束條件是基于ROI區(qū)域中圓形的缺失面積而得到的。前文中得到的ROI區(qū)域是包含圓形交通標(biāo)志牌的矩形區(qū)域,如下圖所示,可以將整個(gè)ROI區(qū)域分成1、2、3、4四塊,其中紅色部分為交通標(biāo)志牌,灰色部分為ROI區(qū)域中的無效像素。可以直觀地看到,1、2、3、4四塊的無效像素滿足一定的數(shù)學(xué)關(guān)系,
有如下約束
圓度代碼即為一個(gè)約束條件,對(duì)面?zhèn)€輪廓檢測(cè)得到的ROI進(jìn)行驗(yàn)證,無效像素面積約束則代碼如下:
Cpp代碼
boolisCircle(constMatsrcBw,Mat&mytemp)//(待改進(jìn))
{//輸入的是一個(gè)灰度圖像
Mattemp=Mat::zeros(srcBw.size(),CV_8UC1);;
booliscircle=false;
//獲得srcBw信息
intw=srcBw.cols;
inth=srcBw.rows;
intcount1=0;//各部分的缺失像素計(jì)數(shù)器
intcount2=0;
intcount3=0;
intcount4=0;
//將srcBw平均分成四份,進(jìn)行訪問缺失的像素個(gè)數(shù)、所占比重
//先訪問左上
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<"result:?"?<
<","?<
//限定每個(gè)比率的差值范圍
if((c1>0.037&&c1<0.12)&&(c2>0.037&&c2<0.12)&&(c2>0.037&&c2<0.12)&&(c2>0.037
&&c2<0.12))??
{
//限制差值,差值比較容錯(cuò),相鄰塊之間差值相近,如左上=右上&&左下=右下或左上=左下&&右上=右下
if((abs(c1-c2)<0.04&&abs(c3-c4)<0.04)||(abs(c1-c3)<0.04&&abs(c2-c4)<0.04))??
{
iscircle=true;
}
}
returniscircle;
}
利用輪廓檢測(cè)、圓度約束和無效面積約束,可以測(cè)試得到如下效果圖,
最后,目標(biāo)區(qū)域提取的效果如下:
2>識(shí)別:SVM分類。
有了上文提取ROI的基礎(chǔ),分類過程實(shí)際上和我之前寫過的箭頭分類如出一轍。
圖像預(yù)處理,首先將無效像素全部去除,只留下圓形ROI有效區(qū)域
然后進(jìn)行二值化處理,二值化后的圖像特征更為清晰
可以選擇所有像素作為特征,當(dāng)然更科學(xué)的是Hu不變矩、Zernike不變矩、二者混合矩等特征。關(guān)于Hu、Zernike特征的代碼網(wǎng)上比比皆是,這里僅推薦一個(gè)作為參考。本文為了簡(jiǎn)單實(shí)現(xiàn)框架,拿全部像素特征進(jìn)行訓(xùn)練。準(zhǔn)備好樣本和測(cè)試集,并給這五類交通標(biāo)牌設(shè)置標(biāo)簽“stop”,“20t”,“car forbidden”,“5”,“stop2”
SVM代碼框架如下。
SVM訓(xùn)練
Cpp代碼
//*********************SVM訓(xùn)練部分***********************
//準(zhǔn)備開始訓(xùn)練
CvSVMclassifier;
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訓(xùn)練,線性核上述參數(shù)C起作用
SVM保存
Cpp代碼
classifier.save("model180.txt");
SVM讀取
Cpp代碼
//這里載入分類器,方便直接訓(xùn)練
CvSVMclassifier;
classifier.load("model180.txt");
SVM預(yù)測(cè)
Cpp代碼
for(inti=0;i
{
intresult=(int)classifier.predict(testdata[i]);
std::cout<<"測(cè)試樣本"<"的測(cè)試結(jié)果為:"??
<"?"?<
}
最后得到了文中開始展示的效果。
本文完整代碼和數(shù)據(jù),已托管在Github上https://github.com/lps683/TrafficsSignDetection。這些東西也許在高手看來不值一提,但是,若能給一部分人帶來一些哪怕一點(diǎn)點(diǎn)收獲,那么花這么多功夫?qū)戇@篇文章也不算白費(fèi)。
-
圖像采集
+關(guān)注
關(guān)注
2文章
301瀏覽量
41307 -
智能汽車
+關(guān)注
關(guān)注
30文章
2882瀏覽量
107438 -
交通標(biāo)志識(shí)別
+關(guān)注
關(guān)注
0文章
4瀏覽量
3311
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論