Mastering OpenCV with Practical Computer Vision Projects / Опановуюємо OpenCV через практичні проекти комп'ютерного зору: Розділ 1. Ефект мультиплікації та зміна кольору шкіри для Android

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

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

Щоб користувач точно зрозумів, куди слід помістити обличчя, виведемо екран повідомлення!

Atronax 13.02.21 в 10:16

0

// Напишемо текст з ефектом згладжування.
int fontFace = FONT_HERSHEY_COMPLEX;
float fontScale = 1.0f;
int fontThickness = 2;
char *szMsg = "Помістіть ваше обличчя тут";
putText(faceOutline, szMsg, Point(sw * 23/100, sh * 10/100),
fontFace, fontScale, color, fontThickness, CV_AA);

Atronax 13.02.21 в 10:19

0

Маючи контур обличчя, ми можемо розмістити його над зображенням за допомогою альфа змішування та об'єднати мультиплікацію з намальованим контуром:
addWeighted(dst, 1.0, faceOutline, 0.7, 0, dst, CV_8UC3);
Результати цього процесу можна побачити на наступному малюнку, на якому зображається, де користувач має розмістити своє обличчя, щоб нам не довелося розпізнавати його місцеположення:

Atronax 13.02.21 в 10:57

0

Реалізація зміни кольору шкіри
Замість того, щоб розпізнавати колір шкіри та фрагменти з ним, можна використати функцію OpenCV floodFill(), яка діє аналогічно до інструменту заливки кольором у багатьох програмах редагування зображень. Ми знаємо, що фрагменти в центрі екрану повинні мати пікселі шкіри (адже ми просимо користувача розмістити своє обличчя посередині), тому, щоб змінити колір всього обличчя на зелений, ми можемо просто застосувати заливку зеленим кольором на центральному пікселі, що в будь-якому випадку змінить колір як мінімум частини обличчя. Насправді колір, насиченість та яскравість вірогідно відрізнятимуться для різних фрагментів обличчя, тому заливка навряд чи покриє всі пікселі шкіри, якщо поріг не настільки низький, що воно покриє навіть небажані пікселі за межами обличчя. Тому, замість виконання єдиної заливки в центрі зображення, застосуємо її в 6 різних точках, які мають бути пікселями шкіри.

Atronax 13.02.21 в 10:58

0

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

Atronax 13.02.21 в 11:19

0

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

Atronax 13.02.21 в 11:27

0

яскравість від кольору. По-перше, можна використати простір HSV (відтінок, насиченість, яскравість), адже він розмежовує ці характеристики. На жаль, простір HSV надбудовує значення відтінку над червоною компонентою, а оскільки шкіра має здебільшого рожевий колір, нам доведеться працювати з відтінками менше ніж 10 і більше ніж 90 відсотків, адже і ті, й інші червоні. Тому замість цього ми використаємо простір Y'CrCb (варіант YUV, що підтримується OpenCV), тому що він виокремлює яскравість та має єдиний діапазон значень для типового кольору шкіри, а не два. Майте на увазі, що більшість камер, зображень та відео на ділі використовують деякі види просторів YUV до того, як будуть конвертовані в RGB, тому у багатьох випадках ви отримаєте зображення YUV без необхідності конвертувати його власноруч.

Atronax 13.02.21 в 11:30

0

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

Atronax 13.02.21 в 11:54

0

Mat yuv = Mat(smallSize, CV_8UC3);
cvtColor(smallImg, yuv, CV_BGR2YCrCb);

Atronax 13.02.21 в 11:54

0

Ми також маємо зменшити маску меж, щоб вона мала ті ж розміри, що й зображення картини. Є певна складність у роботі з функцією floodFill() бібліотеки OpenCV, а саме, коли відбувається запис маски в окреме зображення, воно повинно також мати границю шириною 1 піксель, тому якщо на вхід подано кадр місткістю WxH пікселів, маска має містити (W+2)x(H+2) пікселів. Але функція floodFill() дозволяє також налаштувати маску межами, які її алгоритм ні в якому разі не перетне. Скористаємось цією особливістю в надії, що заливка не вийде за контури обличчя.

Atronax 13.02.21 в 12:37

0

Таким чином, нам необхідно сформувати два зображення: маску меж з розмірами WxH і таку ж, але розмірами (W+2)x(H+2), бо вона включатиме границі навколо неї. При роботі з бібліотекою можливо мати декілька об'єктів cv::Mat (чи заголовків) що відносяться до тих же даних, чи навіть один об'єкт cv::Mat, що позначає частину основного cv::Mat. Тому замість виділення двох окремих кадрів чи копіювання пікселів меж, виділимо єдине зображення маски включно з границею і створимо додатковий об'єкт cv::Mat розмірами WxH (який лише вказує на зону інтересу в масці заливки без границі). Іншими словами, існує єдиний масив пікселів розмірами (W+2)x(H+2), але два об'єкти cv::Mat, один з яких відповідає цілісному зображенню (W+2)x(H+2), а інше - фрагменту WxH в його центрі:

Atronax 13.02.21 в 13:08

0

int sw = smallSize.width;
int sh = smallSize.height;
Mat mask, maskPlusBorder;
maskPlusBorder = Mat::zeros(sh+2, sw+2, CV_8UC1);
mask = maskPlusBorder(Rect(1,1,sw,sh)); // Маска в maskPlusBorder.
resize(edge, mask, smallSize); // Розміщуємо межі в обох зображеннях.

Atronax 13.02.21 в 13:25

0

Маска меж (показана з лівого боку наступного малюнку) повна гострих і слабких меж; але нам потрібні лише гострі, тому застосуємо бінарний поріг (результатом якого є середнє зображення). Щоб з'єднати прогалини між межами, ми скомбінуємо морфологічні оператори dilate() та erode(), інакше кажучи, виконаємо оператор "замикання", що сформує праву частину малюнку:

Atronax 13.02.21 в 13:29

0

const int EDGES_THRESHOLD = 80;
threshold(mask, mask, EDGES_THRESHOLD, 255, THRESH_BINARY);
dilate(mask, mask, Mat());
erode(mask, mask, Mat());

Atronax 13.02.21 в 13:29

0

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

Atronax 13.02.21 в 13:32

0

int const NUM_SKIN_POINTS = 6;
Point skinPts[NUM_SKIN_POINTS];
skinPts[0] = Point(sw/2, sh/2 - sh/6);
skinPts[1] = Point(sw/2 - sw/11, sh/2 - sh/6);
skinPts[2] = Point(sw/2 + sw/11, sh/2 - sh/6);
skinPts[3] = Point(sw/2, sh/2 + sh/16);
skinPts[4] = Point(sw/2 - sw/9, sh/2 + sh/16);
skinPts[5] = Point(sw/2 + sw/9, sh/2 + sh/16);

Atronax 13.02.21 в 13:32

0

Тепер треба знайти слушні нижні та верхні границі для заливки. Майте на увазі, цей процес виконується в просторі кольорів Y'CrCb, тому по суті ми вирішуємо, наскільки може змінюватися яскравість, червоний та синій компоненти. Яскравість має змінюватися досить сильно, щоб включати тіні, освітлення та відблиск, але кольори повинні залишатися приблизно тими ж:

Atronax 13.02.21 в 13:38

0

const int LOWER_Y = 60;
const int UPPER_Y = 80;
const int LOWER_Cr = 25;
const int UPPER_Cr = 15;
const int LOWER_Cb = 20;
const int UPPER_Cb = 15;
Scalar lowerDiff = Scalar(LOWER_Y, LOWER_Cr, LOWER_Cb);
Scalar upperDiff = Scalar(UPPER_Y, UPPER_Cr, UPPER_Cb);
Ми використаємо floodFill() зі значеннями по замовчуванню, за винятком того, що ми хочемо зберегти результат у зовнішній масці, тому використаємо FLOODFILL_MASK_ONLY:
const int CONNECTED_COMPONENTS = 4; // Щоб заповнити по діагоналі, використаємо 8.
const int flags = CONNECTED_COMPONENTS | FLOODFILL_FIXED_RANGE | FLOODFILL_MASK_ONLY;
Mat edgeMask = mask.clone(); // Збережемо копію маски меж.
// В maskPlusBorder додаємо межі, за які floodFill() не зможе вийти.
for (int i=0; i< NUM_SKIN_POINTS; i++) {
floodFill(yuv, maskPlusBorder, skinPts[i], Scalar(), NULL, lowerDiff, upperDiff, flags);
}

Atronax 13.02.21 в 13:41

0

Ліве зображення наступного малюнку зображає 6 позицій для заливки (показані у вигляді синіх кіл), на правому можна бачити згенеровану маску, у якій шкіра має сірий колір, а межі - білий. Майте на увазі, що зображення з правого боку модифіковане для цієї книги таким чином, щоб пікселі шкіри (зі значенням 1) можна було роздивитися.

Atronax 13.02.21 в 13:46

0

Зображення маски (показане з правого боку попереднього малюнку) відтепер має пікселі з такими значеннями:
255 для меж
1 для шкіри
0 для інших фрагментів
Тим часом edgeMask зберігає лише пікселі меж (255). Тому, щоб отримати тільки пікселі шкіри, можна відняти їх від усієї маски:
mask -= edgeMask;
Тепер зображення маски зберігає лише одиниці для пікселів шкіри та нулі для інших фрагментів. Щоб змінити колір шкіри та яскравість оригінального кадра, можна використати функцію cv::add() для маски шкіри, підсиливши таким чином зелений компонент на BGR зображенні:
int Red = 0;
int Green = 70;
int Blue = 0;
add(smallImgBGR, CV_RGB(Red, Green, Blue), smallImgBGR, mask);

Atronax 13.02.21 в 13:53

0

Далі зображені початковий кадр з лівого боку та прибулець з ефектом мультиплікації з правого, на якому мінімум 6 частин обличчя відтепер зелені! Пам'ятайте, ми зробили шкіру не тільки зеленою, але й більш яскравою (аби бути схожими на прибульця, що світиться у темряві). Якщо потрібно змінити лише колір, без жодних втручань у яскравість, використайте інші методи, наприклад додайте 70 до зеленої компоненти й відніміть те ж значення від червоної та синьої, або конвертуйте зображення в HSV за допомогою cvtColor(src, dst, "CV_BGR2HSV_FULL") та налаштуйте відтінок і насиченість.
От і все! Перевірте програму в різних режимах, поки не будете готові перенести її на смартфон.

Atronax 13.02.21 в 14:04

0

Переносимо настільну версію програми на Android

Atronax 13.02.21 в 14:04

0

