Wie wird das Bild eines Etiketts auf einem Lebensmittelglas abgeflacht?


40

Ich möchte Etiketten auf einem Glas Lebensmittel fotografieren und sie so umwandeln, dass das Etikett flach ist, wobei die Größe der rechten und linken Seite so geändert wird, dass sie mit der Bildmitte übereinstimmt.

Im Idealfall möchte ich den Kontrast zwischen dem Etikett und dem Hintergrund verwenden, um die Kanten zu finden und die Korrektur anzuwenden. Ansonsten kann ich den Benutzer bitten, die Ecken und Seiten des Bildes irgendwie zu identifizieren.


Ich suche nach allgemeinen Techniken und Algorithmen, um ein Bild aufzunehmen, das sphärisch (in meinem Fall zylindrisch) verzerrt ist und das Bild abflachen kann. Gegenwärtig weist das Bild eines Etiketts, das um ein Glas oder eine Flasche gewickelt ist, Merkmale und Text auf, die schrumpfen, wenn es sich rechts oder links vom Bild zurückzieht. Auch die Linien, die den Rand des Etiketts kennzeichnen, sind nur in der Bildmitte parallel und verlaufen am rechten und linken Ende des Etiketts gegeneinander.

Nachdem ich das Bild bearbeitet habe, möchte ich ein fast perfektes Rechteck erhalten, in dem der Text und die Merkmale einheitlich dimensioniert sind, als hätte ich ein Foto des Etiketts aufgenommen, als es nicht auf dem Glas oder der Flasche war.

Außerdem würde es mir gefallen, wenn die Technik die Kanten des Etiketts automatisch erkennen könnte, um die geeignete Korrektur anzuwenden. Andernfalls müsste ich meinen Benutzer bitten, die Etikettengrenzen anzugeben.

Ich habe bereits gegoogelt und Artikel wie diesen gefunden: Abflachen gekrümmter Dokumente , aber ich suche etwas Einfacheres, da ich Etiketten mit einer einfachen Kurve benötige.


Nikie hat eine scheinbar umfassende Lösung. Es wird jedoch viel einfacher, wenn Sie wissen, dass die Kamera immer "quadratisch" zum Glas ist, ohne verwirrenden Hintergrund. Dann finden Sie die Ränder des Glases und wenden die einfache trigonometrische Transformation (Arcussinus?) An, ohne viel zusätzliches Fummeln. Sobald das Bild abgeflacht ist, können Sie das Etikett selbst isolieren.
Daniel R Hicks

@Daniel Das habe ich hier gemacht . Im Idealfall würde man auch die nicht perfekt parallele Projektion berücksichtigen, aber ich tat es nicht.
Szabolcs

Die Arbeit ist sehr gut. aber der Code zeigt Fehler in meinem System. Ich benutze Matlab 2017a ist es kompatibel mit ihm. Danke,
Satish Kumar

Antworten:


60

Eine ähnliche Frage wurde bei Mathematica.Stackexchange gestellt . Meine Antwort dort hat sich weiterentwickelt und ist am Ende ziemlich lang geworden, deshalb werde ich den Algorithmus hier zusammenfassen.

Abstrakt

Die Grundidee ist:

  1. Suchen Sie das Etikett.
  2. Finden Sie die Ränder des Etiketts
  3. Suchen Sie eine Zuordnung, die Bildkoordinaten Zylinderkoordinaten so zuordnet, dass sie die Pixel am oberen Rand des Etiketts ([etwas] / 0), die Pixel am rechten Rand (1 / [etwas]) usw. zuordnet.
  4. Transformieren Sie das Bild mithilfe dieser Zuordnung

Der Algorithmus funktioniert nur für Bilder, bei denen:

  1. das Etikett ist heller als der Hintergrund (dies wird für die Etikettenerkennung benötigt)
  2. Das Etikett ist rechteckig (dies wird verwendet, um die Qualität eines Mappings zu messen).
  3. das Glas ist (fast) vertikal (dies wird verwendet, um die Zuordnungsfunktion einfach zu halten)
  4. das Gefäß ist zylindrisch (dies wird verwendet, um die Zuordnungsfunktion einfach zu halten)

Der Algorithmus ist jedoch modular. Zumindest im Prinzip könnten Sie Ihre eigene Etikettenerkennung schreiben, die keinen dunklen Hintergrund erfordert, oder Sie könnten Ihre eigene Qualitätsmessfunktion schreiben, die mit elliptischen oder achteckigen Etiketten umgehen kann.

Ergebnisse

Diese Bilder wurden vollautomatisch verarbeitet, dh der Algorithmus nimmt das Quellbild auf, arbeitet einige Sekunden und zeigt dann das Mapping (links) und das unverzerrte Bild (rechts):

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Die nächsten Bilder wurden mit einer modifizierten Version des Algorithmus verarbeitet, wobei der Benutzer den linken und den rechten Rand des Glases (nicht das Etikett) auswählte, da die Krümmung des Etiketts nicht aus dem Bild in einer Frontalaufnahme (dh dem Ein vollautomatischer Algorithmus würde Bilder zurückgeben, die leicht verzerrt sind.

Bildbeschreibung hier eingeben

Bildbeschreibung hier eingeben

Implementierung:

1. Suchen Sie das Etikett

Das Etikett ist hell vor einem dunklen Hintergrund, sodass ich es mithilfe der Binarisierung leicht finden kann:

src = Import["http://i.stack.imgur.com/rfNu7.png"];
binary = FillingTransform[DeleteBorderComponents[Binarize[src]]]

digitalisiertes Bild

Ich wähle einfach die größte verbundene Komponente aus und gehe davon aus, dass dies die Bezeichnung ist:

labelMask = Image[SortBy[ComponentMeasurements[binary, {"Area", "Mask"}][[All, 2]], First][[-1, 2]]]

größte Komponente

2. Finden Sie die Ränder des Etiketts

Nächster Schritt: Finden Sie die oberen / unteren / linken / rechten Ränder mit einfachen abgeleiteten Faltungsmasken:

topBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1}, {-1}}]];
bottomBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1}, {1}}]];
leftBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{1, -1}}]];
rightBorder = DeleteSmallComponents[ImageConvolve[labelMask, {{-1, 1}}]];

Bildbeschreibung hier eingeben

