web-dev-qa-db-fra.com

Détecter le demi-cercle à l'opencv

J'essaie de détecter des cercles et des demi-cercles dans une image.enter image description here

Je suis la procédure mentionnée ci-dessous: Image de traitement (y compris la détection de Canny Edge) Trouvez les contours et dessinez-les sur une image vide, afin que je puisse éliminer les composants indésirables . Je veux.) Détecter les cercles en utilisant HoughCircles. Et c'est ce que je reçois.

enter image description here

J'ai essayé de faire varier les paramètres dans HoughCircles mais les résultats ne sont pas cohérents car ils varient en fonction de l'éclairage et de la position des cercles dans l'image ..__ J'accepte ou refuse un cercle en fonction de sa taille. Donc, le résultat n'est pas acceptable. De plus, j'ai une longue liste de cercles "acceptables", il me faut donc un peu de marge dans les paramètres HoughCircle . Quant aux cercles pleins, c'est facile - je peux simplement trouver la "rondeur" du contour. Le problème est en demi-cercle!

S'il vous plaît trouver l'image modifiée avant hough transformerenter image description here

26
harsh

Utilisez houghCircle directement sur votre image, n'extrayez pas d'abord les contours . Ensuite, testez, pour chaque cercle détecté, le pourcentage réellement présent dans l'image:

int main()
{
    cv::Mat color = cv::imread("../houghCircles.png");
    cv::namedWindow("input"); cv::imshow("input", color);

    cv::Mat canny;

    cv::Mat gray;
    /// Convert it to gray
    cv::cvtColor( color, gray, CV_BGR2GRAY );

    // compute canny (don't blur with that image quality!!)
    cv::Canny(gray, canny, 200,20);
    cv::namedWindow("canny2"); cv::imshow("canny2", canny>0);

    std::vector<cv::Vec3f> circles;

    /// Apply the Hough Transform to find the circles
    cv::HoughCircles( gray, circles, CV_HOUGH_GRADIENT, 1, 60, 200, 20, 0, 0 );

    /// Draw the circles detected
    for( size_t i = 0; i < circles.size(); i++ ) 
    {
        Point center(cvRound(circles[i][0]), cvRound(circles[i][1]));
        int radius = cvRound(circles[i][2]);
        cv::circle( color, center, 3, Scalar(0,255,255), -1);
        cv::circle( color, center, radius, Scalar(0,0,255), 1 );
    }

    //compute distance transform:
    cv::Mat dt;
    cv::distanceTransform(255-(canny>0), dt, CV_DIST_L2 ,3);
    cv::namedWindow("distance transform"); cv::imshow("distance transform", dt/255.0f);

    // test for semi-circles:
    float minInlierDist = 2.0f;
    for( size_t i = 0; i < circles.size(); i++ ) 
    {
        // test inlier percentage:
        // sample the circle and check for distance to the next Edge
        unsigned int counter = 0;
        unsigned int inlier = 0;

        cv::Point2f center((circles[i][0]), (circles[i][1]));
        float radius = (circles[i][2]);

        // maximal distance of inlier might depend on the size of the circle
        float maxInlierDist = radius/25.0f;
        if(maxInlierDist<minInlierDist) maxInlierDist = minInlierDist;

        //TODO: maybe paramter incrementation might depend on circle size!
        for(float t =0; t<2*3.14159265359f; t+= 0.1f) 
        {
            counter++;
            float cX = radius*cos(t) + circles[i][0];
            float cY = radius*sin(t) + circles[i][1];

            if(dt.at<float>(cY,cX) < maxInlierDist) 
            {
                inlier++;
                cv::circle(color, cv::Point2i(cX,cY),3, cv::Scalar(0,255,0));
            } 
           else
                cv::circle(color, cv::Point2i(cX,cY),3, cv::Scalar(255,0,0));
        }
        std::cout << 100.0f*(float)inlier/(float)counter << " % of a circle with radius " << radius << " detected" << std::endl;
    }

    cv::namedWindow("output"); cv::imshow("output", color);
    cv::imwrite("houghLinesComputed.png", color);

    cv::waitKey(-1);
    return 0;
}

Pour cette entrée:

enter image description here

Il donne cette sortie:

enter image description here

Les cercles rouges correspondent aux résultats de Hough.

Les points verts échantillonnés sur le cercle sont inliers.

Les points bleus sont des valeurs aberrantes.

Sortie de la console:

100 % of a circle with radius 27.5045 detected
100 % of a circle with radius 25.3476 detected
58.7302 % of a circle with radius 194.639 detected
50.7937 % of a circle with radius 23.1625 detected
79.3651 % of a circle with radius 7.64853 detected

Si vous voulez tester RANSAC au lieu de Hough, regardez this .

34
Micka

Voici une autre façon de le faire, une version simple de RANSAC (beaucoup d’optimisation est nécessaire pour améliorer la vitesse), qui fonctionne sur l’image Edge.

