С печатью и подписью

С печатью и подписью

Как вставить печать в документ, чтобы боги не убили котёнка

Была у пользователей Эльбы мечта — вставлять изображения печатей и подписей в счета, акты, накладные и прочие серьезные документы. Отчего бы не порадовать мечтателей, подумали мы. Оглядевшись по сторонам, мы поняли, что обычно в таких случаях всю грязную работу сваливают на пользователя (ну, вы знаете: «картинка должна быть 300 на 400 пикселей, с высоким контрастом, хорошим разрешением и идеально белым фоном»). Но судя по опыту нашей команды, которая бывает в колл-центре, даже простая загрузка изображения с фотоаппарата повергает пользователей в глубокую депрессию, и вызволять их приходится богопротивными способами, а-ля «вставьте картинку в Ворд». Конечно, не может быть и речи о том, чтобы заставлять пользователей чистить печати в фотошопах — пусть фотографируют как умеют, а Эльба выполнит за них все остальное!

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

Способ первый: научный подход

Любую картинку можно рассматривать как совокупность точек, каждая из которых имеет определенный цвет. Исходя из этого, наша задача выглядит очень просто — взять только те точки, которые являются частью печати или подписи.
Идея выглядит так: некоторым образом превратим исходную картинку в черно-белое изображение (фон — чёрный, печать — белая), затем набор всех белых точек (маску) пересекаем с исходным изображением. Те части исходного изображения, которые совпадают по координатам с белыми точками считаем печатью. Более детально:

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

    А вот как края нашлись в нашем изображении:
  4. Итак, мы получили контур печати, но он неоднороден: помимо черных и белых участков (фона и печати), есть довольно много точек, которые близки по цвету к фону или печати. Объявив такие точки фоном или изображением, мы сразу улучшим качество распознавания. Огрубляем нашу картинку:
  5. После всех этих преобразований мы довольно неплохо выделили область печати. Но по всей картинке остается мусор — «одинокие» белые точки. «Одинокий» — это ключевое слово, вокруг всегда много черного. Теперь уменьшим разрешение нашего изображения, вместо каждого квадрата 20×20 точек сделаем одну большую точку. Ее цвет определим как среднее от цветов всех точек, которые вошли в этот квадрат. Белые одинокие точки неизбежно почернеют. И после этого опять огрубим картинку:
  6. В результате весь мусор в виде одиноких точек пропал, мы хорошо выделили область в которой гарантированно находится печать. Кроме того у нас есть края (помните, нашли «по Собелу»). Просто пересечем края с тем, что только что получили.

Вот что выйдет, если применить это в качестве маски к исходной печати:
Все, что находится достаточно далеко от элементов изображения, мы выкинули, и это, конечно, успех. Но мы не убрали детали фона вблизи от печати. Так получилось, потому что цвет фона вблизи изображения недостаточно отличался от самой картинки (фотография некачественная, неравномерно освещенная и т.д.) и во время наших огрублений эти участки не были отнесены к фону. Сразу напрашивается очень простой шаг — максимально увеличим разницу между печатью и участками фона вблизи печати. Сделать это очень просто: увеличим контрастность. Для этого прогоним изначальную фотографию через сглаживание и HistogramEqualization:
В результате фон далекий от печати стал такого же цвета, как печать, но нам уже наплевать, мы научились это отбрасывать и работаем только с участками фона, близкими к картинке. Дальше дело техники, никаких новых идей уже не будет: в серый, инвертируем, огрубляем.
Итак, мы умеем вычищать все, кроме мусора около печати, и уже видели, что получится в результате. Только что мы научились хорошо чистить мусор около печати, теперь применим то, что получили к нашему первому результату:
Уже неплохо. Понятно, что надо немного размыть, поднять контраст, сделать фон прозрачным и т.п.
Правда, у нас обнаружилась проблема с фотографиями в большом разрешении (гонка за мегапикселями фотокамеры, увы, не пощадила и владельцев телефонов) — широкие (10, а иной раз и 100 пикселей) линии печати стали распадаться в результате поиска краев на два отдельных штриха.
Для устранения подобных мелких неприятностей можно воспользоваться замыканием (closing). В нашем случае замыкание приведет к тому, что все участки фона между парными штрихами будут замазаны, но только если расстояние между штрихами не слишком велико. Вот пример работы замыкания из документации к фильтру AForge.Closing:
Видно, что полости больше нескольких пикселов подряд замыкание замазывать не умеет. А размер полости у нас зависит от разрешения, с которым сфотографировали печать.
Казалось бы — ну приведем разрешение в соответствие с нужным нам (конкретное значение тупо экспериментально установим). Однако, проблема в том, что люди вполне могут (и любят) фотографировать печать с огромными белыми полями.
После сжатия картинки до «оптимального размера», мы на выходе получим крохотную печать в углу фото.
В итоге мы решили не заморачиваться и прогнать алгоритм 2 раза. Первый раз мы уберем крупный мусор (и, возможно, мелкие части печати), а также поймём, где же на картинке печать. После этого снова берем оригинальную картинку, вырезаем теперь уже известное нам место с печатью, масштабируем до нужного размера и опять прогоняем алгоритм очистки от фона.
Казалось, цель достигнута, но когда мы начали брать примеры печатей и подписей из интернета, столкнулись с новой проблемой. Если для большинства печатей наш алгоритм работал вполне сносно, то с подписями все обстояло значительно хуже: уровень контраста фотографий порой был таким, что поиск краев просто терял половину линий, а понижать планку для поиска тоже опасно — рискуем получить кучу «мусора» впридачу.

