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

в 19:32, , рубрики: caffe, detection, detector, DNN, face, facemark, facial landmark, lbf, lbph, open source, opencv, php, php-opencv, recognition, recognizer, torch, машинное обучение, обработка изображений, Программирование

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

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

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

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

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

Как_множатся_стандарты.jpg

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

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

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

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

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

Итак, что же я сделал кроме логотипа?
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 4
(надеюсь, что 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 = cvimread("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);

полный код примера
Результат:
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 5
Как видно из примера, не составляет проблем найти лицо даже на фото в гриме зомби. Очки также не мешают нахождению лица.

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

Для этого в 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 (уверенность)

полный код примера
Наборы лиц:
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 6

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

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

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

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

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

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

После моего первого пулреквеста я вдохновился и стал смотреть, что можно сделать ещё с помощью 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 = CVDNNreadNetFromCaffe('models/ssd/res10_300x300_ssd_deploy.prototxt', 'models/ssd/res10_300x300_ssd_iter_140000.caffemodel');

$blob = CVDNNblobFromImage($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 МБ, полный код примера.
Результат:
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 10
Как видно из примера, нейронная сеть не всегда выдаёт хорошие результаты при использовании её «в лоб». Не было найдено четвёртое лицо, при этом если четвёртое фото вырезать и отправить в сеть отдельно, то лицо будет найдено.

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

Я уже давно слышал про библиотеку waifu2x, которая позволяет устранять шум и увеличивать размеры иконок/фото. Сама библиотека написана на lua, а под капотом использует несколько моделей (для увеличения иконок, устранения шума фото и т.д.) натренированных в torch. Автор библиотеки экспортировал эти модели в caffe и помог мне использовать их из opencv. В результате чего был написан пример на php для увеличения разрешения иконок.
Размер модели 2 МБ, полный код примера.
Оригинал:
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 11
Результат:
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 12
Увеличение картинки без использования нейронной сети:
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 13

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

Нейронная сеть MobileNet, обученная на наборе данных ImageNet позволяет классифицировать изображение. Всего она может определять 1000 классов, что по-моему достаточно не мало.
Размер модели 16 МБ, полный код примера.
Оригинал:
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 14
Результат:
87% — Egyptian cat, 4% — tabby, tabby cat, 2% — tiger cat

Tensorflow Object Detection API

Нейронная сеть MobileNet SSD (Single Shot MultiBox Detector), натренированная в Tensorflow на датасете COCO может не только классифицировать изображение, но и возвращать регионы, правда всего определять она может только 182 класса.
Размер модели 19 МБ, полный код примера.
Оригинал:
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 15
Результат:
Компьютерное зрение и машинное обучение в PHP используя библиотеку opencv - 16

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

В репозиторий с примерами я также добавил файл phpdoc.php. Благодаря ему Pphstorm подсвечивает синтакис функций, классов и их методов, а также работает автодополнение кода. Этот файл не нужно подключать в свой код (иначе будет ошибка), его достаточно положить в свой проект. Лично мне это упрощает жизнь. В этом файле описано большинство функций 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 github.com/php-opencv/php-opencv-examples.git && cd php-opencv-examples

Запуск:

php detect_face_by_dnn_ssd.php

PS

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

Ссылки:
php-opencv-examples — все примеры из статьи
php-opencv/php-opencv — мой форк с поддержкой модуля dnn
hihozhou/php-opencv — оригинальный репозиторий, без поддержки модуля dnn (я создал пулреквест, но он пока ещё не был принят).
Перевод статьи на английский язык — я слышал, что англичане и американцы очень терпеливы к тем кто делает ошибки на английском, но мне кажется, что всему есть передел и я пересёк эту черту :) вообщем лайкните, кому не жалко. Тоже самое на реддите.

Автор: Владимир Гончаров

Источник

Поделиться

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