Как мы спасали глаза с OpenCV

в 5:00, , рубрики: Без рубрики

Материалы этого поста задержались с выходом в свет на 4 месяца. Мы — молодая команда разработчиков, и только учимся нарушать dead-лайны, но кажется, что получается уже неплохо. Предыстория в этой статье, где мы и обещали выложить продолжение. Рассказ пойдёт о том, как же наше приложение работает (или не работает, решать читателю).

Какое приложение? Мы — команда проекта Viewaide (бывший EyeDoc) и пишем софт, который при помощи веб-камеры определяет параметры усталости глаз и выводит уведомления, задача которых снизить риск ухудшения зрения вследствие долгой работы у монитора. Чем 100 раз услышать, лучше 1 раз увидеть.

Скачать и попробовать можно по этой ссылке, как говорится, “бесплатно, без смс”. Кроме софта, у нас имеется ещё и часть web-сервиса, но обо всём по порядку.

О том, как нам больно осознавать, что монитор вреден для глаз, описано в предыдущей статье, но вкратце хочется сказать, что заниматься такими вещами мы начали не ради наживы (хотя кто от неё откажется), а ради решения собственной проблемы. Еще год назад, когда мы только начинали работу над проектом, проблема стояла остро: один сооснователь с трудом ловил маршрутки, а второй активно приближался к показателям зрения первого. Чтобы не быть голословным, немного цифр.

Как мы спасали глаза с OpenCV

Компьютерный зрительный синдром (КЗС) — это временное состояние, возникающее в результате длительной беспрерывной фокусировки глаз на дисплее.

По некоторым источникам, зрительным симптомам данного синдрома подвергается около 75% людей, которые работают за компьютером более 3 часов в день.

КЗС возникает в результате усталости глаз, следствием чего являются: головные боли, затуманенное зрение, покраснение в глазах, напряжение, усталость, сухость глаз, а в результате — потеря остроты зрения. Также это может послужить поводом для развития более серьезных глазных заболеваний. Что уж говорить, если около 40 миллионов американцев страдают от синдрома сухого глаза. Симптомы сухого глаза являются причиной № 1 для визитов к офтальмологу в Соединенных Штатах.

Изучив подробнее материал по этой теме в сети, пообщавшись с офтальмологами, мы узнали, что усталость глаз можно определить по некоторым параметрам:

  • прищуривание
  • частота моргания
  • средняя дистанция между монитором и юзером и т.д.

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

Откровенно говоря, идея пришла не после дотошного исследования рынка и анализа современных тенденций IT, а после того, как мы подумали: “Чего бы такого закодить, чтоб покруче?” Год назад крутым показался Image Processing. Затем мы пораскинули мозгами: “Web-камера постоянно перед носом, неужели она нужна только для того, чтобы поговорить в Skype?” Хотелось создать не только что-то крутое, но и полезное. Так и появилась мысль, что если глаза всегда находятся в зоне видимости камеры, то нужно только правильно обработать их изображение, учитывая различные факторы (параметры усталости глаз приведены выше), и авось что-то получится.

Выбор средств разработки пал на Qt + OpenCV. Qt приглянулся не только потому, что был красивого зелёного цвета, c удобным редактором кода и подсветкой синтаксиса, а и потому, что давал шанс создавать кроссплатформенные приложения людям с закалкой С++. Сейчас зарождается надежда на то, что можно будет писать полноценные мобильные приложения. Поживём — увидим. Если Qt отвечал за GUI, то нужно было еще что-то для работы с изображениями. OpenCV — библиотека, написанная адептами компьютерного зрения из Intel. В ней есть очень подробная документация и множество примеров в сети. Ах да, все эти инструменты разработки являются бесплатными, в том числе и для коммерческого пользования, что является большим плюсом.

Задача №1 Получить изображения глаз с помощью web-камеры.

Если с изображением можно совершать какие-то манипуляции, то для начала его нужно получить. А в данном случае достаточно получить всего лишь изображение глаз. Получение потокового видео из web-камеры опустим, в сети имеется предостаточно примеров.