Способ второй: Великое Изобретение Велосипеда

Мы решили, а зачем нам эти поиски краев и прочие навороты? В конце концов, подпись — это ж очень простая штука: несколько линий, нарисованных темной ручкой на светлой бумаге.
На первый взгляд, отделить темное от светлого — невелика наука. Для начала алгоритм под кодовым названием «кто не с нами — тот против нас» выглядел очень просто: перебираем подряд все точки, у которых яркость выше, чем у серого цвета, записываем их в «фон» и уничтожаем. Все, что темнее, оставляем, ибо «ручка».
Прогнали на первой попавшейся подписи — ура, круто обрабатывает!
Прогнали на второй — полный фэйл.
Первую мысль — написать на форме загрузки «фотайте поконтрастнее, пацаны» и забить — проектировщики интерфейсов почему-то не одобрили. Пришлось включать мозг. Прикинули, раз на одних фотках работает, а на других нет, надо просто немного нормализовать фотки самим. Взяли фотку, прошлись по всем точкам, построили простейшую гистограмму: банально для каждой из 256 возможных яркостей посчитали количество точек этой самой яркости. Нашли минимальную яркость, потом максимальную, выбрали точку «где-то посередине» и вырезали фон.
Ура, сказали мы и начали на радостях искать примеры трэшевых подписей. Ну, чтобы найти как можно больше доказательств, какие мы молодцы. Жизнь, как всегда, оказалась интереснее: буквально на второй фотке нас опять ждал жесткий фэйл! Как бы мы ни выбирали ту самую точку «где-то посередине», либо фон оставался в углу, либо часть подписи пропадала.
Обреченно взглянув на результат, открыли оригинал и начали думать.
Головоломки, в общем-то и не было, просто свет лег так, что фон на одном краю фото был темнее, чем ручка на другом (как потом оказалось — такое в реальной жизни бывает довольно часто). Очевидно, что в этой ситуации искомой точки просто не существует.
Поняли, что дальше крутить параметры бессмысленно. Стали рассуждать логически: «Вот мы смотрим на фотку и подпись отлично видим. Значит, контраст достаточный. По крайней мере, локальный контраст».
При слове «локальный» резко оживились и решили: раз для всей картинки нельзя выделить точку вида «ручка темнее, фон светлее», то попробуем это сделать на части изображения!
Разбили на прямоугольники (экспериментально нас устроила сетка 10х10) и применили алгоритм к каждой клетке отдельно. Все бы ничего, но часть клеток оказалась заполнена исключительно фоном. Тут уже проще — раз кроме фона ничего нет, значит, локальный контраст крайне низок. Значит, самая яркая точка и самая темная точка в гистограмме совсем рядом.
Вроде получилось.
Берем отдельную ячейку, строим по ней гистограмму, смотрим левый край (минимум яркости) и правый край (максимум яркости). Затем берем дельту, которая и есть контраст. Если контраст меньше некой величины (как минимум, вычисляется на основе общего контраста картинки), то считаем всю ячейку фоном и выкидываем лишнее. Если контраст больше — определяем точку «раздела» и отрезаем все, что ярче.

Способ третий, заключительный

Два варианта нам показалось мало, решили добавить третий — простенькую обработку «почти идеальных» фоток, в которых весь фон белый (ну, или почти белый). Для этого выбили все пиксели светлее 95% от максимальной яркости и обрезали поля.
В итоге, как мы уже говорили в начале, пользователь выбирает один из трех вариантов. В случае, если ни один из вариантов не подошел (что бывает крайне редко), показываеминструкцию, как правильно нажимать на фотоаппарате кнопку «шедевр».
Можете попробовать сами, даже если у вас нет печати, то расписываться вы наверняка умеете ;)

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *