Готовый перевод Mastering OpenCV with Practical Computer Vision Projects / Опановуюємо OpenCV через практичні проекти комп'ютерного зору: Розділ 3. Безмаркерна доповнена реальність
У цьому розділі читачі дізнаються, як створювати стандартні проєкти OpenCV, що діють в режимі реального часу, а також як застосувати метод безмаркерної доповненої реальності, при якому дійсне середовище використовується як вхідні дані замість надрукованих квадратних маркерів. Подальший текст опише дещо з теорії безмаркерної AR та покаже як пристосувати її в корисних програмах.
Далі подано список тем, описаних у даному розділі:
Заснована на маркерах доповнена реальність проти безмаркерної
Використання дескрипторів особливих точок для пошуку конкретних зображень на відео
Оцінка пози шаблона
Інфраструктура застосунку
Підтримка візуалізації OpenGL в OpenCV
Візуалізація доповненої реальності
Демонстрація
Перш ніж почнемо, подамо короткий список необхідних знань та програмного забезпечення:
Базові знання CMake. Це кросплатформова відкрита система, розроблена для побудови, тестування та формування програмних пакетів. Подібно до бібліотеки OpenCV, демонстраційний проєкт для цього розділу також використовує CMake. Його можна завантажити з ресурсу http://www.cmake.org/.
Знання основ програмування з використанням мови C++ також необхідне. Однак, всі складні частини коду застосунку пояснимо детальніше.
Заснована на маркерах доповнена реальність проти безмаркерної
З попереднього розділу ви дізналися, як використовувати особливі зображення, що називаються маркерами, щоб доповнювати реальні сцени. Переваги маркерів такі:
Дешевий алгоритм розпізнавання
Стійкість до змін умов освітлення
Маркери мають і недоліки. Вони такі:
Не працюють, якщо частково перекриті іншими об'єктами
Зображення маркера має бути чорно-білим
Має квадратну форму у більшості випадків (адже її легше розпізнати)
Неестетичний візуальний вигляд маркера
Не має нічого спільного з реальними об'єктами
Таким чином, маркери - це непоганий старт для роботи з доповненою реальністю; але якщо потрібні більші можливості, час переходити з маркерної до безмаркерної AR. Це техніка розпізнавання об'єктів, що існують в реальному світі. Декілька прикладів цілей для цього виду: обкладинки журналів, логотипи компаній, іграшки тощо. Взагалі кажучи, будь-який об'єкт, що має достатньо описових характеристик та відрізняється від решти сцени може бути ціллю для доповненої реальності без використання маркерів.
Переваги безмаркерного підходу такі:
Можна використати для розпізнавання реальних об'єктів
Працює навіть якщо цільовий об'єкт частково перекритий
Може мати різноманітну форму та текстуру (крім повністю заповнених чи градієнтних)
Безмаркерні системи AR здатні використовувати зображення та об'єкти, щоб розміщувати камеру в тривимірному просторі та формувати вражаючі ефекти на поверхні кадру. Їхнім ядром є алгоритми розпізнавання зображення та пошуку об'єктів. У противагу маркерам, чиї форма та внутрішня структура відома, реальні об'єкти не можна визначити таким чином. До того ж, об'єкти можуть мати складну форму, тому потребують модифікованих алгоритмів оцінки пози для пошуку коректних 3D перетворень.
Примітка
Щоб зрозуміти ідею безмаркерної доповненої реальності, в якості цілі використаємо плоске зображення. Об'єкти зі складними формами не будуть враховатися. Їх використання обговоримо пізніше у даному розділі.
Безмаркерна доповнена реальність потребує складних обчислень на процесорі, тому мобільний пристрій часто не здатний підтримувати прийнятний рівень FPS. У нашому випадку цільовими машинами є стаціонарні, як-от ПК чи Mac. Саме тому потрібна кросплатформова система побудови. У розділі використано CMake.
Використовуємо дескриптори особливих точок для пошуку конкретних зображень на відео
Розпізнавання зображення - це техніка комп'ютерного зору, що дозволяє знайти вхідний результат для певного растрового шаблону. Наш алгоритм повинен розпізнавати, навіть якщо змінені його масштаб, поворот чи він має відмінну від оригінального зображення яскравість.
Як порівняти шаблонне зображення з іншими? Оскільки на нього можуть впливати перспективні перетворення, очевидно, не можна напряму порівнювати пікселі шаблону та тестового зображення. У подібних випадках зарадять особливі точки та дескриптори. Не існує універсального, точного визначення, чим саме є особлива точка. Воно часто відрізняється залежно від цілі та типу застосунку. Звичайно, особливою точкою позначають "цікаву" частину зображення, а їх набір використовують як початкові дані для багатьох алгоритмів комп'ютерного зору. У цьому розділі скористаємось терміном "особлива точка", згідно з яким вона є частиною зображення з певним центром, радіусом та орієнтацією. Кожен алгоритм розпізнавання намається знайти ті ж особливі точки незалежно від застосованих перспективних перетворень.
Виокремлюємо особливі точки
Розпізнавання особливих точок - це метод виявлення зон інтересу на вхідному зображенні. Існує чимало алгоритмів, які призначені для пошуку меж, кутів чи скупчень. У даному випадку ми зацікавлені в розпізнаванні кутів, виокремлення яких потребує аналізу меж зображення. Такий алгоритм шукає швидкі зміни на градієнті зображення, а для цього, у більшості випадків, потрібно обчислити екстремуми першої похідної градієнтів у напрямках осей X та Y.
Орієнтацію особливої точки звичайно визначають як напрям домінатного градієнта зображення у певній зоні. Якщо його повертають або змінюють масштаб, ця характеристика повторно обчислюється алгоритмом розпізнавання особливих точок. Таким чином, їх орієнтація залишиться тією ж, не зважаючи на поворот зображення. Такі точки називають інваріантними відносно повороту.
Також варто врахувати такі важливі факти. Деякі алгоритми працюють з особливими точками фіксованого розміру, у той час як інші обчислюють оптимальний для кожної окремо. Знаючи розмір, ми зможемо знаходити ті ж точки на збільшених чи зменшених кадрах, що робить їх інваріантними відносно зміни масштабу вхідного зображення.
OpenCV має декілька алгоритмів розпізнавання особливих точок. Усі вони походять від базового класу cv::FeatureDetector. Створити об'єкт для розпізнавання можна двома шляхами:
Явно викликати конструктор певного виду детектора:
cv::Ptr<cv::FeatureDetector> detector =
cv::Ptr<cv::FeatureDetector>(new cv::SurfFeatureDetector());
Чи створити детектор, вказавши назву алгоритму:
cv::Ptr<cv::FeatureDetector> detector =
cv::FeatureDetector::create("SURF");
Обидва методи мають свої переваги, тому оберіть той, що підходить саме вам. Явне створення екземпляра класу дозволяє передати додаткові аргументи конструктуру детектора особливих точок, а створення з використанням назви полегшує зміну алгоритму в процесі роботи програми.
Щоб розпізнати особливі точки, маємо викликати наступний метод:
std::vector<cv::KeyPoint> keypoints;
detector->detect(image, keypoints);
Знайдені особливі точки поміщуються у контейнері keypoints. Кожна з них описана центром, радіусом, кутом,
рейтингом та корелює з "якістю" та "силою" особливої точки.
Кожен алгоритм розпізнавання має власний спосіб обчислення рейтингу, тому цю характеристику особливих точок можна порівняти між собою.
Примітка
Засновані на кутах детектори особливих точок використовують зображення у відтінках сірого. Алгоритми виокремлення дескрипторів також працюють з ними. Звісно, і ті, й інші можуть здійснити зміну простору кольорів вручну. Але в такому разі процес відбудеться двічі. Можна покращити продуктивність, зробивши явне перетворення кольорів у відтінки сірого та використавши це для пошуку особливих точок та дескрипторів.
Кращих результатів у розпізнаванні шаблонів можна досягнути, якщо детектор вирахує орієнтацію та розмір ключових точок. Це зробить їх інваріантними відносно повороту та змін масштабу. Найвідоміші й найточніші методи розпізнавання особливих точок добре відомі, вони використовуються в алгоритмах SIFT та SURF.
На жаль, вони запатентовані, тобто не безкоштовні для комерційного використання. Однак, в OpenCV доступна реалізація, тому можна вільно використати їх для особистих проєктів. Але існують непогані безкоштовні замінники. Можна застосувати алгоритми ORB чи FREAK. Детектор ORB - це модифікований FAST, оригінальна версія якого надзвичайно швидка, але не обчислює орієнтацію чи розміри ключової точки. На щастя, алгоритм ORB оцінює орієнтацію, але розмір все ще залишається фіксованим. З наступних параграфів ви дізнаєтесь непогані дешеві способи виправити це. Але спершу дозвольте пояснити, чому особливі точки такі важливі для розпізнавання зображень.
Маючи справу із зображеннями, глибина кольору яких складає 24 біти на піксель, для роздільної здатності 640x480 отримаємо 912 кб даних. Як знайти зображення шаблону в реальному світі? Попіксельний пошук співпадінь займає надто багато часу, а ще ж маємо врахувати поворот та скалювання. Це явно не вихід. Проблему вирішують особливі точки. Розпізнавши їх, можемо бути певні, що вони описують частини зображення з корисними даними (так відбувається тому, що засновані на кутах детектори повертають межі, кути та інші гострі фігури). Тому, щоб знайти відповідності між двома кадрами, залишилось лише перевірити ключові точки на співпадіння.
З визначеного фрагмента зображення видобуваємо вектор під назвою дескриптор. Це форма представлення особливої точки. Отримати його можна різними способами, кожен з яких має свої переваги та недоліки. Наприклад, алгоритми SIFT та SURF займають багато процесорного часу, але забезпечують точними дескрипторами з непоганою виразністю. У нашому проєкті застосовано алгоритм пошуку дескрипторів ORB, адже ми можемо використати його також як детектор особливих точок.
Примітка
Завжди корисно застосовувати детектор особливих точок і видобувач дескрипторів з того ж алгоритму, оскільки вони спроєктовані для ідеальної спільної роботи.
Дескриптор особливої точки представлений вектором фіксованого розміру (16 чи більше елементів). Нехай наше зображення має роздільну здатність 640x480 пікселів та 1500 ключових точок. Тоді для виконання алгоритму SURF знадобиться 1500*16*sizeof(float) = 96 кб даних. Це вдесятеро менше, аніж розмір початкових даних. Також, з дескрипторами набагато легше працювати, аніж з растровими зображеннями. Для двох дескрипторів особливих точок представимо рейтинг схожості - метрику, яка визначатиме подібність двох векторів. Зазвичай, це норма L2 чи відстань Хеммінга (залежить від виду використаного дескриптора).
Алгоритми пошуку дескрипторів особливих точок походять від базового класу cv::DescriptorExtractor. Подібним чином до методів розпізнавання, вони можуть бути створені з визначенням назви або явним викликом конструктора.
Визначаємо об'єкт шаблона
Для опису об'єкта шаблона використаємо клас Pattern, який вміщуватиме тренувальне зображення, список особливих точок та здобутих дескрипторів, а також 2D-3D відповідності для початкової позиції шаблона:
/**
* Зберігаємо дані зображення та знайдені дескриптори цільового шаблона
*/
struct Pattern
{
cv::Size size;
cv::Mat data;
std::vector<cv::KeyPoint> keypoints;
cv::Mat descriptors;
std::vector<cv::Point2f> points2d;
std::vector<cv::Point3f> points3d;
};
Перевіряємо особливі точки на співпадіння
Процес знаходження покадрових відповідностей можна сформулювати як пошук найближчого сусіда серед одного набору дескрипторів для кожного елемента з іншого набору. Це так звана процедура "пошуку співпадінь". У бібліотеці OpenCV існує два головні алгоритми:
Повний перебір (cv::BFMatcher)
Апроксимація найближчих сусідів Flann (cv::FlannBasedMatcher)
Перший спосіб шукає кожен дескриптор у першому наборі та найближчий до нього в другому, перевіряючи кожну пару по черзі (вичерпний пошук), у той час як cv::FlannBasedMatcher для пошуку відповідностей застосовує алгоритм швидкої апроксимації найближчого сусіда (для цього використовується окрема бібліотека).
Результатом процесу є список відповідностей між двома наборами дескрипторів. Перший частіше за все називають тренувальним, адже від відповідає зображенню шаблона. Другий відомий під назвою набору запиту, оскільки він належить зображенню, на якому здійснюється пошук об'єкта. Чим більше коректних співпадінь знайдемо (існує більше відповідностей між шаблоном та зображенням), тим більше шансів, що потрібний об'єкт присутній на кадрі.
Щоб прискорити процедуру, можна заздалегідь натренувати пошук співпадінь за допомогою функції match. Даний етап можна використати для оптимізації продуктивності cv::FlannBasedMatcher. З цією метою тренувальний клас сформує дерева індексів для дескрипторів. Це сприятиме більш швидкому пошуку співпадінь для великих наборів даних (наприклад, якщо потрібно порівняти сотні зображень). Для cv::BFMatcher відповідний клас не робить нічого, адже даних для обробки не існує; він всього лише зберігає тренувальні дескриптори у внутрішніх полях.
PatternDetector.cpp
Наступний блок тренує пошук співпадінь з використанням шаблонного зображення:
void PatternDetector::train(const Pattern& pattern)
{
// Зберігаємо об'єкт шаблона
m_pattern = pattern;
// Програмний інтерфейс cv::DescriptorMatcher має нюанси
// Спершу очищаємо старі тренувальні дані:
m_matcher->clear();
// Потім додаємо вектор дескрипторів
// (кожна матриця описує одне зображення).
// Це дозволяє здійснювати пошук на декількох кадрах:
std::vector<cv::Mat> descriptors(1);
descriptors[0] = pattern.descriptors.clone();
m_matcher->add(descriptors);
// Після завантаження даних здійснюємо власне тренування:
m_matcher->train();
}
Щоб знайти співпадіння серед дескрипторів запиту, застосуємо один з наступних методів cv::DescriptorMatcher:
Пошук простого списку найкращих відповідностей:
void match(const Mat& queryDescriptors, vector<DMatch>&
matches,
const vector<Mat>& masks=vector<Mat>() );
Виокремлення K найближчих співпадінь з кожним дескриптором:
void knnMatch(const Mat& queryDescriptors,
vector<vector<DMatch> >& matches, int k, const vector<Mat>&
masks=vector<Mat>(),bool compactResult=false );
Визначення відповідностей, чиї відстані не більші за вказану:
void radiusMatch(const Mat& queryDescriptors,
vector<vector<DMatch> >& matches, maxDistance, const
vector<Mat>& masks=vector<Mat>(), bool compactResult=false );
Видаляємо зайві точки
Під час етапу порівняння можуть траплятися невірні співпадіння. Це нормально. Існує два типи помилок:
Хибна поява, при якій відповідності визначені неправильно.
Хибна відсутність, якщо особливі точки видно на обох зображеннях, але вони не знаходяться.
Хибна відсутність співпадінь - це, звичайно, суттєвий недолік. Але рішення проблеми немає, адже алгоритм їх не врахував. Нашою метою є лише мінімізація кількості хибних появ. Щоб видалити невірні відповідності, можна скористатися технікою перехресної перевірки. Ідея полягає в порівнянні тренувальних дескрипторів з набором запиту та навпаки. У цьому разі результатом будуть виключно спільні співпадіння. Така техніка зазвичай формує кращі результати з мінімальною кількістю зайвих точок у випадку достатньої кількості співпадінь.
Фільтруємо перехресні співпадіння
Ця процедура доступна в класі cv::BFMatcher. Щоб під'єднати перехресну перевірку, створимо об'єкт cv::BFMatcher з другим аргументом true:
cv::Ptr<cv::DescriptorMatcher> matcher(new
cv::BFMatcher(cv::NORM_HAMMING, true));
Результат використання перехресних перевірок показаний на наступному скріншоті:
Перевірка відношень
Іншою відомою технікою видалення зайвих точок є перевірка відношень. У цьому випадку здійснюємо пошук KNN-співпадінь спершу для K=2. Для кожного з них повертається два найближчі дескриптори, причому лише за умови, що відношення відстані між першим та другим співпадінням достатньо велике (зазвичай поріг рівний близько 2).
PatternDetector.cpp
Код здійснює пошук співпадінь дескрипторів шляхом перевірки відношень:
void PatternDetector::getMatches(const cv::Mat& queryDescriptors,
std::vector<cv::DMatch>& matches)
{
matches.clear();
if (enableRatioTest)
{
// Щоб уникнути значення NaN для найкращого співпадіння з
// нульовою відстанню, скористаємось оберненим відношенням.
const float minRatio = 1.f / 1.5f;
// функція KNN поверне два найближчих
// співпадіння для кожного дескриптора запиту
m_matcher->knnMatch(queryDescriptors, m_knnMatches, 2);
for (size_t i=0; i<m_knnMatches.size(); i++)
{
const cv::DMatch& bestMatch = m_knnMatches[i][0];
const cv::DMatch& betterMatch = m_knnMatches[i][1];
float distanceRatio = bestMatch.distance /
betterMatch.distance;
// Повернемо лише ті, для яких відношення відстані
// між найближчими співпадіннями більше за 1.5
// (критерій розбіжності)
if (distanceRatio < minRatio)
{
matches.push_back(bestMatch);
}
}
}
else
{
// Виконуємо звичайну перевірку
m_matcher->match(queryDescriptors, matches);
}
}
Тест відношень здатний видалити практично всі зайві точки. Але в деяких випадках хибні появи можуть проходити його. У наступній частині розділу покажемо, як видалити їх та залишити лише коректні співпадіння.
Оцінюємо гомографію
Щоб зробити алгоритм ще більш ефективним, можемо відфільтрувати зайві точки шляхом використання методу консенсусу випадкової вибірки RANSAC. Оскільки ми працюємо із зображенням (плоским об'єктом) та вважаємо, що воно суцільне, можна шукати перетворення гомографії між особливими точками шаблону, з одного боку, та зображення запиту з іншого. Їх метою є перенесення точок шаблону в координатну систему останнього. Для цього скористаємось функцією cv::findHomography, що застосовує RANSAC для пошуку найкращої матриці гомографії методом випробування підмножин вхідних точок. Як побічний ефект, функціія відмічає кожну відповідність як ключову чи зайву точку, залежно від похибки репроєкції для обчисленої матриці.
PatternDetector.cpp
Наступний код використовує обчислення матриці гомографії з використанням алгоритму RANSAC для відфільтрування геометрично невірних співпадінь:
bool PatternDetector::refineMatchesWithHomography
(
const std::vector<cv::KeyPoint>& queryKeypoints,
const std::vector<cv::KeyPoint>& trainKeypoints,
float reprojectionThreshold,
std::vector<cv::DMatch>& matches,
cv::Mat& homography
)
{
const int minNumberMatchesAllowed = 8;
if (matches.size() < minNumberMatchesAllowed)
return false;
// Підготуємо дані для cv::findHomography
std::vector<cv::Point2f> srcPoints(matches.size());
std::vector<cv::Point2f> dstPoints(matches.size());
for (size_t i = 0; i < matches.size(); i++)
{
srcPoints[i] = trainKeypoints[matches[i].trainIdx].pt;
dstPoints[i] = queryKeypoints[matches[i].queryIdx].pt;
}
/ Знайдемо матрицю гомографії та маску внутрішніх точок
std::vector<unsigned char> inliersMask(srcPoints.size());
homography = cv::findHomography(srcPoints,
dstPoints,
CV_FM_RANSAC,
reprojectionThreshold,
inliersMask);
std::vector<cv::DMatch> inliers;
for (size_t i=0; i<inliersMask.size(); i++)
{
if (inliersMask[i])
inliers.push_back(matches[i]);
}
matches.swap(inliers);
return matches.size() > minNumberMatchesAllowed;
}
Це візуалізація співпадінь, оброблених з використанням даної техніки:
Крок пошуку гомографії важливий, адже отримані перетворення є ключем для пошуку положення шаблона в зображенні запиту.
Уточнюємо гомографію
Коли ми знаходимо перетворення гомографії, вже наявні всі дані, необхідні для пошуку положення точок у тривимірному просторі. Однак, можна значно покращити результат, знайшовши більш точні кути шаблона. Для цього деформуємо вхідний кадр з використанням обрахованої гомографії, щоб отримати знайдений шаблон. Результат має бути досить близьким до вихідного тренувального зображення. Уточнення гомографії допоможе знайти ефективніші перетворення.
Далі отримаємо інші гомографію та набір внутрішніх точок. Кращим результатом буде матриця, що дорівнює добутку першої (H1) та другої (H2).
PatternDetector.cpp
Наступний блок коду описує фінальну версію процедури розпізнавання шаблона:
bool PatternDetector::findPattern(const cv::Mat& image,
PatternTrackingInfo& info)
{
// Конвертуємо вхідне зображення у відтінки сірого
getGray(image, m_grayImg);
// Видобуваємо особливі точки
extractFeatures(m_grayImg, m_queryKeypoints, m_queryDescriptors);
// Знаходимо співпадіння з поточним шаблоном
getMatches(m_queryDescriptors, m_matches);
// Знаходимо перетворення гомографії та розпізнаємо якісні співпадіння
bool homographyFound = refineMatchesWithHomography(
m_queryKeypoints,
m_pattern.keypoints,
homographyReprojectionThreshold,
m_matches,
m_roughHomography);
if (homographyFound)
{
// Якщо уточнення гомографії увімкнено,
// покращуємо знайдені перетворення
if (enableHomographyRefinement)
{
// Деформуємо зображення знайденою гомографією
cv::warpPerspective(m_grayImg, m_warpedImg,
cv::warpPerspective(m_grayImg, m_warpedImg,
m_roughHomography, m_pattern.size, cv::WARP_INVERSE_MAP |
cv::INTER_CUBIC);
// Отримуємо оброблені співпадіння:
std::vector<cv::KeyPoint> warpedKeypoints;
std::vector<cv::DMatch> refinedMatches;
// Розпізнаємо особливі точки на деформованому зображенні
extractFeatures(m_warpedImg, warpedKeypoints,
m_queryDescriptors);
// Шукаємо співпадіння з шаблоном
getMatches(m_queryDescriptors, refinedMatches);
// Обчислюємо нову уточнену гомографію
homographyFound = refineMatchesWithHomography(
warpedKeypoints,
m_pattern.keypoints,
homographyReprojectionThreshold,
refinedMatches,
m_refinedHomography);
// Отримуємо результат як добуток матриць
// уточненої та грубої гомографій:
info.homography = m_roughHomography * m_refinedHomography;
// Трансформуємо контур з використанням точної гомографії
cv::perspectiveTransform(m_pattern.points2d,
info.points2d, info.homography);
}
else
{
info.homography = m_roughHomography;
// Трансформуємо контур з використанням грубої гомографії
cv::perspectiveTransform(m_pattern.points2d,
info.points2d, m_roughHomography);
}
}
return homographyFound;
}
Якщо після етапів видалення зайвих точок кількість співпадінь все ще досить велика (як мінімум 25 відсотків особливих точок шаблона мають відповідності із вхідним зображенням), можна бути певними, що шаблон розміщений правильно. Якщо це так, переходимо на наступний етап - оцінку 3D пози шаблона відносно камери.
З'єднуємо все докупи
Для зберігання екземплярів детектора особливих точок, видобувача дескрипторів та алгоритмів пошуку співпадінь використаємо клас PatternMatcher, що інкапсулюватиме ці дані. Він керуватиме алгоритмами пошуку особливих точок та дескрипторів, логікою порівняння та налаштуваннями, що контролюють процес розпізнавання.
Клас пропонує методи обчислення всіх необхідних даних для формування шаблону із вказаного зображення:
void PatternDetector::computePatternFromImage(const cv::Mat& image,
Pattern& pattern);
Цей метод шукає особливі точки на вхідному зображенні та видобуває дескриптори, використовуючи налаштовані алгоритми детектора та екстрактора, та формує шаблон для більш пізнього використання. Коли його знайдено, можемо натренувати детектор викликом методу:
void PatternDetector::train(const Pattern& pattern)
Аргументом функції є поточний цільовий шаблон, який має бути знайденим. До того ж, вона тренує механізм пошуку співпадінь набором дескрипторів. Викликавши метод, ми вже готові знайти тренувальне зображення. Розпізнавання шаблона здійснюється в останній відкритій функції findPattern. Цей метод інкапсулює процедуру як описано раніше, включно з розпізнаванням, видобуванням дескрипторів та пошуком співпадінь з фільтрацією зайвих точок.
Підіб'ємо підсумки коротким списком виконаних кроків:
1. Конвертували вхідне зображення у відтінки сірого.
2. Розпізнали особливі точки на кадрі запиту з використанням відповідного алгоритму.
3. Видобули дескриптори із вхідного зображення для знайдених особливих точок.
4. Порівняли дескриптори зображення з дескрипторами шаблона.
5. Виконали перехресні перевірки та тест відношень для видалення зайвих точок.
6. Знайшли перетворення гомографії, використовуючи внутрішні співпадіння.
7. Уточнили гомографію, деформувавши зображення запиту гомографією з попереднього кроку.
8. Знайшли точну гомографію як результат добутку грубої та уточненої.
9. Трансформували кути шаблона в координатну систему зображення, щоб отримати положення шаблона на вхідному кадру.
Оцінюємо позу шаблона
Цей процес виконується подібно до оцінки пози маркера з попереднього розділу. Як звичайно, для визначення зовнішніх параметрів камери нам знадобляться 2D-3D відповідності. Чотири тривимірні точки мають відповідати кутам одиничного прямокутника, що лежить в площині XY (вісь Z напрямлена вверх), а двовимірні - кутам растрового зображення.
PatternDetector.cpp
Процедура buildPatternFromImage створює об'єкт Pattern із вхідного зображення таким чином:
void PatternDetector::buildPatternFromImage(const cv::Mat& image,
Pattern& pattern) const
{
int numImages = 4;
float step = sqrtf(2.0f);
// Зберігаємо початкове зображення в структурі шаблона
pattern.size = cv::Size(image.cols, image.rows);
pattern.frame = image.clone();
getGray(image, pattern.grayImg);
// Будуємо 2d та 3d контури (3d контур лежить в площині XY,
// оскільки він плоский)
pattern.points2d.resize(4);
pattern.points3d.resize(4);
// Розміри зображення
const float w = image.cols;
const float h = image.rows;
// Нормалізовані розміри:
const float maxSize = std::max(w,h);
const float unitW = w / maxSize;
const float unitH = h / maxSize;
pattern.points2d[0] = cv::Point2f(0,0);
pattern.points2d[1] = cv::Point2f(w,0);
pattern.points2d[2] = cv::Point2f(w,h);
pattern.points2d[3] = cv::Point2f(0,h);
pattern.points3d[0] = cv::Point3f(-unitW, -unitH, 0);
pattern.points3d[1] = cv::Point3f( unitW, -unitH, 0);
pattern.points3d[2] = cv::Point3f( unitW, unitH, 0);
pattern.points3d[3] = cv::Point3f(-unitW, unitH, 0);
extractFeatures(pattern.grayImg, pattern.keypoints,
pattern.descriptors);
}
Дана конфігурація кутів вигідна, адже початок системи координат знаходитиметься прямо в його центрі місцеположення шаблона, що лежить в площині XY, а вісь Z при цьому напрямлена в бік камери.
Отримуємо матрицю внутрішніх характеристик камери
Внутрішні параметри камери можна вирахувати за допомогою програми camera_cailbration.exe, що включена до дистрибутивного пакету бібліотеки OpenCV. За рахунок обробки шаблонних зображень вона визначить властивості лінзи, серед яких фокусна відстань, кардинальна точка та коефіцієнти викривлення. Скажімо, маємо набір з восьми зображень для калібрування, що зняті з різних точок зору:
У такому разі команда для здійснення калібрування виглядатиме таким чином:
imagelist_creator imagelist.yaml *.png
calibration -w 9 -h 6 -o camera_intrinsic.yaml imagelist.yaml
Перша команда створить список зображень PNG поточної теки у форматі YAML, який інструмент для калібрування очікує отримати як вхідні дані. Можна також вказати точні назви файлів, наприклад img1.png, img2.png чи img3.png. Згенерований файл imagelist.yaml потім передається застосунку для калібрування. Крім цього, інструмент може отримувати зображення зі звичайної вебкамери.
Ми визначаємо розміри шаблона калібрування, а також вхідні та вихідні файли для отриманих даних.
Після завершення калібрування файл формату YAML матиме наступний вигляд:
%YAML:1.0
calibration_time: "06/12/12 11:17:56"
image_width: 640
image_height: 480
board_width: 9
board_height: 6
square_size: 1.
flags: 0
camera_matrix: !!opencv-matrix
rows: 3
cols: 3
dt: d
data: [ 5.2658037684199849e+002, 0., 3.1841744018680112e+002, 0.,
5.2465577209994706e+002, 2.0296659047014398e+002, 0., 0., 1. ]
distortion_coefficients: !!opencv-matrix
rows: 5
cols: 1
dt: d
data: [ 7.3253671786835686e-002, -8.6143199924308911e-002,
-2.0800255026966759e-002, -6.8004894417795971e-004,
-1.7750733073535208e-001 ]
avg_reprojection_error: 3.6539552933501085e-001
У першу чергу нас цікавить camera_matrix, що описує матрицю 3х3 внутрішніх характеристик. Вона має наступний вигляд:
Нас цікавлять чотири компоненти: fx, fy, cx та cy. Використавши ці дані, можемо створити екземпляр об'єкта для калібрування камери таким чином:
CameraCalibration calibration(526.58037684199849e,
524.65577209994706e, 318.41744018680112, 202.96659047014398)
Без коректного калібрування камери неможливо створити доповнену реальність з природнім виглядом. Знайдені перетворення перспективи відрізнятимуться від трансформацій камери. Це змусить доповнені об'єкти виглядати так, немовби вони знаходяться надто близько чи, навпаки, далеко. Далі знаходиться приклад скріншоту зі штучно зміненими параметрами калібрування:
Вочевидь, перспективний погляд на куб відрізняється від решти сцени.
Щоб оцінити позу шаблона, скористаємось функцією cv::solvePnP бібліотеки OpenCV для пошуку перспективи й позиції. Вірогідно, ви вже знайомі з нею, адже ми застосовували процес для маркерної доповненої реальності. Нам необхідні координати кутів шаблона на поточному зображенні та відповідні 3D-координати, визначені раніше.
Примітка
Функція cv::solvePnP може працювати більш ніж з 4 точками. Крім цього, вона відіграє ключову роль у створенні доповненої реальності з шаблонами складної форми. Ідея залишається тією ж - достатньо визначити 3D структуру шаблона та знайти відповідні 2D точки. Звісно, пошук гомографії в цьому разі не застосовується.
Розглянемо тривимірні точки тренувального об'єкта шаблона та відповідні проєкції у двовимірному просторі зі структури PatternTrackingInfo; калібрування камери зберігається в приватних полях класу PatternDetector.
Pattern.cpp
Оцінюємо положення шаблона в тривимірному просторі за рахунок функції computePose:
void PatternTrackingInfo::computePose(const Pattern& pattern, const
CameraCalibration& calibration)
{
cv::Mat camMatrix, distCoeff;
cv::Mat(3,3, CV_32F, const_cast<float*>
(&calibration.getIntrinsic().data[0])).copyTo(camMatrix);
cv::Mat(4,1, CV_32F, const_cast<float*>
(&calibration.getDistorsion().data[0])).copyTo(distCoeff);
cv::Mat Rvec;
cv::Mat_<float> Tvec;
cv::Mat raux,taux;
cv::solvePnP(pattern.points3d, points2d, camMatrix,
distCoeff,raux,taux);
raux.convertTo(Rvec,CV_32F);
taux.convertTo(Tvec ,CV_32F);
cv::Mat_<float> rotMat(3,3);
cv::Rodrigues(Rvec, rotMat);
// Копіюємо до матриці перетворень
pose3d = Transformation();
for (int col=0; col<3; col++)
{
for (int row=0; row<3; row++)
{
pose3d.r().mat[row][col] = rotMat(row,col);
// Копіюємо компоненту повороту
}
pose3d.t().data[col] = Tvec(col);
// Копіюємо компоненту перенесення
}
// Оскільки solvePnP знаходить позу камери відносно маркера,
// щоб отримати позу маркера, інвертуємо її.
pose3d = pose3d.getInverted();
}
Інфраструктура застосунку
Дотепер ми познайомились з процесом розпізнавання шаблона та оцінки його позиції відносно камери. Настав час поєднати ці алгоритми в готовий застосунок. Ця частина призначена пояснити процес використання OpenCV для захоплення відео з вебкамери та створення контексту для 3D візуалізації.
Оскільки ми бажаємо пояснити, як використати ключові особливості безмаркерної AR, створимо простий застосунок для командної стрічки, що зможе розпізнавати будь-які шаблони на послідовності відеокадрів чи звичайних зображеннях.
Для зберігання логіки обробки зображення та посередніх даних використаємо клас ARPipeline. Це базовий об'єкт, що включає всі компоненти, необхідні для створення доповненої реальності, та здійснює всі процедури обробки вхідних кадрів. Далі знаходиться UML-діаграма класу ARPipeline та його елементи:
Він складається з:
Об'єкта калібрування камери
Екземпляра детектора
Тренованого об'єкта шаблона
Посередніх даних для його відслідковування.
ARPipeline.hpp
Наступний код оголошує клас ARPipeline:
class ARPipeline
{
public:
ARPipeline(const cv::Mat& patternImage, const CameraCalibration&
calibration);
bool processFrame(const cv::Mat& inputFrame);
const Transformation& getPatternLocation() const;
private:
CameraCalibration m_calibration;
Pattern m_pattern;
PatternTrackingInfo m_patternInfo;
PatternDetector m_patternDetector;
};
У конструкторі ARPipeline налаштовується об'єкт шаблона, а також у приватних полях зберігаються дані калібрування. Функція processFrame реалізує алгоритм розпізнавання та процедуру оцінки пози. Повернене значення при цьому вказує на успіх процесу. Знайдену позу шаблона можна отримати, викликавши функцію getPatternLocation.
ARPipeline.cpp
Наступний код відноситься до реалізації класу ARPipeline:
ARPipeline::ARPipeline(const cv::Mat& patternImage, const
CameraCalibration& calibration)
: m_calibration(calibration)
{
m_patternDetector.buildPatternFromImage (patternImage, m_pattern);
m_patternDetector.train(m_pattern);
}
bool ARPipeline::processFrame(const cv::Mat& inputFrame)
{
bool patternFound = m_patternDetector.findPattern(inputFrame,
m_patternInfo);
if (patternFound)
{
m_patternInfo.computePose(m_pattern, m_calibration);
}
return patternFound;
}
const Transformation& ARPipeline::getPatternLocation() const
{
return m_patternInfo.pose3d;
}
Під'єднуємо підтримку 3D візуалізації в OpenCV
Як і в попередньому розділі, для роботи з 3D застосуємо OpenGL. Але на відміну від середовища iOS, для якого ми мали дотримуватись певних вимог до архітектури, у цьому разі отримаємо більше свободи. На платформах Windows чи Mac можна обирати серед доступних двигунів. Далі в розділі навчимось створювати кросплатформову 3D візуалізацію з використанням OpenCV. Починаючи з версії 2.4.2, бібліотека підтримує спільну роботу з OpenGL. Це значить, що можна легко візуалізувати будь-який 3D контент в OpenCV.
Щоб налаштувати вікно OpenGL, спершу потрібно побудувати OpenCV з підтримкою цієї бібліотеки. Інакше у разі використання необхідних функцій формуються вийнятки. Щоб здобути підтримку бібліотеки, будувати необхідно з активованим флагом ENABLE_OPENGL=YES.
Примітка
Що ж до поточної версії (OpenCV 2.4.2), підтримка OpenGL відімкнена за замовчуванням. Не існує гарантії, але це може змінитися в майбутньому. В такому разі не існуватиме потреби будувати OpenCV власноруч.
Щоб мати можливість використовувати вікно OpenGL в OpenCV, здійснимо наступні кроки:
Клонуємо репозиторій OpenCV, розміщений на https://github.com/Itseez/opencv. Для цього необхідні або інструменти git для командної стрічки, або застосунок GitHub.
Обираємо необхідні параметри OpenCV та формуємо робочий простір для IDE. Для виконання цього кроку потрібен застосунок CMake, який можна завантажити з ресурсу: http://www.cmake.org/cmake/resources/software.html.
Щоб налаштувати OpenCV, можна або виконати команду CMake таким чином (у теці, де має бути розміщеним згенерований проєкт):
cmake -D ENABLE_OPENGL=YES <шлях до теки з вихідними файлами OpenCV>
Або, якщо ви потребуєте графічний інтерфейс, використайте CMake-GUI для більш зручної конфігурації проєкта:
Створивши робочий простір OpenCV для обраного IDE, відкрийте проєкт та виберіть "install target" для побудови бібліотеки. Коли процес завершиться, можна налаштувати приклад з використанням новозбудованої OpenCV.
Створюємо вікно OpenGL в середовищі OpenCV
Встановивши бібліотеку OpenCV з підтримкою OpenGL, можемо створити й перше вікно. Налаштування починається з виклику функції для створення вікна з відповідними параметрами - назвою та відміткою OpenGL:
cv::namedWindow(ARWindowName, cv::WINDOW_OPENGL);
ARWindowName - це константна стрічка, що позначає назву вікна. У нашому випадку використаємо безмаркерну доповнену реальність. Виклик створить відповідне вікно. Відмітка cv::WINDOW_OPENGL вказує на те, що воно призначене для OpenGL. Потім встановлюємо бажаний розмір вікна:
cv::resizeWindow(ARWindowName, 640, 480);
Після цього налаштовуємо контекст малювання:
cv::setOpenGlContext(ARWindowName);
Тепер вікно готове для використання. Щоб на ньому зобразити будь-що, маємо зареєструвати функцію зворотного виклику таким чином:
cv::setOpenGlDrawCallback(ARWindowName, drawAR, NULL);
У цьому разу drawAR викликатиметься як відповідь на кожну подію відображення вікна. Перший аргумент встановлює назву, другий - функцію зворотного виклику, а третій - опціональний аргумент останньої.
Функція drawAR повинна мати такий вигляд:
void drawAR(void* param)
{
// Малюємо щось з використанням OpenGL
}
Щоб повідомити системі про необхідність перемалювати вікно, використовуємо функцію cv::updateWindow:
cv::updateWindow(ARWindowName);
Захоплюємо відео за допомогою OpenCV
Бібліотека OpenCV дозволяє легко отримувати кадри з більшості вебкамер та відеофайлів. Щоб захопити відео, можна скористатися класом cv::VideoCapture як показано в частині "Отримуємо доступ до вебкамери" у розділі 1 "Ефект мультиплікація та зміна кольору шкіри для Android".
Візуалізуємо доповнену реальність
Покажемо структуру ARDrawingContext, що зберігатиме всі важливі дані для візуалізації:
Останнє зображення, отримане з камери
Матриця калібрування камери
Поза шаблона в 3D (за наявності)
Внутрішні дані, пов'язані з OpenGL (ідентифікатор текстури і так далі)
ARDrawingContext.hpp
Наступний код оголошує клас ARDrawingContext:
class ARDrawingContext
{
public:
ARDrawingContext(const CameraCalibration& c);
bool patternPresent;
Transformation patternPose;
//! Зображає вікно OpenGL
void draw();
//! Встановлює нове зображення в якості фону
void updateBackground(const cv::Mat& frame);
private:
//! Активує відеокадр в якості фону
void drawCameraFrame ();
//! Зображає доповнену реальність
void drawAugmentedScene();
//! Будує дійсну матрицю проєкції
//! на основі даних калібрування камери для AR
void buildProjectionMatrix(const Matrix33& calibration, int w, int
h, Matrix44& result);
//! Малює вісь координат
void drawCoordinateAxis();
//! Зображає модель куба
void drawCubeModel();
private:
bool m_textureInitialized;
unsigned int m_backgroundTextureId;
CameraCalibration m_calibration;
cv::Mat m_backgroundImage;
};
ARDrawingContext.cpp
Налаштування вікна OpenGL відбувається в конструкторі так:
ARDrawingContext::ARDrawingContext(std::string windowName, cv::Size
frameSize, const CameraCalibration& c)
: m_isTextureInitialized(false)
, m_calibration(c)
, m_windowName(windowName)
{
// Створюємо вікно з підтримкою OpenGL
cv::namedWindow(windowName, cv::WINDOW_OPENGL);
// Змінюємо його розміри, щоб відповідати характеристиками відео
cv::resizeWindow(windowName, frameSize.width, frameSize.height);
// Встановлюємо функцію зворотного виклику OpenGL:
cv::setOpenGlContext(windowName);
cv::setOpenGlDrawCallback(windowName, ARDrawingContextDrawCallback,
this);
}
Оскільки тепер маємо окремий клас для зберігання стану візуалізації, модифікуємо виклик cv::setOpenGlDrawCallback та передаємо в якості параметра екземпляр ARDrawingContext.
Змінена функція зворотного вигляду матиме вигляд:
void ARDrawingContextDrawCallback(void* param)
{
ARDrawingContext * ctx = static_cast<ARDrawingContext*>(param);
if (ctx)
{
ctx->draw();
}
}
ARDrawingContext повністю відповідальна за формування ефекту доповненої реальності. Візуалізація кадра починається із зображення фону з урахуванням ортографічної проєкції. Потім малюємо 3D модель з коректною перспективною проєкцією та перетвореннями. Наступний код описує фінальну версію функції:
void ARDrawingContext::draw()
{
// Очищуємо весь екран
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
// Малюємо фон
drawCameraFrame();
// Доповнюємо об'єктами AR
drawAugmentedScene();
}
Очистивши екран та буфер глибини, перевіримо наявність і коректність текстури відеокадра. Якщо вона налаштована як слід, продовжуємо малювати фон, інакше створюємо нову 2D-текстуру викликом glGenTextures.
Щоб зобразити фон, встановлюємо ортографічну проєкцію та малюємо заповнений прямокутник, що покриє весь вид екрану. Він нерозривно пов'язаний з текстурою, що відповідає вмісту об'єкта m_backgroundImage. Цей контент завантажується в пам'ять OpenGL заздалегідь. Функція аналогічна до описаної в попередньому розділі, тому не показуватимемо її код тут.
Після зображення фонового зображення, перемикаємося на роботу з доповненою реальністю. У цьому разі важливо встановити коректні перспективні проєкції, що відповідають калібруванню камери.
Подальший код пояснює, як будувати матрицю проєкції OpenGL з урахуванням калібрування камери, а також як візуалізувати сцену:
void ARDrawingContext::drawAugmentedScene()
{
// Налаштовуємо проєкцію доповнення
Matrix44 projectionMatrix;
int w = m_backgroundImage.cols;
int h = m_backgroundImage.rows;
buildProjectionMatrix(m_calibration, w, h, projectionMatrix);
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(projectionMatrix.data);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
if (isPatternPresent)
{
// Встановлюємо перетворення шаблона
Matrix44 glMatrix = patternPose.getMat44();
glLoadMatrixf(reinterpret_cast<const GLfloat*>
(&glMatrix.data[0]));
// Зображаємо модель
drawCoordinateAxis();
drawCubeModel();
}
}
Функцію buildProjectionMatrix взято без змін з попереднього розділу. Після застосування перспективної проєкції встановлюємо матрицю GL_MODELVIEW для перетворення зображення. Щоб довести коректність оцінки пози, у позиції шаблона малюємо одиничну систему координат. Підготовчі процеси практично повністю виконано. Тепер формуємо алгоритм розпізнавання та оцінюємо позу знайденого шаблона у тривимірному просторі, а також будуємо систему візуалізації доповненої реальності. Поглянемо на UML-діаграму процедури обробки кадра у нашому застосунку:
Демонстрація
Даний проєкт підтримує обробку звичайних зображень, записаного відеоматеріалу та прямого ефіру з вебкамери. Створимо дві функції, що допоможуть здійснити це.
main.cpp
Функція processVideo призначена для обробки відео, а processSingleImage - одного кадру, як показано далі:
void processVideo(const cv::Mat& patternImage, CameraCalibration&
calibration, cv::VideoCapture& capture);
void processSingleImage(const cv::Mat& patternImage,
CameraCalibration& calibration, const cv::Mat& image);
Виходячи з назв, перша працює з джерелом відео, а інша - з єдиним зображенням (корисна для налагодження). Обидві здійснюють подібну процедуру обробки відео, розпізнавання шаблона, візуалізації та взаємодії з користувачем.
Функція processFrame описує кроки так:
/**
* Виконує повну процедуру розпізнавання на кадрі з камери
.* та малює сцену з використанням контексту.
* Крім цього, фукнція виводить інформацію для налагодження
.* над вікном доповненої реальності. Повертає true,
.* якщо цикл обробки має бути зупинений; інакше - false.
*/
bool processFrame(const cv::Mat& cameraFrame, ARPipeline& pipeline,
ARDrawingContext& drawingCtx)
{
// Клонуємо зображення для фону (намалюємо
// дещо на його поверхні)
cv::Mat img = cameraFrame.clone();
// Зображаємо дані:
if (pipeline.m_patternDetector.enableHomographyRefinement)
cv::putText(img, "Уточнення пози: увімкнено (натисніть 'h', щоб вимкнути)",
cv::Point(10,15), CV_FONT_HERSHEY_PLAIN, 1, CV_RGB(0,200,0));
else
cv::putText(img, "Уточнення пози: вимкнено (натисніть 'h', щоб увімкнути)",
cv::Point(10,15), CV_FONT_HERSHEY_PLAIN, 1, CV_RGB(0,200,0));
cv::putText(img, "Поріг RANSAC: " +
ToString(pipeline.m_patternDetector.homographyReprojectionThreshold) +
"(Натисніть '-'/'+' для регулювання)", cv::Point(10, 30), CV_FONT_HERSHEY_PLAIN,
1, CV_RGB(0,200,0));
// Встановлюємо новий кадр камери:
drawingCtx.updateBackground(img);
// Знаходимо шаблон та оновлюємо статус розпізнавання:
drawingCtx.isPatternPresent = pipeline.processFrame(cameraFrame);
// Актуалізуємо позу шаблона:
drawingCtx.patternPose = pipeline.getPatternLocation();
// Запитуємо оновлення вікна:
drawingCtx.updateWindow();
// Зчитуємо ввід з клавіатури:
int keyCode = cv::waitKey(5);
bool shouldQuit = false;
if (keyCode == '+' || keyCode == '=')
{
pipeline.m_patternDetector.homographyReprojectionThreshold +=
0.2f;
0.2f;
pipeline.m_patternDetector.homographyReprojectionThreshold =
std::min(10.0f,
pipeline.m_patternDetector.homographyReprojectionThreshold);
}
else if (keyCode == '-')
{
pipeline.m_patternDetector.homographyReprojectionThreshold -=
0.2f;
pipeline.m_patternDetector.homographyReprojectionThreshold =
std::max(0.0f,
pipeline.m_patternDetector.homographyReprojectionThreshold);
}
else if (keyCode == 'h')
{
pipeline.m_patternDetector.enableHomographyRefinement =
!pipeline.m_patternDetector.enableHomographyRefinement;
}
else if (keyCode == 27 || keyCode == 'q')
{
shouldQuit = true;
}
return shouldQuit;
}
Налаштування ARPipeline та ARDrawingContext здійснюється або в функції processSingleImage, або в processVideo таким чином:
void processSingleImage(const cv::Mat& patternImage,
CameraCalibration& calibration, const cv::Mat& image)
{
cv::Size frameSize(image.cols, image.rows);
ARPipeline pipeline(patternImage, calibration);
ARDrawingContext drawingCtx("Безмаркерна AR", frameSize,
calibration);
bool shouldQuit = false;
do
{
shouldQuit = processFrame(image, pipeline, drawingCtx);
} while (!shouldQuit);
}
Створюємо об'єкт ARPipeline з урахуванням шаблону та аргументів калібрування. З використанням останніх також налаштовуємо ARDrawingContext. Після здійснення цих кроків матимемо готове для роботи вікно OpenGL. Потім завантажуємо зображення запиту в контекст малювання та викликаємо функцію ARPipeline.processFrame для пошуку шаблона. Якщо шаблон знайдено, копіюємо його положення в контекст для подальшої візуалізації. Якщо ж ні, візуалізуємо кадр без доповненої реальності. Демонстраційний проєкт можна запустити одним з наступних способів:
Для обробки єдиного зображення:
markerless_ar_demo pattern.png test_image.png
Для обробки збереженого відеопотоку:
markerless_ar_demo pattern.png test_video.avi
Для обробки прямого ефіру з вебкамери:
markerless_ar_demo pattern.png
Результат доповнення одного кадру показаний на наступному скріншоті:
Висновки
У цьому розділі ви дізналися про дескриптори особливих точок та їх використання для опису шаблонів, інваріантних до змін масштабу та повороту. Цей опис можна використати для пошуку подібних фрагментів на інших зображеннях. Також пояснено сильні та слабкі сторони найбільш відомих детекторів ключових точок. У другій половині частини ми навчились об'єднувати OpenGL та OpenCV для спільної візуалізації доповненої реальності.
Посилання
Distinctive Image Features from Scale-Invariant Keypoints
(http://www.cs.ubc.ca/~lowe/papers/ijcv04.pdf)
SURF: Speeded Up Robust Features (http://www.vision.ee.ethz.ch/~surf/eccv06.pdf)
Model-Based Object Pose in 25 Lines of Code, Dementhon and L.S Davis, International
Journal of Computer Vision, edition 15, pp. 123-141, 1995
Linear N-Point Camera Pose Determination, L.Quan, IEEE Trans. on Pattern Analysis and
Machine Intelligence, vol. 21, edition. 7, July 1999
Random Sample Consensus: A Paradigm for Model Fitting with Applications to Image
Analysis and Automated Cartography, M. Fischer and R. Bolles, Graphics and Image
Processing, vol. 24, edition. 6, pp. 381-395, June 1981
Multiple View Geometry in Computer Vision, R. Hartley and A.Zisserman, Cambridge
University Press (http://www.umiacs.umd.edu/~ramani/cmsc828d/lecture9.pdf)
Camera Pose Revisited – New Linear Algorithms, M. Ameller, B.Triggs, L.Quan
(http://hal.inria.fr/docs/00/54/83/06/PDF/Ameller-eccv00.pdf)
Closed-form solution of absolute orientation using unit quaternions, Berthold K. P. Horn,
Journal of the Optical Society A, vol. 4, 629–642
Внимание! Этот перевод, возможно, ещё не готов.
Его статус: перевод редактируется
Переведено на Нотабеноиде
http://notabenoid.org/book/81521/499468
Переводчики: Atronax
Настройки
Готово:
