Як розрівняти зображення етикетки на харчовій банці?


40

Я хотів би сфотографувати етикетки на баночці з їжею та мати змогу перетворити їх, щоб етикетка була плоскою, з правою та лівою стороною розміру розміром, щоб бути рівним в центрі зображення.

В ідеалі я хотів би використовувати контраст між міткою та фоном, щоб знайти краї та застосувати виправлення. В іншому випадку я можу попросити користувача якось визначити кути та сторони зображення.


Я шукаю загальні прийоми та алгоритми, щоб зробити зображення, яке викривлене сферично (у моєму випадку циліндрично) і може згладити зображення. В даний час зображення етикетки, яка обгорнута навколо банки або пляшки, матиме функції та текст, який скорочується, коли він відступає праворуч або ліворуч від зображення. Також лінії, що позначають край етикетки, будуть паралельними лише в центрі зображення і будуть коситись у бік правої та лівої крайньої мітки етикетки.

Після маніпулювання зображенням я хотів би залишитись майже ідеальним прямокутником, де текст і особливості мають однаковий розмір, як би я сфотографував етикетку, коли її не було на банці чи пляшці.

Також мені хотілося б, якби техніка могла автоматично виявити краї етикетки, щоб застосувати відповідну корекцію. Інакше мені доведеться попросити свого користувача вказати межі мітки.

Я вже в Google і знайшов такі статті: вирівнювання вигнутих документів , але я шукаю щось трохи простіше, оскільки мої потреби полягають у етикетках із простою кривою.


У Нікі є всеосяжне рішення. Однак це стає набагато простіше, якщо ви знаєте, що камера завжди "квадратна" до банки, не плутаючи фону. Потім ви знайдете краї баночки і застосуєте просте тригонометричне (дугоподібне?) Перетворення, без особливих додаткових хитань. Як тільки зображення вирівняне, ви можете ізолювати саму мітку.
Даніель Р Хікс

@Daniel Саме це я і зробив тут . В ідеалі можна було б врахувати і не ідеально паралельну проекцію, але я цього не зробив.
Саболч

робота дуже хороша. але код, що відображає помилку в моїй системі. я використовую matlab 2017a чи сумісний він з ним. дякую,
Сатиш Кумар

Відповіді:


60

Аналогічне питання було поставлено на Mathematica.Stackexchange . Моя відповідь там розвинулася і отримала досить довго, врешті-решт, тому я підсумую алгоритм тут.

Анотація

Основна ідея:

  1. Знайдіть етикетку.
  2. Знайдіть межі етикетки
  3. Знайдіть зіставлення, яке відображає координати зображень на координати циліндрів, щоб воно відображало пікселі уздовж верхньої межі мітки до ([нічого] / 0), пікселів уздовж правої межі до (1 / [що-небудь]) тощо.
  4. Перетворіть зображення за допомогою цього відображення

Алгоритм працює лише для зображень, де:

  1. ярлик яскравіший за фон (це потрібно для виявлення мітки)
  2. мітка прямокутна (використовується для вимірювання якості відображення)
  3. баночка (майже) вертикальна (це використовується для спрощення функції картографування)
  4. баночка циліндрична (використовується для простоти функціонування карти)

Однак алгоритм модульний. Принаймні в принципі, ви можете написати власне виявлення міток, яке не потребує темного фону, або ви можете написати власну функцію вимірювання якості, яка може впоратися з еліптичними або восьмикутними мітками.

Результати

Ці зображення обробляються повністю автоматично, тобто алгоритм приймає вихідне зображення, працює протягом декількох секунд, потім показує відображення (зліва) та неспотворене зображення (праворуч):

введіть тут опис зображення

введіть тут опис зображення

введіть тут опис зображення

введіть тут опис зображення

введіть тут опис зображення

введіть тут опис зображення

введіть тут опис зображення

Наступні зображення оброблялися з модифікованою версією алгоритму, якщо користувач вибирав ліву і праву межі jar (не мітка), тому що кривизна мітки не може бути оцінена із зображення у лобовому кадрі (тобто повністю автоматичний алгоритм повертає зображення, дещо спотворені):

введіть тут опис зображення

введіть тут опис зображення

Впровадження:

