Pull to refresh

Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv

Reading time 9 min
Views 33K
Всем привет. Это моя юбилейная статья на Хабре. За почти 7 лет я написал 10 статей (включая эту), 8 из них — технические. Общее количество просмотров всех статей — около полумиллиона.
Основной вклад я внёс в два хаба: PHP и Серверное администрирование. Мне нравится работать на стыке этих двух областей, но сфера моих интересов гораздо шире.

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

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



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

Регулярно выходят новые статьи, в которых описаны новые подходы к решению той или иной задачи. На гитхабе можно найти реализацию описанного в статьях подхода. В качестве языков программирования чаще используются: c / c++, python 2/3, lua и matlab, а в качестве фремворков: caffe, tensorflow, torch. Каждый пишет — кто на чём горазд. Большая сегментация по языкам программирования и фреймворкам сильно усложняет процедуру поиска того, что тебе нужно и интеграцию этого в проект. К тому же в последнее время очень много исходного кода с комментариями на китайском языке.

Чтобы как-то уменьшить весь этот хаос в opencv добавили модуль dnn, который позволяет использовать модели, натренированные в основных фреймворках. Я со своей стороны покажу как этот модуль можно использовать из php.

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


Jeremy Howard (создатель бесплатного практического курса «машинное обучение для кодеров») считает, что сейчас есть большой порог между изучением машинного обучения и применении его на практике.


Howard говорит, что для начала изучения машинного обучения достаточно одного года опыта программирования. Я с ним полностью согласен и надеюсь, что моя статья поможет снизить порог вхождения в opencv для php-разработчиков, которые мало знакомы с машинным обучением и ещё не уверены хотят ли они вообще этим заниматься или нет, а также постараюсь описать все моменты, на которые я тратил часы и дни, чтобы у вас на это уходило не больше минуты.

Итак, что же я сделал кроме логотипа?


(надеюсь, что opencv не засудит меня за плагиат)

Я рассматривал возможность написать модуль php-opencv самостоятельно с помощью SWIG и потратил на это кучу времени, но так ничего и не добился. Всё осложнялось тем, что я не знал с/с++ и не писал расширений под php 7. К сожалению большинство материалов в интернете по php-расширениям было написано для php 5, поэтому приходилось собирать информацию по крупицам, а возникающие проблемы решать самостоятельно.

Потом я нашёл на просторах гитхаба библиотеку php-opencv, она представляет из себя модуль для php7, который делает вызовы методов opencv. Чтобы скомпилировать, установить и запустить примеры у меня ушло несколько вечеров. Я начал пробовать различные возможности этого модуля, но мне не хватало некоторых методов, я их добавил самостоятельно, создал пулреквест, а автор библиотеки принял. Позже я добавил ещё больше функций.

Возможно читатель на этом моменте задаст себе вопрос: зачем вообще автору нужны были такие проблемы, почему было просто не начать использовать python и tensorflow?

Ответ. Осторожно, занудство и отмазки!
Дело в том, что я не профессиональный специалист по машинному обучению, я не могу на данном этапе разработать свой собственный подход к решению той или иной узкой задачи, в которой я достигну результатов на пару процентов лучше, чем другие исследователи, а потом ещё получить на это дело патент. Например, так сделали пять китайских парней с научными степенями, которые разработали mtcnn и написали реализацию на matlab и caffe. Потом другие три китайских парня перенесли этот код на C++ & caffe, Python & mxnet, Python & caffe. Как вы наверное уже догадались, на знании только python и tensorflow далеко не уедешь. Придётся постоянно сталкиваться с кодом на разных языках с использованием разных фреймворков и комментариями на китайском.
Другой пример, я хотел использовать facemark из opencv, но к сожалению авторы не добавили поддержку этого модуля при работе из python. При этом, чтобы добавить биндинги facemark в php у меня ушёл один вечер.
Я так же пытался скомпилировать opencv для работы с nodejs, согласно нескольким инструкциям но у меня выдавались различные ошибки и не получилось достигнуть результата.
По большей части мне было интересно этим заниматься не смотря на все трудности.

В общем, пока меня устраивает работать с opencv на php.

