OpenCV C++ / Obj - C: détection d'une feuille de papier / détection carrée

j'ai implémenté avec succès L'exemple de détection carrée OpenCV dans mon application de test, mais j'ai maintenant besoin de filtrer la sortie, parce que c'est assez embrouillé - ou mon code est-il erroné?

je suis intéressé par les quatre points de coin du papier pour la réduction de la déformation (comme que ) et la transformation ultérieure ...

D'Entrée Et De Sortie: Input & Output

image originale:

, cliquez sur

Code:

double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 ) {
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

- (std::vector<std::vector<cv::Point> >)findSquaresInImage:(cv::Mat)_image
{
    std::vector<std::vector<cv::Point> > squares;
    cv::Mat pyr, timg, gray0(_image.size(), CV_8U), gray;
    int thresh = 50, N = 11;
    cv::pyrDown(_image, pyr, cv::Size(_image.cols/2, _image.rows/2));
    cv::pyrUp(pyr, timg, _image.size());
    std::vector<std::vector<cv::Point> > contours;
    for( int c = 0; c < 3; c++ ) {
        int ch[] = {c, 0};
        mixChannels(&timg, 1, &gray0, 1, ch, 1);
        for( int l = 0; l < N; l++ ) {
            if( l == 0 ) {
                cv::Canny(gray0, gray, 0, thresh, 5);
                cv::dilate(gray, gray, cv::Mat(), cv::Point(-1,-1));
            }
            else {
                gray = gray0 >= (l+1)*255/N;
            }
            cv::findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);
            std::vector<cv::Point> approx;
            for( size_t i = 0; i < contours.size(); i++ )
            {
                cv::approxPolyDP(cv::Mat(contours[i]), approx, arcLength(cv::Mat(contours[i]), true)*0.02, true);
                if( approx.size() == 4 && fabs(contourArea(cv::Mat(approx))) > 1000 && cv::isContourConvex(cv::Mat(approx))) {
                    double maxCosine = 0;

                    for( int j = 2; j < 5; j++ )
                    {
                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                        maxCosine = MAX(maxCosine, cosine);
                    }

                    if( maxCosine < 0.3 ) {
                        squares.push_back(approx);
                    }
                }
            }
        }
    }
    return squares;
}

EDIT 17/08/2012:

pour dessiner les carrés détectés sur l'image utilisez ce code:

cv::Mat debugSquares( std::vector<std::vector<cv::Point> > squares, cv::Mat image )
{
    for ( int i = 0; i< squares.size(); i++ ) {
        // draw contour
        cv::drawContours(image, squares, i, cv::Scalar(255,0,0), 1, 8, std::vector<cv::Vec4i>(), 0, cv::Point());

        // draw bounding rect
        cv::Rect rect = boundingRect(cv::Mat(squares[i]));
        cv::rectangle(image, rect.tl(), rect.br(), cv::Scalar(0,255,0), 2, 8, 0);

        // draw rotated rect
        cv::RotatedRect minRect = minAreaRect(cv::Mat(squares[i]));
        cv::Point2f rect_points[4];
        minRect.points( rect_points );
        for ( int j = 0; j < 4; j++ ) {
            cv::line( image, rect_points[j], rect_points[(j+1)%4], cv::Scalar(0,0,255), 1, 8 ); // blue
        }
    }

    return image;
}
156
demandé sur Eric Platon 2011-12-29 16:20:57

5 réponses

il s'agit d'un sujet récurrent dans Stackoverflow et comme je n'ai pas pu trouver de mise en œuvre pertinente, j'ai décidé d'accepter le défi.

j'ai fait quelques modifications à la démo de carrés présents dans OpenCV et le code C++ résultant ci-dessous est capable de détecter une feuille de papier dans l'image:

void find_squares(Mat& image, vector<vector<Point> >& squares)
{
    // blur will enhance edge detection
    Mat blurred(image);
    medianBlur(image, blurred, 9);

    Mat gray0(blurred.size(), CV_8U), gray;
    vector<vector<Point> > contours;

    // find squares in every color plane of the image
    for (int c = 0; c < 3; c++)
    {
        int ch[] = {c, 0};
        mixChannels(&blurred, 1, &gray0, 1, ch, 1);

        // try several threshold levels
        const int threshold_level = 2;
        for (int l = 0; l < threshold_level; l++)
        {
            // Use Canny instead of zero threshold level!
            // Canny helps to catch squares with gradient shading
            if (l == 0)
            {
                Canny(gray0, gray, 10, 20, 3); // 

                // Dilate helps to remove potential holes between edge segments
                dilate(gray, gray, Mat(), Point(-1,-1));
            }
            else
            {
                    gray = gray0 >= (l+1) * 255 / threshold_level;
            }

            // Find contours and store them in a list
            findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE);

            // Test contours
            vector<Point> approx;
            for (size_t i = 0; i < contours.size(); i++)
            {
                    // approximate contour with accuracy proportional
                    // to the contour perimeter
                    approxPolyDP(Mat(contours[i]), approx, arcLength(Mat(contours[i]), true)*0.02, true);

                    // Note: absolute value of an area is used because
                    // area may be positive or negative - in accordance with the
                    // contour orientation
                    if (approx.size() == 4 &&
                            fabs(contourArea(Mat(approx))) > 1000 &&
                            isContourConvex(Mat(approx)))
                    {
                            double maxCosine = 0;

                            for (int j = 2; j < 5; j++)
                            {
                                    double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                                    maxCosine = MAX(maxCosine, cosine);
                            }

                            if (maxCosine < 0.3)
                                    squares.push_back(approx);
                    }
            }
        }
    }
}

après cette procédure est exécutée, la feuille de papier sera le plus grand carré dans vector<vector<Point> > :

opencv paper sheet detection

je vous laisse écrire la fonction pour trouver le plus grand carré. ;)

144
répondu karlphillip 2012-01-14 15:10:09

à moins qu'il N'y ait une autre exigence non spécifiée, Je convertirais simplement votre image couleur en échelle de gris et ne travaillerais qu'avec cela (pas besoin de travailler sur les 3 canaux, le contraste présent est déjà trop élevé). En outre, à moins qu'il n'y ait un problème spécifique concernant le redimensionnement, je travaillerais avec une version à échelle réduite de vos images, car elles sont relativement grandes et leur taille n'ajoute rien au problème résolu. Puis, enfin, votre problème est résolu avec un filtre médian, certains outils morphologiques de base, et statistiques (principalement pour le battage Otsu, ce qui est déjà fait pour vous).

voici ce que j'obtiens avec votre image d'échantillon et une autre image avec une feuille de papier que j'ai trouvé autour:

enter image description here enter image description here

le filtre médian est utilisé pour supprimer des détails mineurs de l'image, maintenant en échelle de gris. Il sera peut-être supprimer les lignes minces à l'intérieur du papier blanchâtre, qui est bon parce qu'alors vous finirez avec de minuscules composants connectés qui sont faciles à jeter. Après la médiane, appliquer un gradient morphologique (simplement dilation - erosion ) et binariser le résultat par Otsu. Le gradient morphologique est une bonne méthode pour garder les bords forts, il devrait être utilisé plus. Puis, comme ce gradient va augmenter la largeur du contour, appliquer un amincissement morphologique. Maintenant, vous pouvez jeter les petits composants.

À ce point, voici ce que nous avons avec l'image de droite ci-dessus (avant le dessin du polygone bleu), la gauche n'est pas montrée parce que le seul composant restant est celui qui décrit le papier:

enter image description here