la méthode boucle ces étapes jusqu'à ce qu'elle soit annulée

  1. choisissez au hasard 3 pixels de bord
  2. estimer le cercle à partir d'eux (3 points suffisent pour identifier un cercle)
  3. vérifier ou falsifier qu'il s'agit bien d'un cercle: compter combien de pourcentage du cercle est représenté par les arêtes données
  4. si un cercle est vérifié, supprimez le cercle de input/egdes

    int main()
    {
    //RANSAC
    
    //load Edge image
    cv::Mat color = cv::imread("../circleDetectionEdges.png");
    
    // convert to grayscale
    cv::Mat gray;
    cv::cvtColor(color, gray, CV_RGB2GRAY);
    
    // get binary image
    cv::Mat mask = gray > 0;
    //erode the edges to obtain sharp/thin edges (undo the blur?)
    cv::erode(mask, mask, cv::Mat());
    
    std::vector<cv::Point2f> edgePositions;
    edgePositions = getPointPositions(mask);
    
    // create distance transform to efficiently evaluate distance to nearest Edge
    cv::Mat dt;
    cv::distanceTransform(255-mask, dt,CV_DIST_L1, 3);
    
    //TODO: maybe seed random variable for real random numbers.
    
    unsigned int nIterations = 0;
    
    char quitKey = 'q';
    std::cout << "press " << quitKey << " to stop" << std::endl;
    while(cv::waitKey(-1) != quitKey)
    {
        //RANSAC: randomly choose 3 point and create a circle:
        //TODO: choose randomly but more intelligent, 
        //so that it is more likely to choose three points of a circle. 
        //For example if there are many small circles, it is unlikely to randomly choose 3 points of the same circle.
        unsigned int idx1 = Rand()%edgePositions.size();
        unsigned int idx2 = Rand()%edgePositions.size();
        unsigned int idx3 = Rand()%edgePositions.size();
    
        // we need 3 different samples:
        if(idx1 == idx2) continue;
        if(idx1 == idx3) continue;
        if(idx3 == idx2) continue;
    
        // create circle from 3 points:
        cv::Point2f center; float radius;
        getCircle(edgePositions[idx1],edgePositions[idx2],edgePositions[idx3],center,radius);
    
        float minCirclePercentage = 0.4f;
    
        // inlier set unused at the moment but could be used to approximate a (more robust) circle from alle inlier
        std::vector<cv::Point2f> inlierSet;
    
        //verify or falsify the circle by inlier counting:
        float cPerc = verifyCircle(dt,center,radius, inlierSet);
    
        if(cPerc >= minCirclePercentage)
        {
            std::cout << "accepted circle with " << cPerc*100.0f << " % inlier" << std::endl;
            // first step would be to approximate the circle iteratively from ALL INLIER to obtain a better circle center
            // but that's a TODO
    
            std::cout << "circle: " << "center: " << center << " radius: " << radius << std::endl;
            cv::circle(color, center,radius, cv::Scalar(255,255,0),1);
    
            // accept circle => remove it from the Edge list
            cv::circle(mask,center,radius,cv::Scalar(0),10);
    
            //update Edge positions and distance transform
            edgePositions = getPointPositions(mask);
            cv::distanceTransform(255-mask, dt,CV_DIST_L1, 3);
        }
    
        cv::Mat tmp;
        mask.copyTo(tmp);
    
        // prevent cases where no fircle could be extracted (because three points collinear or sth.)
        // filter NaN values
        if((center.x == center.x)&&(center.y == center.y)&&(radius == radius))
        {
            cv::circle(tmp,center,radius,cv::Scalar(255));
        }
        else
        {
            std::cout << "circle illegal" << std::endl;
        }
    
        ++nIterations;
        cv::namedWindow("RANSAC"); cv::imshow("RANSAC", tmp);
    }
    
    std::cout << nIterations <<  " iterations performed" << std::endl;
    
    
    cv::namedWindow("edges"); cv::imshow("edges", mask);
    cv::namedWindow("color"); cv::imshow("color", color);
    
    cv::imwrite("detectedCircles.png", color);
    cv::waitKey(-1);
    return 0;
    }
    
    
    float verifyCircle(cv::Mat dt, cv::Point2f center, float radius, std::vector<cv::Point2f> & inlierSet)
    {
     unsigned int counter = 0;
     unsigned int inlier = 0;
     float minInlierDist = 2.0f;
     float maxInlierDistMax = 100.0f;
     float maxInlierDist = radius/25.0f;
     if(maxInlierDist<minInlierDist) maxInlierDist = minInlierDist;
     if(maxInlierDist>maxInlierDistMax) maxInlierDist = maxInlierDistMax;
    
     // choose samples along the circle and count inlier percentage
     for(float t =0; t<2*3.14159265359f; t+= 0.05f)
     {
         counter++;
         float cX = radius*cos(t) + center.x;
         float cY = radius*sin(t) + center.y;
    
         if(cX < dt.cols)
         if(cX >= 0)
         if(cY < dt.rows)
         if(cY >= 0)
         if(dt.at<float>(cY,cX) < maxInlierDist)
         {
            inlier++;
            inlierSet.Push_back(cv::Point2f(cX,cY));
         }
     }
    
     return (float)inlier/float(counter);
    }
    
    
    inline void getCircle(cv::Point2f& p1,cv::Point2f& p2,cv::Point2f& p3, cv::Point2f& center, float& radius)
    {
      float x1 = p1.x;
      float x2 = p2.x;
      float x3 = p3.x;
    
      float y1 = p1.y;
      float y2 = p2.y;
      float y3 = p3.y;
    
      // PLEASE CHECK FOR TYPOS IN THE FORMULA :)
      center.x = (x1*x1+y1*y1)*(y2-y3) + (x2*x2+y2*y2)*(y3-y1) + (x3*x3+y3*y3)*(y1-y2);
      center.x /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) );
    
      center.y = (x1*x1 + y1*y1)*(x3-x2) + (x2*x2+y2*y2)*(x1-x3) + (x3*x3 + y3*y3)*(x2-x1);
      center.y /= ( 2*(x1*(y2-y3) - y1*(x2-x3) + x2*y3 - x3*y2) );
    
      radius = sqrt((center.x-x1)*(center.x-x1) + (center.y-y1)*(center.y-y1));
    }
    
    
    
    std::vector<cv::Point2f> getPointPositions(cv::Mat binaryImage)
    {
     std::vector<cv::Point2f> pointPositions;
    
     for(unsigned int y=0; y<binaryImage.rows; ++y)
     {
         //unsigned char* rowPtr = binaryImage.ptr<unsigned char>(y);
         for(unsigned int x=0; x<binaryImage.cols; ++x)
         {
             //if(rowPtr[x] > 0) pointPositions.Push_back(cv::Point2i(x,y));
             if(binaryImage.at<unsigned char>(y,x) > 0) pointPositions.Push_back(cv::Point2f(x,y));
         }
     }
    
     return pointPositions;
    }
    

