Z powodzeniem wdrożyłem przykład wykrywania kwadratu OpenCV w mojej aplikacji testowej, ale teraz muszę filtrować dane wyjściowe, ponieważ jest to dość bałagan - czy mój kod jest nieprawidłowy?
Interesują mnie cztery punkty narożne papieru w celu zmniejszenia pochylenia (jak to ) i dalszego przetwarzania…
Oryginalny obraz:
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;
}
EDYCJA 17/08/2012:
Aby narysować wykryte kwadraty na obrazie, 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;
}
Odpowiedzi:
Jest to powtarzający się temat w Stackoverflow, a ponieważ nie mogłem znaleźć odpowiedniej implementacji, postanowiłem zaakceptować wyzwanie.
Wprowadziłem kilka modyfikacji kwadratu demonstracyjnego obecnego w OpenCV i wynikowy kod C ++ poniżej jest w stanie wykryć arkusz papieru na obrazie:
Po wykonaniu tej procedury arkusz papieru będzie największym kwadratem w
vector<vector<Point> >
:Pozwalam ci napisać funkcję znalezienia największego kwadratu. ;)
źródło
for (int c = 0; c < 3; c++)
, który odpowiada za iterację na każdym kanale obrazu. Na przykład możesz ustawić iterację tylko na jednym kanale :) Nie zapomnij podnieść głosu.angle()
to funkcja pomocnicza . Jak stwierdzono w odpowiedzi, ten kod jest oparty na próbkach / cpp / squares.cpp obecnych w OpenCV.O ile nie określono innych wymagań, po prostu przekonwertowałbym twój kolorowy obraz na skalę szarości i pracowałem tylko z tym (nie trzeba pracować na 3 kanałach, obecny kontrast jest już zbyt wysoki). Ponadto, chyba że istnieje jakiś konkretny problem dotyczący zmiany rozmiaru, pracowałbym ze zmniejszoną wersją twoich obrazów, ponieważ są one stosunkowo duże, a rozmiar nic nie dodaje do rozwiązania problemu. Wreszcie twój problem został rozwiązany dzięki filtrowi medianowemu, niektórym podstawowym narzędziom morfologicznym i statystykom (głównie w przypadku progowania Otsu, które jest już dla Ciebie zrobione).
Oto, co otrzymuję z twojego przykładowego obrazu i jakiegoś innego obrazu z arkuszem papieru, który znalazłem wokół:
Filtr środkowy 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 drobnymi połączonymi komponentami, które są łatwe do odrzucenia. Po medianie zastosuj gradient morfologiczny (po prostu
dilation
-erosion
) i binaryzuj wynik przez Otsu. Gradient morfologiczny jest dobrą metodą na utrzymanie silnych 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 elementy.W tym momencie oto, co mamy z prawym obrazem powyżej (przed narysowaniem niebieskiego wielokąta), lewy nie jest pokazany, ponieważ jedynym pozostałym składnikiem jest ten opisujący papier:
Biorąc pod uwagę przykłady, teraz pozostaje tylko kwestia rozróżnienia między komponentami, które wyglądają jak prostokąty, a innymi, które nie. Jest to kwestia ustalenia 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 będziesz musiał także odrzucić komponenty znajdujące się w papierze, ale nie w tych przykładach, używając tej metody (niemniej jednak wykonanie tego kroku powinno być bardzo łatwe, zwłaszcza, że można to zrobić bezpośrednio przez OpenCV).
Dla odniesienia, oto przykładowy kod w Mathematica:
Jeśli istnieją 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 przyczyn, ale częstą przyczyną jest zła akwizycja obrazu - następnie spróbuj połączyć -przetwarzanie kroków z pracą opisaną w artykule „Wykrywanie prostokąta na podstawie transformacji Windough Hougha”.
źródło
Concept is the same
. (Nigdy nie korzystałem z Mathematiki, więc nie rozumiem kodu.) Wspomniane różnice są różnicami, ale nie innym podejściem ani głównymi. Jeśli nadal tego nie robiłeś Na przykład sprawdź to:Jestem spóźniony.
Na twoim zdjęciu jest papier
white
, a tło jestcolored
. Tak, to lepiej, aby wykryć papier jestSaturation(饱和度)
kanał wHSV color space
. Najpierw zapoznaj się z wiki HSL_i_HSV . Następnie skopiuję większość pomysłów z mojej odpowiedzi w tym Wykryj kolorowy segment na obrazie .Główne kroki:
BGR
bgr
nahsv
spacjęCanny
lub,HoughLines
jak chcesz, wybieramfindContours
), w przybliżeniu, aby uzyskać rogi.Oto mój wynik:
Kod Python (Python 3.5 + OpenCV 3.3):
Powiązane odpowiedzi:
źródło
Potrzebny jest czworokąt zamiast obróconego prostokąta.
RotatedRect
da nieprawidłowe wyniki. Będziesz także potrzebować projekcji perspektywicznej.Zasadniczo należy zrobić:
Zaimplementowałem klasę
Quadrangle
która zajmuje się konwersją konturu do czworokąta, a także przekształci go we właściwej perspektywie.Zobacz działającą implementację tutaj: Java OpenCV deskewing kontur
źródło
Po wykryciu ramki granicznej dokumentu możesz wykonać czteropunktową transformację perspektywiczną, aby uzyskać widok z góry z lotu ptaka obrazu. To naprawi pochylenie i wyizoluje tylko pożądany obiekt.
Obraz wejściowy:
Wykryty obiekt tekstowy
Widok z góry dokumentu tekstowego
Kod
źródło
Wykrywanie kartki papieru to trochę stara szkoła. Jeśli chcesz zająć się wykrywaniem przekrzywienia, lepiej jest od razu dążyć do wykrycia linii tekstu. Dzięki temu uzyskasz skrajności w 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 tekstu, aby znaleźć najbardziej występujący zakres kątów, a raczej kąt. W ten sposób zawęzisz się do dobrego kąta pochylenia. Teraz ustawiasz te parametry kąt pochylenia i skrajności do prostowania i przycinasz obraz do wymaganego.
Jeśli chodzi o bieżące wymagania dotyczące obrazu, lepiej jest wypróbować CV_RETR_EXTERNAL zamiast CV_RETR_LIST.
Inną metodą wykrywania krawędzi jest trenowanie losowego klasyfikatora lasów 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łym papierze na w przybliżeniu białym tle.
źródło