OpenCV C++ / Obj-C: wykrywanie kartki papieru / wykrywanie kwadratu

Udało mi się zaimplementować przykład wykrywania kwadratów OpenCV w mojej aplikacji testowej, ale teraz muszę filtrować wyjście, ponieważ jest dość bałagan-czy mój kod jest zły?

Interesują mnie cztery narożne punkty papieru do redukcji skosu (jak że) i dalszej obróbki ...

Wejście I Wyjście: Wejście I Wyjście

Oryginalny obraz:

Kliknij

Kod:

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;
}

edytuj 17/08/2012:

Aby narysować wykryte kwadraty na obrazku użyj tego kodu:

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;
}
Author: Eric Platon, 2011-12-29

5 answers

Jest to powtarzający się temat w Stackoverflow i ponieważ nie byłem w stanie znaleźć odpowiedniej implementacji, postanowiłem przyjąć wyzwanie.

Wprowadziłem kilka modyfikacji do dema squares obecnego w OpenCV i uzyskany kod C++ poniżej jest w stanie wykryć kartkę papieru na obrazku:

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);
                    }
            }
        }
    }
}

Po wykonaniu tej procedury arkusz papieru będzie największym kwadratem w vector<vector<Point> >:

wykrywanie arkuszy papieru opencv

Pozwalam ci napisać funkcję, aby znaleźć największy kwadrat. ;)

 144
Author: karlphillip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-14 15:10:09

O ile nie ma innych wymagań, po prostu przekonwertowałbym Twój kolorowy obraz na skalę szarości i pracował tylko z tym (nie trzeba pracować na 3 kanałach, kontrast jest już zbyt wysoki). Ponadto, chyba że istnieje jakiś konkretny problem dotyczący zmiany rozmiaru, pracowałbym z pomniejszoną wersją twoich obrazów, ponieważ są one stosunkowo duże, a rozmiar nic nie dodaje do rozwiązywanego problemu. Następnie, w końcu, twój problem jest rozwiązany z filtrem mediana, niektóre podstawowe narzędzia morfologiczne i statystyka (głównie dla progu Otsu, który jest już zrobiony dla Ciebie).

Oto, co uzyskałem z twojego przykładowego obrazu i innego obrazu z arkusza papieru, który znalazłem wokół:

Tutaj wpisz opis obrazkaTutaj wpisz opis obrazka

Filtr mediana służy do usuwania drobnych szczegółów z obrazu, teraz w skali szarości. Prawdopodobnie usunie cienkie linie wewnątrz białawego papieru, co jest dobre, ponieważ wtedy skończysz z małymi połączonymi komponentami, które łatwo wyrzucić. Po mediana, zastosować gradient morfologiczny (po prostu dilation - erosion) i binaryzować wynik przez Otsu. Gradient morfologiczny jest dobrą metodą na utrzymanie mocnych krawędzi, należy go częściej stosować. Następnie, ponieważ ten gradient zwiększy szerokość konturu, zastosuj przerzedzenie morfologiczne. Teraz możesz odrzucić małe komponenty.

W tym momencie, oto co mamy z prawym obrazkiem powyżej (przed narysowaniem niebieskiego wielokąta), lewy nie jest pokazany, ponieważ jedynym pozostałym składnikiem jest opis referatu:

Tutaj wpisz opis obrazka

Biorąc pod uwagę przykłady, teraz pozostaje tylko rozróżnienie między komponentami wyglądającymi jak prostokąty, a innymi, które nie. Jest to kwestia określenia stosunku między obszarem wypukłego kadłuba zawierającym kształt a obszarem jego obwiedni; stosunek 0,7 działa dobrze dla tych przykładów. Może się zdarzyć, że trzeba również odrzucić komponenty znajdujące się wewnątrz papieru, ale nie w tych przykładach za pomocą tej metody (niemniej jednak, zrobienie tego kroku powinno być bardzo proste, zwłaszcza, że można to zrobić bezpośrednio przez OpenCV).

Dla odniesienia, oto przykładowy kod w 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}]]

Jeśli są bardziej zróżnicowane sytuacje, w których prostokąt papieru nie jest tak dobrze zdefiniowany lub podejście myli go z innymi kształtami - sytuacje te mogą się zdarzyć z różnych powodów, ale częstą przyczyną jest złe pozyskiwanie obrazu-spróbuj połączyć etapy wstępnego przetwarzania z opisaną pracą w artykule "wykrywanie prostokąta w oparciu o transformację okienkową".

 37
Author: mmgp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-02-16 04:20:21

Jestem spóźniona.


Na Twoim obrazku papier to white, podczas gdy tło to colored. Lepiej więc wykryć, że papier jest Saturation(饱和度) kanałem w HSV color space. Najpierw zapoznaj się z wiki HSL_and_HSV. Następnie skopiuję większość pomysłów z mojej odpowiedzi w tym Wykryj kolorowy Segment na obrazku .


Główne kroki:

  1. Czytaj w BGR
  2. Konwersja obrazu z bgr na hsv spacja
  3. próg kanału S
  4. wtedy znajdź maksymalny zewnętrzny kontur (lub zrób Canny, lub HoughLines jak chcesz, wybieram findContours), OK, aby uzyskać rogi.

Oto mój wynik:

Tutaj wpisz opis obrazka


Kod Pythona (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)

Podobne odpowiedzi:

  1. wykrywanie kolorowego segmentu na obrazie
  2. wykrywanie krawędzi w OpenCV android
  3. OpenCV C++ / Obj-C: wykrywanie kartki papieru / kwadratu Detekcja
 9
Author: Silencer,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-12-20 12:19:36

Potrzebujesz czworokąt zamiast obróconego prostokąta. RotatedRect da ci błędne wyniki. Będziesz również potrzebował projekcji perspektywicznej.

Zasadniczo to, co trzeba zrobić, to:

  • Pętla przez wszystkie segmenty wielokąta i połączyć te, które są prawie równe.
  • posortuj je tak, aby miały 4 największe segmenty linii.
  • przeciąć te linie i masz 4 najbardziej prawdopodobne punkty narożne.
  • transformacja macierzy nad perspektywą zebrane z punktów narożnych i proporcji znanego obiektu.

Zaimplementowałem klasę Quadrangle, która zajmuje się konwersją konturu do czworokąta, a także przekształci ją w odpowiednią perspektywę.

Zobacz działającą implementację tutaj: Java OpenCV deskewing a contour

 2
Author: Tim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 10:31:16

Wykrywanie kartek papieru to trochę stara szkoła. Jeśli chcesz poradzić sobie z wykrywaniem pochylenia, lepiej jest od razu dążyć do wykrywania linii tekstu. Z tym otrzymasz skrajności lewo, prawo, góra i dół. Odrzuć dowolną grafikę na obrazie, jeśli nie chcesz, a następnie wykonaj statystyki dotyczące segmentów linii tekstowej, aby znaleźć najbardziej występujący Zakres kąta lub raczej kąt. W ten sposób zawęzisz się do dobrego kąta pochylenia. Teraz po tym kładziesz te parametry kąt pochylenia i ekstrema do deskew i posiekać obraz do tego, co jest wymagane.

Jeśli chodzi o bieżące wymagania dotyczące obrazu, lepiej jest spróbować CV_RETR_EXTERNAL zamiast CV_RETR_LIST.

Inną metodą wykrywania krawędzi jest wytrenowanie losowego klasyfikatora na krawędziach papieru, a następnie użycie klasyfikatora do uzyskania mapy krawędzi. Jest to zdecydowanie solidna metoda, ale wymaga szkolenia i czasu.

Losowe lasy będą działać ze scenariuszami o niskiej różnicy kontrastu, na przykład biała księga na temat mniej więcej białe tło.

 -1
Author: Anubhav Rohatgi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-14 12:23:53