Dies ist eine kleine Hilfsfunktion, die alle weißen Pixel in einem dieser vier Bilder findet und die Indizes in Koordinaten umwandelt ( Positiongibt Indizes zurück, und Indizes sind 1-basierte {y, x} -Tupel, wobei y = 1 am oberen Rand von steht Alle Bildverarbeitungsfunktionen erwarten jedoch Koordinaten, die auf 0-basierenden {x, y} -Tupeln basieren (wobei y = 0 der untere Teil des Bildes ist):

{w, h} = ImageDimensions[topBorder];
maskToPoints = Function[mask, {#[[2]]-1, h - #[[1]]+1} & /@ Position[ImageData[mask], 1.]];

3. Suchen Sie eine Zuordnung von Bild zu Zylinderkoordinaten

Jetzt habe ich vier separate Koordinatenlisten für den oberen, unteren, linken und rechten Rand des Etiketts. Ich definiere eine Zuordnung von Bildkoordinaten zu Zylinderkoordinaten:

arcSinSeries = Normal[Series[ArcSin[\[Alpha]], {\[Alpha], 0, 10}]]
Clear[mapping];
mapping[{x_, y_}] := 
   {
    c1 + c2*(arcSinSeries /. \[Alpha] -> (x - cx)/r) + c3*y + c4*x*y, 
    top + y*height + tilt1*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]] + tilt2*y*Sqrt[Clip[r^2 - (x - cx)^2, {0.01, \[Infinity]}]]
   }

Dies ist eine zylindrische Abbildung, die X / Y-Koordinaten im Quellbild auf zylindrische Koordinaten abbildet. Die Zuordnung hat 10 Freiheitsgrade für Höhe / Radius / Mitte / Perspektive / Neigung. Ich habe die Taylor-Reihe verwendet, um den Arcussinus zu approximieren, da die Optimierung nicht direkt mit ArcSin funktioniert. DasClipAnrufe sind mein Ad-hoc-Versuch, komplexe Nummern während der Optimierung zu verhindern. Hier gibt es einen Kompromiss: Einerseits sollte die Funktion einer exakten zylindrischen Abbildung so nahe wie möglich kommen, um eine möglichst geringe Verzerrung zu erzielen. Wenn es andererseits zu kompliziert ist, wird es viel schwieriger, automatisch optimale Werte für die Freiheitsgrade zu finden. (Das Schöne an der Bildverarbeitung mit Mathematica ist, dass Sie mit solchen mathematischen Modellen sehr einfach herumspielen, zusätzliche Begriffe für verschiedene Verzerrungen einfügen und dieselben Optimierungsfunktionen verwenden können, um endgültige Ergebnisse zu erzielen. Ich habe noch nie etwas tun können so mit OpenCV oder Matlab. Aber ich habe die symbolische Toolbox für Matlab nie ausprobiert, vielleicht macht das es nützlicher.)

Als nächstes definiere ich eine "Fehlerfunktion", die die Qualität eines Bildes misst -> Zylinderkoordinatenzuordnung. Es ist nur die Summe der Fehlerquadrate für die Randpixel:

errorFunction =
  Flatten[{
    (mapping[#][[1]])^2 & /@ maskToPoints[leftBorder],
    (mapping[#][[1]] - 1)^2 & /@ maskToPoints[rightBorder],
    (mapping[#][[2]] - 1)^2 & /@ maskToPoints[topBorder],
    (mapping[#][[2]])^2 & /@ maskToPoints[bottomBorder]
    }];

Diese Fehlerfunktion misst die "Qualität" einer Zuordnung: Sie ist am niedrigsten, wenn die Punkte am linken Rand (0 / [irgendetwas]) zugeordnet sind, Pixel am oberen Rand ([irgendetwas] / 0) usw. .

Jetzt kann ich Mathematica anweisen, Koeffizienten zu finden, die diese Fehlerfunktion minimieren. Ich kann über einige der Koeffizienten "Vermutungen anstellen" (z. B. den Radius und die Mitte des Glases im Bild). Ich benutze diese als Ausgangspunkte für die Optimierung:

leftMean = Mean[maskToPoints[leftBorder]][[1]];
rightMean = Mean[maskToPoints[rightBorder]][[1]];
topMean = Mean[maskToPoints[topBorder]][[2]];
bottomMean = Mean[maskToPoints[bottomBorder]][[2]];
solution = 
 FindMinimum[
   Total[errorFunction], 
    {{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0}, 
     {cx, (leftMean + rightMean)/2}, 
     {top, topMean}, 
     {r, rightMean - leftMean}, 
     {height, bottomMean - topMean}, 
     {tilt1, 0}, {tilt2, 0}}][[2]]

FindMinimumfindet Werte für die 10 Freiheitsgrade meiner Zuordnungsfunktion, die die Fehlerfunktion minimieren. Kombiniere das generische Mapping und diese Lösung und ich erhalte ein Mapping von X / Y-Bildkoordinaten, das zum Beschriftungsbereich passt. Ich kann dieses Mapping mit der ContourPlotFunktion von Mathematica visualisieren :

Show[src,
 ContourPlot[mapping[{x, y}][[1]] /. solution, {x, 0, w}, {y, 0, h}, 
  ContourShading -> None, ContourStyle -> Red, 
  Contours -> Range[0, 1, 0.1], 
  RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[2]] /. solution) <= 1]],
 ContourPlot[mapping[{x, y}][[2]] /. solution, {x, 0, w}, {y, 0, h}, 
  ContourShading -> None, ContourStyle -> Red, 
  Contours -> Range[0, 1, 0.2],
  RegionFunction -> Function[{x, y}, 0 <= (mapping[{x, y}][[1]] /. solution) <= 1]]]

Bildbeschreibung hier eingeben

4. Transformieren Sie das Bild

Schließlich verwende ich Mathematics ImageForwardTransformFunktion, um das Bild gemäß dieser Zuordnung zu verzerren:

ImageForwardTransformation[src, mapping[#] /. solution &, {400, 300}, DataRange -> Full, PlotRange -> {{0, 1}, {0, 1}}]

Das ergibt die oben gezeigten Ergebnisse.

Manuell unterstützte Version

Der obige Algorithmus ist vollautomatisch. Keine Anpassungen erforderlich. Es funktioniert einigermaßen gut, solange das Bild von oben oder unten aufgenommen wird. Wenn es sich jedoch um eine Frontalaufnahme handelt, kann der Radius des Glases nicht anhand der Form des Etiketts geschätzt werden. In diesen Fällen erhalte ich viel bessere Ergebnisse, wenn der Benutzer den linken / rechten Rand des Glases manuell eingibt und die entsprechenden Freiheitsgrade im Mapping explizit einstellt.

Mit diesem Code kann der Benutzer den linken / rechten Rand auswählen:

LocatorPane[Dynamic[{{xLeft, y1}, {xRight, y2}}], 
 Dynamic[Show[src, 
   Graphics[{Red, Line[{{xLeft, 0}, {xLeft, h}}], 
     Line[{{xRight, 0}, {xRight, h}}]}]]]]

LocatorPane

Dies ist der alternative Optimierungscode, bei dem Mittelpunkt und Radius explizit angegeben werden.

manualAdjustments = {cx -> (xLeft + xRight)/2, r -> (xRight - xLeft)/2};
solution = 
  FindMinimum[
   Total[minimize /. manualAdjustments], 
    {{c1, 0}, {c2, rightMean - leftMean}, {c3, 0}, {c4, 0}, 
     {top, topMean}, 
     {height, bottomMean - topMean}, 
     {tilt1, 0}, {tilt2, 0}}][[2]]
solution = Join[solution, manualAdjustments]

11
Entfernt Sonnenbrille ... Mutter Gottes ...
Spacey

Haben Sie zufällig einen Verweis auf das zylindrische Mapping? Und vielleicht Gleichungen für die inverse Abbildung? @ niki-estner
Ita
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.