- PVSM.RU - https://www.pvsm.ru -
В организации, где я тружусь в свободное от отдыха время, очень высокие требования к безопасности. Везде, где только можно, для аутентификации пользователей используются токены [1]. Мне выдали вот такую вот штуку:
и сказали: жмёшь кнопку, смотришь цифры, вводишь пароль и радуешься. «Безопасность, конечно, превыше всего, но и о комфорте забывать не следует» — примерно так подумал я и провёл ревизию имеющегося у меня электронного хлама.
(Честно говоря, мне давно хотелось отвлечься от программирования и поковыряться в железяках, поэтому моя лень — не единственный мотиватор во всей этой затее).
Первым делом на глаза мне попалась старая вем-камера «Logitech QuickCam 3000», отправившаяся в «хлам» в связи с покупкой ноутбука со встроенной веб-камерой. План созрел моментально: снимаем камерой циферки с токена, распознаём их на компьютере и… Профит! Дальше был найден сервопривод (ну не нажимать же кнопку на токене каждый раз руками, правда?), старый USB-хаб (в моём ноутбуке всего два USB-порта, и один из них постоянно занят USB-Ethernet адаптером), и плата Arduino, неизвестно каким образом у меня оказавшаяся. В качестве корпуса для своего девайса я решил использовать конструктор «Лего», купленный впрок моему годовалому сыну (кажется, теперь я понимаю, почему жена ехидно ухмыльнулась при покупке).
К сожалению, фотографий донорских девайсов в целости и сохранности у меня нет, поэтому я могу «похвастаться» только устройством в сборе:
Собственно, всё до смешного просто (я даже схему рисовать не буду): USB-хаб, к которому подключена веб-камера и ардуина. К ардуине подключен сервопривод (через ШИМ). Вот и всё.Исходный код программы, которая заливается в ардуину, тривиален: github.com/dreadatour/lazy/blob/master/servo.ino [2]
Ардуина ждёт букву 'G' на ком-порт, и при её поступлении дёргает серву туда-назад. Задержка (500мс) и угол наклона сервы были подобраны экспериментальным путём.
Единственным языком программирования, которому бы я доверил такую сложную задачу, как «компьютерное зрение» — благославленный Python. Благо в нём, практически из коробки, есть биндинги к такой славной библиотеке, как OpenCV [3]. Собственно, на этом и остановимся.
Я буду давать ссылки на куски кода, которые отвечают за описываемый функционал — на мой взгляд это оптимальный формат подачи информации. Весь код можно посмотреть на гитхабе [4].
Для начала берём изображение с камеры [5]:
Облегчаем себе задачу: камера неподвижна относительно основы, токен может двигаться в своём лотке совсем незначительно, поэтому находим границы возможных положений токена, обрезаем кадр [6] с камеры и (исключительно для нашего удобства) поворачиваем изображение [7] на 90˚:
Дальше делаем некоторые преобразования [8]: конвертируем получившееся изображение в grayscale и находим границы с помощью детектора границ Канни [9] — это будут границы ЖК-экрана токена:
Находим контуры [10] на полученном изображении. Контур представляет собой массив линий — отбрасываем линии меньше определённой длины:
С помощью тупого алгоритма определяем четыре линии [11], ограничивающие наш ЖК-дисплей:
Находим точки персечения [12] этих линий и выполняем несколько проверок:
Далее [15] находим угол, на который повёрнут наш токен, поворачиваем изображение на этот угол и вырезаем изображение ЖК:
Самое сложное позади. Теперь нам нужно повысить контрастность полученного изображения. Для этого мы запоминаем [16] изображение пустого ЖК-экрана (до нажатия на кнопку токена), и просто «вычитаем [17]» это изображение из картинки с цифрами (после нажатия [18] на кнопку):
Получаем чёрно-белое изображение. Для этого с помощью ещё одного тупого алгоритма [19] находим оптимальный порог, который будет разделять все пиксели на изображении на «чёрные» и «белые», конвертируем изображение в Ч/Б и вырезаем [20] символы:
Ну а дальше просто распознаём цифры. Нам нет смысла мучаться с нейронными сетями и прочими штуками, т.к. у нас семисегментный индикатор: есть семь «точек», по которым однозначно определяется [21] каждая цифра:
На всякий случай распознаём цифры с нескольких кадров: если три кадра подряд мы получили одинаковый результат — считаем, что распознавание прошло успешно, выводим результат с помощью программы «growlnotify» пользователю и копируем полученный код в буфер обмена.
Осторожно, звук!
Исходный код целиком [22]
Автор: Dreadatour
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/6616
Ссылки в тексте:
[1] токены: http://ru.wikipedia.org/wiki/%D0%A2%D0%BE%D0%BA%D0%B5%D0%BD_(%D0%B0%D0%B2%D1%82%D0%BE%D1%80%D0%B8%D0%B7%D0%B0%D1%86%D0%B8%D0%B8)
[2] github.com/dreadatour/lazy/blob/master/servo.ino: https://github.com/dreadatour/lazy/blob/master/servo.ino
[3] OpenCV: http://opencv.willowgarage.com/
[4] на гитхабе: https://github.com/dreadatour/lazy/blob/master/lazy.py
[5] берём изображение с камеры: https://github.com/dreadatour/lazy/blob/master/lazy.py#L42
[6] обрезаем кадр: https://github.com/dreadatour/lazy/blob/master/lazy.py#L44-48
[7] поворачиваем изображение: https://github.com/dreadatour/lazy/blob/master/lazy.py#L50-53
[8] делаем некоторые преобразования: https://github.com/dreadatour/lazy/blob/master/lazy.py#L127-133
[9] детектора границ Канни: http://en.wikipedia.org/wiki/Canny_edge_detector
[10] Находим контуры: https://github.com/dreadatour/lazy/blob/master/lazy.py#L135-138
[11] определяем четыре линии: https://github.com/dreadatour/lazy/blob/master/lazy.py#L142-158
[12] точки персечения: https://github.com/dreadatour/lazy/blob/master/lazy.py#L163-167
[13] проверяем: https://github.com/dreadatour/lazy/blob/master/lazy.py#L177-187
[14] проверяем: https://github.com/dreadatour/lazy/blob/master/lazy.py#L189-191
[15] Далее: https://github.com/dreadatour/lazy/blob/master/lazy.py#L193-196
[16] запоминаем: https://github.com/dreadatour/lazy/blob/master/lazy.py#L259
[17] вычитаем: https://github.com/dreadatour/lazy/blob/master/lazy.py#L275
[18] нажатия: https://github.com/dreadatour/lazy/blob/master/lazy.py#L263
[19] тупого алгоритма: https://github.com/dreadatour/lazy/blob/master/lazy.py#L277-282
[20] вырезаем: https://github.com/dreadatour/lazy/blob/master/lazy.py#L286-290
[21] определяется: https://github.com/dreadatour/lazy/blob/master/lazy.py#L291
[22] Исходный код целиком: https://github.com/dreadatour/lazy
Нажмите здесь для печати.