Ich habe das OpenCV-Quadraterkennungsbeispiel erfolgreich in meiner Testanwendung implementiert, muss jetzt aber die Ausgabe filtern, da sie ziemlich chaotisch ist - oder ist mein Code falsch?
Ich bin für Skew Reduktion in den vier Eckpunkten des Papiers interessiert (wie das ) und die weitere Verarbeitung ...
Original Bild:
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:
Verwenden Sie diesen Code, um die erkannten Quadrate auf dem Bild zu zeichnen:
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;
}
Antworten:
Dies ist ein wiederkehrendes Thema in Stackoverflow. Da ich keine relevante Implementierung finden konnte, habe ich mich entschlossen, die Herausforderung anzunehmen.
Ich habe einige Änderungen an der in OpenCV vorhandenen Squares-Demo vorgenommen, und der resultierende C ++ - Code unten kann ein Blatt Papier im Bild erkennen:
Nachdem dieser Vorgang ausgeführt wurde, ist das Blatt Papier das größte Quadrat in
vector<vector<Point> >
:Ich lasse Sie die Funktion schreiben, um das größte Quadrat zu finden. ;)
quelle
for (int c = 0; c < 3; c++)
, der für die Iteration auf jedem Kanal des Bildes verantwortlich ist. Zum Beispiel können Sie festlegen, dass es nur auf einem Kanal iteriert :) Vergessen Sie nicht, die Abstimmung zu erhöhen.angle()
ist eine Hilfsfunktion . Wie in der Antwort angegeben, basiert dieser Code auf samples / cpp / squares.cpp, die in OpenCV vorhanden sind.Sofern keine andere Anforderung nicht angegeben ist, würde ich Ihr Farbbild einfach in Graustufen konvertieren und nur damit arbeiten (keine Notwendigkeit, auf den 3 Kanälen zu arbeiten, der vorhandene Kontrast ist bereits zu hoch). Außerdem würde ich mit einer verkleinerten Version Ihrer Bilder arbeiten, es sei denn, es gibt ein spezifisches Problem bei der Größenänderung, da diese relativ groß sind und die Größe dem zu lösenden Problem nichts hinzufügt. Schließlich wird Ihr Problem mit einem Medianfilter, einigen grundlegenden morphologischen Werkzeugen und Statistiken gelöst (hauptsächlich für die Otsu-Schwellwertbildung, die bereits für Sie durchgeführt wurde).
Folgendes erhalte ich mit Ihrem Beispielbild und einem anderen Bild mit einem Blatt Papier, das ich gefunden habe:
Der Medianfilter wird verwendet, um kleinere Details aus dem jetzt Graustufenbild zu entfernen. Möglicherweise werden dünne Linien im weißlichen Papier entfernt, was gut ist, da Sie dann mit winzigen verbundenen Komponenten enden, die leicht zu verwerfen sind. Wenden Sie nach dem Median einen morphologischen Gradienten an (einfach
dilation
-erosion
) und binarisieren Sie das Ergebnis mit Otsu. Der morphologische Gradient ist eine gute Methode, um starke Kanten zu erhalten. Er sollte häufiger verwendet werden. Wenden Sie dann eine morphologische Ausdünnung an, da dieser Gradient die Konturbreite erhöht. Jetzt können Sie kleine Komponenten verwerfen.An dieser Stelle haben wir Folgendes mit dem rechten Bild oben (vor dem Zeichnen des blauen Polygons), das linke wird nicht angezeigt, da die einzige verbleibende Komponente diejenige ist, die das Papier beschreibt:
In Anbetracht der Beispiele bleibt nur noch die Unterscheidung zwischen Komponenten, die wie Rechtecke aussehen, und anderen, die dies nicht tun. Hierbei wird ein Verhältnis zwischen der Fläche der konvexen Hülle, die die Form enthält, und der Fläche ihres Begrenzungsrahmens bestimmt. Das Verhältnis 0,7 funktioniert für diese Beispiele gut. Es kann vorkommen, dass Sie auch Komponenten im Papier verwerfen müssen, jedoch nicht in diesen Beispielen, indem Sie diese Methode verwenden (dieser Schritt sollte jedoch sehr einfach sein, insbesondere weil er direkt über OpenCV ausgeführt werden kann).
Als Referenz finden Sie hier einen Beispielcode in Mathematica:
Wenn es unterschiedlichere Situationen gibt, in denen das Rechteck des Papiers nicht so gut definiert ist oder der Ansatz es mit anderen Formen verwechselt - diese Situationen können aus verschiedenen Gründen auftreten, aber eine häufige Ursache ist eine schlechte Bildaufnahme -, versuchen Sie, das Pre zu kombinieren -Verarbeitungsschritte mit der im Artikel "Rechteckerkennung basierend auf einer Windowed Hough Transformation" beschriebenen Arbeit.
quelle
Concept is the same
. (Ich habe Mathematica nie verwendet, daher kann ich den Code nicht verstehen.) Und die Unterschiede, die Sie erwähnt haben, sind Unterschiede, aber keine anderen oder größeren Ansätze. Wenn Sie dies beispielsweise noch nicht getan haben, überprüfen Sie Folgendes:Nun, ich bin zu spät.
In Ihrem Bild ist das Papier
white
, während der Hintergrund istcolored
. Es ist also besser zu erkennen, dass das PapierSaturation(饱和度)
kanalisiert istHSV color space
. Lesen Sie zuerst das Wiki HSL_and_HSV . Dann kopiere ich die meisten Ideen aus meiner Antwort in dieses farbige Segment erkennen in ein Bild .Hauptschritte:
BGR
bgr
in denhsv
WeltraumCanny
oderHoughLines
wie Sie möchten, ich wählefindContours
), ca., um die Ecken zu erhalten.Das ist mein Ergebnis:
Der Python-Code (Python 3.5 + OpenCV 3.3):
Verwandte Antworten:
quelle
Was Sie brauchen, ist ein Viereck anstelle eines gedrehten Rechtecks.
RotatedRect
gibt Ihnen falsche Ergebnisse. Außerdem benötigen Sie eine perspektivische Projektion.Grundsätzlich muss Folgendes getan werden:
Ich habe eine Klasse implementiert,
Quadrangle
die sich um die Konvertierung von Konturen in Vierecke kümmert und diese auch über die richtige Perspektive transformiert.Eine funktionierende Implementierung finden Sie hier: Java OpenCV entwirft eine Kontur
quelle
Sobald Sie den Begrenzungsrahmen des Dokuments erkannt haben, können Sie eine Vierpunkt-Perspektiventransformation durchführen , um eine Draufsicht auf das Bild von oben nach unten zu erhalten. Dadurch wird der Versatz behoben und nur das gewünschte Objekt isoliert.
Eingabebild:
Erkanntes Textobjekt
Ansicht des Textdokuments von oben nach unten
Code
quelle
Das Erkennen eines Blattes Papier ist eine alte Schule. Wenn Sie sich mit der Erkennung von Schräglauf befassen möchten, ist es besser, wenn Sie sofort die Erkennung von Textzeilen anstreben. Damit erhalten Sie die Extrema links, rechts, oben und unten. Verwerfen Sie alle Grafiken im Bild, wenn Sie dies nicht möchten, und führen Sie dann einige Statistiken zu den Textzeilensegmenten durch, um den am häufigsten auftretenden Winkelbereich bzw. Winkel zu ermitteln. Auf diese Weise werden Sie auf einen guten Schräglaufwinkel eingrenzen. Danach setzen Sie diese Parameter auf den Schräglaufwinkel und die Extreme, um das Bild zu entschneiden und auf das zu schneiden, was erforderlich ist.
Für die aktuelle Bildanforderung ist es besser, CV_RETR_EXTERNAL anstelle von CV_RETR_LIST zu verwenden.
Eine andere Methode zum Erkennen von Kanten besteht darin, einen zufälligen Waldklassifizierer an den Papierkanten zu trainieren und dann den Klassifizierer zu verwenden, um die Kantenzuordnung zu erhalten. Dies ist bei weitem eine robuste Methode, erfordert jedoch Schulung und Zeit.
Zufällige Wälder funktionieren mit Szenarien mit geringem Kontrastunterschied, z. B. Whitepaper auf ungefähr weißem Hintergrund.
quelle