Розпізнавання контуру тіла у реальному часі. Розпізнавання образів із OpenCV: Контури проти Haartraining




Під час вирішення завдань комп'ютерного зору не обійтися без використання спеціалізованого софту. Хочу познайомити вас з таким – OpenCV – бібліотека з відкритим вихідним кодом на C++. Має набір інструментів для оцифрування зображень, подальшої обробки через чисельні алгоритми або нейромережу.

Базові алгоритми обробки зображень: інтерпретації зображень, калібрування камери за зразком, усунення оптичних спотворень, визначення подібності, аналіз переміщення об'єкта, визначення форми об'єкта та стеження за об'єктом, 3D-реконструкція, сегментація об'єкта, розпізнавання жестів.

Завантажити бібліотеку можна на офіційному сайті http://sourceforge.net/projects/opencvlibrary/

Структура бібліотеки OpenCV

cxcore - ядро
* містить базові структури даних та алгоритми:
- базові операції над багатовимірними числовими масивами
- матрична алгебра, математичні ф-ції, генератори випадкових чисел
- Запис/відновлення структур даних у/з XML
- базові функції 2D графіки

CV - модуль обробки зображень та комп'ютерного зору
- базові операції над зображеннями (фільтрація, геометричні перетворення, перетворення колірних просторів тощо)
- аналіз зображень (вибір відмітних ознак, морфологія, пошук контурів, гістограми)
- аналіз руху, стеження за об'єктами
- Виявлення об'єктів, зокрема осіб
- калібрування камер, елементи відновлення просторової структури

Highgui - модуль для введення/виведення зображень і відео, створення інтерфейсу користувача
- захоплення відео з камер та відео файлів, читання/запис статичних зображень.
- функції для організації простого UI (всі демо-додатки використовують HighGUI)

Cvaux - експериментальні та застарілі функції
- Простір. зір: стерео калібрація, сам калібрація
- пошук стерео-відповідності, кліки у графах
- знаходження та опис рис особи

CvCam - захоплення відео
- дозволяє здійснювати захоплення відео з цифрових відеокамер (підтримка припинена і в останніх версіях цей модуль відсутній)


Встановлення OpenCV під Linux

Після скачування останньої версії OpenCV із сайту розробника http://sourceforge.net/projects/opencvlibrary/ потрібно розпакувати архів та виконати складання через CMake версії 2.6 або вище.

Установка CMake виконується стандартно:

Sudo apt-get install cmake

Для відображення вікон OpenCV потрібно встановити бібліотеки GTK+ 2.x і libgtk2.0-dev

Apt-get install libgtk2.0-dev

Збираємо бібліотеку:

Tar -xjf OpenCV-2.2.0.tar.bz2 cd OpenCV-2.2.0 cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local ./ make make install

Для тестування встановленої бібліотеки можна зібрати приклади і щось запустити:

Cd samples/c/ chmod +x build_all.sh ./build_all.sh ./delaunay

Якщо замість тестової картинки ви побачите помилку "error while loading shared libraries: libopencv_core.so.2.2: cannot open shared object file: No such file or directory", це означає, що програма може знайти бібліотеки. Потрібно явно вказати шлях до них:

$ export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH

Якщо після цього знову помилка:
OpenCV Error: Unspecified error (функція не здійснена. Rebuild the library with Windows, GTK+ 2.x or Carbon support. або configure script) в cvNamedWindow, файл /usr/src/OpenCV-2.2.0/modules/highgui/src/window.cpp, line 274 terminate called after throwing instance of "cv::Exception" what(): /usr /src/OpenCV-2.2.0/modules/highgui/src/window.cpp:274: error: (-2) Ця функція не здійснена. Rebuild the library with Windows, GTK+ 2.x або Carbon support. Якщо ви є на Ubuntu або Debian, натисніть libgtk2.0-dev і pkg-config, повторюйте дзвінок або configure script in function cvNamedWindow
Ви забули встановити GTK+ 2.x: libgtk2.0-dev. Запустіть інсталяцію (див. вище).

Коли установка завершена заголовки будуть доступні в директорії /usr/local/include/opencv , а бібліотечні файли лежать в /usr/local/lib

Зберемо програму з OpenCV:

test.cpp

