В этом месяце я начал изучение OpenCV и, признаюсь честно, я в восторге, штука фантастическая. Меньше недели назад наткнулся на пост с необычной картинкой. Решил что было бы неплохо попробовать решить задачку, заодно потренироваться в работе с OpenCV.
Если вы еще не видели мое видео с демонстрацией работы алгоритма, то рекомендую его посмотреть.
Что это за картинки такие?
А я и сам не знаю). Найти название этой оптической иллюзии мне не удалось. Где-то она упоминается как "Оптическая иллюзия Джона Леннона", где-то как тест для проверки зрения (или на дальтонизм), а где-то просто как "загадочное изображение, которое можно увидеть, покачав головой".
Главная отличительная черта таких картинок - контрастные полосы. Но под определенным углом, прищуром, движением картинки относительно глаз можно различить черты скрытой в такой картинке рисунка.
Не хочу мотать головой! Мне лень. И глаза режет. Хочу готовое!
Вооружаемся Python, OpenCV и начинаем писать код.
С чего начать?
Начнем с идеи
Нужно убирать вертикальные линии, вычислять тона под ними, поднимать контрастность вне полос и выводить изображение на экран.
Звучит не так уж сложно.
Ищем линии
Сделаем простенькую картинку для разбора алгоритма.
Используем threshold() для преобразования изображения. Задаем порог и получаем изображение в бинарном виде (монохром, только белый и черный цвета).
Получаем следующее:
Этот шаг необходим для перехода к главному - поиску контуров findContours(). Тут нужно отметить, что алгоритм сможет найти линии, только если изображение имеет белую рамку. В противном случае контуры будут объединены в несколько крупных, не описывающих линии.
После мы конвертируем в пространство RGB из тонов серого, чтобы иметь возможность нанести на изображение цветовую маркировку.
Перебираем контуры и рисуем найденный контур на изображении красным.
Вычисляем с помощью moments() параметры контура, откуда берем только точку его центра и используем как координату Х центра линии. Отмечаем зеленой линией.
Все, о чем мы упоминали выше умещается в 30 строчек кода и комментариев к нему. Все очень просто!
Дальше немного интереснее. Нужно найти края каждой линии и рассчитать среднюю ширину линий.
Казалось бы, берем контур, смотрим его ширину, складываем ее у всех и делим на число контуров. Но картинки бывают разные!
Например для таких простых было достаточно:
Картинки с серыми тонами первым вариантом алгоритма преобразовывались сносно, но когда попалась картинка практически без тонов серого, то алгоритм сломался на следующем моменте:
Как видно, серый фон не тронут контуром, а вот деформация линии учтена и первым алгоритмом получена ширина выше реальной. Эта ошибка распространяется на остальные линии, вследствие чего восстановить изображение становится невозможно.
Реальный пример:
Значит считаем ширину по-новому:
- Получаем средний Х и длину отрезка из точек контура
- Разделяем отрезки на правые и левые относительно центра
- Вычисляем средний X для каждой стороны, определяя за вес длину отрезка
- Вычисляем ширину линии как разность Х полученных сторон.
Такой подход позволил более точно определять ширину линий и обрабатывать картинки с малым числом градаций серого.
Код немного сложнее, но только в паре мест. Хотя если разобраться, то только на первый взгляд.
А таких картинок, с малым числом градаций, оказалось большинство. Одним из главных компонентов картинок является еле различимые тона между полос. Но интернет суров и картинки, гуляя по нему, пережимаясь раз за разом, теряя драгоценные градации.
Пример с малым числом тонов, изначально содержавший большее количество:
В попытках восстановить большинство картинок число параметров (ползунков) в программе в конечном итоге достигло 7, но некоторые картинки так и не удалось восстановить даже с таким большим числом параметров.
Восстанавливаем
Тут без доски и маркеров не обойтись.
dist_line - половина среднего расстояния между линиями
width_line - половина средней ширины линий
Для левой стороны линии:
llp - левая граница левого сектора
lrp - правая граница левого сектора.
Для правой стороны линии:
rlp - левая граница правого сектора
rrp - правая граница правого сектора.
- Начинаем с инвертирования изображения
- Дальше получаем половину расстояния между соседними линиями (граница, фиолетовая линия)
- Получаем среднюю половину ширины линии
- Считываем ползунки
- Считаем ширину перекрытия (overlap)
- Перебираем линии.
Подготовка не большая. Дальше самое интересное.
- Для каждой стороны от линии вычисляем llp, lrp, rlp, rrp и проходимся по высоте, вычисляя цвета в линии слева, по центру и справа от центра линии
- Вычисляем для каждого сектора цвет. Учитываем допуск (подгоняем цвета левого и правого сектора друг к другу)
- Восстанавливаем попиксельно учитывая средний цвет (цвет линии сектора) с заданным усилением. gain_bg - усиление фона, т.е. вне полос, gain_line - усиление линий, в белый цвет
- Применяем перекрытие средним цветом в месте стыковки секторов линий (overlap, на границе с шириной всех секторов линии в %). Не очень удачный параметр. Чаще просто размывает изображение
- Обновляем изображение, не забывая инвертировать обратно.
На этом основная часть кода закончена. Полный код можно посмотреть и скачать для запуска проекта на вашем ПК с моего github репозитория проекта.
Время тестов!
Но где же картинка из поста, упомянутого в начале статьи?
Картинка будет. Но результат печальный. Алгоритм не может переварить ее, выдав отличный результат. Как ни старайся. Похоже она была не единожды пережата и jpeg взял свое.
Но результаты, как и обещал, представляю:
Выводы
Восстановить можно, но не всегда с хорошим качеством. Все сильно зависит от методов, примененных для преобразования картинки и от методов ее хранения, циклов сжатия и его силы.
—————————————————————————
Спасибо, что дочитали статью!
Подпишитесь пожалуйста на мой канал "Заметки Электроника | Alexander.Chad", этим Вы очень сильно поможете мне. Канал существует только за счет наличия и участия подписчиков.
Если Вам понравился материал - поддержите его лайком или даже донатом (ЮMoney). Есть что сказать? Оставьте комментарий! Это тоже будет помощью.
Сейчас канал нуждается в Вас как никогда прежде!