Распознавание контура тела в реальном времени. Пишем скрипт для поиска книг на изображениях с помощью Python и OpenCV

Евгений Борисов понедельник, 24 июля 2017 г.

В этой статье привёден обзор методов поиска объекта на изображении.

1. Введение

Множество практических задач от автоматизации контроля на производстве до конструирования роботизированных автомобилей непосредственно связаны с задачей поиска объектов на изображении. Для её решения можно применять две разные стратегии, которые зависят от условий съёмки - моделирование фона и моделирование объекта.
  1. Моделирование фона - этот подход можно применять если камера неподвижна, т.е. мы имеем фон, который мало изменяется, и таким образом можно построить его модель. Все точки изображения, которые существенно отклоняются от модели фона, считаем объектами переднего плана. Таким образом можно решать задачи обнаружения и сопровождения объекта.
  2. Моделирование объекта - этот подход более общий, применяеться в случаях когда фон постоянно и существенным образом изменяется. В отличии от предыдущего случая, здесь нам необходимо знать что именно мы хотим найти, т.е. необходимо построить модель объекта, а затем проверить точки картинки на соответствие этой модели.
Иногда условия задачи позволяют комбинировать оба подхода, это может существенно улучшить результаты. Решение задачи с моделированием фона для неподвижной камерой можно найти в . Далее будем рассматривать применение второй стратегии, т.е. моделирование объекта поиска.

2. Обзор методов

В этом разделе мы приведём список подходов, с помощью которых можно успешно решать задачу поиска объекта на изображении, в порядке возрастания сложности.
  1. Цветовые фильтры - если объект существенно выделяется на фоне по цвету, то можно подобрать соответствующий фильтр.
  2. Выделение и анализ контуров - если мы знаем, что объект имеет форму, например, круга, то можно поискать окружности на изображении.
  3. Сопоставление с шаблоном - у нас есть изображение объекта, ищем в другом изображении области совпадающие с этим изображением объекта.
  4. Работа с особыми точками - на картинке с объектом ищем особенности (например углы), которые пытаемся сопоставить с такими особенностями на другом изображении.
  5. Методы машинного обучения - обучаем классификатор на картинках с объектом, некоторым способом разделяем изображение на части, проверяем классификатором каждую часть на наличие объекта.
Далее мы рассмотрим эти методы подробней.

3. Цветовые фильтры

Метод цветовых фильтров можно применять в случаях, когда объект существенно отличаться от фона по цвету и освещение равномерно и не изменяется. Подробней о методе цветовых фильтров можно почитать в .

4. Выделение и анализ контуров

Если объект на фоне по цвету существенным образом не выделяется и/или имеет сложную раскраску, то применение метода цветовых фильтров не даст хороших результатов. В этом случае можно попробовать применить метод выделения и анализа контуров. Для этого мы выделяем границы на изображении. Границы это места резкого изменения градиента яркости, их можно найти с помощью метода Canny . Далее мы можем проверить выделенные линии-границы на соответствие геометрическим контурам объекта, это можно сделать применив метод Хафа (Hough Transform) , например мы можем поискать в границах окружности.





Рис.4: поиск окружностей

Этот метод также можно применять совместно с цветовыми фильтрами. Подробней о выделении и анализе контуров можно почитать в . Исходный код примера c поиском окружностей можно скачать .

5. Сопоставление с шаблоном

Если изображение имеет множество мелких деталей то анализ контуров может быть затруднён. В этом случае можно применить метод сопоставления с шаблоном (template matching). Он заключается в следующем - берем картинку с объектом (рис.5) и ищем на большом изображении области совпадающие с изображением объекта (рис.6,7).


Рис 5: объект для поиска

Подробней о методе сопоставления с шаблоном можно послушать лекцию . Исходный код примера можно скачать .

6. Работа с особыми точками

