[Перевод] «Сладкое» программирование, или Как выделить этикетку с банки варенья в Mathematica?

8e817eeb1e1a431b8d570ac7b1e17864.png


Перевод дискуссии «How to peel the labels from marmalade jars using Mathematica?» с сайта Mathematica at StackExchange.
Код, приведенный в статье, можно скачать здесь (~31 МБ).
Выражаю огромную благодарность Кириллу Гузенко KirillGuzenko за помощь в переводе и подготовке публикации
Как можно выделить содержимое этикетки с указанной ниже банки (точка съёмки кадра, геометрия банки, её содержимое — всё это нам неизвестно),

dda5f02bce10706e331c2f2ee766ca89.gif

чтобы получить нечто подобное — ту же самую этикетку в том виде, в каком она была до того, как оказалась на банке?

0d88f2ad3e99cd03af9852fb9fe894d8.gif

Основная идея заключается в следующем:

  • Находим этикетку.
  • Находим границы этикетки.
  • Находим отображение координат пикселей изображения на цилиндрические координаты.
  • Трансформируем изображение с использованием найденного отображения.


Предлагаемый нами алгоритм работает только для изображений, в которых:

  • Этикетка ярче фона (это нужно для обнаружения этикетки).
  • Этикетка прямоугольная (это нужно для того, чтобы оценить качество отображения).
  • Банка должна занимать вертикальное положение (это нужно для того, чтобы сохранить простую форму функции отображения).
  • Банка должна быть цилиндрической (это нужно для того, чтобы сохранить простую форму функции отображения).


Следует заметить, что алгоритм модульный. То есть вы можете дописать свой алгоритм обнаружения этикетки, который не будет требовать тёмного фона, или можете написать свою функцию оценки качества отображения, которая позволит работать с овальными или многоугольными этикетками.

Получившийся в конечном итоге алгоритм работает полностью автоматически (однако есть опция ручного задания границ банки), то есть берёт исходное изображение, после чего выдаёт изображение с сеткой и этикетку.
Вот моё «быстрое» решение. Оно немного похоже на решение пользователя azdahak, однако вместо цилиндрических координат оно использует приближенное отображение. С другой стороны, параметры управления нельзя задать вручную — все коэффициенты отображения определяется автоматически:

Этикетка ярко выделяется на тёмном фоне, так что я могу легко найти ее с помощью бинаризации:

4a6065c8702901cc56eab882c5cd9a81.png

dda5f02bce10706e331c2f2ee766ca89.gif

022aa122c76c5a79d75a0be51b8e64e2.png

c01a71e8ea8bdf28a5a2d55fbbdec008.gif

Я просто выбираю самый большой элемент и считаю, что это и есть этикетка:

043fb0893183907d2b66cd5f0abf6fda.png

9c56fb2eb0698a1be221f2f7c35079e1.gif

В следующем шаге необходимо выделить все границы этой области с помощью свёртки (ImageConvolve) полученной маски этикетки:

664d525ba9ab9db5b68d14c50765af33.png

6a5a163babad97bd8d5ca93fb735c4cc.gif

Небольшая вспомогательная функция, показанная ниже, позволяет найти все белые пиксели в одном из этих четырех изображений и преобразовать их индексы в координаты (Position возвращает индексы, а индексы есть пары чисел вида {у, х}, которые при у=1 задают полосу изображения толщиной в 1 пиксель сверху изображения. Но все функции обработки изображений принимают координаты в виде пар {х, у}, которые при у=0 задают полосу изображения толщиной в 1 пиксель снизу изображения. Это приводит к необходимости конвертации индексов, выдаваемых Position, в, по сути, обычные декартовы координаты для дальнейшего использования):

b8a26a34d59985f36a2bc827a3c3e46a.gif

Теперь у меня есть четыре отдельных списка координат для верхней, нижней, левой и правой границ этикетки. Определим теперь отображение координат изображения в цилиндрические координаты:

998bef242ef4988ef62e1bdaec8c41cb.gif

Это отображение, очевидно, является грубым приближением к цилиндрическим координатам. Однако, найти оптимальные значения коэффициентов c1, с2, …, c8 оказывается очень просто с помощью функции NMinimize:

90b8f97234094a7127112540ddf981b3.gif

73b1692895fabd4235227010454b4b7f.png

Таким образом мы находим функцию, которая производит оптимальное отображение нашей этикетки на прямоугольник — точки на левой границе отображаются в {0, [что-то]}, а точки на верхней границе отображаются в {[что-то], 1} и так далее.

Отображение будет выглядеть следующим образом:

630c619c600db09033e37d361c211b64.png

7e72d1ec51938ed3261dd7f0ae128b94.gif

