В организации, где я тружусь в свободное от отдыха время, очень высокие требования к безопасности. Везде, где только можно, для аутентификации пользователей используются токены. Мне выдали вот такую вот штуку:
и сказали: жмёшь кнопку, смотришь цифры, вводишь пароль и радуешься. «Безопасность, конечно, превыше всего, но и о комфорте забывать не следует» — примерно так подумал я и провёл ревизию имеющегося у меня электронного хлама.
(Честно говоря, мне давно хотелось отвлечься от программирования и поковыряться в железяках, поэтому моя лень — не единственный мотиватор во всей этой затее).
Анализ технического задания и подбор компонентов
Первым делом на глаза мне попалась старая вем-камера «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