天天看點

【4opencv】求解向量和輪廓的交點

在“學習OpenCV3"的QQ群衆,網友且行且珍惜針對前期部落格(https://www.cnblogs.com/jsxyhelu/p/9345590.html)中的内容提出了以下問題:

【4opencv】求解向量和輪廓的交點

比如這張圖,利用PCA求出了特征向量之後,我想要求解與輪廓的交點,不知道有沒有簡單的方法

【4opencv】求解向量和輪廓的交點

@禾老師 

非常好的問題!在尋找到輪廓的”主方向“後,往往下一個動作就是尋找向量和輪廓的交點,因為往往這才是我們更關心的地方。為了解決這個問題,我認為的思路應該是這樣的:

1、首先要界定範圍。對于交點來說,肯定是在這個輪廓的“最小外接矩形”中的,是以先求出外接矩形作為限定;

2、向量隻是一個方向,要将其變成一條直線(如果在“最小外接矩形”中就是線段),推薦使用LineIterator來表示直線;

3、最後,判斷這條線段上的點是否在輪廓上,可以使用pointpolytest函數。

結合代碼具體講解。為了凸顯本文知識重點,本文采用以下一幅圖像來說明算法

【4opencv】求解向量和輪廓的交點

最後得到的結果是這樣的,其中黃點為主方向向量和外界矩形交點,紅點為和輪廓交點。

【4opencv】求解向量和輪廓的交點

全部代碼為:

/************************************************************************/

// 求解向量和輪廓的交點

// 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中專門用來周遊直線的;後者是專門用來計算點和輪廓的關系的,應該說這裡的應用還是非常高效的。

感謝閱讀至此,希望有所幫助。

目前方向:圖像拼接融合、圖像識别

聯系方式:[email protected]

繼續閱讀