Вот так выглядит загрузка изображения:

$image = cv\imread("images/faces.jpg");

Для сравнения, на питоне это выглядит так:

image = cv2.imread("images/faces.jpg")

При чтении изображения в php (также как и в с++) информация сохраняется в объект Mat (матрица). В php её аналогом является многомерный массив, но в отличие от многомерного массива этот объект позволяет различные быстрые манипуляции, например, деление всех элементов на число. В питоне при загрузке изображения возвращается объект numpy.

Осторожно, легаси! Так уж вышло, что imread (в php, c++ и pyton) загружает изображение не в формате RGB, а в BGR. Поэтому в примерах с opencv можно часто увидеть процедуру конвертации BGR->RGB и обратно.

Поиск лиц на фото


Первым делом я попробовал эту функцию. Для неё в opencv есть класс CascadeClassifier, который может использовать предобученную модель в формате xml. Перед нахождением лица рекомендуется переводить изображение в чёрно-белый формат.

$src = imread("images/faces.jpg");
$gray = cvtColor($src, COLOR_BGR2GRAY);

$faceClassifier = new CascadeClassifier();
$faceClassifier->load('models/lbpcascades/lbpcascade_frontalface.xml');

$faceClassifier->detectMultiScale($gray, $faces);

Полный код примера

Результат:



Как видно из примера, не составляет проблем найти лицо даже на фото в гриме зомби. Очки также не мешают нахождению лица.

Распознавание (узнавание) лиц на фото


Для этого в opencv есть класс LBPHFaceRecognizer и методы train/predict.

Если мы хотим узнать кто присутствует на фотографии, то сначала нужно натренировать модель с помощью метода train, он принимает два параметра: массив изображений лиц и массив числовых меток для этих изображений. После можно вызвать метод predict на тестовом изображении (лице) и получить числовую метку, которой оно соответствует.

$faceRecognizer = LBPHFaceRecognizer::create();
$faceRecognizer->train($myFaces, $myLabels = [1,1,1,1]); // 4 мои лица
$faceRecognizer->update($angelinaFaces, $angelinaLabels = [2,2,2,2]); // 4 лица Анжелины
$label = $faceRecognizer->predict($faceImage, $confidence);
// получаем label (1 или 2) и $confidence (уверенность)

Полный код примера

Наборы лиц:





Результат:



Когда я начинал работать с LBPHFaceRecognizer, у него не было возможности сохранения/загрузки/дообучения готовой модели. Собственно первый мой пулреквест добавил эти методы: write/read/update.

Нахождение меток на лицах


Когда я начинал знакомиться с opencv, то часто натыкался на фотографии лиц, на которых точками отмечены глаза, нос, губы и т.д. Мне хотелось повторить этот эксперимент самостоятельно, но в версии opencv для питона этого не реализовали. У меня ушёл вечер, чтобы добавить поддержку FacemarkLBF на php и отправить второй пулреквест. Всё работает просто, загружаем предобученную модель, подаём на вход массив лиц, получаем массив точек для каждого лица.

$facemark = FacemarkLBF::create();
$facemark->loadModel('models/opencv-facemark-lbf/lbfmodel.yaml');
$facemark->fit($src, $faces, $landmarks);

Полный код примера

Результат:



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

После моего первого пулреквеста я вдохновился и стал смотреть, что можно сделать ещё с помощью opencv и наткнулся на статью Deep Learning, теперь и в OpenCV. Не долго думая, я решил добавить в php-opencv возможность использования предобученных моделей, которых полно на просторах интернета. Это оказалось не сильно сложно для загрузки caffe-моделей, правда позже у меня ушло куча времени чтобы получить научиться работать с многомерными матрицами, половина из которого ушла на разбирательство с c++ и изучение внутренностей opencv, а вторая на python и работу с моделями caffe/torch/tensorflow без использования opencv.

Поиск лиц на фото с помощью модуля dnn


Итак, opencv позволяет загружать предобученные модели в Caffe с помощью функции readNetFromCaffe. Она принимает два параметра — пути до файлов .prototxt и .caffemodel. В prototxt-файле лежит описание модели, а в caffemodel — веса, вычисленные во время тренировки модели.

