Найти в Дзене

Как узнать, что прячется на картинке с полосами, если лень мотать головой? Вооружаемся OpenCV!

В этом месяце я начал изучение OpenCV и, признаюсь честно, я в восторге, штука фантастическая. Меньше недели назад наткнулся на пост с необычной картинкой. Решил что было бы неплохо попробовать решить задачку, заодно потренироваться в работе с OpenCV. Если вы еще не видели мое видео с демонстрацией работы алгоритма, то рекомендую его посмотреть. Что это за картинки такие? А я и сам не знаю). Найти название этой оптической иллюзии мне не удалось. Где-то она упоминается как "Оптическая иллюзия Джона Леннона", где-то как тест для проверки зрения (или на дальтонизм), а где-то просто как "загадочное изображение, которое можно увидеть, покачав головой". Главная отличительная черта таких картинок - контрастные полосы. Но под определенным углом, прищуром, движением картинки относительно глаз можно различить черты скрытой в такой картинке рисунка. Не хочу мотать головой! Мне лень. И глаза режет. Хочу готовое! Вооружаемся Python, OpenCV и начинаем писать код. С чего начать? Начнем с идеи Нужно у
Оглавление

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

Если вы еще не видели мое видео с демонстрацией работы алгоритма, то рекомендую его посмотреть.

Что это за картинки такие?

А я и сам не знаю). Найти название этой оптической иллюзии мне не удалось. Где-то она упоминается как "Оптическая иллюзия Джона Леннона", где-то как тест для проверки зрения (или на дальтонизм), а где-то просто как "загадочное изображение, которое можно увидеть, покачав головой".

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

Не хочу мотать головой! Мне лень. И глаза режет. Хочу готовое!

Вооружаемся Python, OpenCV и начинаем писать код.

С чего начать?

Начнем с идеи

Нужно убирать вертикальные линии, вычислять тона под ними, поднимать контрастность вне полос и выводить изображение на экран.

Звучит не так уж сложно.

Ищем линии

Сделаем простенькую картинку для разбора алгоритма.

-2

Используем threshold() для преобразования изображения. Задаем порог и получаем изображение в бинарном виде (монохром, только белый и черный цвета).

Получаем следующее:

-3

Этот шаг необходим для перехода к главному - поиску контуров findContours(). Тут нужно отметить, что алгоритм сможет найти линии, только если изображение имеет белую рамку. В противном случае контуры будут объединены в несколько крупных, не описывающих линии.

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

Перебираем контуры и рисуем найденный контур на изображении красным.

Вычисляем с помощью moments() параметры контура, откуда берем только точку его центра и используем как координату Х центра линии. Отмечаем зеленой линией.

-4

Все, о чем мы упоминали выше умещается в 30 строчек кода и комментариев к нему. Все очень просто!

-5

Дальше немного интереснее. Нужно найти края каждой линии и рассчитать среднюю ширину линий.

Казалось бы, берем контур, смотрим его ширину, складываем ее у всех и делим на число контуров. Но картинки бывают разные!

Например для таких простых было достаточно:

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

-7

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

Реальный пример:

Значит считаем ширину по-новому:

  1. Получаем средний Х и длину отрезка из точек контура
  2. Разделяем отрезки на правые и левые относительно центра
  3. Вычисляем средний X для каждой стороны, определяя за вес длину отрезка
  4. Вычисляем ширину линии как разность Х полученных сторон.

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

-9

Код немного сложнее, но только в паре мест. Хотя если разобраться, то только на первый взгляд.

-10

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

Пример с малым числом тонов, изначально содержавший большее количество:

В попытках восстановить большинство картинок число параметров (ползунков) в программе в конечном итоге достигло 7, но некоторые картинки так и не удалось восстановить даже с таким большим числом параметров.

Восстанавливаем

Тут без доски и маркеров не обойтись.

-12
dist_line - половина среднего расстояния между линиями
width_line - половина средней ширины линий
Для левой стороны линии:
llp - левая граница левого сектора
lrp - правая граница левого сектора.
Для правой стороны линии:
rlp - левая граница правого сектора
rrp - правая граница правого сектора.
  • Начинаем с инвертирования изображения
  • Дальше получаем половину расстояния между соседними линиями (граница, фиолетовая линия)
  • Получаем среднюю половину ширины линии
  • Считываем ползунки
  • Считаем ширину перекрытия (overlap)
  • Перебираем линии.
-13

Подготовка не большая. Дальше самое интересное.

  • Для каждой стороны от линии вычисляем llp, lrp, rlp, rrp и проходимся по высоте, вычисляя цвета в линии слева, по центру и справа от центра линии
  • Вычисляем для каждого сектора цвет. Учитываем допуск (подгоняем цвета левого и правого сектора друг к другу)
  • Восстанавливаем попиксельно учитывая средний цвет (цвет линии сектора) с заданным усилением. gain_bg - усиление фона, т.е. вне полос, gain_line - усиление линий, в белый цвет
  • Применяем перекрытие средним цветом в месте стыковки секторов линий (overlap, на границе с шириной всех секторов линии в %). Не очень удачный параметр. Чаще просто размывает изображение
  • Обновляем изображение, не забывая инвертировать обратно.
-14

На этом основная часть кода закончена. Полный код можно посмотреть и скачать для запуска проекта на вашем ПК с моего github репозитория проекта.

Время тестов!

Но где же картинка из поста, упомянутого в начале статьи?

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

Но результаты, как и обещал, представляю:

Выводы

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

—————————————————————————

Спасибо, что дочитали статью!

Подпишитесь пожалуйста на мой канал "Заметки Электроника | Alexander.Chad", этим Вы очень сильно поможете мне. Канал существует только за счет наличия и участия подписчиков.

Если Вам понравился материал - поддержите его лайком или даже донатом (ЮMoney). Есть что сказать? Оставьте комментарий! Это тоже будет помощью.

Сейчас канал нуждается в Вас как никогда прежде!