Mastering OpenCV with Practical Computer Vision Projects / Опановуюємо OpenCV через практичні проекти комп'ютерного зору: Розділ 8. Розпізнавання обличчя за методами Eigenfaces та Fisherfaces

← →
Английский оригинал Перевод на украинский

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

Етап 2. Попередня обробка обличчя
Як раніше зазначено, процедура розпізнавання досить сильно залежить від умов освітлення, орієнтації та виразу обличчя і так далі, тому цю різницю потрібно мінімізувати якомога краще. Інакше алгоритм вважатиме, що обличчя двох різних людей в однакових умовах більш подібні між собою, аніж два обличчя тієї ж особи. Найлегшим способом попередньо обробити обличчя - просто застосувати функцію equalizeHist() для вирівнювання гістограми, як і було зроблено на етапі пошуку обличчя.

Atronax 22.03.21 в 14:37

0

Етап 2. Попередня обробка обличчя
Як раніше зазначено, процедура розпізнавання досить сильно залежить від умов освітлення, орієнтації та виразу обличчя і так далі, тому цю різницю потрібно мінімізувати якомога краще. Інакше алгоритм вважатиме, що обличчя двох різних людей в однакових умовах більш подібні між собою, аніж два обличчя тієї ж особи. Найлегшим способом попередньо обробити обличчя - просто застосувати функцію equalizeHist() для вирівнювання гістограми, як і було зроблено на етапі пошуку обличчя.

Atronax 22.03.21 в 14:37

0

Пошук ока

Atronax 22.03.21 в 14:37

0

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

Atronax 22.03.21 в 14:37

0

Деякі із заздалегідь натренованих детекторів, що постачаються разом з бібліотекою OpenCV v2.4, можуть розпізнавати і відкриті, і закриті очі, у той час як інші - тільки закриті.

Atronax 22.03.21 в 14:37

0

Детектори першого типу зберігаються у таких файлах:
haarcascade_mcs_lefteye.xml та haarcascade_mcs_righteye.xml
haarcascade_lefteye_2splits.xml та haarcascade_righteye_2splits.xml
Лише одне око розпізнають такі детектори:
haarcascade_eye.xml
haarcascade_eye_tree_eyeglasses.xml

Atronax 22.03.21 в 14:37

0

Примітка
Оскільки процес розпізнавання відкритого чи закритого стану визначає, яке саме око використовується для тренування, потрібні окремі детектори для лівого та правого. Що ж до детекторів винятково для відкритого ока, їх об'єкт для розпізнавання залишається однаковим для обох.

Atronax 22.03.21 в 14:38

0

Детектор haarcascade_eye_tree_eyeglasses.xml здатний розпізнати очі, якщо людина носить окуляри, але не дуже надійний, якщо це не так.
Якщо назва файлу XML включає словосполучення "ліве око", це значить саме ліве око людини, а не будь-яке інше. Тобто на кадрі з камери воно знаходитиметься з правого боку обличчя, а не лівого! Список чотирьох зазначених детекторів ока упорядкований від найбільш надійних до найменш. Якщо вам не потрібно розпізнавати людей в окулярах, перший варіант надаватиме найкращі результати.

Atronax 22.03.21 в 14:38

0

Зони пошуку ока

Atronax 22.03.21 в 14:38

0

У процесі розпізнавання важливим кроком є виокремлення фрагмента з приблизним положенням ока, для цього потрібно відшукати обличчя та вирізати невеликий прямокутник з лівим або правим оком залежно від застосованого детектора. Якщо ви виконаєте розпізнавання ока на усьому обличчі чи фотографії, страждатиме швидкість та надійність. Різні детектори ока краще працюють для певних фрагментів обличчя, наприклад haarcascade_eye.xml формує якісніші результати для дуже малих прямокутників навколо самого ока, а детектори haarcascade_mcs_lefteye.xml та haarcascade_lefteye_2splits.xml діють краще для великих фрагментів.

Atronax 22.03.21 в 14:38

0

У таблиці перелічено непогані регіони пошуку обличчя для різних детекторів ока (з використанням алгоритму розпізнавання LBP), з такими відносними координатами у межах знайденого прямокутника:
Каскадний класифікатор EYE_SX EYE_SY EYE_SW EYE_SH
haarcascade_eye.xml 0.16 0.26 0.30 0.28
haarcascade_mcs_lefteye.xml 0.10 0.19 0.40 0.36
haarcascade_lefteye_2splits.xml 0.12 0.17 0.37 0.36

Atronax 22.03.21 в 14:38

0

Далі вкажемо код для вирізання фрагментів з лівим та правим оком зі знайденого обличчя:
int leftX = cvRound(face.cols * EYE_SX);
int topY = cvRound(face.rows * EYE_SY);
int widthX = cvRound(face.cols * EYE_SW);
int heightY = cvRound(face.rows * EYE_SH);
int rightX = cvRound(face.cols * (1.0-EYE_SX-EYE_SW));
Mat topLeftOfFace = faceImg(Rect(leftX, topY, widthX,
heightY));
Mat topRightOfFace = faceImg(Rect(rightX, topY, widthX,
heightY));

Atronax 22.03.21 в 14:39

0

Наступний малюнок зображає ідеальні регіони пошуку для різних детекторів ока. Бачимо, що haarcascade_eye.xml та haarcascade_eye_tree_eyeglasses.xml краще підходять для невеликих, а haarcascade_mcs_*eye.xml та haarcascade_*eye_2splits.xml - для великих. Також показано знайдений прямокутник обличчя, щоб проілюструвати, наскільки великі зони пошуку ока порівняно з ним.

Atronax 22.03.21 в 14:39

0

Ось приблизні значення параметрів розпізнавання для різних детекторів ока:

Atronax 22.03.21 в 14:39

0

Каскадний класифікатор Надійність* Швидкість** Тип знайденого ока Діє для окулярів
haarcascade_mcs_lefteye.xml 80% 18мс відкрите або закрите ні
haarcascade_lefteye_2splits.xml 60% 7мс відкрите або закрите ні
haarcascade_eye.xml 40% 5мс лише відкрите ні
haarcascade_eye_tree_eyeglasses.xml 15% 10мс лише відкрите так

Atronax 22.03.21 в 14:39

0

* Значення надійності вказує на те, наскільки часто обидва ока будуть знайдені за допомогою алгоритму розпізнавання фронтального обличчя LBP, якщо особа не носить окуляри, а очі відкриті. Якщо ж вони закриті, або окуляри мають певні дефекти, надійність і швидкість може знизитися.

Atronax 22.03.21 в 14:39

0

** Значення швидкості вказані в мілісекундах для зображень, масштабованих до розмірів 320х240 пікселів на процесі Intel Core i7 2.2 GHz (середнє серед тисячі фотографій). Зазвичай швидкість більша, коли очі знаходяться, ніж у протилежному випадку, бо для цього може знадобитися просканувати все зображення, але навіть у такому разі детектор haarcascade_mcs_lefteye.xml все ще повільніший за інші.

Atronax 22.03.21 в 14:39

0

Наприклад, ви маєте зменшити розміри фотографії до 320х240 пікселів, здійснити вирівнювання гістограми, скористатися детектором фронтальних облич LBP, виокремити регіони лівого та правого ока за допомогою значень haarcascade_mcs_lefteye.xml, потім виконати вирівнювання гістограми для кожного з них. Якщо використовуєте детектор haarcascade_mcs_lefteye.xml для лівого ока (у верхній правій частині зображення) та haarcascade_mcs_righteye.xml для правого (у верхньому лівому куті), кожен детектор ока має діяти для 90 відсотків фотографій зі знайденими фронтальними обличчями LBP. Тому, якщо потрібно розпізнати обидва ока, складений алгоритм повертатиме результат для близько 80 відсотків зображень знайдених облич.

Atronax 22.03.21 в 14:39

0

Хоча й рекомендовано вирізати відповідний фрагмент кадру камери перед пошуком обличчя, очі потрібно розпізнавати на оригінальному зображенні. Вочевидь, вони значно менші за обличчя, тому роздільна здатність має бути якомога більшою.

Atronax 22.03.21 в 14:39

0

Примітка
Згідно таблиці, обирати детектор потрібно залежно від того, чи бажаєте ви розпізнавати закриті очі, чи лише відкриті. Пам'ятайте, що можна скористатися одним алгоритмом, а якщо він не поверне прийнятні результати, спробувати інший. Для багатьох задач важливо розпізнати, відкриті очі чи закриті, тому якщо швидкість роботи програми неважлива, спочатку варто здійснити пошук за допомогою детектора msc_*eye, а якщо результати не вдасться знайти, спробувати eye_2splits.

Atronax 22.03.21 в 14:40

0

Але у процесі розпізнавання обличчя людина видаватиметься дещо іншою, якщо її очі закриті, тому спочатку варто скористатися простим детектором haarcascade_eye, а потім - здійснити пошук за допомогою haarcascade_eye_tree_eyeglasses.

Atronax 22.03.21 в 14:41

0

Можна скористатися тією ж функцією detectLargestObject(), що й на етапі розпізнавання обличчя для пошуку очей, але замість вирізання фрагментів зображень перед етапом пошуку визначаємо ширину регіону повного ока, щоб отримати кращі результати для процесу. Ліве око легко знайти за допомогою одного детектора, а якщо він не поверне результати, можна спробувати інший (той же процес повторимо й для правого). Розпізнавання ока реалізоване таким чином:

Atronax 22.03.21 в 14:41

0

CascadeClassifier eyeDetector1("haarcascade_eye.xml");
CascadeClassifier
eyeDetector2("haarcascade_eye_tree_eyeglasses.xml");
...
Rect leftEyeRect; // зберігаємо розпізнане око
// знайдемо лівий фрагмент за допомогою детектора першого ока
detectLargestObject(topLeftOfFace, eyeDetector1, leftEyeRect,
topLeftOfFace.cols);
// якщо результатів немає, спробуємо другий детектор
if (leftEyeRect.width <= 0)
detectLargestObject(topLeftOfFace, eyeDetector2,
leftEyeRect, topLeftOfFace.cols);
// знаходимо центр лівого ока, якщо хоча б один з детекторів спрацював
Point leftEye = Point(-1,-1);
if (leftEyeRect.width <= 0) {
leftEye.x = leftEyeRect.x + leftEyeRect.width/2 + leftX;
leftEye.y = leftEyeRect.y + leftEyeRect.height/2 + topY;
}
// робимо те ж для правого ока
...
// перевіряємо, чи були розпізнані обидва ока
if (leftEye.x >= 0 && rightEye.x >= 0) {
...
}

Atronax 22.03.21 в 14:42

0

Маючи розпізнані обличчя та очі, здійснимо такі етапи попередньої обробки зображення:
Геометричні перетворення та вирізання. Цей процес включає масштабування, поворот та перенесення зображень з метою вирівнювання очей та видалення лоба, щік, вух і фону зображень.

Atronax 22.03.21 в 14:44

0

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

Atronax 22.03.21 в 14:44

0

Еліптична маска. Видаляє волосся та фон із зображення.

Atronax 22.03.21 в 14:45

0

Наступний малюнок ілюструє етапи 1-4 попередньої обробки знайденого фрагмента обличчя. Результат має однакові показники яскравості та контрасту на обох боках обличчя, у той час як оригінальні зображення - ні.

Atronax 22.03.21 в 14:45

0

Геометричні перетворення

Atronax 22.03.21 в 14:45

0

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

Atronax 22.03.21 в 14:45

0

Щоб отримати кращі результати для вирівнювання, розпізнаємо очі та розмістимо їх чітко у бажаних позиціях. Геометричні перетворення здійснимо за допомогою функції warpAffine(), простої операції, яка робить чотири речі:
Повертає обличчя, щоб отримати горизонтальні положення очей.

Atronax 22.03.21 в 14:45

0

Масштабує обличчя, щоб відстань між очима не змінювалася.
Переносить обличчя, щоб очі були горизонтально відцентровані та мали бажану висоту.
Видаляє верхні частини обличчя, оскільки нам не потрібні фон та волосся, лоб, вуза та щоки.

Atronax 22.03.21 в 14:45

0

Афінні викривлення діють на матрицю, що переносить положення двох знайдених очей у бажані позиції, а потім виконує певні операції, щоб отримати бажані розміри та позицію. Щоб згенерувати афінну матрицю, знайдемо центральну точку між очима, обчислимо кут та відстань між ними:

Atronax 22.03.21 в 14:45

0

// знайдемо центральну точку між очима
Point2f eyesCenter;
eyesCenter.x = (leftEye.x + rightEye.x) * 0.5f;
eyesCenter.y = (leftEye.y + rightEye.y) * 0.5f;
// обчислимо кут між очима
double dy = (rightEye.y - leftEye.y);
double dx = (rightEye.x - leftEye.x);
double len = sqrt(dx*dx + dy*dy);
// конвертуємо радіани в градуси
double angle = atan2(dy, dx) * 180.0/CV_PI;
// ручні виміри для центра лівого ока в ідеальному випадку
// мають становити приблизно (0.16, 0.14) для масштабованого зображення обличчя
const double DESIRED_LEFT_EYE_X = 0.16;
const double DESIRED_RIGHT_EYE_X = (1.0f – 0.16);

Atronax 22.03.21 в 14:46

0

// встановлюємо фіксовані бажані розміри обличчя
const int DESIRED_FACE_WIDTH = 70;
const int DESIRED_FACE_HEIGHT = 70;
double desiredLen = (DESIRED_RIGHT_EYE_X – 0.16);
double scale = desiredLen * DESIRED_FACE_WIDTH / len;
Тепер можемо здійснити перетворення (поворот, масштабування та перенесення), щоб отримати два ока у бажаних позиціях ідеального обличчя таким чином:
// отримуємо матрицю перетворень для бажаних кута та розмірів
Mat rot_mat = getRotationMatrix2D(eyesCenter, angle, scale);

Atronax 22.03.21 в 14:47

0

// зміщуємо центральні точки очей у бажаний центр
double ex = DESIRED_FACE_WIDTH * 0.5f - eyesCenter.x;
double ey = DESIRED_FACE_HEIGHT * DESIRED_LEFT_EYE_Y –
eyesCenter.y;
rot_mat.at<double>(0, 2) += ex;
rot_mat.at<double>(1, 2) += ey;
// перетворюємо зображення обличчя з урахуванням бажаних
// кута, розмірів та позиції; також встановлюємо сірий
// колір фону
Mat warped = Mat(DESIRED_FACE_HEIGHT, DESIRED_FACE_WIDTH,
CV_8U, Scalar(128));
warpAffine(gray, warped, rot_mat, warped.size());

Atronax 22.03.21 в 14:47

0

Окремі вирівнювання гістограм для лівого та правого боків

Atronax 22.03.21 в 14:47

0

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

Atronax 22.03.21 в 14:48

0

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

Atronax 22.03.21 в 14:48

0

Наступний малюнок ілюструє, як змішати між собою зображення з відрегульованими значеннями зліва, справа та повного кадру:

Atronax 22.03.21 в 14:48

0

Для цього потрібні копії відрегульованих фрагментів зображення, які можна отримати таким чином:
int w = faceImg.cols;
int h = faceImg.rows;
Mat wholeFace;
equalizeHist(faceImg, wholeFace);
int midX = w/2;
Mat leftSide = faceImg(Rect(0,0, midX,h));
Mat rightSide = faceImg(Rect(midX,0, w-midX,h));
equalizeHist(leftSide, leftSide);
equalizeHist(rightSide, rightSide);

Atronax 22.03.21 в 14:48

0

Тепер комбінуємо три зображення. Оскільки вони малі, можна легко отримати доступ до пікселів напряму за допомогою функції image.at<uchar>(y,x), навіть якщо вона повільна; тому поєднаємо три кадри, сформувавши вихідні пікселі з відповідників на цих зображеннях:

Atronax 22.03.21 в 14:48

0

for (int y=0; y<h; y++) {
for (int x=0; x<w; x++) {
int v;
if (x < w/4) {
// 25% зліва з краю: використаємо лише ліву частину обличчя
v = leftSide.at<uchar>(y,x);
}
else if (x < w*2/4) {
// 25% зліва від центру: змішаємо ліву частину та все обличчя
int lv = leftSide.at<uchar>(y,x);
int wv = wholeFace.at<uchar>(y,x);
// додаватимемо більшу пропорцію цілого обличчя, чим
// правіше рухатимося вздовж нього
float f = (x - w*1/4) / (float)(w/4);
v = cvRound((1.0f - f) * lv + (f) * wv);
}
else if (x < w*3/4) {
// 25% справа від центру: змішаємо праву частину та все обличчя
int rv = rightSide.at<uchar>(y,x-midX);
int wv = wholeFace.at<uchar>(y,x);
// додаватимемо більшу пропорцію правої частини обличчя, чим
// правіше рухатимося вздовж нього
float f = (x - w*2/4) / (float)(w/4);
v = cvRound((1.0f - f) * wv + (f) * rv);
}
else {
// 25% справа з краю: використаємо лише праву частину обличчя
v = rightSide.at<uchar>(y,x-midX);
}
faceImg.at<uchar>(y,x) = v;
}// кінець циклу x
}// кінець циклу y

Atronax 22.03.21 в 14:49

0

Подібна процедура окремого вирівнення гістограм повинна значно знизити ефект різного освітлення з лівого та правого боків обличчя, але не повністю, оскільки обличчя - це складна тривимірна форма з багатьма тінями.

Atronax 22.03.21 в 14:50

0

Згладжування

Atronax 22.03.21 в 14:50

0

Щоб зменшити ефект піксельного шуму, застосуємо двопрохідний фільтр, адже він досить добре згладжує зображення, залишаючи межі гострими. Вирівнювання гістограми може суттєво підвищити зашумленість, тому підвищимо силу фільтру до 20, щоб видалити шум, але встановимо значення два в якості кількості сусідів, оскільки ми бажаємо згладити лише крихітні острівці піксельного шуму, а не величезні регіони зображень:

Atronax 22.03.21 в 14:50

0

Mat filtered = Mat(warped.size(), CV_8U);
bilateralFilter(warped, filtered, 0, 20.0, 2.0);

Atronax 22.03.21 в 14:50

0

Еліптична маска

Atronax 22.03.21 в 14:50

0

Хоча ми вже видалили більшу частину фону зображення, лоб та волосся, коли здійснювали геометричні перетворення, варто скористатися еліптичною маскою, щоб запобігти появі кутових фрагментів, наприклад шиї з тінню від обличчя, особливо якщо погляд не спрямований чітко на камеру. Щоб створити маску, намалюємо заповнений чорним кольором еліпс на білому фоні. Вона може мати горизонтальний радіус 0.5 (ідеально покриває ширину обличчя) і вертикальний 0.8 (оскільки обличчя звичайно мають більшу висоту), та мати центральну точку з координатами (0.5, 0.4). Далі показано, як за допомогою еліптичної маски видалено небажані кути обличчя:

Atronax 22.03.21 в 14:50

0

Скористатися маскою можна за допомогою функції cv::setTo(), яка зазвичай встановлює всі пікселі зображення у певне значення, але якщо на вхід подати маску, відповідний показник встановиться лише для певних його частин.

Atronax 22.03.21 в 14:50

0

// малюємо заповнений чорним кольором еліпс посередині зображення
// спочатку встановлюємо білий колір фону маски
Mat mask = Mat(warped.size(), CV_8UC1, Scalar(255));
double dw = DESIRED_FACE_WIDTH;
double dh = DESIRED_FACE_HEIGHT;
Point faceCenter = Point( cvRound(dw * 0.5),
cvRound(dh * 0.4) );
Size size = Size( cvRound(dw * 0.5), cvRound(dh * 0.8) );
ellipse(mask, faceCenter, size, 0, 0, 360, Scalar(0),
CV_FILLED);
// застосуємо еліптичну маску до обличчя, щоб видалити кути
// встановлюємо сірий колір для кутів, не дотикаючись до внутрішній частин
filtered.setTo(Scalar(128), mask);

Atronax 22.03.21 в 14:51

0
Минутку...

← Предыдущая страница

Следующая страница →