基于OpenCV的聚光灯去噪与圆检测

kcugc4gi  于 2023-06-06  发布在  其他
关注(0)|答案(1)|浏览(159)

我一直在尝试使用给定聚光灯的不同时刻的图片,以便检测聚光灯内的圆,并确定其中心和圆的直径。在与它合作时,我遇到了一些问题。
1.使用二进制阈值适用于大多数图像,但也有一些图像,如下面显示的

对此应用二进制阈值会产生以下结果:

这是阈值的聚光灯的错误部分,甚至试图使用自适应阈值似乎没有做太多的问题。
1.有许多图像没有完全填充的圆圈,并且有些像素化,这意味着阈值产生的结果看起来像这样:

并且应用阈值处理可以做到这一点:

对于这些示例,我可以做些什么来构建一个圆并去除噪声,以便我可以检测到圆及其特征?

muk1a3rh

muk1a3rh1#

如果你不能使用固定的阈值,最简单的方法之一是尝试可能的阈值,并选择一个看起来不错的。
在下面的示例代码(C++)中,以升序尝试阈值,直到二进制化结果形状变为“足够圆”。

//Circle fitting (Simple Least Square)
void FitCircle( const std::vector<cv::Point> &Ps, cv::Point2f &C, float &r )
{
    cv::Mat A( (int)Ps.size(), 3, CV_32F );
    cv::Mat B( (int)Ps.size(), 1, CV_32F );
    for( int i=0; i<Ps.size(); ++i )
    {
        const auto &P = Ps[i];
        A.at<float>( i,0 ) = (float)P.x;
        A.at<float>( i,1 ) = (float)P.y;
        A.at<float>( i,2 ) = 1.0f;
        B.at<float>( i ) = (float)(P.x*P.x + P.y*P.y);
    }
    cv::Mat X;
    cv::solve( A,B, X, cv::DECOMP_SVD );
    C.x = X.at<float>(0) * 0.5f;
    C.y = X.at<float>(1) * 0.5f;
    r = sqrt( X.at<float>(2) + C.x*C.x + C.y*C.y );
}

int main()
{
    //Load image as gray-scale
    cv::Mat SrcImg = cv::imread( "SpotLight.png", cv::IMREAD_GRAYSCALE );
    if( SrcImg.empty() )return 0;
    cv::imshow( "Src", SrcImg );

    //Decide threshold value range [Min,Max) to try
    int Min=0, Max=255;
    {
        unsigned int Hist[256] = { 0 };
        for( int y=0; y<SrcImg.rows; ++y )
        {
            const unsigned char *p = SrcImg.ptr<unsigned char>(y);
            for( int x=0; x<SrcImg.cols; ++x, ++p )
            {   ++Hist[ *p ];   }
        }
        {//Decide Min
            unsigned int Thresh = cvRound( SrcImg.rows * SrcImg.cols * 0.9 );
            unsigned int Sum = 0;
            while( Sum < Thresh ){  Sum += Hist[Min];   ++Min;  }
        }
        //Decide Max
        while( Hist[Max]==0 ){  --Max;  }
    }
    
    //Try for each threshold
    for( int Thresh=Min; Thresh<Max-1; ++Thresh )
    {
        //Binalize
        cv::Mat Bin;
        cv::threshold( SrcImg, Bin, Thresh, 255, cv::THRESH_BINARY );

        //Fit circle to largest contour
        //to evaluate how close the binalization result shape is to a circle
        cv::Point2f Center;
        float Radius;
        double FittingErr = 0;
        {
            //Extract Largest Contour
            std::vector< std::vector<cv::Point> > Conts;
            cv::findContours( Bin, Conts, cv::RETR_EXTERNAL, cv::CHAIN_APPROX_NONE );
            if( Conts.empty() )continue;
            int iLargestCont = 0;
            double MaxArea = 0;
            for( int i=0; i<Conts.size(); ++i )
            {
                double Area = cv::contourArea( Conts[i] );
                if( Area >= MaxArea ){  MaxArea = Area; iLargestCont = i;   }
            }
            //Fit circle to the Contour and evaluate the fitting result
            FitCircle( Conts[iLargestCont], Center, Radius );
            for( const auto &P : Conts[iLargestCont] )
            {
                double dx = (double)P.x - Center.x;
                double dy = (double)P.y - Center.y;

                double e = fabs( sqrt(dx*dx + dy*dy) - Radius );
                FittingErr += e;
            }
            FittingErr /= Conts[iLargestCont].size();
        }
        //Check if Contour shape is "enoughly circle"
        if( FittingErr <= 0.5 )
        {   //Show Result
            cv::Mat Show;
            std::cout << "Thresh = " << Thresh << std::endl;
            cv::cvtColor( SrcImg, Show, cv::COLOR_GRAY2BGR );
            Show.setTo( cv::Scalar(0,0,255), Bin );
            cv::imshow( "Result", Show );
            cv::waitKey();
            return 0;
        }
    }

    //
    std::cout << "(All try Failed)" << std::endl;
    return 0;
}

该样品的结果(注:我使用缩小尺寸的图像输入):

  • 阈值= 167
  • 二值化结果表示为红色部分。

相关问题