Застосунок працює як треба на ПК, тому приступимо до створення версії для Android чи iOS. Подані нижче деталі специфічні для Android, але їх можна використати на iOS, для Apple iPhone, iPad та інших подібних пристроїв. При розробці для Android користуватися OpenCV можна напряму з Java, але результат навряд чи буде настільки ж ефективним, як нативний код C/C++, ще й не дозволить запускати той же код на мобільному. Тому рекомендовано користуватися C/C++ для розробки більшості програм OpenCV для Android (читачі, які бажають користуватися виключно Java, можуть звернути увагу на JavaCV (автор Samuel Audet), доступну за адресою http://code.google.com/p/javacv/, це дасть змогу запускати на ПК той же код, що й на Android).

Atronax 13.02.21 в 14:20

0

Примітка
Android проєкт використовує камеру для живого вводу, тому не спрацює в емуляторі. Він потребує мобільного пристрою з камерою, на основі ОС Android 2.2 (Froyo) чи більш пізньої.
Користувацький інтерфейс має бути запрограмований на Java, але для обробки зображення використаємо той же файл cartoon.cpp, що й для ПК. Щоб мати можливість запускати код С/C++ на Android, необхідна NDK (нативний набір для розробки), основою якого є JNI (нативний інтерфейс Java). Ми створимо JNI обгортку для функції cartoonifyImage(), щоб її потім використати на Android.

Atronax 13.02.21 в 14:43

0

Налаштування Android-проєкту, що використовує OpenCV

Atronax 13.02.21 в 14:45

0

Портування OpenCV на Android кожного року суттєво змінюється, як і метод для доступу до камери, тому книга - не найкращий спосіб дізнатися, як її правильно налаштувати. Читачеві краще знайти інструкції на сторінці http://opencv.org/platforms/android.html, щоб відрегулювати та збудувати нативний (з використанням NDK) Android-застосунок з OpenCV. В бібліотеці є приклад проєкту з назвою Sample3Native, що отримує доступ до камери та виводить модифіковане зображення. Цей приклад корисний як основа застосунку, що буде розроблений далі, тому читачам варто з ним ознайомитися (зараз доступний за адресою http://docs.opencv.org/doc/tutorials/introduction/android_binary_package/android_binary_package_using_with_NDK.html). Потім ми модифікуємо основу так, щоб він зміг оброблювати кадри ефектом мультиплікації та виводити результат.

Atronax 13.02.21 в 15:00

0

Якщо розробка OpenCV програм для Android здається вам складною, наприклад ви отримуєте помилки компіляції чи камера видає чорні кадри, спробуйте знайти рішення на цих сайтах:
1. Уроки роботи з бінарним пакетом Android NDK, що був зазначений раніше.
2. Офіційна група Google, присвячена Android OpenCV (https://groups.google.com/forum/?
fromgroups#!forum/android-opencv).
3. Питання та відповіді на OpenCV (http://answers.opencv.org).
4. Питання та відповіді на StackOverflow (http://stackoverflow.com/questions/tagged/opencv+android).
5. Мережа (наприклад, http://www.google.com).
6. Якщо ви все ще не можете виправити проблему, спробувавши вище зазначені кроки, можна запостити питання з деталями помилки в групі Google, і так далі.

Atronax 13.02.21 в 15:09

0

Формати кольору для обробки зображень на Android

Atronax 13.02.21 в 15:09

0

Під час розробки для ПК ми мали справу з форматом пікселя BGR, адже вхідні дані з камери, зображення чи відеофайлу мали саме цей формат, вихідні, до речі, теж. Але при розробці для мобільних пристроїв доведеться конвертувати нативні формати самостійно.

Atronax 13.02.21 в 15:24

0

Формат кольору для вводу з камери
У коді прикладу jni\jni_part.cpp змінна myuv позначає кольорове зображення у форматі камери Android за замовчуванням: "NV21" YUV420sp. Перша частина - це масив пікселів у відтінках сірого, за яким слідує вдвічі менший, розділений на канали U та V. Тому, якщо потрібно лише отримати доступ до зображення у відтінках сірого, його можна отримати з першої частини напівплоского кадру YUV420sp без жодних конверсій. Але якщо є потреба в кольоровому зображенні (наприклад, BGR чи BGRA), знадобиться конвертувати формат через cvtColor().

Atronax 13.02.21 в 15:36

0

Формат кольору для виводу на екран

Atronax 13.02.21 в 15:38

0

У коді Sample3Native змінна mbgra позначає кольорове зображення, що виводитиметься на пристрої Android, у форматі BGRA. Формат OpenCV за замовчуванням - це BGR (зворотний порядок байтів, аніж при RGB), а BGRA всього лише додає невикористаний байт на кінці пікселя, тобто кожен з них зберігається у вигляді "синій, зелений, червоний, невикористаний". Можна або виконати обробку в форматі BGR, а потім конвертувати BGR в BGRA до того, як вивести його на екран, або відрегулювати код обробки так, щоб він міг підтримувати формат BGRA замість або в доповнення до BGR. Досить часто це легко зробити в OpenCV, багато з його функцій здатні оброблювати BGRA зображення, але треба впевнитися, що ви створюєте зображення з тією ж кількістю каналів, що й вхідне, визначивши за допомогою функції Mat::channels() його значення. Крім цього, якщо у коді ви працюєте з пікселями напряму, доведеться відокремити код обробки 3-канального BGR та 4-канальних зображень BGRA.

Atronax 13.02.21 в 15:55

0

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

Atronax 13.02.21 в 16:02

0

Як зазначено раніше, кадр камери прибуває до коду у форматі YUV420sp, а відбувати має як BGRA. Тому, якщо ми підготуємо об'єкти cv::Mat для вводу та виводу, нам доведеться лише конвертувати YUV420sp в BGRA з використанням cvtColor(). Щоб мати можливість користуватися кодом C/C++ в застосунку Java для Android, потрібні особливі імена функцій JNI, що збігаються з іменами класу та пакету Java, що використає функцію JNI, у такому форматі:
JNIEXPORT <Return> JNICALL Java_<Package>_<Class>_<Function>(JNIEnv* env, jobject, <Args>)

Atronax 13.02.21 в 16:10

0

Почнемо з функції C/C++ ShowPreview(), яку потім використаємо з класу Java CartoonifierView в пакеті Cartoonifier. Додайте її в jni\jni_part.cpp:

Atronax 13.02.21 в 16:35

0

// Показати звичайний кадр камери без модифікацій.
JNIEXPORT void JNICALL
Java_com_Cartoonifier_CartoonifierView_ShowPreview(
JNIEnv* env, jobject,
jint width, jint height, jbyteArray yuv, jintArray bgra)
{
jbyte* _yuv = env->GetByteArrayElements(yuv, 0);
jint* _bgra = env->GetIntArrayElements(bgra, 0);
Mat myuv = Mat(height + height/2, width, CV_8UC1, (uchar *)_yuv);
Mat mbgra = Mat(height, width, CV_8UC4, (uchar *)_bgra);
// Конвертуємо формат камери NV21 "YUV420sp"
// в кольорове зображення Android BGRA.
cvtColor(myuv, mbgra, CV_YUV420sp2BGRA);
// OpenCV відтепер може отримати доступ до / змінювати зображення BGRA "mbgra" ...
env->ReleaseIntArrayElements(bgra, _bgra, 0);
env->ReleaseByteArrayElements(yuv, _yuv, 0);
}

Atronax 13.02.21 в 16:39

0

Хоча код виглядає досить складним, на перший погляд, перші дві стрічки функції лише дають нативний доступ до даних масивів Java, наступні дві будують об'єкти cv::Mat над заданими буферами пікселів (таким чином, вони не розміщують нові зображення, а дозволяють myuv отримати доступ до пікселів в масиві _yuv), а останні дві звільняють нативну прив'язку, яку ми встановили для масивів Java. Єдиною реальною роботою є конвертація формату YUV у BGRA, тому ця функція є лише основою, яку можна використати для створення нових. Тепер розширимо код аналізом та модифікацією BGRA об'єкта cv::Mat до виводу на екран.

Atronax 13.02.21 в 16:51

0

Примітка
Приклад jni\jni_part.cpp в OpenCV v2.4.2 використовує наступний код:
cvtColor(myuv, mbgra, CV_YUV420sp2BGR, 4);
Це схоже на конвертацію в 3-канальний формат BGR (за замовчуванням), але оскільки використано параметр "4", конвертація відбувається в 4-канальний BGRA (формат виводу Android)! Виклик ідентичний до нижче вказаного коду, більш зрозумілого:
cvtColor(myuv, mbgra, CV_YUV420sp2BGRA);
Оскільки маємо вхідне та вихідне зображення у форматі BGRA замість основного BGR бібліотеки OpenCV, залишається два варіанти обробки:

Atronax 13.02.21 в 17:16

0

Конвертувати зображення з формату BGRA в BGR до етапу обробки, виконати алгоритм над форматом BGR, а потім повернути результат в BGRA, яким можна скористатися в Android.
Модифікувати код таким чином, щоб мати змогу оброблювати формат BGRA в доповнення (або замість) BGR і відмовитись від необхідності здійснювати повільні конвертації між ними.
Щоб спростити викладання матеріалу, застосуємо перетворення BGRA в BGR і навпаки замість підтримки обох форматів одночасно. Створюючи програму реального часу, варто додати можливість працювати з 4-канальним BGRA задля потенційного підвищення швидкості виконання. Зробимо ще одну корисну річ, яка прискорить справи: конвертуємо вхід з YUV420sp в BGRA, а потім з BGRA в BGR, що дасть змогу конвертувати YUV420sp в BGR!

Atronax 14.02.21 в 07:17

0

Спершу краще компілювати й перевірити показану раніше функцію ShowPreview() на ПК, щоб мати можливість повернутися у випадку більш пізніх проблем з кодом C/C++. Щоб викликати її з Java, додамо нативне оголошення поряд з Java-версією CartoonifyImage() в нижній частині CartoonifyView.java:
public native void ShowPreview(int width, int height, byte[] yuv, int[] rgba);
Потім зможемо викликати її подібно до прикладу FindFeatures() бібліотеки. Помістіть цей фрагмент всередині функції processFrame() файлу CartoonifierView.java:
ShowPreview(getFrameWidth(), getFrameHeight(), data, rgba);
Збудуйте рішення та запустіть програму на своєму мобільному пристрої, щоб просто побачити кадри з камери в реальному часі.

Atronax 14.02.21 в 07:40

0

Використовуємо код ефекту мультиплікації в застосунку Android NDK
Маємо під'єднати модуль cartoon.cpp, використаний раніше для ПК. Файл jni\Android.mk налаштовує вихідні файли C/C++/Assembly, шляхи до заголовків, нативних бібліотек, та властивості компілятора GCC для проєкту:

Atronax 14.02.21 в 08:42

0

1. Додайте cartoon.cpp (та ImageUtils_0.7.cpp, якщо потрібні інструменти для налагодження) в LOCAL_SRC_FILES, але врахуйте, що вони знаходяться в каталозі desktop замість jni за замовчуванням. Тому включіть це після LOCAL_SRC_FILES := jni_part.cpp:
LOCAL_SRC_FILES += ../../Cartoonifier_Desktop/cartoon.cpp
LOCAL_SRC_FILES += ../../Cartoonifier_Desktop/ImageUtils_0.7.cpp

Atronax 14.02.21 в 08:54

0

2. Додайте шлях для пошуку заголовків, аби NDK зміг знайти cartoon.h:
LOCAL_C_INCLUDES += $(LOCAL_PATH)/../../Cartoonifier_Desktop

Atronax 14.02.21 в 09:05

0

3. У верхній частині файлу jni\jni_part.cpp замініть #include <vector> на:
#include "cartoon.h" // ефект мультиплікації
#include "ImageUtils.h" // опціональні функції для налагодження OpenCV

Atronax 14.02.21 в 09:07

0

4. Додайте у файл JNI-функцію CartoonifyImage(), код обробки ефектом мультиплікації. Почнемо з дублювання створеної раніше функції ShowPreview(), яка лише виводить на екран кадри з камери без модифікацій. Примітьте, що конвертація відбувається з формату YUV420sp в BGR, адже ми не бажаємо працювати із зображеннями BGRA:

Atronax 14.02.21 в 09:17

0

Модифікуємо зображення камери фільтром мультиплікації.
JNIEXPORT void JNICALL
Java_com_Cartoonifier_CartoonifierView_CartoonifyImage(
JNIEnv* env, jobject,
jint width, jint height, jbyteArray yuv, jintArray bgra)
{
// Отримуємо нативний доступ до заданих масивів Java.
jbyte* _yuv = env->GetByteArrayElements(yuv, 0);
jint* _bgra = env->GetIntArrayElements(bgra, 0);
// Формуємо надбудови OpenCV над вхідними та вихідними даними.
Mat myuv(height + height/2, width, CV_8UC1, (uchar *)_yuv);
Mat mbgra(height, width, CV_8UC4, (uchar *)_bgra);
// Конвертуємо формат кольору з напівплоского формату YUV420sp камери

Atronax 14.02.21 в 09:20

0

// у формат OpenCV's за замовчуванням.
Mat mbgr(height, width, CV_8UC3); // Виділяємо буфер для нового зображення.
cvtColor(myuv, mbgr, CV_YUV420sp2BGR);
// OpenCV може змінювати BGR-зображення "mbgr", і повинен
// зберігати результат як BGR-зображення "displayedFrame".
Mat displayedFrame(mbgr.size(), CV_8UC3);
// ТИМЧАСОВО: Показати лише кадри з камери без модифікацій
displayedFrame = mbgr;
// Конвертувати вивід з формату BGR бібліотеки OpenCV у формат
// BGRA ОС Android.
cvtColor(displayedFrame, mbgra, CV_BGR2BGRA);
// Звільняємо нативну прив'язку масивів Java.
env->ReleaseIntArrayElements(bgra, _bgra, 0);
env->ReleaseByteArrayElements(yuv, _yuv, 0);
}

Atronax 14.02.21 в 09:25

0

5. Попередній код не змінює зображення, однак ми бажаємо застосувати фільтр мультиплікації, розроблений раніше у цьому розділі. Тому доєднаємо виклик наявної функції cartoonifyImage() з файлу cartoon.cpp застосунку для ПК. Замініть тимчасову стрічку коду displayedFrame = mbgr на:
cartoonifyImage(mbgr, displayedFrame);

Atronax 14.02.21 в 09:30

0

6. Це все! Збудуйте рішення (Eclipse має скомпілювати код C/C++, використовуючи ndk-build) та запустіть його на мобільному пристрої. Ви маєте отримати робочу версію програми Cartoonifier для Android (на початку розділу є приклад, як має виглядати результат)! Якщо виявляться проблеми з компіляцією чи запуском, поверніться на попередні кроки та виправте помилки (можете ознайомитись з кодом, що йде разом з книжкою). Перевірте наступні кроки, коли впораєтесь з цим.
Огляд програми для Android
Робота програми на мобільному пристрої швидко виявить чотири проблеми:
Вона дуже повільна; потребує багато секунд на кожен кадр! Тому ми маємо лише вивести зображення з камери без змін, а його обробка має відбутися винятково при дотику користувача до екрана.

Atronax 14.02.21 в 10:05

0

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

Atronax 14.02.21 в 10:19

0
Минутку...

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

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