如何寻找已知轮廓的最大内接圆

本博客相关代码已经被OpenCV收录:/samples/cpp/tutorial_code/ShapeDescriptors/pointPolygonTest_demo.cpp
 
一、问题的提出:
所谓内切圆,是指“与多边形各边都相切“。我们这里需要找的是所谓”内接圆“,可以简单认为是”圆点在轮廓中,到轮廓中所有点的距离一样的图像“。在这所有的”内接圆“中,寻找半径最大的哪一个。
这个问题已经广泛讨论了,比如
这样的图像,寻找轮廓的最大内接圆。
 
二、解决方法:
利用计算机图像学技术中轮廓的相关思路,可以直接从圆的定义解决此问题。基于OpenCV的代码和注释如下:
 
#include "stdafx.h"
#include <iostream>
 
using namespace std;
using namespace cv;
 
VP FindBigestContour(Mat src){    
    int imax = 0; //代表最大轮廓的序号
    int imaxcontour = -1; //代表最大轮廓的大小
    std::vector<std::vector<cv::Point>>contours;    
    findContours(src,contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
    for (int i=0;i<contours.size();i++){
        int itmp =  contourArea(contours[i]);//这里采用的是轮廓大小
        if (imaxcontour < itmp ){
            imax = i;
            imaxcontour = itmp;
        }
    }
    return contours[imax];
}
int main(int argc, char* argv[])
{
    Mat src = imread("e:/template/cloud.png");
    Mat temp;
    cvtColor(src,temp,COLOR_BGR2GRAY);
    threshold(temp,temp,100,255,THRESH_OTSU);
    imshow("src",temp);
    //寻找最大轮廓
    VP VPResult = FindBigestContour(temp);
    //寻找最大内切圆
    int dist = 0;
    int maxdist = 0;
    Point center;
    for(int i=0;i<src.cols;i++)
    {
        for(int j=0;j<src.rows;j++)
        {
            dist = pointPolygonTest(VPResult,cv::Point(i,j),true);
            if(dist>maxdist)
            {
                maxdist=dist;
                center=cv::Point(i,j);
            }
        }
    }
    //绘制结果
    circle(src,center,maxdist,Scalar(0,0,255));
    imshow("dst",src);
    waitKey();
}
其中
PointPolygonTest
测试点是否在多边形中
double cvPointPolygonTest( const CvArr* contour, CvPoint2D32f pt, int measure_dist );
contour 输入轮廓.
pt 针对轮廓需要测试的点。
measure_dist 如果非0,函数将估算点到轮廓最近边的距离。
函数cvPointPolygonTest 决定测试点是否在轮廓内,轮廓外,还是轮廓的边上(或者共边的交点上),它的返回值是正负零,相对应的,当measure_dist=0时,返回值是1, -1,0, 同样当 measure_dist≠0 ,它是返回一个从点到最近的边的带符号距离。
结果:

 

 

三、优化的思路:

这里对圆心的遍历,是遍历了所有的图像上面的点。然而根据”内接圆心一定在轮廓内部“这个先验知识,可以缩小循环范围,提高算法效率。
 
2018年7月27日22:01:01 对opencv的官方例子进行修改,并提交github

 

 

/**
 * @function pointPolygonTest_demo.cpp
 * @brief Demo code to use the pointPolygonTest function...fairly easy
 * @author OpenCV team
 */
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
 
using namespace cv;
using namespace std;
 
//return the biggest contour by size
vector<Point> FindBiggestContour(Mat src){    
    int icount = 0; 
    int imaxcontour = -1; 
    std::vector<std::vector<cv::Point>>contours;    
    findContours(src,contours,CV_RETR_LIST,CV_CHAIN_APPROX_SIMPLE);
    for (int i=0;i<contours.size();i++){
        int itmp =  contourArea(contours[i]);
        if (imaxcontour < itmp ){
            icount = i;
            imaxcontour = itmp;
        }
    }
    return contours[icount];
}
 
/**
 * @function main
 */
int main( void )
{
    /// Create an image
    const int r = 100;
    Mat src = Mat::zeros( Size( 4*r, 4*r ), CV_8U );
 
    /// Create a sequence of points to make a contour
    vector<Point2f> vert(6);
    vert[0] = Point( 3*r/2, static_cast<int>(1.34*r) );
    vert[1] = Point( 1*r, 2*r );
    vert[2] = Point( 3*r/2, static_cast<int>(2.866*r) );
    vert[3] = Point( 5*r/2, static_cast<int>(2.866*r) );
    vert[4] = Point( 3*r, 2*r );
    vert[5] = Point( 5*r/2, static_cast<int>(1.34*r) );
 
    /// Draw it in src
    for( int i = 0; i < 6; i++ )
    {
        line( src, vert[i],  vert[(i+1)%6], Scalar( 255 ), 3 );
    }
 
    /// Get the contours
    vector<vector<Point> > contours;
    findContours( src, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
 
    /// Calculate the distances to the contour
    Mat raw_dist( src.size(), CV_32F );
    for( int i = 0; i < src.rows; i++ )
    {
        for( int j = 0; j < src.cols; j++ )
        {
            raw_dist.at<float>(i,j) = (float)pointPolygonTest( contours[0], Point2f((float)j, (float)i), true );
        }
    }
 
    double minVal, maxVal;
    minMaxLoc( raw_dist, &minVal, &maxVal );
    minVal = abs(minVal);
    maxVal = abs(maxVal);
 
    /// Depicting the  distances graphically
    Mat drawing = Mat::zeros( src.size(), CV_8UC3 );
    for( int i = 0; i < src.rows; i++ )
    {
        for( int j = 0; j < src.cols; j++ )
        {
            if( raw_dist.at<float>(i,j) < 0 )
            {
                drawing.at<Vec3b>(i,j)[0] = (uchar)(255 - abs(raw_dist.at<float>(i,j)) * 255 / minVal);
            }
            else if( raw_dist.at<float>(i,j) > 0 )
            {
                drawing.at<Vec3b>(i,j)[2] = (uchar)(255 - raw_dist.at<float>(i,j) * 255 / maxVal);
            }
            else
            {
                drawing.at<Vec3b>(i,j)[0] = 255;
                drawing.at<Vec3b>(i,j)[1] = 255;
                drawing.at<Vec3b>(i,j)[2] = 255;
            }
        }
    }
 
    //get the biggest Contour
    vector<Point> biggestContour = FindBiggestContour(src);
    //find the maximum enclosed circle 
    int dist = 0;
    int maxdist = 0;
    Point center;
    for(int i=0;i<src.cols;i++)
    {
        for(int j=0;j<src.rows;j++)
        {
            dist = pointPolygonTest(biggestContour,cv::Point(i,j),true);
            if(dist>maxdist)
            {
                maxdist=dist;
                center=cv::Point(i,j);
            }
        }
    }
    circle(drawing,center,maxdist,Scalar(255,255,255));
    /// Show your results
    imshow( "Source", src );
    imshow( "Distance and maximum enclosed circle", drawing );
 
    waitKey();
    return 0;
}

 

 

 

我的最终解:
/**
 * @function pointPolygonTest_demo.cpp
 * @brief Demo code to use the pointPolygonTest function...fairly easy
 * @author OpenCV team
 *         jsxyhelu(jsxyhelu@foxmail.com)
 */
 
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"
#include <iostream>
 
using namespace cv;
using namespace std;
 
//get the biggest contour by size
static vector<Point> FindBiggestContour(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];
}
/**
 * @function main
 */
int main( void )
{
    /// Create an image
    const int r = 100;
    Mat src = Mat::zeros( Size( 4*r, 4*r ), CV_8U );
 
    /// Create a sequence of points to make a contour
    vector<Point2f> vert(6);
    vert[0] = Point( 3*r/2, static_cast<int>(1.34*r) );
    vert[1] = Point( 1*r, 2*r );
    vert[2] = Point( 3*r/2, static_cast<int>(2.866*r) );
    vert[3] = Point( 5*r/2, static_cast<int>(2.866*r) );
    vert[4] = Point( 3*r, 2*r );
    vert[5] = Point( 5*r/2, static_cast<int>(1.34*r) );
 
    /// Draw it in src
    for( int i = 0; i < 6; i++ )
    {
        line( src, vert[i],  vert[(i+1)%6], Scalar( 255 ), 3 );
    }
 
    /// Get the contours
    vector<vector<Point> > contours;
    findContours( src, contours, RETR_TREE, CHAIN_APPROX_SIMPLE);
 
    /// Calculate the distances to the contour
    Mat raw_dist( src.size(), CV_32F );
    for( int i = 0; i < src.rows; i++ )
    {
        for( int j = 0; j < src.cols; j++ )
        {
            raw_dist.at<float>(i,j) = (float)pointPolygonTest( contours[0], Point2f((float)j, (float)i), true );
        }
    }
 
    double minVal, maxVal;
    minMaxLoc( raw_dist, &minVal, &maxVal );
    minVal = abs(minVal);
    maxVal = abs(maxVal);
 
    /// Depicting the  distances graphically
    Mat drawing = Mat::zeros( src.size(), CV_8UC3 );
    for( int i = 0; i < src.rows; i++ )
    {
        for( int j = 0; j < src.cols; j++ )
        {
            if( raw_dist.at<float>(i,j) < 0 )
            {
                drawing.at<Vec3b>(i,j)[0] = (uchar)(255 - abs(raw_dist.at<float>(i,j)) * 255 / minVal);
            }
            else if( raw_dist.at<float>(i,j) > 0 )
            {
                drawing.at<Vec3b>(i,j)[2] = (uchar)(255 - raw_dist.at<float>(i,j) * 255 / maxVal);
            }
            else
            {
                drawing.at<Vec3b>(i,j)[0] = 255;
                drawing.at<Vec3b>(i,j)[1] = 255;
                drawing.at<Vec3b>(i,j)[2] = 255;
            }
        }
    }
 
    //get the biggest Contour
    vector<Point> biggestContour = FindBiggestContour(src);
    //handle case if biggestContour is empty
    CV_Assert(0 != biggestContour.size());
    //find the maximum enclosed circle
    double maxdist = 0;
    Point center;
    //get the rect bounding the BiggestContour
    Rect rectBoundingBiggestContour = boundingRect(Mat(biggestContour));
    for(int i=0;i<rectBoundingBiggestContour.width;i++)
    {
        for(int j=0;j<rectBoundingBiggestContour.height;j++)
        {
            Point tmpPoint = Point(rectBoundingBiggestContour.x + i,rectBoundingBiggestContour.y + j);
            double dist = pointPolygonTest(biggestContour,tmpPoint,true);
            if(dist>maxdist)
            {
                maxdist=dist;
                center= tmpPoint;
            }
         }
    }
    circle(drawing,center,(int)maxdist,Scalar(255,255,255));
 
    /// Show your results
    imshow( "Source", src );
    imshow( "Distance and maximum enclosed circle", drawing );
 
    waitKey();
    return 0;
}

 

posted on 2022-12-03 15:31  jsxyhelu  阅读(684)  评论(0编辑  收藏  举报

导航