Comment aplatir l'image d'une étiquette sur un pot de nourriture?

40

J'aimerais prendre des photos d'étiquettes sur un pot d'aliments et pouvoir les transformer de manière à ce que l'étiquette soit plate, le côté droit et le côté gauche étant redimensionnés de manière à être au même niveau que le centre de l'image.

Idéalement, j'aimerais utiliser le contraste entre l'étiquette et l'arrière-plan pour trouver les bords et appliquer la correction. Sinon, je peux demander à l'utilisateur d'identifier en quelque sorte les coins et les côtés de l'image.


Je recherche des techniques générales et des algorithmes pour prendre une image asymétrique sphérique (cylindrique dans mon cas) et pouvant aplatir l'image. Actuellement, l'image d'une étiquette qui est enroulée autour d'un pot ou d'une bouteille aura des caractéristiques et du texte qui se contracte à mesure qu'elle se rétracte à droite ou à gauche de l'image. De plus, les lignes qui désignent le bord de l'étiquette ne seront que parallèles au centre de l'image et seront inclinées l'une vers l'autre à l'extrême droite et à l'extrême gauche de l'étiquette.

Après avoir manipulé l'image, j'aimerais disposer d'un rectangle presque parfait où le texte et les éléments sont de taille uniforme, comme si j'avais pris une photo de l'étiquette alors qu'elle n'était ni sur le pot ni sur la bouteille.

Aussi, j'aimerais que la technique détecte automatiquement les bords de l'étiquette, afin d'appliquer la correction appropriée. Sinon, je devrais demander à mon utilisateur d'indiquer les limites de l'étiquette.

J'ai déjà googlé et trouvé des articles comme celui-ci: aplatir les documents incurvés , mais je cherche quelque chose d'un peu plus simple, car j'ai besoin d'utiliser des étiquettes avec une simple courbe.

Mahboudz
la source
Nikie propose ce qui semble être une solution complète. Cela devient beaucoup plus simple, cependant, si vous savez que la caméra est toujours "carrée" par rapport au bocal, sans arrière-plan confus. Ensuite, vous trouvez les bords du bocal et appliquez la transformation trigonométrique simple (arcsine?), Sans beaucoup plus de bidouillage. Une fois l'image aplatie, vous pouvez isoler l'étiquette elle-même.
Daniel R Hicks
@ Daniel C'est ce que j'ai fait ici . Idéalement, on prendrait également en compte la projection non parfaitement parallèle, mais je ne l'ai pas fait.
Szabolcs
le travail est très bon. mais le code montrant une erreur dans mon système. J'utilise Matlab 2017a est-ce compatible avec elle. merci,
Satish Kumar

Réponses:

60

Une question similaire a été posée sur Mathematica.Stackexchange . Ma réponse a évolué et a été assez longue à la fin, donc je vais résumer l’algorithme ici.

Abstrait

L'idée de base est:

  1. Trouvez l'étiquette.
  2. Trouver les bordures de l'étiquette
  3. Trouvez un mappage qui mappe les coordonnées de l'image aux coordonnées du cylindre afin qu'il mappe les pixels le long du bord supérieur de l'étiquette sur ([tout] / 0), les pixels le long du bord droit sur (1 / [tout]), etc.
  4. Transformer l'image en utilisant ce mappage

L'algorithme ne fonctionne que pour les images où:

  1. l'étiquette est plus claire que l'arrière-plan (cela est nécessaire pour la détection de l'étiquette)
  2. l'étiquette est rectangulaire (elle sert à mesurer la qualité d'une cartographie)
  3. le pot est (presque) vertical (cela permet de garder la fonction de cartographie simple)
  4. le pot est cylindrique (ceci permet de garder la fonction de cartographie simple)

Cependant, l'algorithme est modulaire. Au moins en principe, vous pouvez écrire votre propre détection d’étiquette qui ne nécessite pas d’arrière-plan sombre ou votre propre fonction de mesure de la qualité, compatible avec les étiquettes elliptiques ou octogonales.

Résultats

Ces images ont été traitées de manière entièrement automatique, c’est-à-dire que l’algorithme prend l’image source, fonctionne pendant quelques secondes, puis affiche le mappage (à gauche) et l’image non déformée (à droite):

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

entrez la description de l'image ici

Les images suivantes ont été traitées avec une version modifiée de l’algorithme, si l’utilisateur sélectionne les bords gauche et droit du pot (et non l’étiquette), car la courbure de l’étiquette ne peut pas être estimée à partir de l’image dans un plan frontal (c.-à-d. Le algorithme entièrement automatique renvoie des images légèrement déformées):

entrez la description de l'image ici

entrez la description de l'image ici

La mise en oeuvre:

1. Trouver l'étiquette