Метод сопоставления с шаблоном, описанный в предыдущем разделе, ищет точные совпадения точек шаблона с точками изображения. Если изображение повёрнуто или масштабировано относительно параметров шаблона то этот метод работает плохо. Для преодоления этих ограничений применяют методы основанные на т.н. особых точках, их мы рассмотрим далее. Особая точка (key point) это небольшая область, которая существенным образом выделяется на изображении. Существует несколько методов определения таких точек, это могут быть углы (Harris corner detector) или блобы (blob, капля) , т.е. небольшие области одинаковой яркости, достаточно чёткой границей, выделяющиеся на общем фоне. Для особой точки вычисляют т.н. дескриптор - характеристику особой точки. Дескриптор вычисляют по заданной окрестности особой точки, как направления градиентов яркости разных частей этой окрестности. Существует несколько методов расчёта дескрипторов для особых точек: SIFT, SURF, ORB и др. Надо отметить, что некоторые методы расчёта дескрипторов являются запатентованными (например SIFT) и их коммерческое использование ограничено. Подробней про особые точки на изображениях и методах работы с ними можно послушать лекцию . Особые точки можно применять для поиска объекта на изображении. Для этого нам необходимо иметь изображение искомого объекта и далее выполнить следующие действия.
  1. На картинке с объектом ищем особые точки объекта и вычисляем их дескрипторы.
  2. На анализируемом изображении тоже ищем особые точки и вычисляем для них дескрипторы.
  3. Сравниваем дескрипторы особых точек объекта и дескрипторы особых точек, найденных на изображении.
  4. Если найдено достаточное количество соответствий то помечаем область с соответствующими точками.
Ниже на рис.8 представлены результаты работы метода поиска объекта по особым точкам.


Рис 8: детектор объектов по особым точкам

Исходный код примера можно скачать .

7. Методы машинного обучения

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

Литература

  1. Е.С.Борисов Детектор объектов для неподвижных камер.
    - http://сайт/cv-backgr.html
  2. Е.С.Борисов Обработка видео: детектор объектов на основе цветовых фильтров.
    - http://сайт/cv-detector-color.html
  3. Е.С.Борисов Базовые методы обработки изображений.
    - http://сайт/cv-base.html
  4. Антон Конушин Компьютерное зрение (2011). Лекция 3. Простые методы анализа изображений. Сопоставление шаблонов.
    - http://www.youtube.com/watch?v=TE99wDbRrUI
  5. OpenCV documentation: Harris Corner Detection
    - http://docs.opencv.org/3.0-beta/doc/py_tutorials/py_feature2d/py_features_harris/py_features_harris.html
  6. Wikipedia: Blob_detection
    - http://en.wikipedia.org/wiki/Blob_detection
  7. Антон Конушин Компьютерное зрение (2011). Лекция 5. Локальные особенности
    - http://www.youtube.com/watch?v=vFseUICis-s

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

Прелюдии.

Итак, после изучения всех средств, которые возможно использовать для реализации поставленной задачи, я остановился на среде разработки Microsoft Visual Studio© 2010 , с использованием чудесной библиотеки OpenCV .

Сам процесс работы с OpenCV предполагает предварительные танцы с бубном, о которых есть достаточно подробных описаний:

Второй акт танцев с бубном.

