¿Cómo aplanar la imagen de una etiqueta en un frasco de comida?

40

Me gustaría tomar fotos de las etiquetas en un frasco de comida y poder transformarlas para que la etiqueta sea plana, con los lados derecho e izquierdo redimensionados para que estén al mismo nivel que el centro de la imagen.

Idealmente, me gustaría usar el contraste entre la etiqueta y el fondo para encontrar los bordes y aplicar la corrección. De lo contrario, puedo pedirle al usuario que identifique de alguna manera las esquinas y los lados de la imagen.


Estoy buscando técnicas y algoritmos generales para tomar una imagen que esté sesgada esféricamente (cilíndrica en mi caso) y que pueda aplanar la imagen. Actualmente, la imagen de una etiqueta que se envuelve alrededor de un frasco o botella tendrá características y texto que se encogerá a medida que retrocede a la derecha o izquierda de la imagen. Además, las líneas que denotan el borde de la etiqueta, solo serán paralelas en el centro de la imagen, y se sesgarán entre sí en el extremo derecho e izquierdo de la etiqueta.

Después de manipular la imagen, me gustaría quedar con un rectángulo casi perfecto donde el texto y las características tienen un tamaño uniforme, como si tomara una foto de la etiqueta cuando no estaba en el frasco o la botella.

Además, me gustaría que la técnica pudiera detectar automáticamente los bordes de la etiqueta para aplicar la corrección adecuada. De lo contrario, tendría que pedirle a mi usuario que indique los límites de la etiqueta.

Ya busqué en Google y encontré artículos como este: aplanar documentos curvos , pero estoy buscando algo un poco más simple, ya que mis necesidades son etiquetas con una curva simple.

mahboudz
fuente
Nikie tiene lo que parece ser una solución integral. Sin embargo, se vuelve mucho más simple si sabes que la cámara siempre está "cuadrada" en el frasco, sin un fondo confuso. Luego, encuentra los bordes del frasco y aplica la transformación trigonométrica simple (¿arcoseno?), Sin demasiados ajustes adicionales. Una vez que se aplana la imagen, puede aislar la etiqueta.
Daniel R Hicks
@Daniel Eso es lo que hice aquí . Lo ideal sería tener en cuenta la proyección no perfectamente paralela también, pero no lo hice.
Szabolcs
El trabajo es muy bueno. pero el código muestra error en mi sistema. Estoy usando matlab 2017a si es compatible con él. gracias,
Satish Kumar

Respuestas:

60

Se hizo una pregunta similar en Mathematica.Stackexchange . Mi respuesta allí evolucionó y se hizo bastante larga al final, así que resumiré el algoritmo aquí.

Abstracto

La idea básica es:

  1. Encuentra la etiqueta.
  2. Encuentra los bordes de la etiqueta
  3. Busque una asignación que asigne las coordenadas de la imagen a las coordenadas del cilindro de modo que asigne los píxeles a lo largo del borde superior de la etiqueta a ([cualquier cosa] / 0), los píxeles a lo largo del borde derecho a (1 / [cualquier cosa]) y así sucesivamente.
  4. Transforma la imagen usando este mapeo

El algoritmo solo funciona para imágenes donde:

  1. la etiqueta es más brillante que el fondo (esto es necesario para la detección de la etiqueta)
  2. la etiqueta es rectangular (esto se usa para medir la calidad de un mapeo)
  3. el frasco es (casi) vertical (esto se usa para mantener la función de mapeo simple)
  4. el frasco es cilíndrico (esto se usa para mantener la función de mapeo simple)

Sin embargo, el algoritmo es modular. Al menos en principio, podría escribir su propia detección de etiquetas que no requiera un fondo oscuro, o podría escribir su propia función de medición de calidad que pueda hacer frente a etiquetas elípticas u octogonales.

Resultados

Estas imágenes se procesaron de forma totalmente automática, es decir, el algoritmo toma la imagen de origen, funciona durante unos segundos y luego muestra el mapeo (izquierda) y la imagen sin distorsión (derecha):

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Las siguientes imágenes se procesaron con una versión modificada del algoritmo, donde el usuario selecciona los bordes izquierdo y derecho del frasco (no la etiqueta), porque la curvatura de la etiqueta no puede estimarse a partir de la imagen en una toma frontal (es decir, el algoritmo completamente automático devolvería imágenes que están ligeramente distorsionadas):

ingrese la descripción de la imagen aquí

ingrese la descripción de la imagen aquí

Implementación:

1. Encuentra la etiqueta

La etiqueta es brillante frente a un fondo oscuro, por lo que puedo encontrarla fácilmente usando la binarización:

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

imagen binarizada

Simplemente elijo el componente conectado más grande y asumo que esa es la etiqueta:

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

componente más grande

2. Encuentra los bordes de la etiqueta

Siguiente paso: encuentre los bordes superior / inferior / izquierdo / derecho utilizando máscaras de convolución derivadas simples:

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

ingrese la descripción de la imagen aquí

Esta es una pequeña función auxiliar que encuentra todos los píxeles blancos en una de estas cuatro imágenes y convierte los índices en coordenadas ( Positiondevuelve índices, y los índices están basados ​​en 1 {y, x} -tuplas, donde y = 1 está en la parte superior de la imagen, pero todas las funciones de procesamiento de imágenes esperan coordenadas, que son tuplas {x, y} basadas en 0, donde y = 0 es la parte inferior de la imagen):

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

3. Encuentre un mapeo de la imagen a las coordenadas del cilindro

Ahora tengo cuatro listas separadas de coordenadas de los bordes superior, inferior, izquierdo y derecho de la etiqueta. Defino una asignación de coordenadas de imagen a coordenadas de cilindro:

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

Esta es una asignación cilíndrica, que asigna coordenadas X / Y en la imagen de origen a coordenadas cilíndricas. El mapeo tiene 10 grados de libertad para altura / radio / centro / perspectiva / inclinación. Utilicé la serie Taylor para aproximar el seno del arco, porque no pude lograr que la optimización funcionara directamente con ArcSin. losClipLas llamadas son mi intento ad-hoc para evitar números complejos durante la optimización. Aquí hay una compensación: por un lado, la función debe estar lo más cerca posible de un mapeo cilíndrico exacto, para dar la menor distorsión posible. Por otro lado, si es demasiado complicado, se vuelve mucho más difícil encontrar valores óptimos para los grados de libertad automáticamente. (Lo bueno de procesar imágenes con Mathematica es que puedes jugar con modelos matemáticos como este muy fácilmente, introducir términos adicionales para diferentes distorsiones y usar las mismas funciones de optimización para obtener resultados finales. Nunca he podido hacer nada así usando OpenCV o Matlab. Pero nunca probé la caja de herramientas simbólica para Matlab, quizás eso lo haga más útil).

A continuación defino una "función de error" que mide la calidad de una imagen -> mapeo de coordenadas de cilindro. Es solo la suma de los errores al cuadrado de los píxeles del borde:

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

Esta función de error mide la "calidad" de una asignación: es más baja si los puntos en el borde izquierdo se asignan a (0 / [cualquier cosa]), los píxeles en el borde superior se asignan a ([cualquier cosa] / 0) y así sucesivamente .

Ahora puedo decirle a Mathematica que busque coeficientes que minimicen esta función de error. Puedo hacer "conjeturas educadas" sobre algunos de los coeficientes (por ejemplo, el radio y el centro del frasco en la imagen). Los uso como puntos de partida de la optimización:

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]]

FindMinimumencuentra valores para los 10 grados de libertad de mi función de mapeo que minimizan la función de error. Combino el mapeo genérico y esta solución y obtengo un mapeo de coordenadas de imagen X / Y, que se ajusta al área de la etiqueta. Puedo visualizar este mapeo usando la ContourPlotfunción de Mathematica :

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]]]

ingrese la descripción de la imagen aquí

4. Transforma la imagen

Finalmente, uso la ImageForwardTransformfunción de Mathematica para distorsionar la imagen de acuerdo con este mapeo:

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

Eso da los resultados como se muestra arriba.

Versión asistida manualmente

El algoritmo anterior es completamente automático. No se requieren ajustes. Funciona razonablemente bien siempre que la imagen se tome desde arriba o desde abajo. Pero si se trata de un disparo frontal, el radio del frasco no puede estimarse a partir de la forma de la etiqueta. En estos casos, obtengo resultados mucho mejores si dejo que el usuario ingrese los bordes izquierdo / derecho del frasco manualmente y establezca los grados de libertad correspondientes en el mapeo explícitamente.

Este código le permite al usuario seleccionar los bordes izquierdo / derecho:

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

Panel localizador

Este es el código de optimización alternativo, donde el centro y el radio se dan explícitamente.

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]
Niki Estner
fuente
11
Se quita las gafas de sol ... madre de dios ...
Spacey
¿Tiene alguna referencia al mapeo cilíndrico? ¿Y quizás ecuaciones para el mapeo inverso? @ niki-estner
Ita