// // for testing // // Robocraft.ru // #include #include #include #include int main(int argc, char* argv) ( IplImage* image=0, *dst=0; // ім'я картинки char filename = "Image0.jpg"; // отримуємо картинку image = cvLoadImage(filename, 1); printf( "[i] image: %s\n", filename); assert(image != 0); // покажемо зображення cvNamedWindow("image"); cvShowImage("image", image); // чекаємо натискання клавіші cvWaitKey( 0), // звільняємо ресурси cvReleaseImage(& image); cvReleaseImage(&dst); // видаляємо вікна cvDestroyAllWindows();

Makefile

CC:= g++ CFLAGS:= -I/usr/local/include/opencv -L/usr/local/lib OBJECTS:= LIBRARIES:= -lopencv_core -lopencv_imgproc -lopencv_highgui . ) $(CFLAGS) -o test test.cpp $(LIBRARIES) clean: rm -f *.o

Запуск складання командою make.


Hello World!

OpenCV встановлений та готовий до роботи. Напишемо свій перший додаток Hello World!

#include #include int main(int argc, char** argv) ( // задаємо висоту і ширину картинки int height = 620; int width = 440; // задаємо точку для виведення тексту CvPoint pt = cvPoint(height/4, width/2); // Створюємо 8-бітну, 3-канальну картинку IplImage* hw = cvCreateImage(cvSize(height, width), 8, 3);// заливаємо картинку чорним кольором cvSet(hw,cvScalar(0,0,0)); / ініціалізація шрифту CvFont font; cvInitFont(&font, CV_FONT_HERSHEY_COMPLEX,1.0, 1.0, 0, 1, CV_AA); 0, 150));// створюємо віконце cvNamedWindow("Hello World", 0); // показуємо картинку у створеному вікні cvShowImage("Hello World", hw); // чекаємо натискання клавіші cvWaitKey(0); // звільняємо ресурси cvReleaseImage(&hw);cvDestroyWindow("Hello World"); return 0;

Завантаження зображення

Цей приклад буде основою всіх ваших програм на OpenCV. Ми завантажимо у середу зображення з файлу Image0.jpg

#include #include #include #include IplImage* image = 0; IplImage* src = 0; int main(int argc, char* argv) ( // ім'я картинки задається першим параметром char* filename = argc == 2 ? argv : "Image0.jpg"; // отримуємо картинку image = cvLoadImage(filename,1); // клонуємо картинку src = cvCloneImage(image); printf("[i] image: %s\n", filename); assert(src != 0); / показуємо картинку cvShowImage("original",image); // виводимо в консоль інформацію про картинку printf("[i] channels: %d\n", image->nChannels); printf("[i] pixel depth: % d bits\n", image->depth); printf("[i] width: %d pixels\n", image->width); printf("[i] height: %d pixels\n", image- >height);printf("[i] image size: %d bytes\n", image->imageSize); printf("[i] width step: %d bytes\n", image->widthStep); // чекаємо натискання клавіші cvWaitKey(0);// звільняємо ресурси cvReleaseImage(& image);cvReleaseImage(&src);

Типи форматів зображень, що підтримуються:

  • Windows bitmaps - BMP, DIB
  • JPEG files - JPEG, JPG, JPE
  • Portable Network Graphics - PNG
  • Portable image format - PBM, PGM, PPM
  • Sun rasters - SR, RAS
  • TIFF files - TIFF, TIF

Для отримання доступу до зображення можна здійснювати такі дзвінки:

Image->nChannels // число каналів картинки (RGB, хоча OpenCV - BGR) (1-4) image->depth // глибина в бітах image->width // ширина картинки в пікселях image->height // висота картинки в пікселях image->imageSize // пам'ять займана картинкою (==image->height*image->widthStep) image->widthStep // відстань між сусідніми по вертикалі точками зображення (число байт в одному рядку картинки - може знадобитися для самостійного обходу всіх пікселів зображення)

Завантаження відео

Завантаження відео не набагато складніше, ніж завантаження зображення за тим винятком, що буде цикл, який перебирає кадри.
Затримка між кадрами задана 33 мілісекунди т.к. така затримка дозволяє обробляти відеопотік зі стандартною частотою 30 кадрів за секунду.

