Как сгладить изображение этикетки на баночке с едой?

40

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

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


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

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

Кроме того, мне бы хотелось, чтобы техника могла автоматически определять края метки, чтобы применить подходящую коррекцию. В противном случае мне пришлось бы попросить моего пользователя указать границы ярлыка.

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

mahboudz
источник
У Ники есть то, что кажется всеобъемлющим решением. Однако становится намного проще, если вы знаете, что камера всегда «квадратная» к банке, без запутанного фона. Затем вы находите края банки и применяете простое тригонометрическое (арксинус?) Преобразование без особых дополнительных действий. После выравнивания изображения вы можете изолировать саму этикетку.
Даниэль Р Хикс
@Daniel Это то, что я сделал здесь . В идеале можно было бы принять во внимание не совсем параллельную проекцию, но я этого не сделал.
Сабольч
работа очень хорошая но код, показывающий ошибку в моей системе. Я использую Matlab 2017a, совместим ли он с ним. спасибо,
Сатиш Кумар

Ответы:

60

Аналогичный вопрос был задан на Mathematica.Stackexchange . Мой ответ там развивался и в итоге получился довольно длинным, поэтому я кратко изложу алгоритм здесь.

Аннотация

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

  1. Найдите этикетку.
  2. Найдите границы метки
  3. Найдите отображение, которое отображает координаты изображения на координаты цилиндра, чтобы оно отображало пиксели вдоль верхней границы метки на ([что угодно] / 0), пиксели вдоль правой границы на (1 / [что угодно]) и так далее.
  4. Преобразуйте изображение, используя это отображение

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

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

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

Полученные результаты

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

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

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

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

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

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

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

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

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

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

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

Реализация:

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 напрямую. Clipвызовы - это моя специальная попытка предотвратить сложные числа во время оптимизации. Здесь есть компромисс: с одной стороны, функция должна быть как можно ближе к точному цилиндрическому отображению, насколько это возможно, чтобы дать минимально возможное искажение. С другой стороны, если это сложно, становится намного сложнее автоматически найти оптимальные значения для степеней свободы. (Хорошая особенность обработки изображений с помощью 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
Ита