Аналогічне питання було поставлено на Mathematica.Stackexchange . Моя відповідь там розвинулася і отримала досить довго, врешті-решт, тому я підсумую алгоритм тут.
Анотація
Основна ідея:
- Знайдіть етикетку.
- Знайдіть межі етикетки
- Знайдіть зіставлення, яке відображає координати зображень на координати циліндрів, щоб воно відображало пікселі уздовж верхньої межі мітки до ([нічого] / 0), пікселів уздовж правої межі до (1 / [що-небудь]) тощо.
- Перетворіть зображення за допомогою цього відображення
Алгоритм працює лише для зображень, де:
- ярлик яскравіший за фон (це потрібно для виявлення мітки)
- мітка прямокутна (використовується для вимірювання якості відображення)
- баночка (майже) вертикальна (це використовується для спрощення функції картографування)
- баночка циліндрична (використовується для простоти функціонування карти)
Однак алгоритм модульний. Принаймні в принципі, ви можете написати власне виявлення міток, яке не потребує темного фону, або ви можете написати власну функцію вимірювання якості, яка може впоратися з еліптичними або восьмикутними мітками.
Результати
Ці зображення обробляються повністю автоматично, тобто алгоритм приймає вихідне зображення, працює протягом декількох секунд, потім показує відображення (зліва) та неспотворене зображення (праворуч):
Наступні зображення оброблялися з модифікованою версією алгоритму, якщо користувач вибирав ліву і праву межі 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}}]}]]]]
Це альтернативний код оптимізації, де центр та радіус явно вказані.
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]