#include #include #include #include IplImage* frame =0; int main(int argc, char* argv) ( // ім'я файлу задається першим параметром char* filename = argc == 2 ? argv : "test.avi"; printf("[i] file: %s\n", filename ); // вікно для відображення картинки cvNamedWindow("original",CV_WINDOW_AUTOSIZE); // отримуємо інформацію про відео-файл CvCapture* capture = cvCreateFileCapture(filename); while(1)( // отримуємо наступний кадр frame = cvQueryFrame(capture) if(!frame) ( break; ) // тут можна вставити // процедуру обробки // показуємо кадр cvShowImage("original", frame); char c = cvWaitKey(33); if (c == 27) ( // якщо натиснуто ESC - виходимо break; ) ) // звільняємо ресурси cvReleaseCapture(&capture); // видаляємо вікно cvDestroyWindow("original"); return 0; )

Для захоплення відео з камери потрібно трохи модифікувати код замість функції cvCreateFileCapture() буде використовуватися cvCreateCameraCapture(). При натисканні ESC відтворення перерветься і вікно закриється, а при натисканні Enter поточний кадр збережеться jpg файл.

#include #include #include #include int main(int argc, char* argv) ( // отримуємо будь-яку підключену камеру CvCapture* capture = cvCreateCameraCapture(CV_CAP_ANY); //cvCaptureFromCAM(0); assert(capture); //cvSetCaptureProperty(capture, CV_CAP) /1280); //cvSetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT, 480);//960); // Дізнаємося ширину і висоту кадру double width = cvGetCaptureProperty (capture, CV_CAP_PROP_FRAME_WIDTH); double height = cvGetCaptureProperty(capture, CV_CAP_PROP_FRAME_HEIGHT); printf("[i] %.0f x %.0f\n", width, height); IplImage* frame=0; cvNamedWindow("capture", CV_WINDOW_AUTOSIZE); printf("[i] press Enter for capture image and Esc for quit!\n\n"); int counter = 0; char filename; while(true)( // отримуємо кадр frame = cvQueryFrame(capture); // показуємо cvShowImage("capture", frame); char c = cvWaitKey(33); if (c == 27) ( // натиснута ESC break; ) else if(c == 13) ( // Enter // зберігаємо кадр у файл sprintf(filename, "Image%d.jpg", counter); printf("[i] capture... %s\n", filename), cvSaveImage(filename, frame); counter++; ) ) // звільняємо ресурси cvReleaseCapture(&capture); cvDestroyWindow("capture"); return 0; )

OpenCV v1.0 показує та зберігає картинку мінімальної роздільної здатності камери 320x240.


Розпізнавання об'єктів за шаблоном

Для розпізнавання областей на вихідному зображенні шаблону існує функція cvMatchTemplate(). Функція накладає шаблон зображення на поточне зображення і згідно з вибраним алгоритмом виконує пошук кореляції між ними. Визначення меж знайденого шаблону на вихідному зображенні виконується функцією cvMinMaxLoc, а для нормалізації алгоритму пошуку cvNormalize().

// // приклад cvMatchTemplate() // порівняння зображення із шаблоном // #include #include #include #include IplImage* image = 0; IplImage* templ = 0; int main(int argc, char* argv) ( // ім'я картинки задається першим параметром char* filename = argc >= 2 ? argv : "Image0.jpg"; // отримуємо картинку image = cvLoadImage(filename,1); printf( "[i] image: %s\n", filename); assert(image != 0); // шаблон char* filename2 = argc >= 3 ? argv : "eye.jpg"; printf("[i] template : %s\n", filename2); templ = cvLoadImage(filename2,1); assert(templ != 0); cvNamedWindow("original", CV_WINDOW_AUTOSIZE); , CV_WINDOW_AUTOSIZE), cvNamedWindow("res", CV_WINDOW_AUTOSIZE); // розмір шаблону int width = templ->width; int height = templ->height; template", templ); // зображення для зберігання результату порівняння // розмір результату: якщо image WxH і templ wxh, то result = (W-w+1)x(H-h+1) IplImage *res = cvCreateImage(cvSize ((image->width-templ->width+1), (image->height-templ->height+1)), IPL_DEPTH_32F, 1); // порівняння зображення із шаблоном cvMatchTemplate(image, templ, res, CV_TM_SQDIFF); // покажемо, що отримали cvShowImage("res", res); // визначення найкраще положення для порівняння // (Пошук мінімумів і максимумів на зображенні) double minval, maxval; CvPoint minloc, maxloc; cvMinMaxLoc(res, &minval, &maxval, &minloc, &maxloc, 0); // нормалізуємо cvNormalize(res,res,1,0,CV_MINMAX); cvNamedWindow("res norm", CV_WINDOW_AUTOSIZE); cvShowImage("res norm", res); // виділимо область прямокутником cvRectangle(image, cvPoint(minloc.x, minloc.y), cvPoint(minloc.x+templ->width-1, minloc.y+templ->height-1), CV_RGB(255, 0 , 0), 1, 8); // показуємо зображення cvShowImage("Match", image); // Чекаємо натискання клавіші cvWaitKey(0); // звільняємо ресурси cvReleaseImage(&image); cvReleaseImage(&templ); cvReleaseImage(&res); cvDestroyAllWindows(); return 0; )

У цій статті використано C++ інтерфейс, FREAK та детектування безлічі об'єктів. Надійність детектування об'єктів за допомогою FREAK нижче, ніж SURF, проте його робота набагато швидша, що дозволяє використовувати алгоритм на мобільних та вбудованих системах. Приклад роботи представлений малюнку:

Розглянемо вихідний кодщо дозволяє цього досягти. Код наведено повністю для бажаючих швидко вставити його у свій проект.
#include #include #include #include #include #include #include #include keypointsImageTemple, keypointsImage; Mat descriptorsImageTemple, descriptorsImage; std::vector matches; // Ініціалізація класу детектора особливостей, 1000 - порогове значення для відсіювання // малозначимих особливостей SurfFeatureDetector detector(1000); //Клас для FREAK особливостей. Можна настроювати режими порівняння особливостей: // FREAK extractor(true, true, 22, 4, std::vector matcher; // Детектування double t = (double) getTickCount (); detector.detect(ImageTemple, keypointsImageTemple); detector.detect(Image, keypointsImage); t = ((double)getTickCount() - t)/getTickFrequency(); std::cout<< "detection time [s]: " << t/1.0 << std::endl; // Извлечение особенностей t = (double)getTickCount(); extractor.compute(ImageTemple, keypointsImageTemple, descriptorsImageTemple); extractor.compute(Image, keypointsImage, descriptorsImage); t = ((double)getTickCount() - t)/getTickFrequency(); std::cout << "extraction time [s]: " << t << std::endl; // Сравнение t = (double)getTickCount(); matcher.match(descriptorsImageTemple, descriptorsImage, matches); t = ((double)getTickCount() - t)/getTickFrequency(); std::cout << "matching time [s]: " << t << std::endl; // Отобразить на изображении Mat imgMatch; drawMatches(ImageTemple, keypointsImageTemple, Image, keypointsImage, matches, imgMatch); imwrite("matches.jpeg", imgMatch); std::vectorObj; std::vector Scene; for(int i = 0; i< matches.size(); i++) { obj.push_back(keypointsImageTemple[ matches[i].queryIdx ].pt); scene.push_back(keypointsImage[ matches[i].trainIdx ].pt); } Mat H = findHomography(obj, scene, CV_RANSAC); std::vector Scene_corners(4); perspectiveTransform(obj_corners, scene_corners, H); //-- Draw lines between the corners (maped object in the scene - image_2) line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0 , 0), 4); line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); line(imgMatch, scene_corners + Point2f(ImageTemple.cols, 0), scene_corners + Point2f(ImageTemple.cols, 0), Scalar(0, 255, 0), 4); imwrite("matches3.jpeg", imgMatch); return 0; )

Для будь-яких особливостей OpenCV необхідно ініціалізувати клас SurfFeatureDetector. Перша дія після різних ініціалізацій – це детектування особливостей detector.detect для еталонного зображення та зображення сцени. Після чого для кожного зображення за результатами роботи детектора обчислюються особливості FREAK: extractor.compute.
Порівняння схожості особливостей здійснюється за допомогою matcher.match.
Далі є цикл з формуванням точок з особливостей для обох зображень. На підставі точок обчислюється гомографія зображень findHomography. Положення та поворот об'єкта обчислюється за допомогою функції перспективногоперетворення. Ну а потім висновок на зображення.
Еталонне зображення:

Зображення сцени:


Результат представлений на початку.
Однак тут виникає питання, як розраховувати оптимальний поріг особливостей: SurfFeatureDetector detector(1000);. Відповідь – експериментально. Деяку інформацію з цього питання ви можете отримати.
Припустимо, що у нас на зображенні кілька об'єктів:


Результат роботи програми буде наступним:


Звичайно, що така ситуація не влаштовує. Щоб детектувати всі об'єкти, необхідно розділити зображення на кілька частин. Однак тут слід пам'ятати, що якщо зображення розділити на блоки, що не перетинаються (приклад зображення 100x100 розділити на 4 блоки по 50x50), то може виникнути ситуація, коли об'єкт буде частково знаходитися в декількох блоках і не буде детектований. Для уникнення цього необхідно робити блоки, що перетинаються, що дещо сповільнить роботу, але поліпшить якість (приклад зображення 100x100 розділити на 9 блоків по 50x50 так, як показано в прикладі). Приклад програми детектирує безліч об'єктів нижче:
#include #include #include #include #include #include #include #include using namespace cv; int main(int argc, char** argv) ( if(argc != 3) return 1; Mat ImageTemple = imread(argv, CV_LOAD_IMAGE_GRAYSCALE); if(!ImageTemple.data) return 2; // Помилка Mat Image = imread( argv, CV_LOAD_IMAGE_GRAYSCALE), if(!Image.data) return 3; // Помилка std::vector keypointsImageTemple; Mat descriptorsImageTemple; std::vector matches; // Ініціалізація класу детектора особливостей, 1000 - порогове значення для відсіювання // малозначимих особливостей SurfFeatureDetector detector(1000); detector.detect(ImageTemple, keypointsImageTemple); int maxy = 3; int maxx = 3; Mat Draw_mat = imread(argv, 1); for(int y = 0; y< maxy; y++) for(int x = 0; x < maxx; x++) { // Класс для FREAK особенностей. Можно настраивать режимы сравнения особенностей: // FREAK extractor(true, true, 22, 4, std::vector()); FREAK extractor; // Використовується для визначення збігів особливостей - міра Хеммінга BruteForceMatcher matcher; std::vector keypointsImage; Mat descriptorsImage; CvRect Rect = cvRect (x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1)), 2 * (Image.cols / (maxx + 1)), 2 * ( Image.rows/(maxy + 1))); Mat ImageROI (Image, Rect); detector.detect(ImageROI, keypointsImage); extractor.compute(ImageTemple, keypointsImageTemple, descriptorsImageTemple); extractor.compute(ImageROI, keypointsImage, descriptorsImage); matcher.match(descriptorsImageTemple, descriptorsImage, matches); // Відкидання надто розбіжних значень for (int i = 0; i< matches.size(); i++) { if(matches[i].distance >150) ( matches.erase (matches.begin() + i); ) ) std::vector Obj; std::vector Scene; for(int i = 0; i< matches.size(); i++) { obj.push_back(keypointsImageTemple[ matches[i].queryIdx ].pt); scene.push_back(keypointsImage[ matches[i].trainIdx ].pt); } Mat H = findHomography(obj, scene, CV_RANSAC); std::vectorObj_corners(4); obj_corners = cvPoint(0,0); obj_corners = cvPoint(ImageTemple.cols, 0); obj_corners = cvPoint(ImageTemple.cols, ImageTemple.rows); obj_corners = cvPoint(0, ImageTemple.rows); std::vector Scene_corners(4); perspectiveTransform(obj_corners, scene_corners, H); //-- Draw lines between the corners (maped object in the scene - image_2) line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1))), y * (Image.rows / (maxy + 1))), scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalar (0, 255, 0), 4) ; line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1)))), scene_corners + Point2f (x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalar (0, 255, 0), 4); line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1)))), scene_corners + Point2f (x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalar (0, 255, 0), 4); line(Draw_mat, scene_corners + Point2f(x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1)))), scene_corners + Point2f (x * (Image.cols / (maxx + 1)), y * (Image.rows / (maxy + 1))), Scalar (0, 255, 0), 4); ) imwrite("draw_mat.jpeg", Draw_mat); return 0; )

Результат роботи наступний:


Очевидно, що всі об'єкти детектовані. Причому деякі двічі (через те, що потрапили до двох блоків).

Бібліотека комп'ютерного зору та машинного навчання з відкритим кодом. До неї входять понад 2500 алгоритмів, у яких є як класичні, і сучасні алгоритми для комп'ютерного зору машинного навчання. Ця бібліотека має інтерфейси різними мовами, серед яких є Python (у цій статті використовуємо його), Java, C++ та Matlab.