L'étiquette est brillante devant un fond sombre, je peux donc la trouver facilement en utilisant la binarisation:

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

image binarisée

Je choisis simplement le plus grand composant connecté et suppose que c'est l'étiquette:

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

plus grand composant

2. Trouver les bordures de l'étiquette

Étape suivante: recherchez les bordures haut / bas / gauche / droite à l’aide de simples masques de convolution dérivés:

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

entrez la description de l'image ici

Il s’agit d’une petite fonction d’aide qui trouve tous les pixels blancs dans l’une de ces quatre images et convertit les index en coordonnées ( Positionrenvoie les index et les index sont des {{, x} -tuples basés sur 1, où y = 1 est au sommet de l’image, mais toutes les fonctions de traitement de l’image attendent des coordonnées, qui sont des {x, y} -tuples basés sur 0, où y = 0 est le bas de l’image):

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

3. Trouver une correspondance entre les coordonnées de l’image et les coordonnées du cylindre

Maintenant, j'ai quatre listes distinctes de coordonnées des bordures supérieure, inférieure, gauche et droite de l'étiquette. Je définis une correspondance entre les coordonnées de l'image et les coordonnées du cylindre:

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

Il s'agit d'une application cylindrique, qui mappe les coordonnées X / Y de l'image source en coordonnées cylindriques. La cartographie présente 10 degrés de liberté hauteur / rayon / centre / perspective / inclinaison. J'ai utilisé la série de Taylor pour approximer l'arc sinus, car je ne pouvais pas utiliser l'optimisation directement avec ArcSin. leCliples appels sont ma tentative ad hoc d’empêcher les nombres complexes lors de l’optimisation. Il y a un compromis à faire ici: d'une part, la fonction doit être aussi proche que possible d'une représentation cylindrique exacte, afin de produire la distorsion la plus faible possible. D'autre part, si c'est trop compliqué, il devient beaucoup plus difficile de trouver automatiquement des valeurs optimales pour les degrés de liberté. (Ce qui est bien avec le traitement des images avec Mathematica, c’est que vous pouvez manipuler des modèles mathématiques comme celui-ci très facilement, introduire des termes supplémentaires pour différentes distorsions et utiliser les mêmes fonctions d’optimisation pour obtenir des résultats finaux. Je n’ai jamais rien pu faire. comme cela avec OpenCV ou Matlab. Mais je n’ai jamais essayé la boîte à outils symbolique pour Matlab, c’est peut-être plus utile.)

Ensuite, je définis une "fonction d'erreur" qui mesure la qualité d'une représentation image -> cylindre. C'est juste la somme des erreurs au carré pour les pixels de bordure:

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

Cette fonction d'erreur mesure la "qualité" d'un mappage: sa valeur la plus basse si les points du bord gauche sont mappés sur (0 / [rien]), les pixels du bord supérieur sont mappés sur ([rien] / 0), etc. .

Je peux maintenant dire à Mathematica de trouver des coefficients qui minimisent cette fonction d'erreur. Je peux faire des "suppositions éclairées" sur certains des coefficients (par exemple, le rayon et le centre du pot dans l'image). Je les utilise comme points de départ de l'optimisation:

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

FindMinimumtrouve des valeurs pour les 10 degrés de liberté de ma fonction de mappage qui minimisent la fonction d'erreur. Combinez le mappage générique et cette solution et vous obtenez un mappage à partir des coordonnées d'image X / Y, qui correspond à la zone d'étiquette. Je peux visualiser cette cartographie en utilisant la ContourPlotfonction 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]]]

entrez la description de l'image ici

4. Transformer l'image

Enfin, j'utilise la ImageForwardTransformfonction Mathematica pour déformer l'image en fonction de ce mappage:

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

Cela donne les résultats comme indiqué ci-dessus.

Version assistée manuellement

L'algorithme ci-dessus est complètement automatique. Aucun ajustement requis. Cela fonctionne raisonnablement bien tant que la photo est prise d'en haut ou en bas. Mais s'il s'agit d'un tir frontal, le rayon du pot ne peut pas être estimé à partir de la forme de l'étiquette. Dans ces cas, j'obtiens de bien meilleurs résultats si je laisse l'utilisateur entrer manuellement les bordures gauche / droite du pot et définir explicitement les degrés de liberté correspondants dans le mappage.

Ce code permet à l'utilisateur de sélectionner les bordures gauche / droite:

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

LocatorPane

C'est le code d'optimisation alternatif, où le centre et le rayon sont donnés explicitement.

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
la source
11
Enlève les lunettes de soleil ... mère de dieu ...
Spacey
Avez-vous une référence à la cartographie cylindrique? Et peut-être des équations pour la cartographie inverse? @ niki-estner
Ita