étant donné les exemples, la seule question qui reste est de faire la distinction entre les éléments qui ressemblent à des rectangles et les autres qui ne ressemblent pas à des rectangles. Il s'agit de déterminer un rapport entre la surface de la coque convexe contenant la forme et la surface de sa limite encadré; le ratio 0.7 fonctionne très bien pour ces exemples. Il se peut que vous ayez également besoin de jeter les composants qui sont à l'intérieur du papier, mais pas dans ces exemples en utilisant cette méthode (néanmoins, faire cette étape devrait être très facile, surtout parce qu'il peut être fait par OpenCV directement).

pour référence, voici un exemple de code dans Mathematica:

f = Import["http://thwartedglamour.files.wordpress.com/2010/06/my-coffee-table-1-sa.jpg"]
f = ImageResize[f, ImageDimensions[f][[1]]/4]
g = MedianFilter[ColorConvert[f, "Grayscale"], 2]
h = DeleteSmallComponents[Thinning[
     Binarize[ImageSubtract[Dilation[g, 1], Erosion[g, 1]]]]]
convexvert = ComponentMeasurements[SelectComponents[
     h, {"ConvexArea", "BoundingBoxArea"}, #1 / #2 > 0.7 &], 
     "ConvexVertices"][[All, 2]]
(* To visualize the blue polygons above: *)
Show[f, Graphics[{EdgeForm[{Blue, Thick}], RGBColor[0, 0, 1, 0.5], 
     Polygon @@ convexvert}]]

S'il existe des situations plus variées où le rectangle du papier n'est pas aussi bien défini, ou l'approche le confond avec d'autres formes -- ces situations peuvent se produire en raison de diverses raisons, mais une cause commune est l'acquisition de mauvaise image -- puis essayer de combiner les étapes de pré-traitement avec le travail décrit dans le papier "détection de Rectangle basé sur une transformation de Hough fendue".

37
répondu mmgp 2013-02-16 04:20:21

Je suis en retard.


dans votre image, le papier est white , tandis que le fond est colored . Donc, il est préférable de détecter le papier est Saturation(饱和度) canal HSV color space . Reportez-vous d'abord au wiki HSL_and_HSV . Alors je vais copier la plupart des idées de ma réponse dans ce détecter Segment de couleur dans une image .


étapes principales:

  1. Lire BGR
  2. Convertissez l'image de bgr en hsv espace
  3. Seuil de la S canal
  4. puis trouver le contour externe max(ou faire Canny , ou HoughLines comme vous voulez, je choisis findContours ), approx pour obtenir les coins.

Voici mon résultat:

enter image description here


le code Python(Python 3.5 + OpenCV 3.3):

#!/usr/bin/python3
# 2017.12.20 10:47:28 CST
# 2017.12.20 11:29:30 CST

import cv2
import numpy as np

##(1) read into  bgr-space
img = cv2.imread("test2.jpg")

##(2) convert to hsv-space, then split the channels
hsv = cv2.cvtColor(img, cv2.COLOR_BGR2HSV)
h,s,v = cv2.split(hsv)

##(3) threshold the S channel using adaptive method(`THRESH_OTSU`) or fixed thresh
th, threshed = cv2.threshold(s, 50, 255, cv2.THRESH_BINARY_INV)

##(4) find all the external contours on the threshed S
_, cnts, _ = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
canvas  = img.copy()
#cv2.drawContours(canvas, cnts, -1, (0,255,0), 1)

## sort and choose the largest contour
cnts = sorted(cnts, key = cv2.contourArea)
cnt = cnts[-1]

## approx the contour, so the get the corner points
arclen = cv2.arcLength(cnt, True)
approx = cv2.approxPolyDP(cnt, 0.02* arclen, True)
cv2.drawContours(canvas, [cnt], -1, (255,0,0), 1, cv2.LINE_AA)
cv2.drawContours(canvas, [approx], -1, (0, 0, 255), 1, cv2.LINE_AA)

## Ok, you can see the result as tag(6)
cv2.imwrite("detected.png", canvas)

réponses:

  1. Détecter Segment de Couleur dans une image
  2. détection de contours dans opencv android
  3. OpenCV C++ / Obj-C: Détection d'une feuille de papier / détection carrée
9
répondu Silencer 2017-12-20 12:19:36

ce dont vous avez besoin est un quadrangle au lieu d'un rectangle tournant. RotatedRect vous donnera des résultats incorrects. Vous aurez également besoin d'une projection en perspective.

ce qui doit être fait est Basicly:

  • les trier de sorte que vous avez les 4 plus grands segments de ligne.
  • Croisent ceux les lignes et vous avez les 4 points de coin les plus probables.
  • transformer la matrice sur la perspective recueillie des points de coin et le rapport d'aspect de l'objet connu.

j'ai mis en œuvre une classe Quadrangle qui prend soin de la conversion contour to quadrangle et qui la transformera également au-dessus de la bonne perspective.

voir la mise en œuvre ici: Java OpenCV correction d'un contour

2
répondu Tim 2017-05-23 10:31:16

la feuille de papier est un peu vieille école. Si vous voulez vous attaquer à la détection de biais, alors il est préférable que vous visiez tout de suite la détection de ligne de texte. Avec cela, vous obtiendrez les extrémités gauche, droite, haut et bas. Jeter tous les graphiques dans l'image si vous ne voulez pas et puis faire quelques statistiques sur les segments de ligne de texte pour trouver la gamme d'angle plus se produisant ou plutôt l'angle. C'est ainsi que vous réduirez l'angle de déviation à un bon angle. Maintenant, après cela, vous mettez ces paramètres la faille angle et les extrémités de la table et couper l'image à ce qui est nécessaire.

quant à l'exigence actuelle d'image, il est préférable d'essayer CV_RETR_EXTERNAL au lieu de CV_RETR_LIST.

une autre méthode de détection des bordures consiste à former un classificateur aléatoire des forêts sur les bordures de papier, puis à utiliser le classificateur pour obtenir la carte des bordures. Il s'agit de loin d'une méthode robuste, mais elle exige de la formation et du temps.

forêts Aléatoires vont travailler avec de faibles les scénarios de différence de contraste par exemple le livre blanc sur le fond plus ou moins Blanc.

-1
répondu Anubhav Rohatgi 2016-12-14 12:23:53