- PVSM.RU - https://www.pvsm.ru -
Всем привет. Это моя юбилейная статья на хабре. За почти 7 лет я написал 10 статей (включая эту), 8 из них — технические. Общее количество просмотров всех статей — около полумиллиона.
Основной вклад я внёс в два хаба: PHP и Серверное администрирование. Мне нравится работать на стыке этих двух областей, но сфера моих интересов гораздо шире.
Как и многие разработчики я часто пользуюсь результатами чужого труда (статьи на хабре, код на гитхабе, ...), поэтому я всегда рад делиться с сообществом своими результатами в ответ. Написание статей — это не только возврат долга сообществу, но так же позваляет найти единомышленников, получить комментарии от профессионалов в узкой сфере и ещё больше углубить свои знания в исследуемой области.
Собственно эта статья об одном из таких моментов. В ней я опишу чем занимался почти всё своё свободное время за последние полгода. Кроме тех моментов, когда я ходил купаться в море через дорогу [1], смотрел сериалы или игрался в игры.
Сейчас очень сильно развивается «Машинное обучение», по нему написано уже очень много статей, в том числе на хабре и практически каждый разработчик хотел бы взять и начать его использовать в своих рабочих задачах и домашних проектах, но с чего начать и к чему применять не всегда понятно. Большинство статей для начинающих предлагают кучу литературы, на прочтение которой не хватит и жизни, англоязычные курсы (а не все из нас могут усваивать материал на английском так же эффективно как и на русском), «недорогие» русскоязычные курсы и т.д.
Регулярно выходят новые статьи, в которых описаны новые подходы к решению той или иной задачи. На гитхабе можно найти реализацию описанного в статьях подхода. В качестве языков программирования чаще используются: c / c++, python 2/3, lua и matlab, а в качестве фремворков: caffe, tensorflow, torch. Каждый пишет — кто на чём горазд. Большая сегментация по языкам программирования и фреймворкам сильно усложняет процедуру поиска того, что тебе нужно и интеграцию этого в проект. К тому же в последнее время очень много исходного кода с комментариями на китайском языке.
Чтобы как-то уменьшить весь этот хаос в opencv добавили модуль dnn [2], который позволяет использовать модели, натренированные в основных фреймворках. Я со своей стороны покажу как этот модуль можно использовать из php.
Jeremy Howard [3] (создатель бесплатного практического курса «машинное обучение для кодеров» [4]) считает, что сейчас есть большой порог между изучением машинного обучения и применении его на практике.
Howard говорит, что для начала изучения машинного обучения достаточно одного года опыта программирования [5]. Я с ним полностью согласен и надеюсь, что моя статья поможет снизить порог вхождения в opencv для php-разработчиков, которые мало знакомы с машинным обучением и ещё не уверены хотят ли они вообще этим заниматься или нет, а также постараюсь описать все моменты, на которые я тратил часы и дни, чтобы у вас на это уходило не больше минуты.
Итак, что же я сделал кроме логотипа?
(надеюсь, что opencv не засудит меня за плагиат)
Я рассматривал возможность написать модуль php-opencv самостоятельно с помощью SWIG [6] и потратил на это кучу времени, но так ничего и не добился. Всё осложнялось тем, что я не знал с/с++ и не писал расширений под php 7. К сожалению большинство материалов в интернете по php-расширениям было написано для php 5, поэтому приходилось собирать информацию по крупицам, а возникающие проблемы решать самостоятельно.
Потом я нашёл на просторах гитхаба библиотеку php-opencv [7], она представляет из себя модуль для php7, который делает вызовы методов opencv. Чтобы скомпилировать, установить и запустить примеры у меня ушло несколько вечеров. Я начал пробовать различные возможности этого модуля, но мне не хватало некоторых методов, я их добавил самостоятельно, создал пулреквест, а автор библиотеки принял. Позже я добавил ещё больше функций.
Возможно читатель на этом моменте задаст себе вопрос: зачем вообще автору нужны были такие проблемы, почему было просто не начать использовать python и tensorflow?
Вообщем, пока меня устраивает работать с 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);
полный код примера [10]
Результат:
Как видно из примера, не составляет проблем найти лицо даже на фото в гриме зомби. Очки также не мешают нахождению лица.
Для этого в 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 (уверенность)
полный код примера [11]
Наборы лиц:
Результат:
Когда я начинал работать с LBPHFaceRecognizer, у него не было возможности сохранения/загрузки/дообучения готовой модели. Собственно первый мой пулреквест добавил эти методы: write/read/update.
Когда я начинал знакомиться с opencv, то часто натыкался на фотографии лиц, на которых точками отмечены глаза, нос, губы и т.д. Мне хотелось повторить этот эксперимент самостоятельно, но в версии opencv для питона этого не реализовали. У меня ушёл вечер, чтобы добавить поддержку FacemarkLBF на php и отправить второй пулреквест. Всё работает просто, загружаем предобученную модель, подаём на вход массив лиц, получаем массив точек для каждого лица.
$facemark = FacemarkLBF::create();
$facemark->loadModel('models/opencv-facemark-lbf/lbfmodel.yaml');
$facemark->fit($src, $faces, $landmarks);
полный код примера [12]
Результат:
Как видно из примера, грим зомби может ухудшить нахождение опорных точек на лице. Очки также могут помешать нахождению лица. Засветка тоже влияет. При этом посторонние предметы во рту (клубника, сигарета и т.д.) могут и не мешать.
После моего первого пулреквеста я вдохновился и стал смотреть, что можно сделать ещё с помощью opencv и наткнулся на статью Deep Learning, теперь и в OpenCV [2]. Не долго думая, я решил добавить в php-opencv возможность использования предобученных моделей, которых полно на просторах интернета. Это оказалось не сильно сложно для загрузки caffe-моделей, правда позже у меня ушло куча времени чтобы получить научиться работать с многомерными матрицами, половина из которого ушла на разбирательство с c++ и изучение внутренностей opencv, а вторая на python и работу с моделями caffe/torch/tensorflow без использования opencv.
Итак, opencv позволяет загружать предобученные модели в Caffe с помощью функции readNetFromCaffe [13]. Она принимает два параметра — пути до файлов .prototxt и .caffemodel. В prototxt-файле лежит описание модели, а в caffemodel — веса, вычисленные во время тренировки модели.
Вот пример начала prototxt-файла [14]:
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 [15].
После этого нам остаётся только подать blob на вход сети с помощью метода setInput [16] и вызвать метод forward [17], который вернёт нам готовый результат.
$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 МБ, полный код примера [18].
Результат:
Как видно из примера, нейронная сеть не всегда выдаёт хорошие результаты при использовании её «в лоб». Не было найдено четвёртое лицо, при этом если четвёртое фото вырезать и отправить в сеть отдельно, то лицо будет найдено.
Я уже давно слышал про библиотеку waifu2x [19], которая позволяет устранять шум и увеличивать размеры иконок/фото. Сама библиотека написана на lua, а под капотом использует несколько моделей (для увеличения иконок, устранения шума фото и т.д.) натренированных в torch. Автор библиотеки экспортировал эти модели в caffe и помог мне использовать их из opencv. В результате чего был написан пример на php для увеличения разрешения иконок.
Размер модели 2 МБ, полный код примера [20].
Оригинал:
Результат:
Увеличение картинки без использования нейронной сети:
Нейронная сеть MobileNet [21], обученная на наборе данных ImageNet [22] позволяет классифицировать изображение. Всего она может определять 1000 классов [23], что по-моему достаточно не мало.
Размер модели 16 МБ, полный код примера [24].
Оригинал:
Результат:
87% — Egyptian cat, 4% — tabby, tabby cat, 2% — tiger cat
Нейронная сеть MobileNet SSD (Single Shot MultiBox Detector), натренированная в Tensorflow [25] на датасете COCO [26] может не только классифицировать изображение, но и возвращать регионы, правда всего определять она может только 182 класса [27].
Размер модели 19 МБ, полный код примера [28].
Оригинал:
Результат:
В репозиторий с примерами я также добавил файл phpdoc.php [29]. Благодаря ему 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, но пока не осилил туда заливку и не нашёл ничего лучше, чем просто залить пакеты на гитхаб [30]. Также я создал заявку на создание аккаунта в 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 [31].
Также я собрал docker-образ размером 168 МБ [32].
Скачивание:
git clone github.com/php-opencv/php-opencv-examples.git && cd php-opencv-examples
Запуск:
php detect_face_by_dnn_ssd.php
Прошу всех заинтересованных лиц ответить на опросы после статьи, ну и подписывайтесь, чтобы не пропустить мои следующие статьи, ставьте лайки, чтобы мотивировать меня на их написание и пишите в комментария вопросы, предлагайте варианты для новых экспериментов/статей.
Традиционно предупреждаю, что я не консультирую и не помогаю через личные сообщения хабра и соцсети.
Вы всегда можете задать вопросы, создав Issue на гитхабе (можно на русском).
Ссылки:
php-opencv-examples [33] — все примеры из статьи
php-opencv/php-opencv [34] — мой форк с поддержкой модуля dnn
hihozhou/php-opencv [7] — оригинальный репозиторий, без поддержки модуля dnn (я создал пулреквест, но он пока ещё не был принят).
Перевод статьи на английский язык [35] — я слышал, что англичане и американцы очень терпеливы к тем кто делает ошибки на английском, но мне кажется, что всему есть передел и я пересёк эту черту :) вообщем лайкните, кому не жалко. Тоже самое на реддите [36].
Автор: Владимир Гончаров
Источник [37]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/283162
Ссылки в тексте:
[1] купаться в море через дорогу: https://habr.com/post/352118/
[2] opencv добавили модуль dnn: https://habr.com/company/intel/blog/333612/
[3] Jeremy Howard: https://en.wikipedia.org/wiki/Jeremy_Howard_(entrepreneur)
[4] «машинное обучение для кодеров»: http://course.fast.ai/
[5] одного года опыта программирования: http://www.fast.ai/2017/11/16/what-you-need/#the-background-you-need-1-year-of-coding
[6] SWIG: https://ru.wikipedia.org/wiki/SWIG
[7] php-opencv: https://github.com/hihozhou/php-opencv
[8] mtcnn: https://github.com/kpzhang93/MTCNN_face_detection_alignment
[9] facemark: https://www.learnopencv.com/facemark-facial-landmark-detection-using-opencv/
[10] полный код примера: https://github.com/php-opencv/php-opencv-examples/blob/master/detect_face_by_cascade_classifier.php
[11] полный код примера: https://github.com/php-opencv/php-opencv-examples/blob/master/recognize_face_by_lbph.php
[12] полный код примера: https://github.com/php-opencv/php-opencv-examples/blob/master/detect_facemarks_by_lbf.php
[13] readNetFromCaffe: https://docs.opencv.org/3.4.1/d6/d0f/group__dnn.html#ga29d0ea5e52b1d1a6c2681e3f7d68473a
[14] prototxt-файла: https://github.com/php-opencv/php-opencv-examples/blob/master/models/ssd/res10_300x300_ssd_deploy.prototxt
[15] blobFromImage: https://docs.opencv.org/3.4.1/d6/d0f/group__dnn.html#ga152367f253c81b53fe6862b299f5c5cd
[16] setInput: https://docs.opencv.org/3.4.1/db/d30/classcv_1_1dnn_1_1Net.html#a672a08ae76444d75d05d7bfea3e4a328
[17] forward: https://docs.opencv.org/3.4.1/db/d30/classcv_1_1dnn_1_1Net.html#a98ed94cb6ef7063d3697259566da310b
[18] полный код примера: https://github.com/php-opencv/php-opencv-examples/blob/master/detect_face_by_dnn_ssd.php
[19] waifu2x: https://github.com/nagadomi/waifu2x
[20] полный код примера: https://github.com/php-opencv/php-opencv-examples/blob/master/upscale_image_x2_by_dnn_waifu2x.php
[21] MobileNet: https://habr.com/post/352804/
[22] ImageNet: https://ru.wikipedia.org/wiki/ImageNet
[23] 1000 классов: https://github.com/php-opencv/php-opencv-examples/blob/master/models/mobilenet/classes.txt
[24] полный код примера: https://github.com/php-opencv/php-opencv-examples/blob/master/classify_image_by_dnn_mobilenet.php
[25] Tensorflow: https://github.com/tensorflow/models/blob/master/research/object_detection/g3doc/detection_model_zoo.md
[26] COCO: http://cocodataset.org/
[27] 182 класса: https://github.com/php-opencv/php-opencv-examples/blob/master/models/ssdlite_mobilenet_v2_coco/classes.txt
[28] полный код примера: https://github.com/php-opencv/php-opencv-examples/blob/master/detect_objects_by_dnn_mobilenet.php
[29] phpdoc.php: https://github.com/php-opencv/php-opencv-examples/blob/master/phpdoc.php
[30] пакеты на гитхаб: https://github.com/php-opencv/php-opencv-packages
[31] Все варианты установки на ubuntu: https://github.com/php-opencv/php-opencv-examples/wiki/Installation-on-ubuntu
[32] docker-образ размером 168 МБ: https://github.com/php-opencv/php-opencv-examples/wiki/installation-via-docker
[33] php-opencv-examples: https://github.com/php-opencv/php-opencv-examples
[34] php-opencv/php-opencv: https://github.com/php-opencv/php-opencv
[35] Перевод статьи на английский язык: https://medium.com/@morozovsk/computer-vision-and-machine-learning-in-php-using-the-opencv-library-3131fe9df94b
[36] реддите: https://www.reddit.com/r/PHP/comments/8rtbk7/computer_vision_and_machine_learning_in_php_using/
[37] Источник: https://habr.com/post/358902/?utm_source=habrahabr&utm_medium=rss&utm_campaign=358902
Нажмите здесь для печати.