contribution:

enter image description here

sortie:

enter image description here

sortie de la console:

    press q to stop
    accepted circle with 50 % inlier
    circle: center: [358.511, 211.163] radius: 193.849
    accepted circle with 85.7143 % inlier
    circle: center: [45.2273, 171.591] radius: 24.6215
    accepted circle with 100 % inlier
    circle: center: [257.066, 197.066] radius: 27.819
    circle illegal
    30 iterations performed`

l'optimisation devrait inclure:

  1. utiliser tous les éléments pour former un meilleur cercle

  2. ne calculez pas la transformation de distance après chaque cercle détecté (c'est assez cher). calculez l'inlier à partir du point/de l'arête défini directement et supprimez les arêtes de l'inlier de cette liste.

  3. s'il y a beaucoup de petits cercles dans l'image (et/ou beaucoup de bruit), il est peu probable que vous atteigniez au hasard 3 pixels de bord ou un cercle. => essayez d’abord de détecter les contours et de détecter les cercles pour chaque contour. Après cela, essayez de détecter tous les "autres" cercles laissés dans l'image.

  4. beaucoup d'autres choses

9
Micka

Je sais qu'il est un peu tardif, mais j'ai utilisé une approche différente, ce qui est beaucoup plus simple… .. À partir de cv2.HoughCircles(...), vous obtenez le centre du cercle et le diamètre (x, y, r). Je passe donc simplement par tous les points centraux des cercles et je vérifie s’ils sont plus éloignés du bord de l’image que leur diamètre. 

Voici mon code:

        height, width = img.shape[:2]

        #test top Edge
        up = (circles[0, :, 0] - circles[0, :, 2]) >= 0

        #test left Edge
        left = (circles[0, :, 1] - circles[0, :, 2]) >= 0

        #test right Edge
        right = (circles[0, :, 0] + circles[0, :, 2]) <= width

        #test bottom Edge
        down = (circles[0, :, 1] + circles[0, :, 2]) <= height

        circles = circles[:, (up & down & right & left), :]
0
hory

Le demi-cercle détecté par l'algorithme hough est probablement correct. Le problème ici pourrait être que si vous ne contrôlez pas strictement la géométrie de la scène, c'est-à-dire la position exacte de la caméra par rapport à la cible, de sorte que l'axe de l'image soit normal par rapport au plan cible, vous obtiendrez des ellipses plutôt que des cercles projetés sur l'image. avion. Sans parler des distorsions causées par le système optique, qui dégénère davantage la figure géométrique. Si vous comptez sur la précision, je recommanderais le calibrage de la caméra .

0
brunocodutra

Vous feriez mieux d'essayer avec différents kernel for gaussian blur . Cela vous aidera

GaussianBlur( src_gray, src_gray, Size(11, 11), 5,5);

alors changez size(i,i),j,j)

0
sandeep