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

J’ai réussi à implémenter l’exemple de détection de carrés OpenCV dans mon application de test, mais maintenant il faut filtrer la sortie, car elle est en désordre – ou mon code est-il incorrect?

Je suis intéressé par les quatre points d’angle du papier pour la réduction de l’asymésortinge (comme ça ) et le traitement ultérieur…

Entrée sortie: Entrée sortie

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 >)findSquaresInImage:(cv::Mat)_image { std::vector<std::vector > 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 > contours; for( int c = 0; c < 3; c++ ) { int ch[] = {c, 0}; mixChannels(&timg, 1, &gray0, 1, ch, 1); for( int l = 0; l = (l+1)*255/N; } cv::findContours(gray, contours, CV_RETR_LIST, CV_CHAIN_APPROX_SIMPLE); std::vector approx; for( size_t i = 0; i  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 > 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(), 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; } 

Ceci est un sujet récurrent dans Stackoverflow et comme je n’ai pas pu trouver d’implémentation pertinente, j’ai décidé d’accepter le défi.

J’ai apporté quelques modifications à la démo de carrés présente 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 >& squares) { // blur will enhance edge detection Mat blurred(image); medianBlur(image, blurred, 9); Mat gray0(blurred.size(), CV_8U), gray; vector > 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 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); } } } } } 

Une fois cette procédure exécutée, la feuille de papier sera le plus grand carré du vector > :

détection de feuille de papier opencv

Je vous laisse écrire la fonction pour trouver le plus grand carré. 😉

À moins qu’il y ait une autre exigence non spécifiée, je convertirais simplement votre image couleur en niveaux de gris et ne travaillerais qu’avec cela (pas besoin de travailler sur les 3 canaux, le contraste actuel est déjà trop élevé). En outre, sauf en cas de problème spécifique de redimensionnement, je travaillerais avec une version réduite de vos images, car elles sont relativement volumineuses et leur taille n’ajoute rien au problème résolu. Enfin, votre problème est résolu avec un filtre médian, des outils morphologiques de base et des statistiques (principalement pour le seuillage Otsu, qui est déjà fait pour vous).

Voici ce que j’obtiens avec votre exemple d’image et une autre image avec une feuille de papier trouvée autour de moi:

entrer la description de l'image icientrer la description de l'image ici

Le filtre médian est utilisé pour supprimer les détails mineurs de l’image, maintenant en niveaux de gris. Il supprimera peut-être les lignes fines à l’intérieur du papier blanchâtre, ce qui est une bonne chose car vous finirez avec de minuscules composants connectés qui sont faciles à éliminer. Après la médiane, appliquer un gradient morphologique (simplement dilationerosion ) et binariser le résultat par Otsu. Le gradient morphologique est une bonne méthode pour conserver des arêtes solides, il faut l’utiliser davantage. Puis, comme ce gradient va augmenter la largeur du contour, appliquer un amincissement morphologique. Vous pouvez maintenant éliminer les petits composants.

À ce stade, voici ce que nous avons avec la bonne image ci-dessus (avant de dessiner le polygone bleu), celle de gauche n’est pas affichée car le seul composant restant est celui qui décrit le papier:

entrer la description de l'image ici

Compte tenu des exemples, le seul problème qui rest est de distinguer les composants qui ressemblent à des rectangles et ceux qui ne le sont pas. Il s’agit de déterminer un rapport entre la surface de la shell convexe contenant la forme et la zone de son cadre de délimitation; le ratio 0,7 fonctionne bien pour ces exemples. Il se peut que vous ayez également besoin de supprimer des composants qui se trouvent à l’intérieur du papier, mais pas dans ces exemples en utilisant cette méthode (néanmoins, cette étape devrait être très simple, car elle peut être effectuée directement via OpenCV).

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 y a des situations plus variées où le rectangle du papier n’est pas bien défini, ou l’approche confond avec d’autres formes – ces situations peuvent se produire pour diverses raisons, mais une cause commune est une mauvaise acquisition d’image – alors essayez de combiner la pré -processus de traitement avec le travail décrit dans le document “Détection de rectangle basé sur une transformation Hough fenêtrée”.

Eh bien, je suis en retard.


Dans votre image, le papier est white , tandis que l’arrière-plan est colored . Il est donc préférable de détecter que le papier est Saturation(饱和度) dans l’ HSV color space . Prenez d’abord en compte le wiki HSL_and_HSV . Ensuite, je copierai la plupart des idées de ma réponse dans ce segment de détection de couleur dans une image .


Étapes principales:

  1. Lire dans BGR
  2. Convertir l’image de hsv en hsv space
  3. Seuil du canal S
  4. Ensuite, trouvez le contour externe maximum (ou faites Canny , ou HoughLines comme vous le souhaitez, je choisis findContours ), approximativement pour obtenir les coins.

Ceci est mon résultat:

entrer la description de l'image ici


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 connexes:

  1. Détecter un segment coloré dans une image
  2. Détection des contours dans opencv android
  3. OpenCV C ++ / Obj-C: Détection d’une feuille de papier / Détection carrée

Ce dont vous avez besoin est un quadrilatère au lieu d’un rectangle pivoté. RotatedRect vous donnera des résultats incorrects. Vous aurez également besoin d’une projection en perspective.

Fondamentalement, ce qui doit être fait est:

  • Traverser tous les segments de polygone et connecter ceux qui sont presque égaux.
  • Triez-les pour avoir les 4 segments de ligne les plus importants.
  • Intersectionnez ces lignes et vous avez les 4 points d’angle les plus probables.
  • Transformez la masortingce sur la perspective collectée à partir des points d’angle et du rapport d’aspect de l’object connu.

J’ai implémenté une classe Quadrangle qui prend en charge la conversion du contour en quadrilatère et la transforme également dans la bonne perspective.

Voir une implémentation fonctionnelle ici: Java OpenCV redressant un contour

Détecter la feuille de papier est une sorte de vieille école. Si vous voulez vous attaquer à la détection de biais, il est préférable que vous cherchiez directement à détecter les lignes de texte. Avec cela, vous obtiendrez les extrêmes à gauche, à droite, en haut et en bas. Supprimez tous les graphiques de l’image si vous ne le souhaitez pas, puis effectuez des statistiques sur les segments de ligne de texte pour trouver la plage d’angles la plus fréquente ou plutôt l’angle. C’est ainsi que vous réduirez votre angle de biais. Maintenant, après cela, vous placez ces parameters sur l’angle d’inclinaison et sur les extrêmes pour qu’ils se redressent et que l’image soit réduite à ce qui est requirejs.

En ce qui concerne l’exigence d’image actuelle, il est préférable d’essayer CV_RETR_EXTERNAL au lieu de CV_RETR_LIST.

Une autre méthode de détection des contours consiste à former un classificateur de forêts aléatoires sur les bords du papier, puis à utiliser le classificateur pour obtenir la carte de bord. C’est de loin une méthode robuste mais nécessite une formation et du temps.

Les forêts aléatoires fonctionneront avec des scénarios à faible contraste, par exemple du papier blanc sur un fond grossièrement blanc.