Поиск объектов на изображении в OpenCV реализован методом Виолы-Джонса. Тема распознавания образов не пристрастна к частым инновациям, метод был представлен в 2001 году, и спустя 13 лет всё ещё является ведущим в своей области. Если вкратце, метод подставляет к изображению так называемые примитивы Хаара, которые являются набором элементарных сочетаний тёмных и светлых областей, и если на изображении найдена область, в которой подходит достаточное количество примитивов, то объект найден. Для того чтобы понять, светлая эта область или тёмная, необходимо просуммировать значение соседних пикселей. Для того чтобы не делать это много раз в процессе поиска объекта, изображение переводится в интегральное представление. Несмотря на все оптимизации по скорости работы метода, поиска лица на видео в real-time на бюджетном ПК добиться нельзя. Или всё же можно?

Как мы спасали глаза с OpenCV

Итак, задача — найти глаза пользователя с помощью web-камеры. Возьмем изображение разрешения 640х480.

сvHaarDetectObjects(frame,left_eye_cascade,storage,1.1,3,CV_HAAR_DO_CANNY_PRUNING,cvSize(22,22);

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

сvHaarDetectObjects(frame,face_cascade,storage,1.1,3,CV_HAAR_DO_CANNY_PRUNING,cvSize(80,80);

Работает уже пошустрее. Что же изменилось? Последний аргумент функции определяет минимальный размер искомого объекта. В многочисленных примерах OpenCV указано, что для глаз нужно искать область как минимум 22х22 пикселя, а для лица можно и 80х80. Тут становится понятно, почему поиск лица работает быстрее: пройти по всему изображению областью 22х22 — это далеко не так быстро, как 80х80. Для сравнения, на моей машине Intel Core 2 Duo 2.2 GHz, RAM 2 Gb функция поиска глаз осуществляется в среднем за 900 миллисекунд, а поиск лица – за 200 миллисекунд.

Как известно, лицо не квадратное (если только наш пользователь не Губка Боб). Давайте сделаем минимальную область поиска прямоугольной. При таких цифрах время работы занимает уже 160 миллисекунд.

сvHaarDetectObjects(frame,face_cascade,storage,1.1,3,CV_HAAR_DO_CANNY_PRUNING,cvSize(80,120);

Что это за 3-й аргумент, равный 1.1? Это шаг, с которым “поисковое окно” расширяется. То есть, если не нашлось лица с размерами 80х120, то размер умножается на 1.1. А что если увеличивать не на 10%, а на 20%? Неплохо, время работы уже 100 миллисекунд.

сvHaarDetectObjects(frame,face_cascade,storage,1.2,3,CV_HAAR_DO_CANNY_PRUNING,cvSize(80,120);

И ещё одна мысль. Зачем нам изображение 640х480? Если уменьшить его в 2 раза (теперь 320х240), то и искать нужный объект можно быстрее. Уменьшив изображение, мы видим, что лицо видно всё так же хорошо. Логично, что и скорость увеличилась в 2 раза, время работы — 50 миллисекунд.

Как мы спасали глаза с OpenCV
Как мы спасали глаза с OpenCV

сvHaarDetectObjects(frame,face_cascade,storage,1.2,3,CV_HAAR_DO_CANNY_PRUNING,cvSize(40,60);

Поиск лица работает более или менее сносно, но нам всё-таки нужны глаза. Нет смысла искать их на всём лице. Лучше выделить области, где они в принципе могут располагаться. Идею легко проиллюстрировать.

Как мы спасали глаза с OpenCV

Уже внутри этих областей мы и будем искать глаза. Согласитесь, что объёма работы стало значительно меньше по сравнению с объемом работы при поиске по всему изображению. К тому же, вероятность ложного срабатывания уже невысока.

А нужно ли нам искать лицо в каждом кадре? Вряд ли. Главная цель — найти глаза. Чтобы не усложнять процессору жизнь, усложним её себе. Когда глаз найден, берём прямоугольник, в котором он находится, и увеличиваем площадь в 2 раза. Затем накладываем эту область на следующий кадр видео с web-камеры, и вуаля! При достаточно высоком fps (а мы только что постарались, чтобы ускорить обработку каждого кадра) в этой области очень вероятно найти наш глаз.

Как мы спасали глаза с OpenCV

На скриншоте — основные этапы оптимизированного поиска глаз. Если глаза не будут найдены, то после нескольких попыток нужно вернуться к поиску лица.Теперь функция поиска, со всеми её составляющими, занимает в среднем 30 миллисекунд процессорного времени, что в 30 раз меньше, чем подход “в лоб”.

Задача №2 Определение расстояния до монитора.

Для решения данной задачи нам запредельно помогла эта статья.В данном примере автор приводит нам достаточно простую геометрию, демонстрируя работу веб-камеры:

Как мы спасали глаза с OpenCV

В этом случае, мы видим, что треугольники ABC и ECD подобны. Проведем две высоты от точки С и уберем всю лишнюю информацию.
Как мы спасали глаза с OpenCV
Теперь, глядя на геометрический вариант работы веб-камеры, мы понимаем, что отношение сторон AB/CF равно отношению ED/CH. Давайте же вычислим это соотношение. Делается это очень даже просто. Расположим линейку 10-и см длиной перед веб-камерой так, чтобы она вмещалась в кадр от края до края. После чего, измерим расстояние от камеры до линейки. Наш результат будет равен 16 см. Отношение расстояния к длине линейки, соответственно, 16/10=1.6.

Если область лица полностью войдет в кадр, мы получим расстояние, равное 24-м см (1.6*15). Почему 15 см? Потому что средняя длина лица человека именно такая. Погрешность из-за усреднённого значения почти незаметна (только если вы, повторюсь, не Губка Боб). Теперь дело за малым. Нужно определить расстояние, если лицо занимает менее 100% в кадре. Для этого достаточно определить количество сантиметров в одном проценте. А это 24/100=0.24. Таким образом, мы вычисляем, что расстояние до монитора = 24 + (100% — процент занятого пространства лицом в кадре) * 0.24.

В действительности, мы берем за точку отсчёта не область лица, а расстояние между глазами. Это обусловлено тем, что поиск глаз осуществляется при каждой итерации работы приложения, а поиск лица — только при необходимости. Да и вообще, It`s all about eyes.

Задача №3 Определение прищуривания/моргания.

В общем-то, прищуривание — параметр очень субъективный. И можно было бы не задумываться о нем, если бы не замечания родителей в детстве “не щурься, юзернейм”. Тем не менее о его реализации нужно рассказать, так как отсюда вытекает распознавание морганий.

Вопрос первый: что отличает полностью открытый глаз от полузакрытого или закрытого? Расстояние между веками. Значит, его и нужно искать. На изображении веки представлены тонкой границей цветового перехода. Тогда решением становится поиск границы на изображении.

Между делом, хотелось бы посоветовать весьма занимательную книгу Марр Д. — Зрение. Информационный подход к изучению представления и обработки зрительных образов. В ней описан достаточно интересный подход к человеческому зрению в соответствии с теорией информации. Если придерживаться описываемой в приведённой литературе теории, то даже такие чудеса зрения, как стереоскопия, начинаются с поиска границ на изображении. Самый популярный детектор границ — детектор границ Кенни. То ли изображение было уж очень маленьким, то ли руки были кривые, то ли луна не в той фазе пребывала, но результаты оказались неточными, поэтому пришлось немного углубиться в детали.

Как мы спасали глаза с OpenCV

Классический поиск границ мы начинаем с применения фильтра Гаусса, который помогает избавиться от шумов. На деле же оказывается, что изображения глаз настолько малы и пикселизированны, что в дополнительном размытии не нуждаются. Следующий шаг — поиск граней. Грани являются ничем иным, как резкими цветовыми переходами. Для удобства можно преобразовать изображение в монохромное. Всё равно границы век представляют собой контрастный переход, а чёрно-белое изображение прекрасно это отражает, только обрабатывать его легче.

Чтобы найти резкий переход яркости, мы используем дифференциальный оператор (для данной операции лучше всего подходит оператор Лапласса. Так же применяют оператор Собеля). Не вдаваясь в подробности, давайте подумаем, почему дифференциальный? Если края представляют собой резкий цветовой переход, то разница между суммой значений соседних пикселей на границе и возле неё должна быть больше, чем на области одного цвета. Если заменить “сумма значений пикселей” словом “функция”, то понятно, что нас интересует места резкого её изменения. Как раз для таких ситуаций товарищи Лейбниц и Ньютон придумали дифференциальное исчисление.

сvLaplace(eye, dst, 9);

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

Чтобы убрать лишние участки в области выделения, да и вообще облегчить себе жизнь, можно бинаризировать изображение (проще говоря, оставить всего 2 цвета: чёрный и белый, никаких оттенков). Для этого можно воспользоваться пороговым преобразованием. Для начала считаем средний цвет пикселей, а затем всё, что ниже этого значения, преобразуем в чёрное, что выше — в белое.

cvThreshold(dst,dst,threshold,255,CV_THRESH_BINARY);

Здесь threshold и есть наше среднее значение. Далее необходимо выполнить несколько косметических улучшений, таких как: кластеризация белых пикселей (граней), чтобы выделить наибольшие скопления и удалить ложные; сортировка кластеров по площади (но это уже не столь важно, к тому же не хочется утомлять читателя или забрасывать его кодом, особенно, если он дочитал досюда, что уже приятно).Проиллюстрируем поэтапно вышеописанный процесс.

Как мы спасали глаза с OpenCV

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

Ах да, как же реализовать распознавание моргания? Всё просто, закрытый глаз характеризуется тем, что его верхнее веко ниже средней линии глаза, именно это условие и проверяется.

Как мы спасали глаза с OpenCV

Задача №4 — Определение уровня освещения.

Уровень освещения также играет важную роль, если вы беспокоитесь о своем зрении. Рабочую зону можно считать не слишком удачно организованной, если вы работаете в темной комнате, а яркий дисплей запредельно слепит ваши глаза. Также лучше не стоит засиживаться перед компьютером, если за экраном вам в глаза светит другой яркий источник света. Еще не надо забывать о бликах, которые монитор может создавать от источника света, располагающийся у вас за спиной. Вряд ли возможно полностью устранить такую проблему с помощью веб-камеры. Но мы все же попытались осуществить частичное ее решение.

Итак, что для этого нужно сделать? Для начала нам необходимо разделить изображение на 2 части: лицо и все остальное, то есть фон. Мы будем сравнивать яркость этих двух картинок. Можно с легкостью преобразовать изображение в монохромное, так как нам неважно, какую насыщенность считать, суммарную или разделенную по трем каналам (RGB). Следующий наш шаг состоит в том, чтобы построить гистограммы по двум изображениям. Эта процедура необходима нам для того, чтобы впоследствии иметь представление об общем уровне яркости изображения.

В итоге, по горизонтальной оси представлена яркость, а по вертикали — относительное число пикселей с конкретным значением яркости. Далее мы условно делим пиксели на светлые и темные. Из каждого изображения выделяется 4 группы. Теперь при определенном анализе и сравнении между собой этих 4 групп мы можем сделать несколько выводов: когда все кол-во темных пикселей значительно преобладает и на лице, и на фоне, то, мы работаем в темной комнате, а свечение издает лишь монитор. Когда кол-во светлых пикселей на лице значительно больше, чем на фоне, то значит, что за монитором есть еще какой-то источник света, который светит нам прямо в лицо. Это может быть как настольная лампа, так и солнце за окном. Пример разбиения изображения на лицо и фон с последующим построением гистограмм представлен ниже.

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

Нельзя не сказать, как занимателен процесс тестирования всей этой радости. И как обидно, когда у тебя дома всё работает “на ура”, а при демонстрации приложения знакомым — то в носу глаз определит, то ещё где похуже. Алгоритмы определённо нуждаются в постоянной оптимизации. Но если после месяца работы результаты пугали своей неточностью, то через год мы сами начали пользоваться своим приложением. В нем всё ещё много неожиданных багов, но на собственном опыте можно сказать, что в целом всё работает сносно. Иногда ты начинаешь щуриться из-за непривычной яркости, иногда, погруженный в работу, ты приближаешься к монитору и действительно не замечаешь этого. Вот тут-то Viewaide и подсказывает тебе невнимательному. Сейчас настал момент поделиться результатом нашей работы с IT-сообществом, с теми, кто чаще задумывается о возможном ухудшении зрения или о снижении работоспособности.

На данный момент доступна полноценная Windows-версия, а так же Beta-версия на Mac. В течение месяца мы планируем выпустить готовое приложение на Mac и Linux. И если читатели заинтересуется, то в свет выйдут 2 статьи о тонкостях портирования Qt приложения с Windows на Mac и Linux.

Мы будем признательны за любую критику. И если вы ещё не скачали Viewaide, то самое время это сделать.

Автор: andheroe

Источник

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


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