Вот пример начала prototxt-файла:

input: "data"
input_shape {
  dim: 1
  dim: 3
  dim: 300
  dim: 300
}

Этот кусок файла описывает, что на вход ожидается 4-х мерная матрица 1x3x300x300. В описании моделей обычно пишут, что ожидается в таком формате, но чаще всего этого означает, что на вход ожидается изображение RGB (3 канала) размером 300x300.

Загружая RGB-изображение размером 300x300 c помощью функции imread мы получаем матрицу 300x300x3.

Для приведения матрицы 300x300x3 к виду 1x3x300x300 в opencv есть функция blobFromImage.
После этого нам остаётся только подать blob на вход сети с помощью метода setInput и вызвать метод forward, который вернёт нам готовый результат.

$src = imread("images/faces.jpg");

$net = \CV\DNN\readNetFromCaffe('models/ssd/res10_300x300_ssd_deploy.prototxt', 'models/ssd/res10_300x300_ssd_iter_140000.caffemodel');

$blob = \CV\DNN\blobFromImage($src, $scalefactor = 1.0, $size = new Size(300, 300), $mean = new Scalar(104, 177, 123), $swapRB = true, $crop = false);

$net->setInput($blob, "");

$result = $net->forward();

В данном случае результат — это матрица 1x1x200x7, т.е. 200 массивов по 7 элементов каждый. На фото с четырьмя лицами сеть нашла нам 200 кандидатов. Каждый из которых выглядит так [,, $confidence, $startX, $startY, $endX, $endY]. Элемент $confidence отвечает за «уверенность», т.е. то что вероятность предсказания удачна, например 0.75. Следующие элементы отвечают за координаты прямоугольника с лицом. В данном примере было найдено только 3 лица с уверенностью больше 50%, а оставшиеся 197 кандидатов лиц имеют уверенность менее 15%.

Размер модели 10 МБ, полный код примера.

Результат:



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

Улучшение качества изображения с помощью нейронной сети


Я уже давно слышал про библиотеку waifu2x, которая позволяет устранять шум и увеличивать размеры иконок/фото. Сама библиотека написана на lua, а под капотом использует несколько моделей (для увеличения иконок, устранения шума фото и т.д.) натренированных в torch. Автор библиотеки экспортировал эти модели в caffe и помог мне использовать их из opencv. В результате чего был написан пример на php для увеличения разрешения иконок.

Размер модели 2 МБ, полный код примера.

Оригинал:



Результат:



Увеличение картинки без использования нейронной сети:



Классификация изображений


Нейронная сеть MobileNet, обученная на наборе данных ImageNet позволяет классифицировать изображение. Всего она может определять 1000 классов, что по-моему достаточно не мало.

Размер модели 16 МБ, полный код примера.

Оригинал:



Результат:

87% — Egyptian cat, 4% — tabby, tabby cat, 2% — tiger cat

Tensorflow Object Detection API


Нейронная сеть MobileNet SSD (Single Shot MultiBox Detector), натренированная в Tensorflow на датасете COCO может не только классифицировать изображение, но и возвращать регионы, правда всего определять она может только 182 класса.

Размер модели 19 МБ, полный код примера.

Оригинал:



Результат:



Подсветка синтаксиса и автодополнение кода


В репозиторий с примерами я также добавил файл phpdoc.php. Благодаря ему Phpstorm подсвечивает синтакис функций, классов и их методов, а также работает автодополнение кода. Этот файл не нужно подключать в свой код (иначе будет ошибка), его достаточно положить в свой проект. Лично мне это упрощает жизнь. В этом файле описано большинство функций opencv, но не все, так что пулреквесты приветствуются.

Установка


Модуль dnn появился в opencv только в версии 3.4 (до этого он был в opencv-contrib).

В ubuntu 18.04 самая последняя версия opencv — 3.2. Сборка opencv из исходников занимает где-то полчаса, поэтому я собрал пакет под ubuntu 18.04 (работает и для 17.10, размер 25МБ), а также собрал пакеты php-opencv для php 7.2 (ubuntu 18.04) и php 7.1 (ubuntu 17.10) (размер 100КБ).

