
Dom
Ich habe das OpenCV-Quadraterkennungsbeispiel erfolgreich in meiner Testanwendung implementiert, muss aber jetzt die Ausgabe filtern, weil es ziemlich chaotisch ist – oder ist mein Code falsch?
Mich interessieren die vier Eckpunkte des Papiers zur Schräglagenreduzierung (so) und Weiterverarbeitung …
Input-Output:

Original Bild:
klicken
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;
}
Dies ist ein wiederkehrendes Thema in Stackoverflow und da ich keine relevante Implementierung finden konnte, habe ich mich entschieden, 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:
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);
}
}
}
}
}
Nachdem dieser Vorgang ausgeführt wurde, ist das Blatt Papier das größte Quadrat vector<vector<Point> >
:

Ich lasse Sie die Funktion schreiben, um das größte Quadrat zu finden. 😉

mmp
Sofern keine andere Anforderung nicht angegeben ist, würde ich Ihr Farbbild einfach in Graustufen konvertieren und nur damit arbeiten (keine Notwendigkeit, an 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 bestimmtes Problem bei der Größenänderung, da sie relativ groß sind und die Größe nichts zur Lösung des Problems beiträgt. Dann wird Ihr Problem schließlich mit einem Medianfilter, einigen grundlegenden morphologischen Werkzeugen und Statistiken (hauptsächlich für die Otsu-Schwellenwertbildung, die bereits für Sie erledigt wurde) gelöst.
Folgendes erhalte ich mit Ihrem Beispielbild und einem anderen Bild mit einem Blatt Papier, das ich in der Nähe gefunden habe:

Der Medianfilter wird verwendet, um kleinere Details aus dem jetzt graustufigen Bild zu entfernen. Es entfernt möglicherweise dünne Linien innerhalb des weißlichen Papiers, was gut ist, da Sie dann mit winzigen verbundenen Komponenten enden, die sich leicht entsorgen lassen. Wenden Sie nach dem Median einen morphologischen Gradienten an (einfach dilation
– erosion
) und das Ergebnis von Otsu binarisieren. 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 Farbverlauf die Konturbreite vergrößert. Jetzt können Sie kleine Komponenten verwerfen.
An diesem Punkt 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:

Angesichts der Beispiele bleibt jetzt nur noch das Problem, zwischen Komponenten zu unterscheiden, die wie Rechtecke aussehen, und anderen, die dies nicht tun. Hierbei geht es darum, ein Verhältnis zwischen der Fläche der konvexen Hülle, die die Form enthält, und der Fläche ihres Begrenzungsrahmens zu bestimmen; das Verhältnis 0,7 funktioniert gut für diese Beispiele. Es kann vorkommen, dass Sie auch Komponenten verwerfen müssen, die sich im Papier befinden, aber nicht in diesen Beispielen, indem Sie diese Methode verwenden (trotzdem sollte dieser Schritt sehr einfach sein, insbesondere weil er direkt über OpenCV ausgeführt werden kann).
Als Referenz ist hier ein Beispielcode in 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}]]
Wenn es unterschiedliche 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 – dann versuchen Sie, die Voreinstellungen zu kombinieren -Verarbeitungsschritte mit der in der Arbeit “Rectangle Detection based on a Windowed Hough Transform” beschriebenen Arbeit.

Kinght 金
Nun, ich bin spät dran.
In Ihrem Bild ist das Papier white
während der Hintergrund ist colored
. Es ist also besser, das Papier zu erkennen Saturation(饱和度)
Kanal ein HSV color space
. Siehe Wiki HSL_und_HSV Erste. Dann kopiere ich die meisten Ideen aus meiner Antwort in diesem Farbsegment erkennen in einem Bild.
Hauptschritte:
- Lies hinein
BGR
- Konvertieren Sie das Bild von
bgr
zu hsv
Platz
- Schwellwert für den S-Kanal
- Finden Sie dann die maximale Außenkontur (oder tun Sie es
Canny
oder HoughLines
wie Sie möchten, wähle ich findContours
), ungefähr um die Ecken zu bekommen.
Das ist mein Ergebnis:

Der Python-Code (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)
cnts = cv2.findContours(threshed, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)[-2]
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)
Verwandte Antworten:
- Wie erkennt man farbige Flecken in einem Bild mit OpenCV?
- Kantenerkennung auf farbigem Hintergrund mit OpenCV
- OpenCV C++/Obj-C: Erkennen eines Blatt Papiers / Square Detection
- Wie verwende ich `cv2.findContours` in verschiedenen OpenCV-Versionen?

Tim
Was Sie brauchen, ist ein Viereck anstelle eines gedrehten Rechtecks.
RotatedRect
wird Ihnen falsche Ergebnisse liefern. Außerdem benötigen Sie eine perspektivische Projektion.
Grundsätzlich ist Folgendes zu tun:
- Schleife durch alle Polygonsegmente und verbinde die, die fast gleich sind.
- Sortieren Sie sie so, dass Sie die 4 größten Liniensegmente haben.
- Wenn Sie diese Linien schneiden, haben Sie die 4 wahrscheinlichsten Eckpunkte.
- Transformieren Sie die Matrix über die aus den Eckpunkten gewonnene Perspektive und das Seitenverhältnis des bekannten Objekts.
Ich habe eine Klasse implementiert Quadrangle
die sich um die Umwandlung von Konturen in Vierecke kümmert und sie auch über die richtige Perspektive umwandelt.
Sehen Sie hier eine funktionierende Implementierung: Java OpenCV Entzerrung einer Kontur
Nachdem Sie den Begrenzungsrahmen des Dokuments erkannt haben, können Sie a Vier-Punkte-Perspektive transformieren um eine Vogelperspektive des Bildes von oben nach unten zu erhalten. Dadurch wird die Schräglage behoben und nur das gewünschte Objekt isoliert.
Eingangsbild:

Erkanntes Textobjekt

Top-down-Ansicht des Textdokuments

Code
from imutils.perspective import four_point_transform
import cv2
import numpy
# Load image, grayscale, Gaussian blur, Otsu's threshold
image = cv2.imread("1.png")
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (7,7), 0)
thresh = cv2.threshold(blur, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)[1]
# Find contours and sort for largest contour
cnts = cv2.findContours(thresh, cv2.RETR_EXTERNAL,cv2.CHAIN_APPROX_SIMPLE)
cnts = cnts[0] if len(cnts) == 2 else cnts[1]
cnts = sorted(cnts, key=cv2.contourArea, reverse=True)
displayCnt = None
for c in cnts:
# Perform contour approximation
peri = cv2.arcLength(c, True)
approx = cv2.approxPolyDP(c, 0.02 * peri, True)
if len(approx) == 4:
displayCnt = approx
break
# Obtain birds' eye view of image
warped = four_point_transform(image, displayCnt.reshape(4, 2))
cv2.imshow("thresh", thresh)
cv2.imshow("warped", warped)
cv2.imshow("image", image)
cv2.waitKey()
Das Erkennen von Papierbögen ist eine Art alte Schule. Wenn Sie die Schräglagenerkennung angehen möchten, ist es besser, wenn Sie direkt auf die Erkennung von Textzeilen abzielen. 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 vorkommenden Winkelbereich bzw. Winkel zu finden. So grenzen Sie einen guten Neigungswinkel ein. Danach stellen Sie diese Parameter ein, den Neigungswinkel und die Extrema, um das Bild zu entzerren und zu schneiden, was erforderlich ist.
Was die aktuelle Bildanforderung betrifft, ist es besser, wenn Sie CV_RETR_EXTERNAL anstelle von CV_RETR_LIST versuchen.
Eine andere Methode zum Erkennen von Kanten besteht darin, einen Klassifikator für zufällige Gesamtstrukturen auf die Papierkanten zu trainieren und dann den Klassifikator zu verwenden, um die Kantenkarte zu erhalten. Dies ist bei weitem eine robuste Methode, erfordert jedoch Training und Zeit.
Random Forests funktionieren mit Szenarien mit geringem Kontrastunterschied, z. B. weißes Papier auf ungefähr weißem Hintergrund.
9973500cookie-checkOpenCV C++/Obj-C: Erkennen eines Blatt Papiers / Square Detectionyes
Originalbild finden Sie hier.
– Karl Philipp
14. Januar 2012 um 15:14 Uhr
Ich denke, Sie können den Titel der Frage für so etwas anpassen Erkennen eines Blatt Papiers wenn Sie denken, dass es angemessener ist.
– Karl Philipp
14. Januar 2012 um 15:19 Uhr
@moosgummi Ich möchte die gleiche Funktionalität haben, die Sie implementiert haben, dh “Ecken des aufgenommenen Bildes / Dokuments erkennen”. Wie haben Sie das erreicht? Kann ich OpenCV in meiner iPhone-Anwendung verwenden? Bitte schlagen Sie mir einen besseren Weg vor, dies zu haben.
– Ajay Sharma
19. Januar 2012 um 7:17 Uhr
Haben Sie jemals etwas mit OpenCV gemacht? Überhaupt eine Bewerbung?
– Karl Philipp
20. Januar 2012 um 11:28 Uhr
Es ist erwähnenswert, dass das Flag CV_RETR_EXTERNAL verwendet werden kann, wenn die Konturen gefunden werden, um alle Konturen innerhalb einer geschlossenen Form abzulehnen.
– mehfoos yacoob
21. August 2013 um 16:31 Uhr