Распознаём изображение с токена при помощью камеры

в 0:19, , рубрики: arduino, opencv, python, метки: , ,

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

(Честно говоря, мне давно хотелось отвлечься от программирования и поковыряться в железяках, поэтому моя лень — не единственный мотиватор во всей этой затее).

Анализ технического задания и подбор компонентов

Первым делом на глаза мне попалась старая вем-камера «Logitech QuickCam 3000», отправившаяся в «хлам» в связи с покупкой ноутбука со встроенной веб-камерой. План созрел моментально: снимаем камерой циферки с токена, распознаём их на компьютере и… Профит! Дальше был найден сервопривод (ну не нажимать же кнопку на токене каждый раз руками, правда?), старый USB-хаб (в моём ноутбуке всего два USB-порта, и один из них постоянно занят USB-Ethernet адаптером), и плата Arduino, неизвестно каким образом у меня оказавшаяся. В качестве корпуса для своего девайса я решил использовать конструктор «Лего», купленный впрок моему годовалому сыну (кажется, теперь я понимаю, почему жена ехидно ухмыльнулась при покупке).

К сожалению, фотографий донорских девайсов в целости и сохранности у меня нет, поэтому я могу «похвастаться» только устройством в сборе:

Распознаём изображение с токена при помощью камеры

Принципиальная схема и прошивка микроконтроллера

Собственно, всё до смешного просто (я даже схему рисовать не буду): USB-хаб, к которому подключена веб-камера и ардуина. К ардуине подключен сервопривод (через ШИМ). Вот и всё.Исходный код программы, которая заливается в ардуину, тривиален: github.com/dreadatour/lazy/blob/master/servo.ino
Ардуина ждёт букву 'G' на ком-порт, и при её поступлении дёргает серву туда-назад. Задержка (500мс) и угол наклона сервы были подобраны экспериментальным путём.

Выбор языка программирования и анализ существующих библиотек «компьютерного зрения»

Единственным языком программирования, которому бы я доверил такую сложную задачу, как «компьютерное зрение» — благославленный Python. Благо в нём, практически из коробки, есть биндинги к такой славной библиотеке, как OpenCV. Собственно, на этом и остановимся.

Алгоритм распознавания кода токена

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

Для начала берём изображение с камеры:
Распознаём изображение с токена при помощью камеры

Облегчаем себе задачу: камера неподвижна относительно основы, токен может двигаться в своём лотке совсем незначительно, поэтому находим границы возможных положений токена, обрезаем кадр с камеры и (исключительно для нашего удобства) поворачиваем изображение на 90˚:
Распознаём изображение с токена при помощью камеры

Дальше делаем некоторые преобразования: конвертируем получившееся изображение в grayscale и находим границы с помощью детектора границ Канни — это будут границы ЖК-экрана токена:
Распознаём изображение с токена при помощью камеры

Находим контуры на полученном изображении. Контур представляет собой массив линий — отбрасываем линии меньше определённой длины:
Распознаём изображение с токена при помощью камеры

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

Находим точки персечения этих линий и выполняем несколько проверок:

  • проверяем, чтобы длина вертикальных и горизонтальных линий примерно совпадала друг с другом и чтобы длина линий примерно совпадала с размером ЖК-дисплея (который мы вычислили экспериментальным путём)
  • проверяем, чтобы диагонали были примерно равны (нам нужно прямоугольник — ЖК-дисплей)

Далее находим угол, на который повёрнут наш токен, поворачиваем изображение на этот угол и вырезаем изображение ЖК:
Распознаём изображение с токена при помощью камеры

Самое сложное позади. Теперь нам нужно повысить контрастность полученного изображения. Для этого мы запоминаем изображение пустого ЖК-экрана (до нажатия на кнопку токена), и просто «вычитаем» это изображение из картинки с цифрами (после нажатия на кнопку):
Распознаём изображение с токена при помощью камеры

Получаем чёрно-белое изображение. Для этого с помощью ещё одного тупого алгоритма находим оптимальный порог, который будет разделять все пиксели на изображении на «чёрные» и «белые», конвертируем изображение в Ч/Б и вырезаем символы:
Распознаём изображение с токена при помощью камеры

Ну а дальше просто распознаём цифры. Нам нет смысла мучаться с нейронными сетями и прочими штуками, т.к. у нас семисегментный индикатор: есть семь «точек», по которым однозначно определяется каждая цифра:
Распознаём изображение с токена при помощью камеры

На всякий случай распознаём цифры с нескольких кадров: если три кадра подряд мы получили одинаковый результат — считаем, что распознавание прошло успешно, выводим результат с помощью программы «growlnotify» пользователю и копируем полученный код в буфер обмена.

Видео работы девайса

Осторожно, звук!

Исходный код целиком

Автор: Dreadatour


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js