Come appiattire l'immagine di un'etichetta su un barattolo di cibo?


40

Vorrei scattare foto di etichette su un barattolo di cibo ed essere in grado di trasformarle in modo che l'etichetta sia piatta, con il lato destro e sinistro ridimensionato per essere uniforme con il centro dell'immagine.

Idealmente, vorrei usare il contrasto tra l'etichetta e lo sfondo per trovare i bordi e applicare la correzione. Altrimenti, posso chiedere all'utente di identificare in qualche modo gli angoli e i lati dell'immagine.


Sto cercando tecniche e algoritmi generali per acquisire un'immagine sferica (nel mio caso cilindrica) e che può appiattire l'immagine. Attualmente l'immagine di un'etichetta che è avvolta attorno a un barattolo o una bottiglia, avrà caratteristiche e testo che si restringono mentre si allontana a destra o a sinistra dell'immagine. Anche le linee che indicano il bordo dell'etichetta, saranno solo parallele al centro dell'immagine e si inclineranno l'una verso l'altra sull'estrema destra e sinistra dell'etichetta.

Dopo aver manipolato l'immagine, mi piacerebbe essere lasciato con un rettangolo quasi perfetto in cui il testo e le caratteristiche sono di dimensioni uniformi, come se avessi scattato una foto dell'etichetta quando non era sul barattolo o sulla bottiglia.

Inoltre, mi piacerebbe se la tecnica potesse rilevare automaticamente i bordi dell'etichetta, al fine di applicare la correzione adeguata. Altrimenti dovrei chiedere al mio utente di indicare i limiti dell'etichetta.

Ho già cercato su Google e trovato articoli come questo: appiattire i documenti curvi , ma sto cercando qualcosa di un po 'più semplice, poiché le mie esigenze sono per le etichette con una curva semplice.


Nikie ha quella che sembra essere una soluzione onnicomprensiva. Diventa molto più semplice, però, se sai che la fotocamera è sempre "quadrata" rispetto al vaso, senza sfondo confuso. Quindi trovi i bordi del barattolo e applichi la semplice trasformazione trigonometrica (arcsine?), Senza troppi armeggi aggiuntivi. Una volta che l'immagine è appiattita è possibile isolare l'etichetta stessa.
Daniel R Hicks,

@Daniel Questo è quello che ho fatto qui . Idealmente si dovrebbe prendere in considerazione anche la proiezione non perfettamente parallela, ma non l'ho fatto.
Szabolcs,

il lavoro è molto buono. ma il codice mostra errore nel mio sistema. sto usando Matlab 2017a è compatibile con esso. grazie,
Satish Kumar,

Risposte:


60

Una domanda simile è stata posta su Mathematica.Stackexchange . La mia risposta laggiù si è evoluta e alla fine è diventata piuttosto lunga, quindi riassumo l'algoritmo qui.

Astratto

L'idea di base è:

  1. Trova l'etichetta.
  2. Trova i bordi dell'etichetta
  3. Trova una mappatura che associ le coordinate dell'immagine alle coordinate del cilindro in modo da mappare i pixel lungo il bordo superiore dell'etichetta su ([nulla] / 0), i pixel lungo il bordo destro su (1 / [nulla]) e così via.
  4. Trasforma l'immagine usando questa mappatura

L'algoritmo funziona solo per immagini in cui:

  1. l'etichetta è più luminosa dello sfondo (questo è necessario per il rilevamento dell'etichetta)
  2. l'etichetta è rettangolare (viene utilizzata per misurare la qualità di una mappatura)
  3. il vaso è (quasi) verticale (questo è usato per mantenere semplice la funzione di mappatura)
  4. il barattolo è cilindrico (serve per mantenere semplice la funzione di mappatura)

Tuttavia, l'algoritmo è modulare. Almeno in linea di principio, potresti scrivere il tuo rilevamento di etichette che non richiede uno sfondo scuro o potresti scrivere la tua funzione di misurazione della qualità in grado di far fronte a etichette ellittiche o ottagonali.

risultati

Queste immagini sono state elaborate in modo completamente automatico, ovvero l'algoritmo prende l'immagine di origine, funziona per alcuni secondi, quindi mostra la mappatura (a sinistra) e l'immagine non distorta (a destra):

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Le immagini successive sono state elaborate con una versione modificata dell'algoritmo, in cui l'utente seleziona i bordi sinistro e destro del vaso (non l'etichetta), poiché la curvatura dell'etichetta non può essere stimata dall'immagine in uno scatto frontale (ovvero l'algoritmo completamente automatico restituirebbe immagini leggermente distorte):

inserisci qui la descrizione dell'immagine

inserisci qui la descrizione dell'immagine

Implementazione:

1. Trova l'etichetta

