【4opencv】求解向量和輪廓的交點
在“學習OpenCV3"的QQ群眾,網友且行且珍惜針對前期部落格(https://www.cnblogs.com/jsxyhelu/p/9345590.html)中的內容提出了以下問題:
比如這張圖,利用PCA求出了特徵向量之後,我想要求解與輪廓的交點,不知道有沒有簡單的方法 @禾老師
非常好的問題!在尋找到輪廓的”主方向“後,往往下一個動作就是尋找向量和輪廓的交點,因為往往這才是我們更關心的地方。為了解決這個問題,我認為的思路應該是這樣的:
1、首先要界定範圍。對於交點來說,肯定是在這個輪廓的“最小外接矩形”中的,所以先求出外接矩形作為限定;
2、向量只是一個方向,要將其變成一條直線(如果在“最小外接矩形”中就是線段),推薦使用LineIterator來表示直線;
3、最後,判斷這條線段上的點是否在輪廓上,可以使用pointpolytest函式。
結合程式碼具體講解。為了凸顯本文知識重點,本文采用以下一幅影象來說明演算法
最後得到的結果是這樣的,其中黃點為主方向向量和外界矩形交點,紅點為和輪廓交點。
全部程式碼為:
/************************************************************************/
// 求解向量和輪廓的交點
// by jsxyhelu(jsxyhelu.cnblogs.com)
// 2018/10/05
/************************************************************************/
# include "stdafx.h"
# include "opencv2/imgcodecs.hpp"
# include "opencv2/highgui.hpp"
# include "opencv2/imgproc.hpp"
# include "opencv2/photo.hpp"
using namespace std;
using namespace cv;
//尋找最大外接輪廓
vector < Point > FindBigestContour(Mat src){
int max_area_contour_idx = 0 ;
double max_area = - 1 ;
vector < vector < Point > > contours;
findContours(src,contours,RETR_LIST,CHAIN_APPROX_SIMPLE);
//handle case if no contours are detected
CV_Assert( 0 != contours.size());
for (uint i = 0 ;i < contours.size();i ++ ){
double temp_area = contourArea(contours[i]);
if (max_area < temp_area ){
max_area_contour_idx = i;
max_area = temp_area;
}
}
return contours[max_area_contour_idx];
}
//程式主要部分
int main( int argc, char * * argv )
{
//讀入影象,轉換為灰度
Mat src = imread( "E:/sandbox/cloud.png" );
Mat src_gray;
cvtColor(src, src_gray, COLOR_BGR2GRAY);
//閾值處理
Mat threshold_output;
cv : : threshold(src_gray,threshold_output, 150 , 255 ,THRESH_OTSU | THRESH_BINARY_INV);
//輪廓分析
vector < vector < Point > > contours;
vector < Vec4i > hierarchy;
vector < Point > biggestContour = FindBigestContour(threshold_output); //尋找最大輪廓
Rect boundRect = boundingRect( Mat(biggestContour) ); //獲得輪廓最小外接矩形
cv : : rectangle(src,boundRect,Scalar( 0 , 0 , 255 ));
//pca分析,求出斜率和經過的一點
Mat data_pts = Mat(biggestContour.size(), 2 , CV_64FC1); //Construct a buffer used by the pca analysis
for ( int i = 0 ; i < data_pts.rows; ++ i)
{
data_pts.at < double > (i, 0 ) = biggestContour[i].x;
data_pts.at < double > (i, 1 ) = biggestContour[i].y;
}
PCA pca_analysis(data_pts, Mat(), CV_PCA_DATA_AS_ROW); //執行PCA運算
Point pos = Point2f(pca_analysis.mean.at < double > ( 0 , 0 ),
pca_analysis.mean.at < double > ( 0 , 1 )); //主方向直線經過的一點
vector < Point2d > eigen_vecs( 2 ); //儲存PCA分析結果,其中0組為主方向,1組為垂直方向
vector < double > eigen_val( 2 );
for ( int i = 0 ; i < 2 ; ++ i)
{
eigen_vecs[i] = Point2d(pca_analysis.eigenvectors.at < double > (i, 0 ),
pca_analysis.eigenvectors.at < double > (i, 1 ));
eigen_val[i] = pca_analysis.eigenvalues.at < double > (i, 0 );
}
line(src, pos - 0 . 02 * Point(eigen_vecs[ 0 ].x * eigen_val[ 0 ],eigen_vecs[ 0 ].y * eigen_val[ 0 ]),
pos + 0 . 02 * Point(eigen_vecs[ 0 ].x * eigen_val[ 0 ],eigen_vecs[ 0 ].y * eigen_val[ 0 ]) , Scalar( 255 , 255 , 0 )); //繪製概略主方向
//求出主方向直線和外接矩形的交點,
float k = eigen_vecs[ 0 ].y / eigen_vecs[ 0 ].x; //斜率
Point2f pt1 = Point2f(boundRect.x,k * (boundRect.x - pos.x) + pos.y);
Point2f pt2 = Point2f((boundRect.x + boundRect.width),k * ((boundRect.x + boundRect.width) - pos.x) + pos.y);
circle(src,pt1, 5 ,Scalar( 0 , 255 , 255 ), - 1 );
circle(src,pt2, 5 ,Scalar( 0 , 255 , 255 ), - 1 );
//遍歷兩個交點之間的線段,得出和輪廓的交點
LineIterator it(src, pt1, pt2, 8 );
for ( int i = 0 ; i < it.count; i ++ , ++ it)
{
Point pt(it.pos()); //獲得線段上的點
if (abs(pointPolygonTest(biggestContour,pt, true )) < 1 )
circle(src,pt, 5 ,Scalar( 0 , 0 , 255 ), - 1 );
}
waitKey();
return 0 ;
}
知識重點:
1、FindBigestContour為尋找輪廓中最大輪廓的函式,目前這個函式還沒有merge到OpenCV中,下一步有這個計劃,注意這個函式的命名規則是按照OpenCV的方法定 義的;
2、我們採用Rect boundRect = boundingRect( Mat(biggestContour) );
來獲得輪廓的最小外接矩形。為什麼要首先獲得這個外接矩形了,因為我們這裡來所有要求的點肯定都在這個矩形中,我們做這一步就能夠降低演算法的計算複雜程度;
3、PCA分析的具體原來和細節,請檢視《如何獲得物體的主要方向?》 https://www.cnblogs.com/jsxyhelu/p/7690699.html
我們這裡使用,主要是獲得兩個資料,一個是該輪廓的重心,這個點是我們最後要求的那條直線肯定經過的;二個是求出直線的斜率。那麼對於一條直線,已經知道 斜率和 經過的一點,就已經能夠被定義出來 ;
4、最後在求該直線和輪廓的交點的時候,採用了LineIterator 和pointPolygonTest,前者是OpenCV中專門用來遍歷直線的;後者是專門用來計算點和輪廓的關係的,應該說這裡的應用還是非常高效的。
感謝閱讀至此,希望有所幫助。