В итоге повернул в сторону тренировки каскадов. «Покурив» в этом направлении понял что мне нужны два инструмента createsampes и haartraining. Но их exe`шники у меня отсутствовали, а компилироваться отказывались. На тот момент версия OpenCV у меня была 2.4.4, настроенная по , во же статье я впервые прочитал про использование Cmake при установке. В итоге решил скачать версию 2.3.1 и переустановить библиотеку. После чего мне удалось запустить нужные инструменты через командную строку и встал вопрос как с ними работать. Все точки над «и» расставили статьи, в которых показаны параметры с которыми нужно запускать createsampes и haartraining с подробным описанием этих параметров.

Код с чистого листа.

Окончательно отказавшись от старого метода, код был переписан для подстановки обученных каскадов.

Код 2.0

#include "stdafx.h" #include #include #include using namespace cv; int main(int argc, char** argv) { Mat frame, gray; string object_cascade = "haarustupi.xml"; CascadeClassifier haar(object_cascade); VideoCapture cap(0); namedWindow("Video", 1); vector objects; while (true) { cap >> frame; cvtColor(frame, gray, CV_BGR2GRAY); haar.detectMultiScale(gray, objects, 1.9, 10, 0,Size(50, 50)); for (vector::const_iterator r = objects.begin(); r != objects.end(); r++) rectangle(frame, r->tl(), r->br(), Scalar(0, 0, 255)); imshow("Video", frame); if (waitKey(33) >= 0) break; } return (EXIT_SUCCESS); }

Среду настраиваем точно так же как и в прошлом проекте.

ПовторениЯ - отцы учения.

Дело за «малым» обучить каскады.)
Тут начинается самое интересное. После чего я решил писать о всех этих мытарствах на хабр и просить совета.
Я заготовил 500 изображений размером 1600х1200. и одно изображение со знаком размером 80х80. Одного изображения будет достаточно, потому что мы детектируем определенный объект, а не огромное разнообразие лиц.

Итак, заготовив картинки и создав файл neg.dat со структурой

Negative/n (1).jpg negative/n (2).jpg negative/n (3).jpg negative/n (4).jpg ... negative/n (500).jpg

запускаем файл opencv_createsamples.exe через CMD со следующими параметрами

C:OpenCV2.3.1buildcommonx86opencv_createsamples.exe -vec C:OpenCV2.3.1buildcommonx86positive.vect -bg C:OpenCV2.3.1buildcommonx86neg.dat -img C:OpenCV2.3.1buildcommonx86ustupi.jpg -num 500 -w 50 -h 50 -bgcolor 0 -bgthresh 0 -show

параметр -show показывает создаваемые позитивные картинки, но они, в отличие от указанных в других статьях
картинок , получается вот такая

маленькая

Т.е утилита обрезает bg-картинку под размер позитивной картинки. Изменение параметров -w и -h результата не дают и заднего фона все равно почти не видно. Если вдруг кто знает в чем тут дело, поделитесь соображениями . Размер негативных изображений уменьшал до 800х600 - результат тот же.

C:OpenCV2.3.1buildcommonx86opencv_haartraining.exe -data C:OpenCV2.3.1buildcommonx86haarustupi -vec C:OpenCV2.3.1buildcommonx86positive.vect -bg C:OpenCV2.3.1buildcommonx86neg.dat -npos 500 -nneg 500 -nstages 6 -nsplits 2 -w 20 -h 24 -mem 1536 -mode ALL -nonsym -minhitrate 0.999 -maxfalsealarm 0.5

после чего вы получите долгожданный xml-файл, который можно подгружать в исходный код программы.
В итоге каскад слегка обучается и, с большим количеством ложных срабатываний, реагирует на, полюбившуюся мне, картинку знака уступи дорогу.
Но я не могу добиться точных срабатываний, как мне кажется, из-за того что обрезается задний фон в позитивных изображениях. И никак не получаются картинки как в мануалах. Но остается еще вариант увеличить количество этапов обучения и, нагрузив свой компьютер на весь день, дождаться пока каскад будет более «образованным». Чем я и планирую заняться до появления других идей.

Эпилог

Вот такая получилась первая HelloHabr-статья у меня. Жду ваших замечаний о стиле изложения материала. Ну и конечно советов по теме.
Надеюсь после полученных советов будет чем продолжить повествование.

Основная идея заключается в учете статистических связей между расположением антропометрических точек лица . На каждом изображении лица точки пронумерованы в одинаковом порядке. По их взаимному расположению осуществляется сравнение лиц.

Для сравнения лиц можно использовать одного и того же положения лица относительно камеры. Более предпочтителен для этого .

Захват видеопотока с камеры и выделение лица

#include using namespace cv; int main() { // Load Face cascade (.xml file) CascadeClassifier face_cascade; face_cascade.load("haarcascade_frontalface_alt2.xml"); Mat img; VideoCapture cap(0); while (true) { cap >> img; //cvtColor(img, img, CV_BGR2GRAY); // Detect faces std::vector faces; face_cascade.detectMultiScale(img, faces, 1.1, 2, 0 | CV_HAAR_SCALE_IMAGE, Size(30, 30)); // Draw circles on the detected faces for (int i = 0; i < faces.size(); i++) { Point center(faces[i].x + faces[i].width*0.5, faces[i].y + faces[i].height*0.5); ellipse(img, center, Size(faces[i].width*0.5, faces[i].height*0.5), 0, 0, 360, Scalar(255, 0, 255), 4, 8, 0); } imshow("Detected Face", img); waitKey(1); } return 0; }

Файлы каскадов находятся в директории c:\opencv\build\etc\… Нужный каскад размещаете в директории проекта, там же, где и исходный файл main.cpp.

Выделение особых точек лица

Приложение создано на основе C++ code for OpenCV Facemark

#include #include #include #include #include #include #include "drawLandmarks.hpp" using namespace std; using namespace cv; using namespace cv::face; int main(int argc, char** argv) { // Load Face Detector CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml"); // Create an instance of Facemark Ptr facemark = FacemarkLBF::create(); // Load landmark detector facemark->loadModel("lbfmodel.yaml"); // Set up webcam for video capture VideoCapture cam(0); // Variable to store a video frame and its grayscale Mat frame, gray; // Read a frame while (cam.read(frame)) { // Find face vector faces; // Convert frame to grayscale because // faceDetector requires grayscale image. cvtColor(frame, gray, COLOR_BGR2GRAY); // Detect faces faceDetector.detectMultiScale(gray, faces); // Variable for landmarks. // Landmarks for one face is a vector of points // There can be more than one face in the image. Hence, we // use a vector of vector of points. vector< vector > landmarks; // Run landmark detector bool success = facemark-> < faces.size(); i++) { cv::rectangle(frame, faces[i], Scalar(0, 255, 0), 3); } for (int i = 0; i < landmarks.size(); i++) { drawLandmarks(frame, landmarks[i]); /*for (size_t j = 0; j < landmarks[i].size(); j++) circle(frame, Point(landmarks[i][j].x, landmarks[i][j].y), 1, Scalar(255, 0, 0), 2);*/ } } // Display results imshow("Facial Landmark Detection", frame); // Exit loop if ESC is pressed if (waitKey(1) == 27) break; } return 0; }

В проекте приложения, там же где и файл main.cpp, разместил файлы haarcascade_frontalface_alt2.xml , drawLandmarks.hpp и lbfmodel.yaml , на которые есть ссылки в коде. Файлы каскадов находятся в директории c:\opencv\build\etc\… Файлы drawLandmarks.hpp и lbfmodel.yaml есть в архиве Facemark_LBF.rar .

После вставки кода появились ошибки из-за того, что в OpenCV 3.4.3-vc14-vc15 отсутствуют ряд библиотек, необходимых для запуска приложения. Скомпоновал свою библиотеку (скачать opencv_new.zip) и установил ее в корень диска C (C:\opencv-new).

Теперь, все настройки, которые выполнялись , необходимо выполнить и для opencv-new:

Выполняю настройки в Windows . Выхожу на окно «Изменить переменную среды» (кнопки Windows->Служебные->Панель управления -> Система и безопасность -> Система -> Дополнительные параметры системы -> Переменные среды -> Path ->Изменить). В этом окне создаю переменную C:\opencv-new \x64\vc14\bin. Перезагружаю Windows .

В свойствах проекта также ссылаюсь на библиотеку opencv_new (вместо opencv). В окне «Property Pages» выполняю действия:

  • C/C++ -> General -> Additional Include Directories -> C:\opencv-new \include
  • Linker -> General -> Additional Library Directories -> C:\opencv-new \x64\vc14\lib
  • Linker -> Input -> Additional Dependencies -> opencv_core400.lib; opencv_face400.lib; opencv_videoio400.lib; opencv_objdetect400.lib; opencv_imgproc400.lib; opencv_highgui400.lib

При запуске программа выдает ошибку, если в установках проекта Debug. Для Release, запуск успешный.


Выбор признаков для фильтрации изображений и распознавания лиц

Точечный каркас лица отображается по разному в зависимости от объективных и субъективных факторов.

Объективные факторы — положение лица относительно камеры.

Субъективные факторы — неравномерное или слабое освещение, искажение лица вследствие эмоций, прищуривание глаз и т.п. В этих случаях точечный каркас может быть некорректным, точки могут даже быть оторваны от лица:

При видеозахвате иногда проскакивают и такие изображения. Их нужно отфильтровывать — как при обучении так и распознавании.

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

Для сравнения лиц можно использовать точечный 2D каркас одного и того же положения лица. Какое положение лица относительно камеры есть наиболее информативным? Очевидно, что фронтальное. Не зря в криминалистике делают фото в анфас и профиль. Пока ограничимся анфасом.

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

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

Итак, прежде чем формировать признаки для обучения и сравнения, необходимо отфильтровать полученные видеозахватом точечные каркасы лиц, которые по субъективным или объективным причинам не есть правильное фронтальное изображение лица (анфас).

Оставляем только те точечные каркасы, которые проходят по следующим признакам:

  • Прямая, которая проходит через крайние точки глаз (линия глаз), перпендикулярна прямой, которая проходит через крайние точки носа (линия носа).
  • Линия глаз параллельна прямой, которая проходит через точки уголков рта (линия рта).
  • Соблюдается симметрия указанных выше точек относительно линии носа.
  • Угловые точки глаз (внешние и внутренние) находятся на одной прямой.

Пример фронтальных изображений, которые проходят по всем признакам:

Пример изображений, которые отфильтровываются:

Попробуйте сами определить, по какому из признаков изображения не проходят.

Как формализуются признаки, которые обеспечивают фильтрацию и распознавание лиц? В основном они построены на условиях определении расстояний между точками, условий параллельности и перпендикулярности. Задача формализации таких признаков рассмотрена в теме .

Алгоритм распознавания лиц по 2D-каркасу точек

Координаты точек каркаса лица изначально задаются в системе координат, которая привязана к верхней левой точке окна. При этом ось Y направлена вниз.

Для удобства определения признаков используем пользовательскую систему координат (ПСК), ось X которой проходит через отрезок между серединами глаз, а ось Y — перпендикулярно этому отрезку через его середину в направлении вверх. Координаты ПСК (от -1 до +1) нормализованы — соотнесены с расстоянием между средними точками глаз.

ПСК обеспечивает удобство и простоту определения признаков. Например, положение лица в анфас определяется признаком симметрии соответствующих точек глаз относительно линии носа. Этот признак формализуется совпадением линии носа с осью Y, т.е X1=X2=0, где X1 и X2 — координаты крайних точек носа (27 и 30) в ПСК.

Определяем относительно оконной СК

Координаты средних точек левого и правого глаз (Left и Right):

XL = (X45 + X42) /2 ; YL = (Y45 + Y42) /2 ; XR = (X39 + X 36) /2; YR = (Y39 + Y 36) /2;

Начало ПСК:

X0 =(XL + XR)/2; Y0 =(YL + YR)/2;

Расстояния между средними точками глаз вдоль осей Х и Y:

DX = XR — XL; DY = YR — YL;

Действительное расстояние L между средними точками глаз (по теореме Пифагора):

L = sqrt (DX** 2 + DY**2)

Тригонометрические функции угла поворота ПСК:

Переходим от координат в оконной СК к координатам в ПСК , используя параметры X0,Y0, L, sin AL, cos AL:

X_User_0 = 2 (X_Window — X0) / L;

Y_User_0 = — 2 (Y_Window — Y0) / L ;

X_User = X_User_0 * cos_AL — Y_User_0 * sin_AL;

Y_User = X_User_0 * sin_AL + Y_User_0 * cos_AL;

Реализуем фильтрацию изображений последовательно проверяя признаки:

1.Признак перпендикулярности линий носа и глаз, а также симметрии угловых точек глаз . Линия носа определяется точками 27 и 30 (см. рисунок во ). Оба признака выполняются, если в ПСК координаты этих точек X1 = X2= 0 (т.е., линия носа совпадает с осью Y).

2.Признак параллельности линии глаз и линии рта . Линия рта определяется точками 48 и 54 (см. рисунок во ). Признак выполняется, если в ПСК Y1-Y2=0.

3. Признак симметрии угловых точек рта . Линия рта определяется точками 48 и 54 (см. рисунок во ). Признак выполняется, если в ПСК X1+X2 =0

4. Признак «Угловые точки глаз находятся на одной прямой» . Прямые определяются парами точек: (36 и 45), а также (39 и 42). Поскольку тест по признаку 1 уже пройден, достаточно лишь определить в ПСК признак Y2-Y1 =0 лишь для точек 36 и 39.

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

Программа сравнения лиц по одному признаку

В качестве признака берется расстояние между точками переносицы и подбородка (Landmarks точками 27 и 8, см. рисунок во ). Признак, нормализован, определяется в ПСК отношением: (Y1 — Y2)/L , где L — расстояние между центрами глаз. При обучении программы признак для конкретного лица определяется числом, которое высвечивается рядом с отслеживаемым лицом (эта часть кода в программе закомментирована). При распознавании значение признака сравнивается с введенным в программу конкретным признаком для каждого лица. При положительном результате сравнения рядом с лицом появляется его идентификатор.

Программа распознает и по фото, на котором я на 15 лет моложе, да и при том, еще с усами. Различие на фото существенное, не каждый человек уловит. Но компьютерную программу не обманешь.

Контрольные задания:

  1. Ознакомиться с программой.
  2. Определить значение признака для своего лица и нескольких своих коллег.
  3. Протестировать программу на предмет идентификации лиц (своего и коллег).

#include #include #include #include #include #include #include "drawLandmarks.hpp" using namespace std; using namespace cv; using namespace cv::face; int main(int argc, char** argv) { // Load Face Detector CascadeClassifier faceDetector("haarcascade_frontalface_alt2.xml"); // Create an instance of Facemark Ptr facemark = FacemarkLBF::create(); // Load landmark detector facemark->loadModel("lbfmodel.yaml"); // Set up webcam for video capture VideoCapture cam(0); // Variable to store a video frame and its grayscale Mat frame, gray; // Read a frame while (cam.read(frame)) { // Find face vector faces; // Convert frame to grayscale because // faceDetector requires grayscale image. cvtColor(frame, gray, COLOR_BGR2GRAY); // Detect faces faceDetector.detectMultiScale(gray, faces); // Variable for landmarks. // Landmarks for one face is a vector of points // There can be more than one face in the image. Hence, we // use a vector of vector of points. vector< vector > landmarks; // Run landmark detector bool success = facemark->fit(frame, faces, landmarks); if (success) { // If successful, render the landmarks on the face for (size_t i = 0; i < faces.size(); i++) { cv::rectangle(frame, faces[i], Scalar(0, 255, 0), 3); } for (int i = 0; i < landmarks.size(); i++) { //if((i >=30)&&(i <= 35)) drawLandmarks(frame, landmarks[i]); for (size_t j = 0; j < landmarks[i].size(); j++) { circle(frame, Point(landmarks[i][j].x, landmarks[i][j].y), 1, Scalar(255, 0, 0), 2); } line(frame, Point(landmarks[i].x, landmarks[i].y), Point(landmarks[i].x, landmarks[i].y), Scalar(0, 0, 255), 2); float XL = (landmarks[i].x + landmarks[i].x) / 2; float YL = (landmarks[i].y + landmarks[i].y) / 2; float XR = (landmarks[i].x + landmarks[i].x) / 2; float YR = (landmarks[i].y + landmarks[i].y) / 2; line(frame, Point(XL, YL), Point(XR, YR), Scalar(0, 0, 255), 2); float DX = XR - XL; float DY = YR - YL; float L = sqrt(DX * DX + DY * DY); float X1 = (landmarks[i].x); float Y1 = (landmarks[i].y); float X2 = (landmarks[i].x); float Y2 = (landmarks[i].y); float DX1 = abs(X1 - X2); float DY1 = abs(Y1 - Y2); float L1 = sqrt(DX1 * DX1 + DY1 * DY1); float X0 = (XL + XR) / 2; float Y0 = (YL + YR) / 2; float sin_AL = DY / L; float cos_AL = DX / L; float X_User_0 = (landmarks[i].x - X0) / L; float Y_User_0 = -(landmarks[i].y - Y0) / L; float X_User27 = X_User_0 * cos_AL - Y_User_0 * sin_AL; float Y_User27 = X_User_0 * sin_AL + Y_User_0 * cos_AL; X_User_0 = (landmarks[i].x - X0) / L; Y_User_0 = -(landmarks[i].y - Y0) / L; float X_User30 = X_User_0 * cos_AL - Y_User_0 * sin_AL; float Y_User30 = X_User_0 * sin_AL + Y_User_0 * cos_AL; if (abs(X_User27 - X_User30) <= 0.1) { //putText(frame, std::to_string(abs(L1 / L)), Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); if (abs((L1 / L) - 1.6) < 0.1) { putText(frame, "Roman", Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); } if (abs((L1 / L) - 1.9) < 0.1) { putText(frame, "Pasha", Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); } if (abs((L1 / L) - 2.1) < 0.1) { putText(frame, "Svirnesvkiy", Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); } } putText(frame, "Incorrect", Point(landmarks[i].x, landmarks[i].y), 1, 2, Scalar(0, 0, 255), 2); } } // Display results imshow("Facial Landmark Detection", frame); // Exit loop if ESC is pressed if (waitKey(1) == 27) break; } return 0; }

Важнейшими источниками информации о внешнем мире для робота являются его оптические датчики и камеры. После получения изображения необходима его обработка для анализа обстановки или принятия решения. Как я говорил ранее, компьютерное зрение объединяет множество методов работы с изображениями. При функционировании робота предполагается, что видеоинформация с камер обрабатывается какой-либо программой, запущенной на контроллере. Чтобы не писать код с нуля, можно воспользоваться готовыми программными решениями. На текущий момент существует множество готовых библиотек компьютерного зрения:

  • Matrox Imaging Library
  • Camellia Library
  • Open eVision
  • HALCON
  • libCVD
  • OpenCV
  • и т.д…
Данные SDK могут сильно различаться по функциональности, условиям лицензирования, используемым языкам программирования. Мы же подробнее остановимся на OpenCV . Она бесплатна как для учебных целей, так и для коммерческого использования. Написана на оптимизированном C/C++, поддерживает интерфейсы C, C++, Python, Java и включает в себя реализации свыше 2500 алгоритмов. Помимо стандартных функций обработки изображений (фильтрация, размытие, геометрические преобразования и т.д…) данный SDK позволяет решать более сложные задачи, к которым относятся обнаружение объекта на фотографии и его «узнавание». Следует понимать, что задачи обнаружения и распознавания могут быть совершенно различными:
  • поиск и распознавание конкретного объекта,
  • поиск объектов одной категории (без распознавания),
  • только распознавание объекта (уже готовое изображение с ним).
Для обнаружения признаков на изображении и проверки на совпадение в OpenCV имеются следующие методы:
  • Гистограмма направленных градиентов HOG(Histogram of Oriented Gradients) — может применяться для обнаружения пешеходов
  • Алгоритм Виолы-Джонса — применяется для поиска лиц
  • Алгоритм обнаружения признаков SIFT (Scale Invariant Feature Transform)
  • Алгоритм обнаружения признаков SURF (Speeded Up Robust Features)
Например, SIFT обнаруживает наборы точек, которые можно использовать для идентификации объекта. Помимо приведенных методик в OpenCV имеются и другие алгоритмы для детектирования и распознавания, а также набор алгоритмов, относящихся к машинному обучению, таких как метод k ближайших соседей, нейронные сети, метод опорных векторов и т.д… В целом OpenCV предоставляет инструментарий, достаточный для решения подавляющего большинства задач компьютерного зрения. Если алгоритм не имеется в составе SDK, то, как правило, он может быть без проблем запрограммирован. Кроме того, существует множество авторских версий алгоритмов, написанных пользователями на основе OpenCV. Также следует отметить, что за последние годы OpenCV сильно расширилась и стала в некоторой степени «тяжеловесной». В связи с этим, разными группами энтузиастов создаются «облегченные» библиотеки, основанные на OpenCV. Примеры: SimpleCV, liuliu ccv, tinycv… Полезные сайты
  1. http://opencv.org/ — Основной сайт проекта
  2. http://opencv.willowgarage.com/wiki/ — Старый сайт проекта с документацией по старым версиям

Из этой статьи вы узнаете, как создать Python-скрипт для подсчёта количества книг на изображении с помощью OpenCV .

Что мы будем делать?

Взглянем на изображение, на котором будем искать книги:

Мы видим, что на изображении находятся четыре книги, а также отвлекающие вещи, такие как кружка кофе, чашка Starbucks, несколько магнитов и конфета.

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

Какие библиотеки нам понадобятся?

Чтобы написать систему для поиска и обнаружения книг на изображениях, мы будем использовать OpenCV для работы с компьютерным зрением и обработки изображений. Нам также необходимо установить NumPy для корректной работы OpenCV. Убедитесь, что у вас установлены эти библиотеки!

Поиск книг на изображениях с помощью Python и OpenCV

Прим. перев. Вы можете заметить, что исходный код в нашей статье отличается от кода в оригинале. Автор, вероятно, использовал установку необходимых библиотек через репозитории. Мы предлагаем использовать pip, что гораздо проще. Во избежание появления ошибок рекомендуем использовать версию кода, приведенную в нашей статье.

Откройте свой любимый редактор кода, создайте новый файл с именем find_books.py и начнем:

# -*- coding: utf-8 -*- # импортируйте необходимые пакеты import numpy as np import cv2 # загрузите изображение, смените цвет на оттенки серого и уменьшите резкость image = cv2.imread("example.jpg") gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) gray = cv2.GaussianBlur(gray, (3, 3), 0) cv2.imwrite("gray.jpg", gray)

Начнем с импорта библиотеки OpenCV. Загрузка изображения с диска обрабатывается функцией cv2.imread . Здесь мы просто загружаем его с диска, а затем преобразуем цветовую гамму из RGB в оттенки серого.

Мы также немного размываем изображение, чтобы уменьшить высокочастотные шумы и повысить точность нашего приложения. После выполнения кода изображение должно выглядеть так:

Мы загрузили изображение с диска, преобразовали его в оттенки серого и немного размыли.

Теперь давайте определим края (т.е. контуры) объектов на изображении:

# распознавание контуров edged = cv2.Canny(gray, 10, 250) cv2.imwrite("edged.jpg", edged)

Теперь наше изображение выглядит следующим образом:

Мы нашли контуры объектов на изображениях. Однако, как вы видите, некоторые из контуров не закрыты - между контурами существуют промежутки. Чтобы убрать промежутки между белыми пикселями изображения, мы применим операцию «закрытия»:

# создайте и примените закрытие kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (7, 7)) closed = cv2.morphologyEx(edged, cv2.MORPH_CLOSE, kernel) cv2.imwrite("closed.jpg", closed)

Теперь пробелы в контурах закрыты:

Следующим шагом является фактическое обнаружение контуров объектов на изображении. Для этого мы будем использовать функцию cv2.findContours:

# найдите контуры в изображении и подсчитайте количество книг cnts = cv2.findContours(closed.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) total = 0

Рассмотрим геометрию книги.

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

Чтобы проверить, является ли контур книгой или нет, нам нужно выполнить цикл по каждому контуру:

# цикл по контурам for c in cnts: # аппроксимируем (сглаживаем) контур peri = cv2.arcLength(c, True) approx = cv2.approxPolyDP(c, 0.02 * peri, True) # если у контура 4 вершины, предполагаем, что это книга if len(approx) == 4: cv2.drawContours(image, , -1, (0, 255, 0), 4) total += 1

Для каждого из контуров мы вычисляем периметр, используя cv2.arcLength , а затем аппроксимируем (сглаживаем) контур, используя cv2.approxPolyDP .

Причина, по которой мы аппроксимируем контур, заключается в том, что он может не быть идеальным прямоугольником. Из-за зашумления и теней на фото вероятность того, что у книги будет ровно 4 вершины, невелика. Аппроксимируя контур, мы решаем эту проблему.

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

Завершим этот пример, показывая полученное изображение и количество найденных книг:

# показываем результирующее изображение print("Я нашёл {0} книг на этой картинке".format(total) cv2.imwrite("output.jpg", image))

На данном этапе наше изображение будет выглядеть так:

Подведем итоги

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

Наш подход состоял в том, чтобы:

  1. Загрузить изображение с диска и преобразовать его в оттенки серого.
  2. Немного размыть изображение.
  3. Применить детектор контуров Canny для обнаружения объектов на изображении.
  4. Закрыть любые промежутки в контурах.
  5. Найти контуры объектов на изображении.
  6. Применить контурную аппроксимацию, чтобы определить, был ли контур прямоугольником и, следовательно, книгой.

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