L'etichetta è luminosa di fronte a uno sfondo scuro, quindi posso trovarla facilmente usando la binarizzazione:

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

immagine binarizzata

Scelgo semplicemente il componente collegato più grande e presumo che sia l'etichetta:

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

componente più grande

2. Trova i bordi dell'etichetta

Passaggio successivo: trova i bordi superiore / inferiore / sinistro / destro utilizzando semplici maschere di convoluzione derivata:

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

inserisci qui la descrizione dell'immagine

Questa è una piccola funzione di supporto che trova tutti i pixel bianchi in una di queste quattro immagini e converte gli indici in coordinate ( Positionrestituisce gli indici e gli indici sono a base {y, x} -tuple, dove y = 1 è nella parte superiore di l'immagine, ma tutte le funzioni di elaborazione delle immagini si aspettano coordinate, che sono a base {0, y} -tuple, dove y = 0 è il fondo dell'immagine):

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

3. Trova una mappatura dall'immagine alle coordinate del cilindro

Ora ho quattro elenchi separati di coordinate dei bordi superiore, inferiore, sinistro, destro dell'etichetta. Definisco una mappatura dalle coordinate dell'immagine alle coordinate del 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]}]]
   }

Questa è una mappatura cilindrica, che mappa le coordinate X / Y nell'immagine sorgente su coordinate cilindriche. La mappatura ha 10 gradi di libertà per altezza / raggio / centro / prospettiva / inclinazione. Ho usato la serie Taylor per approssimare il seno dell'arco, perché non riuscivo a far funzionare l'ottimizzazione direttamente con ArcSin. IlCliple chiamate sono il mio tentativo ad-hoc di prevenire numeri complessi durante l'ottimizzazione. C'è un compromesso qui: da un lato, la funzione dovrebbe essere il più vicino possibile a una mappatura cilindrica esatta, per dare la minima distorsione possibile. D'altra parte, se è complicato, diventa molto più difficile trovare automaticamente valori ottimali per i gradi di libertà. (La cosa bella dell'elaborazione delle immagini con Mathematica è che puoi giocare con modelli matematici come questo molto facilmente, introdurre termini aggiuntivi per distorsioni diverse e utilizzare le stesse funzioni di ottimizzazione per ottenere risultati finali. Non sono mai stato in grado di fare nulla come quello usando OpenCV o Matlab. Ma non ho mai provato la cassetta degli attrezzi simbolica per Matlab, forse questo lo rende più utile.)

Successivamente definisco una "funzione di errore" che misura la qualità di un'immagine -> mappatura delle coordinate del cilindro. È solo la somma degli errori al quadrato per i pixel del bordo:

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

Questa funzione di errore misura la "qualità" di una mappatura: è più bassa se i punti sul bordo sinistro sono mappati su (0 / [nulla]), i pixel sul bordo superiore sono mappati su ([nulla] / 0) e così via .

Ora posso dire a Mathematica di trovare coefficienti che minimizzano questa funzione di errore. Posso fare "ipotesi istruite" su alcuni dei coefficienti (ad esempio il raggio e il centro del vaso nell'immagine). Uso questi come punti di partenza per l'ottimizzazione:

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

FindMinimumtrova valori per i 10 gradi di libertà della mia funzione di mappatura che minimizzano la funzione di errore. Combina la mappatura generica e questa soluzione e ottengo una mappatura dalle coordinate dell'immagine X / Y, che si adatta all'area dell'etichetta. Posso visualizzare questa mappatura usando la ContourPlotfunzione di 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]]]

inserisci qui la descrizione dell'immagine

4. Trasforma l'immagine

Infine, utilizzo la ImageForwardTransformfunzione di Mathematica per distorcere l'immagine secondo questa mappatura:

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

Questo dà i risultati come mostrato sopra.

Versione assistita manualmente

L'algoritmo sopra è completamente automatico. Nessuna regolazione richiesta. Funziona abbastanza bene fintanto che la foto viene scattata dall'alto o dal basso. Ma se si tratta di un colpo frontale, il raggio del barattolo non può essere stimato dalla forma dell'etichetta. In questi casi, ottengo risultati molto migliori se lascio che l'utente inserisca manualmente i bordi sinistro / destro del vaso e imposti esplicitamente i corrispondenti gradi di libertà nella mappatura.

Questo codice consente all'utente di selezionare i bordi sinistro / destro:

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

LocatorPane

Questo è il codice di ottimizzazione alternativo, in cui il centro e il raggio sono indicati esplicitamente.

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
Rimuove gli occhiali da sole ... madre di dio ...
Spacey,

Ti capita di avere un riferimento alla mappatura cilindrica? E forse equazioni per la mappatura inversa? @ niki-estner
Ita,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.