1. Знайдіть етикетку

Етикетка перед темним фоном яскрава, тому я легко знаходжу її за допомогою бінаризації:

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

бінарні зображення

Я просто вибираю найбільший підключений компонент і припускаю, що це мітка:

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

найбільший компонент

2. Знайдіть межі етикетки

Наступний крок: знайдіть верхню / нижню / ліву / праву межі за допомогою простих похідних масок згортки:

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

введіть тут опис зображення

Це невелика допоміжна функція, яка знаходить усі білі пікселі в одному з цих чотирьох зображень і перетворює індекси в координати ( Positionповертає індекси та індекси на 1-основі {y, x} -пари, де y = 1 знаходиться вгорі Але всі функції обробки зображень очікують координат, які базуються на 0 {x, y} -двічі, де y = 0 - нижня частина зображення):

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

3. Знайдіть відображення від зображення до координат циліндра

Зараз у мене є чотири окремих списки координат верхньої, нижньої, лівої, правої меж етикетки. Я визначаю відображення від координат зображення до координат циліндра:

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

Це циліндричне відображення, яке відображає X / Y-координати у вихідному зображенні на циліндричні координати. Відображення має 10 градусів свободи для висоти / радіуса / центру / перспективи / нахилу. Я використовував серію Тейлора, щоб наблизити синус дуги, тому що не міг отримати оптимізацію безпосередньо з ArcSin. TheClipдзвінки - це моя спеціальна спроба запобігти складним номерам під час оптимізації. Тут є компроміс: з одного боку, функція повинна бути максимально наближена до точного циліндричного відображення, щоб надати мінімально можливе спотворення. З іншого боку, якщо це складно, стає набагато складніше автоматично знайти оптимальні значення для ступенів свободи. (Найприємніше в тому, що робити обробку зображень за допомогою Mathematica - це те, що ви можете легко пограти з подібними математичними моделями, ввести додаткові терміни для різних спотворень і використовувати ті самі функції оптимізації, щоб отримати остаточні результати. Я ніколи нічого не міг зробити наприклад, OpenCV або Matlab. Але я ніколи не пробував символічний інструментарій для Matlab, можливо, це робить його більш корисним.)

Далі я визначаю "функцію помилок", яка вимірює якість зображення -> відображення координат циліндра. Це просто сума помилок у квадраті для пікселів межі:

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

Ця функція помилок вимірює "якість" відображення: Найнижча, якщо точки з лівої межі відображаються на (0 / [що-небудь]), пікселі на верхній межі відображаються на ([що-небудь] / 0) тощо. .

Тепер я можу сказати Mathematica знайти коефіцієнти, які мінімізують цю функцію помилок. Я можу зробити «освічені здогадки» про деякі коефіцієнти (наприклад, радіус та центр баночки на зображенні). Я використовую їх як вихідні точки оптимізації:

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

FindMinimumзнаходить значення для 10 градусів свободи моєї функції відображення, які мінімізують функцію помилок. Поєднайте загальне відображення та це рішення, і я отримаю відображення з координат X / Y зображення, що відповідає області мітки. Я можу візуалізувати це відображення за допомогою ContourPlotфункції 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]]]

введіть тут опис зображення

4. Перетворіть зображення

Нарешті, я використовую ImageForwardTransformфункцію Mathematica для спотворення зображення відповідно до цього відображення:

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

Це дає результати, як показано вище.

Версія вручну

Наведений вище алгоритм є повністю автоматичним. Не потрібно коригування. Це працює досить добре, доки знімок зроблений зверху чи знизу. Але якщо це фронтальний знімок, радіус банки не можна оцінити за формою етикетки. У цих випадках я отримую набагато кращі результати, якщо дозволити користувачеві вводити ліву / праву межі баночки вручну та встановлювати відповідні ступені свободи у відображенні чітко.

Цей код дозволяє користувачеві вибирати ліву / праву межі:

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

LocatorPane

Це альтернативний код оптимізації, де центр та радіус явно вказані.

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
Знімає сонцезахисні окуляри ... мати Божа ...
Спейсі

Чи трапляється у вас посилання на циліндричне відображення? І, можливо, рівняння для зворотного відображення? @ niki-estner
Ita
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.