1. 程式人生 > >【4Opencv】如何識別出輪廓準確的長和寬

【4Opencv】如何識別出輪廓準確的長和寬

進一步 scalar mono 麻煩 argv key rotate per 正是

問題來源:實際項目中,需要給出識別輪廓的長度和寬度。初步分析:技術分享圖片輪廓分析的例程為:int main( int argc, char** argv )
{
//read the image
Mat img = imread("e:/sandbox/leaf.jpg");
Mat bw;
bool dRet;
//resize
pyrDown(img,img);
pyrDown(img,img);

cvtColor(img, bw, COLOR_BGR2GRAY);
//morphology operation
threshold(bw, bw, 150, 255
, CV_THRESH_BINARY);
//bitwise_not(bw,bw);
//find and draw contours
vector<vector<Point> > contours;
vector<Vec4i> hierarchy;
findContours(bw, contours, hierarchy, CV_RETR_LIST, CV_CHAIN_APPROX_NONE);
for (int i = 0;i<contours.size();i++)
{
RotatedRect minRect =
minAreaRect( Mat(contours[i]) );
Point2f rect_points[4];
minRect.points( rect_points );
for( int j = 0; j < 4; j++ )
line( img, rect_points[j], rect_points[(j+1)%4],Scalar(255,255,0),2);
}
imshow("img",img);
waitKey();
return 0;
}

得到結果:技術分享圖片

對於這樣 的輪廓分析,標明出來的1和2明顯是錯誤的。但是除了minAreaRect
之外,已經沒有更解近一步的方法。
也嘗試首先對輪廓進行凸包處理,再查找外接矩形,效果同樣不好。
解題思路:仍然要從現有的、穩定運行的代碼裏面找方法。目前OpenCV函數getOrientation能夠通過PCA方法找到圖像/輪廓的方向比如這樣:技術分享圖片

在項目圖片上能夠得到這樣結果:技術分享圖片顯然是更符合實際情況的,當然,葉柄這裏產生了幹擾,但那是另一個問題。獲得主方向後,下一步就是如何獲得準確的長和寬。PCA方法無法獲得長寬,也嘗試通過旋轉矩陣的方法直接獲得結果:////以RotatedRect的方式返回結果 //RotatedRect box; //box.center.x = pos.x; //box.center.y = pos.y; //box.size.width = flong; //box.size.height = fshort; //box.angle = (float)atan2( eigen_vecs[0].y, eigen_vecs[0].x)*180/3.1415926; //弧度轉角度 ////繪制rotateRect //Point2f rect_points[4]; //box.points( rect_points ); //for( int j = 0; j < 4; j++ ) // line( img, rect_points[j], rect_points[(j+1)%4],Scalar(0,0,255),2);但是需要註意的是,這裏的pca獲得的center並不是絕對的center,而且在中線兩邊,輪廓到中線的長度不一定一樣。為了獲得最精確的結果,就需要直接去求出每個邊的長度,並且繪制出來。思路很簡單,就是通過中線(及其中線的垂線)將原輪廓分為兩個部分,分別求這兩個部分的到中線的最大距離(加起來就是長,分開來就是位置)。求的長軸端點:

技術分享圖片求得到中線最遠距離點(藍色),這也就是到中線的距離。
技術分享圖片


距離的計算很多時候只是點的循環。最後存在一個問題,那就是這樣一個圖像,已經知道p0-03的坐標,和兩條軸線的斜率,如何繪制4個角點?技術分享圖片
實際上,這是一個數學問題,並且有解析解: //通過解析方法,獲得最後結果 Point p[4]; p[0].x = (k_long * _p[0].x - k_short * _p[2].x + _p[2].y - _p[0].y) / (k_long - k_short); p[0].y = (p[0].x - _p[0].x)*k_long + _p[0].y; p[1].x = (k_long * _p[0].x - k_short * _p[3].x + _p[3].y - _p[0].y) / (k_long - k_short); p[1].y = (p[1].x - _p[0].x)*k_long + _p[0].y; p[2].x = (k_long * _p[1].x - k_short * _p[2].x + _p[2].y - _p[1].y) / (k_long - k_short); p[2].y = (p[2].x - _p[1].x)*k_long + _p[1].y; p[3].x = (k_long * _p[1].x - k_short * _p[3].x + _p[3].y - _p[1].y) / (k_long - k_short); p[3].y = (p[3].x - _p[1].x)*k_long + _p[1].y;
技術分享圖片成功!!!技術分享圖片得到最後結果,這正是我想要得到的。但是由於算法穩定性方面和效率的考慮,還需要進一步增強。技術分享圖片 p.s重新翻了一下minarearect
cv::RotatedRect cv::minAreaRect( InputArray _points )
{
CV_INSTRUMENT_REGION()

Mat hull;
Point2f out[3];
RotatedRect box;

convexHull(_points, hull, true, true);

if( hull.depth() != CV_32F )
{
Mat temp;
hull.convertTo(temp, CV_32F);
hull = temp;
}

int n = hull.checkVector(2);
const Point2f* hpoints = hull.ptr<Point2f>();

if( n > 2 )
{
rotatingCalipers( hpoints, n, CALIPERS_MINAREARECT, (float*)out );
box.center.x = out[0].x + (out[1].x + out[2].x)*0.5f;
box.center.y = out[0].y + (out[1].y + out[2].y)*0.5f;
box.size.width = (float)std::sqrt((double)out[1].x*out[1].x + (double)out[1].y*out[1].y);
box.size.height = (float)std::sqrt((double)out[2].x*out[2].x + (double)out[2].y*out[2].y);
box.angle = (float)atan2( (double)out[1].y, (double)out[1].x );
}
else if( n == 2 )
{
box.center.x = (hpoints[0].x + hpoints[1].x)*0.5f;
box.center.y = (hpoints[0].y + hpoints[1].y)*0.5f;
double dx = hpoints[1].x - hpoints[0].x;
double dy = hpoints[1].y - hpoints[0].y;
box.size.width = (float)std::sqrt(dx*dx + dy*dy);
box.size.height = 0;
box.angle = (float)atan2( dy, dx );
}
else
{
if( n == 1 )
box.center = hpoints[0];
}

box.angle = (float)(box.angle*180/CV_PI);
return box;
}

那麽,這個官方函數首先就把輪廓找了hull矩。這個當然對於很多問題都是好方法,很簡單直觀(我這裏的方法就繁瑣很多)。但是忽視了一個重要問題:hull變換後,會丟失信息。顯然這就是結果不準確的原因。
我也在answeropencv上進行了咨詢,berak給出的comment是

maybe:

  1. find principal axes of your shape (PCA)
  2. rotate to upright (warp)
  3. boundingRect() (image axis aligned)
這個顯然也不是很妥當,因為這個結果還需要rotate回去,這會很麻煩。感謝閱讀至此,希望有所幫助。


來自為知筆記(Wiz)

附件列表

    【4Opencv】如何識別出輪廓準確的長和寬