Зарегистрировал ppa:php-opencv, но пока не осилил туда заливку и не нашёл ничего лучше, чем просто залить пакеты на гитхаб. Также я создал заявку на создание аккаунта в pecl, но спустя несколько месяцев так и не получил ответа.

Таким образом сейчас установка под ubuntu 18.04 выглядит так:

apt update && apt install -y wget && \
wget https://raw.githubusercontent.com/php-opencv/php-opencv-packages/master/opencv_3.4_amd64.deb && dpkg -i opencv_3.4_amd64.deb && rm opencv_3.4_amd64.deb && \
wget https://raw.githubusercontent.com/php-opencv/php-opencv-packages/master/php-opencv_7.2-3.4_amd64.deb && dpkg -i php-opencv_7.2-3.4_amd64.deb && rm php-opencv_7.2-3.4_amd64.deb && \
echo "extension=opencv.so" > /etc/php/7.2/cli/conf.d/opencv.ini

Установка таким вариантом занимает около 1 минуты. Все варианты установки на ubuntu.
Также я собрал docker-образ размером 168 МБ.

Использование примеров


Скачивание:

git clone https://github.com/php-opencv/php-opencv-examples.git && cd php-opencv-examples

Запуск:

php detect_face_by_dnn_ssd.php

PS


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

Традиционно предупреждаю, что я не консультирую и не помогаю через личные сообщения Хабра и соцсети.

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

Благодарности:
dkurt за быстрые ответы на гитхабе.
arrybn за статью «Deep Learning, теперь и в OpenCV»

Ссылки:

php-opencv-examples — все примеры из статьи
php-opencv/php-opencv — мой форк с поддержкой модуля dnn
hihozhou/php-opencv — оригинальный репозиторий, без поддержки модуля dnn (я создал пулреквест, но он пока ещё не был принят).
Перевод статьи на английский язык — я слышал, что англичане и американцы очень терпеливы к тем кто делает ошибки на английском, но мне кажется, что всему есть передел и я пересёк эту черту :) в общем лайкните, кому не жалко. Тоже самое на реддите.
Only registered users can participate in poll. Log in, please.
Можно ли называть вызов методов opencv из php как «Работа с компьютерным зрением в php»?
75% да, потому что я пишу только php-код без использования С++ 114
4.61% нет, работа с С++ библиотекой opencv из python — это «настоящее» компьютерное зрение 7
0% нет, работа с С++ библиотекой opencv из matlab — это «настоящее» компьютерное зрение 0
0% нет, работа с С++ библиотекой opencv из lua — это «настоящее» компьютерное зрение 0
20.39% нет, работа с opencv на c++ — это «настоящее» компьютерное зрение 31
152 users voted. 96 users abstained.
Only registered users can participate in poll. Log in, please.
Удобный способ установки php-opencv
43.06% установка из pecl 62
45.83% скачивание и установка пакета 66
25.69% установка из ppa-репозитория 37
2.78% установка из snap-репозитория 4
20.14% установка из docker-хаба 29
19.44% установка из исходников 28
144 users voted. 77 users abstained.
Only registered users can participate in poll. Log in, please.
Нужны ли ещё статьи на эту тему?
58% да, нужно больше теории 116
81% да, нужно больше практики 162
62% да, нужны полезные примеры использования предобученных моделей 124
60% да, нужны интересные примеры работы с изображениями 120
39% да, нужны примеры работы с видео 78
62.5% да, нужны примеры обучения сетей 125
4.5% нет, не нужны 9
200 users voted. 48 users abstained.
Only registered users can participate in poll. Log in, please.
php и python
20.93% Я программирую на php и python 54
67.05% Я программирую только на php 173
12.02% Я программирую только на python 31
258 users voted. 40 users abstained.
Only registered users can participate in poll. Log in, please.
Машинное обучение и php-разработчики
1.9% Я php-разработчик и работаю с машинным обучением 4
86.26% Я php-разработчик и интересуюсь машинным обучением 182
11.85% Я php-разработчик и не интересуюсь машинным обучением 25
211 users voted. 58 users abstained.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+41
Comments 21
Comments Comments 21

Articles