Встановлення

Інструкцію зі встановлення на Windows можна подивитися, а на Linux -.

Імпорт та перегляд зображення

import cv2 image = cv2.imread("./шлях/до/зображення.розширення") cv2.imshow("Image", image) cv2.waitKey(0) cv2.destroyAllWindows()

Примітка При читанні способом вище зображення знаходиться в колірному просторі не RGB (як усі звикли), а BGR. Можливо, спочатку це не так важливо, але як тільки ви почнете працювати з кольором - варто знати про цю особливість. Є 2 шляхи вирішення:

  1. Поміняти місцями 1-й канал (R – червоний) з 3-м каналом (B – синій), і тоді червоний колір буде (0,0,255), а не (255,0,0).
  2. Змінити колірний простір на RGB: rgb_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

    І тоді в коді працювати вже не з image, а з rgb_image.

Примітка Щоб закрити вікно, в якому відображається зображення, натисніть будь-яку клавішу. Якщо використовувати кнопку закриття вікна, можна натрапити на підвисання.

Протягом статті для відображення зображень буде використовуватися наступний код:

Import cv2 def viewImage(image, name_of_window): cv2.namedWindow(name_of_window, cv2.WINDOW_NORMAL) cv2.imshow(name_of_window, image) cv2.waitKey(0) cv2.destroyAllWindows()

Кадрування

Пісик після кадрування

Import cv2 cropped = image viewImage(cropped, "Пісик після кадрування")

Де image - це image.

Зміна розміру

Після зміни розміру на 20%

Import cv2 scale_percent = 20 # Відсоток від початкового розміру width = int(img.shape * scale_percent / 100) height = int(img.shape * scale_percent / 100) dim = (width, height) resized = cv2.resize(im , interpolation = cv2.INTER_AREA) viewImage(resized, "Після зміни розміру на 20%")

Ця функція враховує співвідношення сторін оригінального зображення. Інші функції зміни розміру зображень можна побачити.

Поворот

Пісик після повороту на 180 градусів

Import cv2 (h, w, d) = image.shape center = (w // 2, h // 2) M = cv2.getRotationMatrix2D(center, 180, 1.0) rotated = cv2.warpAffine(image, M, (w , h)) viewImage(rotated, "Пісик після повороту на 180 градусів")

image.shape повертає висоту, ширину та канали. M – матриця повороту – повертає зображення на 180 градусів навколо центру. -ve - це кут повороту зображення за годинниковою стрілкою, а +ve відповідно проти годинникової.

Переклад у градації сірого та у чорно-біле зображення на порозі

Песик у градаціях сірого

Чорно-білий песик

Import cv2 gray_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) ret, threshold_image = cv2.threshold(im, 127, 255, 0) viewImage(gray_image, "Песик в градаціях сірого") ")

gray_image – це одноканальна версія зображення.

Функція threshold повертає зображення, в якому всі пікселі, які темніші (менше) 127 замінені на 0, а всі, які яскравіші (більше) 127, - на 255.

Для ясності інший приклад:

Ret, threshold = cv2.threshold (im, 150, 200, 10)

Тут все, що темніше, ніж 150, замінюється на 10, а все, що яскравіше – на 200.

Інші threshold-функції описані .

Розмиття/згладжування

Розмитий песик

Import cv2 blurred = cv2.GaussianBlur(image, (51, 51), 0) viewImage(blurred, "Розмитий песик")

Функція GaussianBlur (розмиття по Гаусс) приймає 3 параметри:

  1. Початкове зображення.
  2. Кортеж із двох позитивних непарних чисел. Чим більше числа, тим більша сила згладжування.
  3. sigmaXі sigmaY. Якщо ці параметри залишити рівними 0, їх значення буде розраховано автоматично.

Малювання прямокутників

Обводимо прямокутником мордочку песика

Import cv2 output = image.copy() cv2.rectangle(output, (2600, 800), (4100, 2400), (0, 255, 255), 10) viewImage(output, "Обводимо прямокутником обличчя песика")

Ця функція приймає 5 параметрів:

  1. Саме зображення.
  2. Координата верхнього лівого кута (x1, y1).
  3. Координата нижнього правого кута (x2, y2).
  4. Колір прямокутника (GBR/RGB залежно від вибраної моделі кольорів).
  5. Товщина прямокутника лінії.

Малювання ліній

2 песика, розділені лінією

Import cv2 output = image.copy() cv2.line(output, (60, 20), (400, 200), (0, 0, 255), 5) viewImage(output, "2 песика, розділені лінією")

Функція line приймає 5 параметрів:

  1. Саме зображення, у якому малюється лінія.
  2. Координата першої точки (x1, y1).
  3. Координата другої точки (x2, y2).
  4. Колір лінії (GBR/RGB залежно від вибраної моделі кольорів).
  5. Товщина лінії.

Текст на зображенні

Зображення з текстом

Import cv2 output = image.copy() cv2.putText(output, "We<3 Dogs", (1500, 3600),cv2.FONT_HERSHEY_SIMPLEX, 15, (30, 105, 210), 40) viewImage(output, "Изображение с текстом")

Функція putText приймає 7 параметрів:

  1. Безпосередньо зображення.
  2. Текст для зображення.
  3. Координата нижнього лівого кута початку тексту (x, y).
  4. Облич виявлено: 2

    Import cv2 image_path = "./шлях/к/фото.розширення" face_cascade = cv2.CascadeClassifier("haarcascade_frontalface_default.xml") image = cv2.imread(image_path) gray = cv2.cvtColor(image, cv2.COL .detectMultiScale(gray, scaleFactor= 1.1, minNeighbors= 5, minSize=(10, 10)) faces_detected = "Облич виявлено: " + format(len(faces)) print(faces_detected) # Малюємо квадрати навколо осіб for (x, y , w, h) in faces: cv2.rectangle(image, (x, y), (x+w, y+h), (255, 255, 0), 2) viewImage(image,faces_detected)

    detectMultiScale – загальна функція для розпізнавання як осіб, так і об'єктів. Щоб функція шукала саме обличчя, ми передаємо відповідний каскад.

    Функція detectMultiScale приймає 4 параметри:

    1. Оброблюване зображення в сірому градації.
    2. Параметр scaleFactor. Деякі особи можуть бути більшими за інших, оскільки знаходяться ближче, ніж інші. Цей параметр компенсує перспективу.
    3. Алгоритм розпізнавання використовує ковзне вікно під час розпізнавання об'єктів. Параметр minNeighbors визначає кількість об'єктів навколо обличчя. Тобто що більше значення цього параметра, то більше вписувалося аналогічних об'єктів необхідно алгоритму, що він визначив поточний об'єкт, як обличчя. Занадто маленьке значення збільшить кількість хибних спрацьовувань, а надто велике зробить алгоритм більш вимогливим.
    4. minSize – безпосередньо розмір цих областей.

    Contours - розпізнавання об'єктів

    Розпізнавання об'єктів здійснюється за допомогою колірної сегментації зображення. Для цього є дві функції: cv2.findContours та cv2.drawContours .

    У цій статті детально описано виявлення об'єктів за допомогою сегментації кольорів. Все, що вам потрібне для неї, знаходиться там.

    Збереження зображення

    import cv2 image = cv2.imread("./імпорт/шлях.розширення") cv2.imwrite("./експорт/шлях.розширення", image)

    Висновок

    OpenCV - відмінна бібліотека з легкими алгоритмами, які можуть використовуватися в 3D-рендері, просунутому редагуванні зображень і відео, відстеження та ідентифікації об'єктів і людей на відео, пошуку ідентичних зображень з набору та для багато чого ще.

    Ця бібліотека є дуже важливою для тих, хто розробляє проекти, пов'язані з машинним навчанням в області зображень.

З цієї статті ви дізнаєтесь, як створити 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) = 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", clo

Тепер прогалини в контурах закриті:

Наступним кроком є ​​фактичне виявлення контурів об'єктів зображення. Для цього ми будемо використовувати функцію 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. Застосувати контурну апроксимацію, щоб визначити, чи був контур прямокутником і, отже, книгою.

Ви можете завантажити вихідний код скрипта та зображення, яке використовується у цій статті.

Основна ідея полягає в обліку статистичних зв'язків між розташуванням антропометричних точок обличчя. На кожному зображенні обличчя точки пронумеровані однаково. За їх взаємним розташуванням здійснюється порівняння осіб.

Для порівняння осіб можна використовувати те саме положення обличчя щодо камери. Більш переважний для цього.

Захоплення відеопотоку з камери та виділення обличчя

#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 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 + X36) /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 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 landmarks на 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; }