Mastering OpenCV with Practical Computer Vision Projects / Опановуюємо OpenCV через практичні проекти комп'ютерного зору: Розділ 8. Розпізнавання обличчя за методами Eigenfaces та Fisherfaces
| Английский оригинал | Перевод на украинский | |
|---|---|---|
Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
Для кожного власного обличчя існує єдине власне значення, тому якщо наявні чотири знімки обличчя для трьох осіб, отримаємо вектор колонки з 12 власними значеннями, відсортованими від найкращих до найгірших: Atronax 22.03.21 в 15:21 |
|
|
Для методу Fisherfaces існує єдине власне значення для кожної особи, тому, якщо матимемо по чотири зображення облич для трьох людей, отримаємо верктор ряду з двома власними значеннями: Atronax 22.03.21 в 15:21 |
||
|
Щоб розглянути власні вектори у вигляді зображень Eigenfaces чи Fisherfaces, ми повинні виокремити їх у вигляді колонок з великої матриці власних векторів. Оскільки дані OpenCV та С/C++ звичайно зберігаються у матрицях, відсортованих відносно рядів, ми повинні скористатися функцією Mat::clone() для того, щоб мати можливість розширити дані, інакше ми не зможемо зробити прямокутну форму. Маючи матрицю з колонок, можемо зобразити власні вектори за допомогою функції getImageFrom1DFloatMat(), як це було зроблено раніше для середнього обличчя: Atronax 22.03.21 в 15:22 |
||
|
// Отримаємо власні вектори Atronax 22.03.21 в 15:22 |
||
|
Наступний малюнок ілюструє власні вектори у вигляді зображень. Можна бачити, що для трьох людей з чотирма зображеннями облич кожного існують 12 власних облич (ліворуч) та 2 обличчя Фішера (праворуч). Atronax 22.03.21 в 15:22 |
||
|
Обличчя власні та Фішера мають певні подібності з характеристиками обличчя, але насправді не схожі на них. Так відбувається тому, що вони отримані відніманням від обличчя середнього зображення. Число вказує на те, яке саме це власне обличчя, тому що вони завжди впорядковані від найважливішого до найменш важливого, і якщо ви маєте 50 чи більше власних облич, решту можна відкинути, вона вказуватимуть лише на випадкові шуми. Atronax 22.03.21 в 15:23 |
||
|
Етап 4. Розпізнавання облич Atronax 22.03.21 в 15:23 |
||
|
Маючи алгоритм машинного навчання Eigenfaces чи Fisherfaces, натренований на основі зображень та відміток, ми готові визначити особу лише по зображенню обличчя! Останній крок називають розпізнаванням чи ідентифікацією. Atronax 22.03.21 в 15:23 |
||
|
Ідентифікація обличчя: розпізнаємо людей Atronax 22.03.21 в 15:23 |
||
|
Завдяки класу FaceRecognizer бібліотеки OpenCV можемо визначити людину на фотографії за допомогою методу FaceRecognizer::predict() так: Atronax 22.03.21 в 15:23 |
||
|
Значенням ідентичності є номер відмітки, використаний для збору облич для тренування. Atronax 22.03.21 в 15:23 |
||
|
Верифікація обличчя: підтвердження його приналежності певній особі Atronax 22.03.21 в 15:23 |
||
|
Щоб перевірити, чи результат вказує на потрібну особу або невідому, здійснюємо верифікацію (яку називають ще аутентифікацією), щоб обчислити метрику впевненості, яка вкаже на рівень схожості обличчя з певною особою (на відміну від ідентифікації, що порівнює один фрагмент з багатьма обличчями людей. Atronax 22.03.21 в 15:24 |
||
|
Клас FaceRecognizer бібліотеки OpenCV здатний повертати метрику впевненості за допомогою виклику функції predict(), але, на жаль, вона заснована на відстані у підпросторі власних векторів, тому не дуже надійна. Скористаємось методом, який відновить зображення обличчя за допомогою власних векторів та власних значень, а також порівняє отриманий результат з вхідними даними. Якщо в тренувальному наборі збережено багато облич однієї особи, етап відновлення має відбутися без жодних проблем, на основі вивчених власних векторів та власних значень, якщо ж облич для певної особи немає (чи немає жодного знімка за однакових умов освітлення та з тим же виразом, як у тестових примірниках), отримане зображення суттєво відрізнятиметься від вхідного, тобто воно, скоріше за все, належить невідомій особі. Atronax 22.03.21 в 15:24 |
||
|
Як сказано раніше, алгоритми Eigenfaces та Fisherfaces базуються на припущенні, що зображення грубо представлене у вигляді набору власних векторів (особливі кадри обличчя) та власних значень (відношення змішування). Якщо ми скомбінуємо ці характеристики для одного обличчя тренувального набору, то зможемо здобути досить схожу на оригінал копію. Те ж стосується інших кадрів, подібних до елементів тренувального набору - скомбінувавши натреновані власні вектори із власними значеннями зі схожого тестового зображення, зможемо відновити фрагмент, який виглядатиме його копією. Atronax 22.03.21 в 15:24 |
||
|
Знову ж таки, клас FaceRecognizer бібліотеки OpenCV дозволяє легко відновлювати обличчя із вхідного зображення за допомогою функції subspaceProject() для проєктування його у власний простір та subspaceReconstruct(), щоб повернутися з власного простого у простір зображення. Нюанс полягає в тому, що нам треба сконвертувати його з матриці рядів плаваючих значень у прямокутне 8-бітне зображення (як ми робили, коли зображали середнє обличчя та власні обличчя), але ми не бажаємо нормувати дані, адже вже маємо ідеальний масштаб для порівняння з оригінальним зображенням. Якщо нормуємо дані, вони матимуть іншу яскравість та контраст, ніж вхідний кадр, у такому разі порівнювати схожість кадрів лише на основі відносної похибки L2 складно. Відповідний фрагмент коду матиме такий вигляд: Atronax 22.03.21 в 15:24 |
||
|
// отримуємо потрібні дані з моделі FaceRecognizer Atronax 22.03.21 в 15:25 |
||
|
Наступний малюнок зображає два типових відновлених обличчя. З лівого боку маємо чітке зображення відомої людини, а з правого - неточне, бо це була невідома особа чи відома, але за інших умов освітлення, з інакшим виразом обличчя чи напрямом погляду. Atronax 22.03.21 в 15:26 |
||
|
Тепер можемо обрахувати, наскільки відновлені обличчя схожі на вхідні кадри за допомогою функції getSimilarity(), яку ми створили для порівняння фрагментів. Нагадаємо, значення, менше за 0.3, вказує на високий рівень подібності. Що стосується власних облич, для кожного з них існує єдиний власний вектор, відновлення ефективне, тому значення порогу звичайно складатиме 0.5. Обличчя Фішера включають один власний вектор для кожної особи, тому відновлення на надає якісні результати. У такому разі потрібен високий рівень порогу, наприклад 0.7. Це робиться таким чином: Atronax 22.03.21 в 15:26 |
||
|
similarity = getSimilarity(preprocessedFace, Atronax 22.03.21 в 15:26 |
||
|
Тепер можна просто вивести значення ідентичності в консолі або використати його як завгодно! Atronax 22.03.21 в 15:26 |
||
|
Фінальні правки: збереження та завантаження файлів Atronax 22.03.21 в 15:26 |
||
|
Звичайно, можна створити метод командної стрічки для оброблення вхідних файлів та збереження їх на диску або навіть здійснити пошук обличчя, попередню обробку чи розпізнавання у вигляді веб-сервісу і так далі. У подібних проєктах досить легко додавати бажану функціональність за допомогою функцій збереження та завантаження класу FaceRecognizer. Це дає також змогу користуватися заздалегідь вивченими даними. Atronax 22.03.21 в 15:26 |
||
|
Зберегти натреновану модель у вигляді файла XML чи YML дуже просто: Atronax 22.03.21 в 15:26 |
||
|
string facerecAlgorithm = "FaceRecognizer.Fisherfaces"; Atronax 22.03.21 в 15:27 |
||
|
Фінальні правки: створюємо гарний інтерактивний інтерфейс Atronax 22.03.21 в 15:28 |
||
|
Хоча вказаного коду й достатньо для системи розпізнавання цілого обличчя, все ще потрібен спосіб розміщення та використання даних. Для дослідження більшість систем розпізнавання обличчя в якості вхідного параметра застосовує текстовий файл зі списком зображень, а також інші важливі дані, наприклад дійсне ім'я чи ідентичність особи і координати дійсних пікселів фрагментів (еталонні положення центра обличчя чи очей). Скоріше за все, ці характеристики будуть зібрані іншою системою розпізнавання. Atronax 22.03.21 в 15:28 |
||
|
Ідеальними вихідними даними в такому разі виявляться текстові файли, що порівнюють результати розпізнавання з еталонами, щоб здобути статистичні дані порівняння різних систем між собою. Atronax 22.03.21 в 15:28 |
||
|
Але система розпізнавання обличчя у цьому розділі розроблена для навчання та практичних цілей, а не конкурування з останніми дослідженими методами, корисно мати зручний інтерфейс користувача, що дозволяє збирати колекцію облич, тренувати та тестувати в інтерактивному режимі. Тому в цій частині опишемо графічний інтерфейс, що надасть подібні можливості. Читач може скористатися кодом, що постачається разом із книгою, або модифікувати під свої вимоги, або проігнорувати та розрозбити власний інтерфейс користувача для здійснення описаних технік розпізнавання. Atronax 22.03.21 в 15:28 |
||
|
Оскільки графічний інтерфейс має виконувати декілька задач, створимо набір режимів чи станів та можливість перемикатися між ними: Atronax 22.03.21 в 15:28 |
||
|
Щоб завершити роботу, користувач має можливість натиснути Escape коли завгодно. Також додамо режим "Видалити все", який перезавантажує роботу системи розпізнавання обличчя та кнопку "Налагодження", яка виводить на екран додаткову інформацію. Можна також створити змінну переліку режимів та виводити поточний. Atronax 22.03.21 в 15:31 |
||
|
Малюємо елементи графічного інтерфейсу Atronax 22.03.21 в 15:31 |
||
|
Щоб вказати на екрані поточний режим, створимо функцію для полегшення виводу тексту. Бібліотека OpenCV постачається із функцію cv::putText(), що має можливості обирати один з декількох шрифтів та згладжування, але розмістити текст у бажаній позиції нелегко. На щастя, існує також функція cv::getTextSize(), яка обчислює обмежувальний прямокутник навколо тексту, тому можемо запрограмувати функцію-обгортку для полегшення цієї процедури. Ми бажаємо отримати можливість розміщувати текст вздовж будь-якої границі вікна та впевнитися, що він видимий повністю, і дозволити вивід декількох стрічок чи слів поряд без накладання. Запропонуємо таку функцію, яка дозволить вказувати положення зліва чи справа, зверху чи знизу, а також повертати обмежувальний прямокутник, аби мати можливість легко зображати декілька стрічок тексту в позиції будь-якого кута чи границі вікна: Atronax 22.03.21 в 15:32 |
||
|
// Малюємо текст на зображенні. За замовчуванням встановлена Atronax 22.03.21 в 15:32 |
||
|
Зупинимось на питанні виведення поточного режиму на графічному інтерфейсі. Оскільки фонове зображення отримане з камери, якщо ми просто намалюємо текст на його поверхні, він матиме той же колір! Тому додамо чорну тінь на відстані 1 піксель завширшки від тексту. Також намалюємо стрічку допоміжного тексту, щоб користувач міг здійснити певні кроки. Ось приклад виводу за допомогою функції drawString(): Atronax 22.03.21 в 15:32 |
||
|
string msg = "Натисніть "Додати особу", якщо готові збирати обличчя.."; Atronax 22.03.21 в 15:33 |
||
|
Частковий скріншот показує режим та дані в нижній частині графічного інтерфейсу, розміщені на поверхні кадру камери: Atronax 22.03.21 в 15:33 |
||
|
// Малюємо кнопку графічного інтерфейсу на зображенні за допомогою drawString(). Atronax 22.03.21 в 15:34 |
||
|
Тепер за допомогою функції drawButton() створимо декілька кнопок, які завжди зображатимуться у верхній лівій частині графічного інтерфейсу, як показано далі: Atronax 22.03.21 в 15:35 |
||
|
Як вже зазначалося, програма з графічним інтерфейсом має декілька режимів, між якими можна перемикатися (як у паттерні FSM), починаючи з режиму Startup. Поточний режим зберігатимемо у змінній m_mode. Atronax 22.03.21 в 15:35 |
||
|
Режим налаштування Atronax 22.03.21 в 15:35 |
||
|
У цьому режимі потрібно завантажити XML-файли детектора для розпізнавання обличчя та очей, а також налаштувати вебкамеру. Ці процеси ми вже пояснили. Також створимо головне вікно графічного інтерфейсу із функцією зворотнього виклику, яку бібліотека OpenCV активуватиме кожен раз, коли користувач переміщує чи натискає кнопку миші у вікні. Крім цього, бажано встановити роздільну здатність камери у прийнятні межі, наприклад 640х480, якщо це значення підтримується. Це робиться таким чином: Atronax 22.03.21 в 15:35 |
||
|
// Створюємо вікно графічного інтерфейсу для виводу на екран. Atronax 22.03.21 в 15:36 |
||
|
Режим пошуку Atronax 22.03.21 в 15:36 |
||
|
У цьому режимі ми шукаємо обличчя та очі, малюємо прямокутники або кола навколо них для вказівки на результат та показуємо попередньо оброблені обличчя. Фактично, ми бажаємо зобразити їх не залежно від режиму. Єдиною особливістю пошуку є перехід до наступного етапу (збір даних), коли користувач натискає на кнопку "Додати особу". Atronax 22.03.21 в 15:36 |
||
|
Якщо ви пам'ятаєте з етапу пошуку, описаного раніше в цьому розділі, вихідні дані такі: Atronax 22.03.21 в 15:36 |
||
|
bool gotFaceAndEyes = false; Atronax 22.03.21 в 15:37 |
||
|
Накладемо поточне попередньо оброблене обличчя у верхній центральній частині вікна таким чином: Atronax 22.03.21 в 15:37 |
||
|
Наступний скріншот зображає графічний інтерфейс у режимі пошуку. Попередньо оброблене обличчя показане у верхній центральній частині вікна, а знайдені обличчя та очі промарковані: Atronax 22.03.21 в 15:38 |
||
|
Режим збору даних Atronax 22.03.21 в 15:38 |
