web-dev-qa-db-fra.com

Détecter le centre et l'angle des rectangles dans une image en utilisant Opencv

J'ai une image comme ci-dessous:

 Sample image containing many rectangle contours

Je dois connaître le nombre de rectangles, le centre de chaque rectangle et mesurer l’angle entre l’axe parallèle au bord le plus long du rectangle passant par le centre et mesurer l’angle dans le sens inverse des aiguilles d’une montre.J'ai découvert le nombre de Je suis frappé en découvrant le centre et l'angle de réflexion. Trouver le centre à travers des moments ne me donne pas la réponse correcte.

Mon code:

import cv2
import numpy as np 
import sys

img = cv2.imread(str(sys.argv[1]),0)
ret,thresh = cv2.threshold(img,127,255,0)
contours,hierarchy = cv2.findContours(thresh,1,2)



for contour in contours:
    area = cv2.contourArea(contour)
    if area>100000:
        contours.remove(contour)




cnt = contours[0]

epsilon = 0.02*cv2.arcLength(cnt,True)
approx = cv2.approxPolyDP(cnt,epsilon,True)

print 'No of rectangles',len(approx)


#finding the centre of the contour
M = cv2.moments(cnt)

cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])

print cx,cy
8

Voici comment vous pouvez le faire avec la fonction minAreaRect d’openCV. C'est écrit en C++ mais vous pouvez probablement l'adapter facilement, puisque seules les fonctions OpenCV ont été utilisées.

    cv::Mat input = cv::imread("../inputData/rectangles.png");

    cv::Mat gray;
    cv::cvtColor(input,gray,CV_BGR2GRAY);

    // since your image has compression artifacts, we have to threshold the image
    int threshold = 200;
    cv::Mat mask = gray > threshold;

    cv::imshow("mask", mask);

    // extract contours
    std::vector<std::vector<cv::Point> > contours;
    cv::findContours(mask, contours, CV_RETR_EXTERNAL, CV_CHAIN_APPROX_NONE);

    for(int i=0; i<contours.size(); ++i)
    {
        // fit bounding rectangle around contour
        cv::RotatedRect rotatedRect = cv::minAreaRect(contours[i]);

        // read points and angle
        cv::Point2f rect_points[4]; 
        rotatedRect.points( rect_points );

        float  angle = rotatedRect.angle; // angle

        // read center of rotated rect
        cv::Point2f center = rotatedRect.center; // center

        // draw rotated rect
        for(unsigned int j=0; j<4; ++j)
            cv::line(input, rect_points[j], rect_points[(j+1)%4], cv::Scalar(0,255,0));

        // draw center and print text
        std::stringstream ss;   ss << angle; // convert float to string
        cv::circle(input, center, 5, cv::Scalar(0,255,0)); // draw center
        cv::putText(input, ss.str(), center + cv::Point2f(-25,25), cv::FONT_HERSHEY_COMPLEX_SMALL, 1, cv::Scalar(255,0,255)); // print angle
    }

résultant en cette image:

 enter image description here

comme vous pouvez le constater, les angles ne sont probablement pas ce que vous voulez (car ils utilisent de manière aléatoire la ligne la plus longue ou la plus petite comme référence). Vous pouvez plutôt extraire les côtés les plus longs des rectangles et calculer l'angle manuellement.

Si vous choisissez le bord le plus long des courbes en rotation et en calculez l'angle, il se présente comme suit:

// choose the longer Edge of the rotated rect to compute the angle
        cv::Point2f Edge1 = cv::Vec2f(rect_points[1].x, rect_points[1].y) - cv::Vec2f(rect_points[0].x, rect_points[0].y);
        cv::Point2f Edge2 = cv::Vec2f(rect_points[2].x, rect_points[2].y) - cv::Vec2f(rect_points[1].x, rect_points[1].y);

        cv::Point2f usedEdge = Edge1;
        if(cv::norm(Edge2) > cv::norm(Edge1))
            usedEdge = Edge2;

        cv::Point2f reference = cv::Vec2f(1,0); // horizontal Edge


        angle = 180.0f/CV_PI * acos((reference.x*usedEdge.x + reference.y*usedEdge.y) / (cv::norm(reference) *cv::norm(usedEdge)));

donnant ce résultat, qui devrait être ce que vous recherchez!

 enter image description here

EDIT: Il semble que l’opérateur n’utilise pas l’image d’entrée qu’il a postée, car les centres de rectangle de référence se situeraient en dehors de l’image.

En utilisant cette entrée (redimensionnée manuellement mais probablement pas toujours optimale):

 enter image description here

J'obtiens ces résultats (les points bleus sont des centres de référence rectangulaires fournis par l'op):

 enter image description here

Comparer la référence aux détections:

reference (x,y,angle)    detection (x,y,angle)
(320,240,0)              (320, 240, 180) // angle 180 is equal to angle 0 for lines
(75,175,90)              (73.5, 174.5, 90)
(279,401,170)            (279.002, 401.824, 169.992)
(507,379,61)             (507.842, 379.75, 61.1443)
(545,95,135)             (545.75, 94.25, 135)
(307,79,37)              (306.756, 77.8384, 37.1042)

J'aimerais bien voir l'image d'entrée REELLE, peut-être que le résultat sera encore meilleur.

14
Micka

Voici comment vous pouvez le faire:

  1. Etiquetage des composants connectés afin de détecter chaque motif (dans votre cas, les rectangles)
  2. Séparez les motifs dans différentes images
  3. (facultatif) si les motifs ne sont pas tous des rectangles, utilisez des indices de forme pour les distinguer
  4. Calculez l’axe principal à l’aide de l’Analyse en composantes principales (ACP), il vous donnera l’angle que vous recherchez.
2
FiReTiTi

approx = cv2.approxPolyDP (cnt, epsilon, True) crée un polygone approximé d'un contour fermé donné. Les segments de ligne dans le polygone ont une longueur variable, ce qui entraîne un calcul de moment incorrect car il s'attend à ce que les points soient échantillonnés à partir d'une grille régulière afin de vous donner le centre correct.

Il y a trois solutions à votre problème:

  1. Utilisez les moments des contours d'origine avant d'appeler la méthode d'approximation de polygone.
  2. Utilisez drawContours pour générer le masque des régions à l'intérieur de chaque contour fermé, puis utilisez les moments du masque généré pour calculer le centre.
  3. Échantillonnez des points à l'unité de distance le long de chaque segment de votre polygone fermé et utilisez les ensembles de points obtenus pour calculer les moments vous-même. Cela devrait vous donner le même centre. 
1
Ajay