Теперь можно передать это отображение непосредственно функции ImageForwardTransformation:

8063fc457b9abbea9ee6dbfac29f81ea.png

6b5143cadb0785208ebd2ee2a558f861.gif

Артефакты на изображении достались нам от исходного изображения. Версия изображения в более высоком разрешении дала бы лучший результат. Искажения в левой части возникли из-за недостаточно качественной функции отображения. Это можно исправить ее улучшением, о чем будет рассказано ниже.

Работа алгоритма с изображениями большего размера


Я попытался применить тот же алгоритм к изображению с более высоким разрешением и результат выглядит так:

39e2b952a8aa2bbbf903a3fd46c335f8.gif

95546dc3b13446df1949bfbe65eac1d7.gif

Полученный результат говорит о том, что стоит немного подправить часть, отвечающую за обнаружение этикетки (сперва DeleteBorderComponents, а затем и FillingTransform), и ещё добавить дополнительные условия в формуле отображения с учётом перспективы (для изображений низкого разрешения изменения будут практически незаметны). Вы можете заметить, что ближе к границам изображения построенная функция отображения второго порядка работает с некоторыми незначительными искажениями.

Улучшение функции отображения


Проведя ряд исследований и экспериментов, удалось найти отображение, которое устраняет цилиндрические искажения (большую их часть, по крайней мере):

ba170f8ef906419382078b3a08bbaa46.gif

78f519b63b84d1fa65a77a4be8bb68a6.png

Это цилиндрическое отображение, которое использует ряд Тейлора для аппроксимации арксинуса, так как прямое использование функции арксинус делает символьную и численную оптимизацию довольно затруднительной. Функция Clip служит для предотвращения появление комплексных чисел в процессе оптимизации. Использование функции NMinimize для решения этой нелинейной задачи оптимизации не даст быстрых по скорости работы результатов, так как эта функция ищет глобальный минимум функционала с помощью гибридных символьно-численных методов, поэтому было решено вместо неё использовать функцию FindMinimium, которая прекрасно ищет с помощью сугубо численных методов минимум.

eed2bc717f5e43d68034627cb129e5b1.gif

6379b4f32ad6b73b9c636c1724556720.png

Вот что получаем в результате отображения:

0cb2fe4aad99368ed7b49d6ec9482b66.gif

Полученное изображение этикетки:

66810853d6ec63532b46c7905d254d39.gif

Полученные границы достаточно хорошо описывают контур этикетки. Символы по размеру выглядят одинаковыми, а значит искажения оказывают небольшое влияние. Решение для оптимизации так же можно проверить напрямую: посредством оптимизации попытаемся оценить радиус цилиндра R и координату X центра цилиндра, и полученные значения лишь на несколько пикселей будут отстоять от их реальных положений на изображении.

Проверка работы алгоритма на реальных данных


В Google были найдены похожие картинки, на которых был апробирован разработанный алгоритм. Ниже показаны результаты работы алгоритма в полностью автоматическом режиме, но некоторые изображение предварительно немного обрезались. Результаты выглядят весьма многообещающе:

811abaec2d1ab136ff388e4b68aa98d3.gif

642fcc10b28801d3eaa83978fef77436.gif

fbdf2c038d9e18ca560e18903d4e7a7f.gif

e1469ba1f863f33be8dc0364cf312a40.gif

3b34bf0b63a4b3f8c5630b9adcee94a5.gif

9b682a4c12de82006464a7d54aa23a91.gif

a2b37e032187ce5a3bc39bcfbb8f6342.gif

8a8a3dbe8f623ae252ae1e989ed80bb3.gif

Как и ожидалось, обнаружение этикетки — наименее устойчивый шаг алгоритма, потому и требовалась обрезка изображений в некоторых случаях. Если отметить точки на этикетке и вне её, то сегментация, основанная на границах, должна будет дать лучший результат.

Дополнительные улучшения алгоритма


Пользователь Szabolcs предложил интерактивный вариант кода, который позволяет улучшить результат.

Со своей стороны я предлагаю улучшить предложенный интерактивный интерфейс функцией ручного выбора левой и правой границ изображения, к примеру, с помощью локаторов:

bc40cfd9934def4e764b9ce12a798906.png

30d4157123899d4c0cb96d029a801475.gif

Тогда будет возможность получить параметры r и cx в явном виде, а не через оптимизацию:

ff4d67fc35242b6c62cba538c6c0cc16.gif

С использованием этого решения результат получается практически без искажений:

84b5d14b89e97e21b71f1a79b471a22e.gif

e46e6055fa2710b86ffa9810d32c50dd.gif

© Habrahabr.ru