問題來源:
實際項目中,需要給出識別輪廓的長度和寬度。
初步分析:

輪廓分析的例程為:
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;
}
{
//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: