Mastering OpenCV with Practical Computer Vision Projects / Опановуюємо OpenCV через практичні проекти комп'ютерного зору: Розділ 1. Ефект мультиплікації та зміна кольору шкіри для Android
| Английский оригинал | Перевод на украинский | |
|---|---|---|
Сожалеем, но текст оригинала доступен только зарегистрированным пользователям. |
У цьому розділі ви дізнаєтесь, як запрограмувати деякі фільтри для обробки зображень на смартфонах і планшетах з ОС Android, використавши спочатку код для Desktop (С/C++), а потім перенісши його на Android (з тим же кодом С/С++, але графічним інтерфейсом користувача JAVA). Це рекомендований сценарій під час розробки програм для мобільних пристроїв. У цьому розділі ви дізнаєтесь: Atronax 12.02.21 в 11:49 |
|
|
Як конвертувати фотографію у схематичне зображення Atronax 12.02.21 в 11:57 |
||
|
Наступний скріншот зображує результат запуску програми Cartoonifier на планшеті з ОС Android: Atronax 12.02.21 в 12:00 |
||
|
Нам потрібно, аби реальні фотографії не відрізнялися від мультиплікації. Основа ідеї полягає в тому, щоб заповнити однотонні частини зображення деяким кольором, а потім намалювати товсті лінії на їх межах. Іншими словами, однотонні фрагменти мають стати більш однотонними, а їх межі - більш акцентованими. Ми знайдемо межі та згладимо однотонні фрагменти, а потім підсилимо межі для того, щоб сформувати ефект мультиплікації чи коміксу. Atronax 12.02.21 в 12:17 |
||
|
При розробці програм комп'ютерного бачення для мобільних пристроїв спочатку варто компілювати повністю робочу версію для ПК, адже її легше запрограмувати та налагодити, аніж мобільний застосунок! Тому цей розділ почнемо з повної настільної версії програми Cartoonifier, яку можна створити у будь-якій з ваших улюблених середовищ для розробки (наприклад, Visual Studio, XCode, Eclipse, Qt Creator і подібні). Коли вона працюватиме як треба, перенесемо її на Android (можливо навіть iOS), використавши при цьому Eclipse. Оскільки ми плануємо створити два різні проєкти, що матимуть спільний код логіки, але різні графічні інтерфейси, ми могли б створити бібліотеку і прив'язати її до кожного, але для простоти викладу матеріалу розмістимо обидва проєкти поряд і налаштуємо доступ версії для Android до деяких файлів (cartoon.cpp та cartoon.h, у яких знаходиться весь код обробки зображень) з каталогу Desktop. Наприклад: Atronax 12.02.21 в 12:35 |
||
|
C:\Cartoonifier_Desktop\cartoon.cpp Atronax 12.02.21 в 12:35 |
||
|
Прикладна програма для ПК використовує вікно OpenCV GUI, налаштовує камеру і для кожного кадру камери викликає функцію cartoonifyImage(), що включає більшість коду цього розділу. Потім оброблене зображення засобами GUI виводиться на поверхні вікна. Подібним чином, програма для Android використовує власне вікно GUI, налаштовує камеру методами Java, і для кожного кадру камери викликає ту ж функцію cartoonifyImage(), але з меню і системою вводу Android. Atronax 12.02.21 в 12:42 |
||
|
У цьому розділі пояснюється, як створити програму для ПК з нуля, а також для Android - на основі одного з прикладів OpenCV Android. Таким чином, спочатку ви маєте запрограмувати версію для настільних пристроїв в улюбленому IDE, файл main_desktop.cpp має зберігати код графічного інтерфейсу, поданий в наступних частинах, такі як головний цикл, функціональність вебкамери та ввід з клавіатури, також потрібно створити файл cartoon.cpp, який використають обидва проєкти. Більшість коду розділу знаходитиметься у функції cartoonifyImage() з файлу cartoon.cpp. Atronax 12.02.21 в 12:46 |
||
|
Отримуємо доступ до вебкамери Atronax 12.02.21 в 12:54 |
||
|
Щоб отримати доступ до комп'ютерної чи мобільної камери, досить викликати метод cv::VideoCapture::open() і передати "0" в якості параметру (індекс камери за замовчуванням). Деякі комп'ютери мають декілька приєднаних камер або не використовують камеру 0; тому слід дозволяти користувачу передавати бажаний індекс як аргумент командної стрічки, на випадок, якщо він забажає спробувати камеру 1, 2, чи, наприклад, -1. Ми також спробуємо встановити роздільну здатність у значення 640х480 за допомогою методу cv::VideoCapture::set(), щоб програма працювала швидше з камерами з високою роздільною здатністю. Atronax 12.02.21 в 13:09 |
||
|
Примітка Atronax 12.02.21 в 13:13 |
||
|
int cameraNumber = 0; Atronax 12.02.21 в 13:15 |
||
|
Після налаштування вебкамери можна отримати поточне зображення у вигляді об'єкта cv::Mat (контейнер OpenCV для роботи з кадрами). Використавши потоковий оператор C++, кожен кадр можна виокремити з об'єкта cv::VideoCapture і помістити в об'єкт cv::Mat, подібно до отримання ввідних даних з консолі. Atronax 12.02.21 в 13:29 |
||
|
Примітка Atronax 12.02.21 в 13:35 |
||
|
Головний цикл взаємодії з камерою програми для ПК Atronax 12.02.21 в 13:36 |
||
|
Щоб зобразити вікно методами OpenCV, для кожного зображення необхідно викликати cv::imshow(), але у цьому випадку знадобиться також cv::waitKey() один раз на кадр, інакше вікна зовсім не оновляться! Якщо викликати функцію cv::waitKey(0), програма чекатиме, поки користувач не натисне клавішу, а позитивне значення, як у випадку cv::waitKey(20) чи вище, змусить потік почекати як мінімум цю кількість мілісекунд. Atronax 12.02.21 в 13:47 |
||
|
Помістіть цей головний цикл у файл main_desktop.cpp, це буде основа для вашої програми з камерою: Atronax 12.02.21 в 13:54 |
||
|
Генеруємо чорно-білий скетч Atronax 12.02.21 в 13:55 |
||
|
Щоб отримати скетч (чорно-біле зображення) на основі кадру з камери, використаємо фільтр розпізнавання меж; для формування кольорового малюнку використаємо двопрохідний фільтр, щоб мати можливість згладити однотонні фрагменти, не змінивши при цьому межі. Наклавши схематичне зображення на кольоровий малюнок, отримаємо ефект мультиплікації, показаний раніше на скріншоті фінальної версії програми. Atronax 12.02.21 в 14:56 |
||
|
Існує багато різних фільтрів розпізнавання меж, наприклад Собеля, Щарра, Лапласа або детектор меж Кенні. Зупинимось на фільтрі Лапласа, бо він знаходить межі, найбільше схожі на ручні скетчі в порівнянні з алгоритмами Собеля чи Щарра, та є більш точним за детектор меж Кенні, що формує дуже плавні малюнки з ліній, але при цьому страждає від випадкових шумів на кадрах, через що зображення змінюватиметься надто сильно між кадрами. Atronax 12.02.21 в 15:04 |
||
|
Хай там що, нам все ще необхідно зменшити шум на зображенні до фільтра Лапласа. Використаємо фільтр Медіани, адже він добре видаляє шум, залишаючи межі при цьому гострими; крім того, він не настільки повільний як двопрохідний. Оскільки фільтрам Лапласа потрібні зображення у відтінках сірого, необхідно конвертувати базовий формат OpenCV BGR у Grayscale. В пустому файлі cartoon.cpp помістіть цей код зверху, щоб отримати доступ до OpenCV та стандартних функцій С++ без повсюдного використання cv:: та std::. Atronax 12.02.21 в 15:26 |
||
|
// Підключаємо С++ інтерфейс бібліотеки OpenCV Atronax 12.02.21 в 15:31 |
||
|
Наступний малюнок презентує оригінальне зображення (зліва) і згенеровану маску меж (справа), що виглядає дуже схожою до скетчу. Коли ми сформуємо кольорову картину (дізнаємось як саме), цю маску розмістимо зверху, щоб отримати зображення з чорними лініями меж. Atronax 12.02.21 в 15:37 |
||
|
Генеруємо кольорову картину та ефект мультиплікації Atronax 12.02.21 в 15:40 |
||
|
Могутній двопрохідний фільтр згладжує однотонні фрагменти та залишає межі гострими, що неабияк підходить для створення автоматичного мультиплікаційного чи картинного фільтра, хоча він дуже повільний (час виконання вимірюється в секундах чи навіть хвилинах, а не в мілісекундах!). Тому ми використаємо деякі трюки, щоб отримати пристойний ефект мультиплікації, що виконуватиметься з прийнятною швидкістю. Найважливіший трюк - це те, що ми можемо виконати двопрохідну фільтрацію на зображеннях зниженої роздільної здатності. Ефект буде той же, що і для вихідного варіанту, але швидкість - значно вищою. Знизимо загальну кількість пікселів в чотири рази (зменшимо ширину і висоту вдвічі): Atronax 12.02.21 в 15:47 |
||
|
Size size = srcColor.size(); Atronax 12.02.21 в 15:48 |
||
|
Замість повного двопрохідного фільтра використаємо багато неповних, щоб отримати гарний ефект мультиплікації за менший час. Обмежимо фільтр (ознайомтесь з наступним малюнком), щоб замість повного розміру (наприклад, 21х21, тобто крива дзвону займає 21 піксель ширини), він використовував лише мінімальний, необхідний для переконливого результату (наприклад, 9х9 для кривої дзвону шириною 21 піксель). Обмежений фільтр опрацює більшу його частину (сіру зону), не витрачаючи час на меншу (біла зона під кривою), тому виконуватиметься у декілька разів швидше. Atronax 12.02.21 в 16:03 |
||
|
Існує чотири параметри, що контролюють двопрохідну фільтрацію: сила кольору, сила позиції, розмір і кількість повторень. Нам знадобиться тимчасовий об'єкт Mat, адже bilateralFilter() не може змінювати вхідні дані (про що ще говорять як про "обробку на місці"), але ми можемо застосувати один фільтр і зберегти результат у тимчасовому Mat, а потім ще один, що повертає дані у вихідний об'єкт Mat. Atronax 12.02.21 в 16:09 |
||
|
Mat tmp = Mat(smallSize, CV_8UC3); Atronax 12.02.21 в 16:12 |
||
|
Пам'ятайте, що цей алгоритм застосовано до зменшеного зображення, тому не забудьте розширити його до початкових розмірів. Після цього ми зможемо розмістити зверху знайдену раніше маску меж. Щоб накласти "скетч", тобто маску меж, на "картину", отриману за допомогою двопрохідного фільтра (ліва частина наступного малюнку), почнемо з чорного фону і скопіюємо пікселі "картини", що не є межами в масці "скетча": Atronax 12.02.21 в 16:20 |
||
|
Mat bigImg; Atronax 12.02.21 в 16:22 |
||
|
Активуємо режим "зла" з використанням фільтрів меж Atronax 12.02.21 в 16:22 |
||
|
Мультиплікація та комікси завжди мали добрих та злих персонажів. Правильно комбінуючи фільтри меж, можна згенерувати страхітливе зображення навіть найневиннішої особи у світі! Суть полягає у тому, щоб застосувати фільтр для малих меж, яких знайдеться чимало, а потім з'єднати їх невеликим Медіанним фільтром. Atronax 13.02.21 в 08:11 |
||
|
Цей процес виконаємо на однотонному зображенні зі зниженим рівнем шуму, тому попередній код для конвертації оригінального зображення у відтінки сірого та обробки останнього Медіанним фільтром 7х7 буде використаний знову (перше зображення на наступному малюнку зображає вивід результату однотонного Медіанного розмиття). Далі, замість фільтра Лапласа та бінарного порогу, отримаємо більш страшний вигляд, якщо застосуємо градієнтний фільтр Щарра розміром 3х3 вздовж осей x та y (друге зображення), а потім активуємо функцію бінарного порогу з дуже малим відрізком (третє зображення) та Медіанний фільтр 3х3, що сформує остаточну маску "зла" (четверте зображення). Atronax 13.02.21 в 08:27 |
||
|
Mat gray; Atronax 13.02.21 в 08:29 |
||
|
Отримавши маску "зла", можемо розмістити її над обробленою ефектом мультиплікації "картиною", подібно до зробленого за допомогою звичайної маски "скетчу" раніше. Результат видно з правого боку наступного малюнку: Atronax 13.02.21 в 08:31 |
||
|
Генеруємо режим "прибульця" з використанням алгоритму розпізнавання шкіри Atronax 13.02.21 в 08:32 |
||
|
Дотепер ми створили режим скетчу, мультиплікації (картина + схематична маска) і жаху (картина + маска зла), а тепер спробуємо дещо складніше: режим "прибульця", визначивши фрагменти шкіри на обличчі та змінивши колір шкіри на зелений. Atronax 13.02.21 в 08:35 |
||
|
Алгоритм розпізнавання шкіри Atronax 13.02.21 в 08:35 |
||
|
Існує багато різних технік, що використовуються для визначення фрагментів шкіри, від простих порогів кольору для значень RGB (червоний, зелений, синій) чи HSV (відтінок, насиченість, яскравість), обчислення та репроєкції гістограми кольорів, до складних алгоритмів машинного навчання (як-от змішування моделей), що потребують калібрування камер в кольоровому просторі CIELab та тренування на багатьох прикладах облич, і так далі. Atronax 13.02.21 в 08:43 |
||
|
Але навіть складні методи не обов'язково однаково ефективні для різних камер, умов світла та типів шкіри. Оскільки алгоритм розпізнавання шкіри має виконуватися на мобільних пристроях без калібрування чи тренування, а ми використаємо його лише для "смішного" фільтру зображення, для нас достатньо застосувати простий метод розпізнавання шкіри. Однак, колір, отриманий від крихітних сенсорів камери мобільного пристрою, має тенденцію досить значно змінюватись, до того ж ми бажаємо підтримувати розпізнавання шкіри для людей з будь-яким його кольором без жодного калібрування, тому нам потрібно дещо краще, аніж прості пороги кольорів. Atronax 13.02.21 в 09:04 |
||
|
Наприклад, простий HSV детектор шкіри може позначати будь-який піксель як шкіру, якщо його відтінок червонуватий, насиченість досить висока, але не занадто, а яскравість ні надто низька, ні надто висока. Але мобільні камери часто мають проблеми з балансом білого, тому шкіра людини може мати злегка синюватий відтінок замість червоного тощо, і саме це є головною проблемою для простого HSV порогу. Atronax 13.02.21 в 09:12 |
||
|
Більш ефективне рішення полягає в тому, щоб виконати розпізнавання обличчя за допомогою каскадного класифікатора Хаара чи LBP (показано в розділі 8 "Розпізнавання облич за допомогою алгоритму Eigenface") а потім розглянути діапазон кольорів для пікселів посередині знайденого обличчя, адже саме вони мають являти собою шкіру людини. Після цього можна просканувати все зображення чи сусідні фрагменти та знайти пікселі тих же кольорів. У цьому є значний плюс, адже дуже вірогідно, що алгоритм знайде як мінімум частину необхідного фрагменту зображення будь-якої людини, неважливо якого кольору її шкіра, синювата вона чи червонувата. Atronax 13.02.21 в 09:38 |
||
|
На жаль, розпізнавання шкіри з використанням каскадних класифікаторів досить повільне на сучасних мобільних пристроях, тому цей метод може не підходити для мобільних застосунків реального часу. З іншого боку, можна скористатися тим фактом, що, працюючи з мобільними програмами, користувач триматиме камеру навпроти свого обличчя на близькій відстані, а оскільки він тримає камеру у власній руці, яку можна легко перемістити у просторі, можна попросити його розмістити своє обличчя у певній позиції та на визначеній відстані, що дозволить не шукати місцеположення та розмір відповідного фрагменту. Це основа багатьох програм для мобільних пристроїв, в яких користувач розміщує своє обличчя в певній позиції чи, можливо, вручну переміщує точки на екрані, щоб показати, де знаходяться його межі на фотографії. Тому просто намалюємо контур в центрі екрану і вкажемо користувачу, якою має бути позиція і розмір його обличчя. Atronax 13.02.21 в 09:54 |
||
|
Вказуємо користувачу, де розмістити його обличчя Atronax 13.02.21 в 09:54 |
||
|
Як тільки-но завантажиться режим прибульця, намалюємо контур обличчя поверх кадру камери, щоб користувач знав, де розмістити своє обличчя. Намалюємо великий еліпс, що займатиме 70 відсотків висоти зображення з фіксованим відношенням сторін 0.72, щоб обличчя не виявилось занадто вузьким чи широким, на що впливають відповідні характеристики камери: Atronax 13.02.21 в 09:59 |
||
|
// Малюємо кольорове обличчя на чорному фоні. Atronax 13.02.21 в 10:02 |
||
|
Щоб користувач зрозумів, що це контур обличчя, намалюємо також контури двох очей. Замість того, щоб малювати око як єдиний еліпс, зробимо його більш реальним (дивіться наступний малюнок), намалювавши обмежений еліпс для верхньої частини ока і ще один - для нижньої, адже, використовуючи функцію ellipse(), ми маємо змогу визначати початковий та кінцевий кути. Atronax 13.02.21 в 10:10 |
||
|
// Малюємо контури очей як дві дуги на кожне око. Atronax 13.02.21 в 10:11 |
||
|
// Малюємо верхню частину лівого ока. Atronax 13.02.21 в 10:14 |
