opencv的實用研究--分析輪廓並尋找邊界點
輪廓是圖像處理中非常常見的。對現實中的圖像進行采樣、色彩變化、灰度變化之后,能夠處理得到的是“輪廓”。它直接地反應你了需要分析對象的邊界特征。而對輪廓的分析,實際上也就是對原圖像特征的分析。
在Opencv中,已經實現了基礎的輪廓算法,但是相比較於比如halcon這樣的專業軟件,在輪廓處理這塊的功能還是比較缺乏的。這里就通過一個具體問題,說明自己的學習研究。不對之處歡迎批評。
P.S這里的輪廓處理相關函數,已經包涵在GOBase中,具體可以到公告中找Github.
一、問題提出
那么如果對於一個簡單的圖像,比如

已經獲得了最大物體的輪廓,比如
//灰度域變化 threshold(gray,gray,0,255,THRESH_BINARY_INV); GaussianBlur(gray,gray,Size(3,3),0,0); //尋找和繪制輪廓 VP bigestContour = FindBigestContour(gray); contours.push_back(bigestContour);

由於在opencv里面,輪廓是以
保存的,那
么如何獲得這個輪廓的四個頂點了?
-
vector<vector<point>>
嘗試直接打印輪廓中第一個點,那么的確是左上角

但是不具有通用性,在一些比較復雜的圖片上面效果不行,比如

那么也就是說,必須通過特征分析的方法獲得已經獲得的輪廓中點的特性,而opencv本身沒有提供相關功能。
二、直觀的解決
現在,對於“左上”和“右下”的兩個點,是比較好分析的。因為在所有的包含在輪廓中的點中,他們一個是x,y同時最小的,一個是x,y同時最大的。
比較復雜的是“左下”和"右上"兩個點,
因為他們的數值不是非常有特征,
比較容易產生混淆。這個時候,如果僅僅是通過x,y值來分析,即使是對於簡單圖像,也很難得到穩定的結果。
-
int itopleft =65535; int idownright =0; Point ptopleft; Point pdownright; Point pdownleft; for(int i=0;i<bigestContour.size();i++){ //左上 if(bigestContour[i].x + bigestContour[i].y <itopleft){ itopleft = bigestContour[i].x + bigestContour[i].y ; ptopleft = bigestContour[i]; } //右下 if(bigestContour[i].x+bigestContour[i].y>idownright){ idownright = bigestContour[i].x+bigestContour[i].y; pdownright = bigestContour[i]; } } int idownleft =65534; //對於左下的點來說,應該是所有y大於左上的點中,x最小的 for(int i=0;i<bigestContour.size();i++){ if(bigestContour[i].y>ptopleft.y){ if(bigestContour[i].x<idownleft){ idownleft = bigestContour[i].x; pdownleft = bigestContour[i]; } } } //繪制 circle(board,ptopleft,10,Scalar(255),5); circle(board,pdownright,10,Scalar(255),5); circle(board,pdownleft,10,Scalar(255),5);
三、利用模型來解決
那么,直觀的方法是不穩定的。這個時候,我想到在進行圖像處理的時候,有所謂“特征點”的說法。比較常見的比如harris
/shift/surf。那么我是否能夠通過分析輪廓圖像,找到輪廓圖像特征點的方法找到我需要的邊角了?
編碼實現:
///在board上尋找角點 ///// Detector parameters int blockSize = 2; int apertureSize = 3; double k = 0.04; int thresh = 1; /// Detecting corners board.convertTo(board,CV_32F); cornerHarris( board,dst,2,3,0.04); ///// Normalizing normalize( dst, dst_norm, 0, 255, NORM_MINMAX, CV_32FC1, Mat() ); convertScaleAbs( dst_norm, dst_norm_scaled ); ///// Drawing a circle around corners for( int j = 0; j < dst_norm.rows ; j++ ) { for( int i = 0; i < dst_norm.cols; i++ ) { if( (int) dst_norm.at<float>(j,i) > thresh ) { circle( dst_norm_scaled, Point( i, j ), 5, Scalar(0), 2, 8, 0 ); circle(src,Point( i, j ), 5, Scalar(255,0,0), -1, 8, 0 ); }
}
}
得到結果

NICE,在圖像中已經明顯的顯示出來了4個邊界點,再加上已經有的兩個點,得到結果不成問題。
四、問題進一步研究
但是這里其實是用了一個“投機取巧”的方法,那就是使用圖像處理的才使用的harris算法來分析輪廓。opencv默認實現的harris速度慢且會內存移除。用在這個簡單的例子里面看似可以,但是無法處理現實問題。所以就必須分析原理。
做圖像處理有一段時間了,我經常反思回憶,在圖像處理中,能夠穩定解決問題的,往往依靠的是“先驗知識,本質特征”;越是分析逼近圖像的本質特征,越能夠發現穩定的解決方法。比如對於輪廓的角來說,很容易想到處於邊角的點和兩邊的點肯定具有一定的關系,而這種關系具有特征性。
所以有目的地尋找論文,很快就有了成果:









對於我的研究來說,這篇論文兩個貢獻:一個是告知
首先要對圖像進行高斯模糊,這個是我之前沒有想到的。特別是對於現實世界中的輪廓,這種方法效果很好。因為邊角經過模糊,那么還是邊角,但毛刺經過模糊,能夠有效去除。
論文中的算法實現是比較簡單的,並且給出了簡化算法,直接編碼驗證:
//遍歷輪廓,求出所有支撐角度 int icount = bigestContour.size(); float fmax = -1;//用於保存局部最大值 int imax = -1; bool bstart = false; for (int i=0;i<bigestContour.size();i++){ Point2f pa = (Point2f)bigestContour[(i+icount-7)%icount]; Point2f pb = (Point2f)bigestContour[(i+icount+7)%icount]; Point2f pc = (Point2f)bigestContour[i]; //兩支撐點距離 float fa = getDistance(pa,pb); float fb = getDistance(pa,pc)+getDistance(pb,pc); float fang = fa/fb; float fsharp = 1-fang; if (fsharp>0.05){ bstart = true; if (fsharp>fmax){ fmax = fsharp; imax = i; } }else{ if (bstart){ circle(board,bigestContour[imax],10,Scalar(255),1); circle(src,bigestContour[imax],10,Scalar(255,255,255),1); imax = -1; fmax = -1; bstart = false; } } }
編碼過程中,相比較於原文,有兩處優化(原文中應該也提到了,但是沒有明說):一是通過取模,使得所有的輪廓點都參與運算;二是通過比較,取出角點的局部最大值。
實現效果,比較理想:

五、小結反思
1、掌握知識,如果不能歸納數學模型,並且編碼實現,不叫真正掌握;
2、分析研究,如果從簡單的情況開始,控制變量,往往能夠左右逢源。