Viz - Новий модуль 3D візуалізації в бібліотеці OpenCV

  
 
Добрий день, сьогоднішній блогпост я хочу присвятити огляду нового модуля для 3D візуалізації Viz в бібліотеці OpenCV , в проектуванні і реалізації якого я брав участь. Напевно тут мені варто представитися, мене звуть Анатолій Бакшеев , я працюю в компанії Itseez , використовую бібліотеку OpenCV ось вже 7 років, і разом з колегами розробляю і розвиваю її.
 
Яке ж відношення має 3D візуалізація до комп'ютерного зору, запитаєте ви, і навіщо нам взагалі потрібен подібний модуль? І будете праві, якщо дивитися на комп'ютерне зір як на область, що працює із зображеннями. Але ми живемо в 21-м столітті, і область застосування комп'ютерного зору вийшла далеко за межі просто обробки зображень, виділення меж об'єктів або розпізнавання осіб. Наука і техніка вже навчилися в більш-менш прийнятній якості вимірювати наш тривимірний світ. Цьому багатьом посприяло і поява кілька років тому на ринку дешевих сенсорів типу Kinect , що дозволили на той час з хорошою точністю та швидкістю отримувати уявлення сцени у вигляді тривимірного кольорового хмари точок, і прогрес в області реконструкції 3D світу даних по серії зображень, і навіть догляд в мобільні технології, де інтегрований гіроскоп і акселерометр значно спрощує завдання оцінки пересування камери мобільного пристрою в 3D світі, а значить і точність реконструкції сцени.
 
Все це підштовхнуло до розвитку різних методів і алгоритмів, що працюють з 3D даними. 3D сегментація, 3D фільтрація шумів, 3D распозваніе об'єктів за формою, 3D розпізнавання особи, 3D стеження за позою тіла, або руки для розпізнавання жестів. Ви напевно знаєте, що коли Kinect для XBox вийшов у продаж, Microsoft надала розробникам ігор SDK за визначенням позиції людського тіла, що призвело до появи великої кількості ігор з цікавим інтерфейсом — коли, наприклад, ігровий персонаж повторює рухи гравця, що стоїть перед Kinect'ом. Результати таких 3D алгоритмів треба якось візуалізувати. Ними є тривимірні траєкторії, відновлена ​​геометрія, або, наприклад, обчислена позиція людської руки в 3D. Також подібні алгоритми треба налагоджувати, найчастіше визуализируя проміжні дані в процесі збіжності розроблюваного алгоритму.
 
 
 Різні способи відображення траєкторій камери в OpenCV Viz
 
Таким чином, раз вектор розробок зміщується в 3D область, в OpenCV буде все більше і більше з'являтися алгоритмів, що працюють з 3D даними. І раз спостерігається такий тренд, поспішаємо створити зручну інфраструктуру для цього. Модуль Viz — це перший крок у даному напрямку. OpenCV завжди була бібліотекою, яка містить дуже зручну базу, на основі якої розроблялися алгоритми та програми комп'ютерного зору. Зручну як через функціональність, так як вона включає практично всі найбільш часто використовувані операції для маніпуляції із зображеннями та даними, так і з-за ретельно виробленого і роками проверенно API (контейнери, базові типи і операції з ними), що дозволяє дуже компактно реалізовувати методи комп'ютерного зору, заощаджуючи час розробника. Сподіваємося, що Viz задовольняє всім цим вимогам.
 
Для нетерплячих привожу ось це відео з демонстрацією можливостей модуля.
 
  
 

Філософія Viz

Ідея створення такого модуля з'явилася у мене, коли мені якось довелося налагоджувати один алгоритм візуальної одометра (vslam ), в умовах обмеженого часу, коли я на власній шкурі відчув, як допоміг би мені такий модуль і яку функціональність я хотів би в ньому бачити. Та й колеги заявляли, що здорого було б мати такий модуль. Всі призвело до початку його розробки, а потім доведення до більш-менш зрілого стану разом з Озанна ТОНКАЛЬ , нашим Google Summer Of Code студентом. Робота над вдосконаленням Viz'а ведеться і зараз.
 
Дизайн ідея в тому, що непогано б мати систему тривимірних віджетів, кожен з яких можна було б малювати в 3D визуализатор, просто передавши позицію і орієнтацію цього віджета. Наприклад, хмара точок, що приходить з Kinect, часто зберігається в системі координат, пов'язаної з положенням камери, і для візуалізації часто доводиться перетворювати всі хмари точок, зняті з різних позицій камери, в якусь глобальну систему координат. І зручно було б не перераховувати дані щоразу в глобальну систему, а просто задати позицію цієї хмари точок. Таким чином, в OpenCV Viz кожен підтримуваний об'єкт-віджет формується у власній системі координат, а потім зсувається і орієнтується вже в процесі отрисовки.
 
Але ні одна хороша ідея не приходить в голову тільки одній людині. Як з'ясувалося, бібліотека VTK для маніпулювання і візуалізації наукових даних теж реалізує такий же підхід. Тому, завдання звелася до написання грамотного врапперов над підмножиною VTK , з інтерфейсом і структурами даних у стилі OpenCV і написанню якогось набору базових віджетів з можливістю в майбутньому розширити це безліч. Крім описаного, VTK задовольняє вимогу багатоплатформеності, тому рішення використовувати її було вибрано практично відразу. Я думаю, невелика незручність через залежність від VTK з лишком компенсується зручністю і розширюваністю в майбутньому.
 
 

Представлення позиції об'єктів в Viz

Позиція в евклідовому просторі задається поворотом і трансляцією. Поворот може представлятися у вигляді матриці повороту, у вигляді вектора повороту (Rodrigues 'vector ) або кватернионами . Трансляція ж — це просто тривимірний вектор. Поворот і трансляцію можна зберігати в окремих змінних або зашити в розширену матрицю афінної перетворення 4x4. Власне, цей спосіб і пропонується для зручності використання. Але… "Теж мені, зручний!", — Скажете ви, — "щоразу формувати таку матрицю при відображенні будь-якого об'єкта!" І я з вами погоджуся, але тільки якщо не надати зручного засобу для створення і маніпулювання позами в такому форматі. Цим засобом є спеціально написаний клас cv :: Affine3d, який до речі, крім як для візуалізації я рекомендую використовувати і при розробці алгоритмів одометра. Так-так, любителі кватернионов вже можуть кидати в мене каміння. Скажу на виправдання, що в майбутньому планується і їх підтримувати.
 
Отже, давайте дамо визначення. Поза кожного об'єкта в Viz — це перетворення з евклідової системи координат, пов'язаної з об'єктом, на якусь глобальну евклидову систему координат. На практиці існують різні угоди, що таке перетворення і що куди перетвориться. У нашому випадку мається на увазі перетворення точок (point transfer) з системи координат об'єкту в глобальну. Т.е:
 
 image
 
де P G , P O — координати точки в глобальній системі координат і в системі координат об'єкту, M — матриця перетворення або поза об'єкта. Давайте розглянемо як можна сформувати позу об'єкта.
 
 
// Если известна система координат связанная с объетом
cv::Vec3d x_axis, y_axis, z_axis, origin;
cv::Affine3d pose = cv::makeTransformToGlobal(x_axis, y_axis, z_axis, origin);

// Если же необходимо вычислить позу камеры
cv::Vec3d position, view_direction, y_direction;
Affine3d pose = makeCameraPose(position, view_direction, y_direction);

// Единичные преобразования, поза объекта совпадает с глобальной системой
Affine3d pose1;  
Affine3d pose2 = Affine3d::Identity();

// Из матрицы поворота и трансляции
cv::Matx33d R;
cv::Vec3d t;
Affine3d pose = Affine3d(R, t);

// Если вы сторонник жесткой оптимизации и храните матрицы как массивы на стеке
double rotation[9];
double translation[3];
Affine3d pose = Affine3d(cv::Matx33d(rotation), cv::Vec3d(translation));

А може бути, ви вже розробляли алгоритми візуальної одометра, і у вашій програмі вже є ці матриці перетворення, що зберігаються всередині cv :: Mat? Тоді позу в новому форматі можна легко отримати:
 
// Для матриц 4x4 или 4х3
cv::Mat pose_in_old_format;
Affine3d pose = Affine3d(pose_in_old_format);

// Для матрицы 3х3 и трансляцией отдельно
cv::Mat R, t;
Affine3d pose = Affine3d(R, translation);

// Для вектора Родригеса и трансляции
cv::Vec3d rotation_vector:
Affine3d pose = Affine3d(rotation_vector, translation);

Крім конструювання даний клас дозволяє ще й маніпулювати позами і застосовувати їх до тривимірним векторах і точкам. Приклади:
 
// Поворот на 90 градусов вокруг Oy затем перемещение на 5 вдоль Ox.
Affine3d pose = Affine3d().rotate(Vec3d(0, CV_PI/2, 0,)).translate(Vec3d(5, 0, 0));

// Применение позы
cv::Vec3d a_vector;
cv::Point3d a_point;
cv::Vec3d transformed_vector = pose * a_vector;
cv::Vec3d transformed_point  = pose * a_point;

// Комбинация двух поз
Affine3d camera1_to_global, camera2_to_global;
Affine3d camera1_to_camera2 = camera2_to_global.inv() * camera1_to_global

Читати це треба так: якщо домножити праворуч на точку в системі координат камери 1, то після першого (праворуч) перетворення отримаємо крапку в глобальній системі, а потім інвертованим перетворенням з глобальної системи переведемо її в систему координат камери 2. Тобто ми отримаємо позу камери 1 щодо системи координат камери 2.
 
// Расстояние между двумя позами можно вычислить так
double distance = cv::norm((cam2_to_global.inv() * cam1_to_global).translation());
double rotation_angle = cv::norm((cam2_to_global.inv() * cam1_to_global).rvec());

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

Візуалізація за допомогою Viz

Найголовніший клас, що відповідає за візуалізацію, називається cv :: viz :: Viz3d. Цей клас відповідає за створення вікна, його ініціалізацію, відображення віджетів і керування і обробку вводу від користувача. Скористатися ним можна таким чином:
 
Viz3d viz1(“mywindow”); // подготавливаем окно с именем mywindow
... добавляем содержимое ...
viz1.spin();    // отображаем; исполнение блокируется, пока окно не будет закрыто

Як і майже вся Високорівнева функціональність в OpenCV, цей клас є по суті розумним покажчиком з підрахунком посилань на його внутрішню реалізацію, тому його вільно можна копіювати, або отримувати по імені з внутрішньої бази даних.
 
Viz3d viz2 = viz1;
Viz3d viz3 = cv::viz::getWindowByName(“mywindow”):
Viz3d viz4(“mywindow”); 

Якщо вікно із запитуваною ім'ям вже існує, одержуваний примірник Viz3d буде вказувати на нього, інакше нове вікно з таким ім'ям буде створено і зареєстровано. Зроблено це для спрощення налагодження алгоритмів — вам тепер не потрібно передавати вікно вглиб стека викликів кожен раз, коли десь щось треба відобразити. Досить на початку функції main () завести вікно, і потім отримувати доступ до нього по імені з будь-якого місця в коді. Ця ідея успадкована від зарекомендувала себе в OpenCV функції cv :: imshow (window_name, image), також дозволяє відобразити картинку в іменоване вікно в будь-якому місці коду.
 
 
Система Віджетів
Як уже згадувалося раніше, для отрисовки різних даних використовується система віджетів. Кожен віджет має кілька конструкторів і іноді методів для управління його внутрішніми даними. Кожен віджет формується у своїй координатної системі. Наприклад:
 
 
// задаем линию двумя точками
WLine line(Point3d(0.0, 0.0, 0.0), Point3d(1.0, 1.0, 1.0), Color::apricot()); 

// задаем куб двумя углами с гранями паралельно осям координат 
WCube cube(Point3d(-1.0, -1.0, -1.0), Point3d(1.0, 1.0, 1.0), true, Color::pink());

 
Як бачимо, ми можемо вказати довільну лінію, однак для куба можливо виставляти тільки позицію, але не орієнтацію щодо осей координат. Однак, це не є обмеження, а швидше навіть фіча, що привчає мислити в стилі Viz. Як ми вже обговорювали раніше, при відображенні можна задати будь-яку позу віджета в глобальній системі координат. Таким чином, ми простим конструктором створюємо віджет в його системі координат, наприклад, задаємо таким чином розміри куба. А потім позиціонуємо і орієнтуємо його в глобальній при відображенні.
 
 
// Вектор Родригеса определяющий поворот вокруг (1.0, 1.0, 1.0) на 3 радиана
Vec3d rvec = Vec3d(1.0, 1.0, 1.0) * (3.0/cv::norm(Vec3d(1.0, 1.0, 1.0));

Viz3d viz(“test1”);
viz.showWidget(“coo”, WCoordinateSystem());
viz.showWidget(“cube”, cube,  Affine3d(rvec, Vec3d::all(0)));
viz.spin();

І ось результат:
 
 
Як ми бачимо, отрисовка відбувається через виклик методу Viz3d :: showWidget () з передачею йому строкового імені об'єкта, примірника створеного віджета і його позиції в глобальній системі координат. Строкове ім'я необхідно для того, щоб можна було додавати, видаляти і оновлювати віджети в 3D сцені по імені. Якщо віджет з таким ім'ям вже присутня, то він видаляється і замінюється на новий.
 
Крім куба і лінії, в Viz реалізовані сфера, циліндр, площина, 2D окружність, картинки і текст в 3D і 2D, різні типи траєкторій, положення камери, ну і, звичайно, хмари точок і віджет для роботи з Мешем (бецветним, розфарбованим або текстурованим). Це безліч віджетів не є фінальним, і буде розширюватися. Більше того, є можливість створення користувацьких віжетов, але про це якось іншим разом. Якщо вас зацікавила ця можливість, читайте ось цей туториал. А зараз давайте розглянемо ще приклад, як потрібно хмари точок:
 
// читаем облако точек с диска. возвращается матрица с типом CV_32FC3
cv::Mat cloud = cv::viz::readCloud(“dragon.ply”); 

// создаем массив цветов для облака и заполняем его случайными данными
cv::Mat colors(cloud.size(), CV_8UC3);
theRNG().fill(colors, RNG::UNIFORM, 50, 255);

// копируем облако точек и выставляем часть точек в NAN - такие точки будут проигнорированы
float qnan = std::numeric_limits<float>::quiet_NaN();
cv::Mat masked_cloud = cloud.clone();
for(int i = 0; i < cloud.total(); ++i)
    if ( i % 16 != 0)
        masked_cloud.at<Vec3f>(i) = Vec3f(qnan, qnan, qnan);
   
Viz3d viz(“dragons”);
viz.showWidget(“coo”, WCoordinateSystem());

// Красный дракон
viz.showWidget(“red”, WCloud(cloud, Color::red()), 
Affine3d().translate(Vec3d(-1.0, 0.0, 0.0)));

// Дракон со случайными цветами
viz.showWidget(“colored”, WCloud(cloud, colors), 
Affine3d().translate(Vec3d(+1.0, 0.0, 0.0)));

// Дракон со случайными цветами и отфильтрованными точками с единичной позой
viz.showWidget(“masked”, WCloud(masked_cloud, colors), Affine3d::Identity());

// Aвтоматическая раскраска, полезно если у нас нет цветов
viz.showWidget(“painted”, WPaintedCloud(cloud), 
Affine3d().translate(Vec3d(+2.0, 0.0, 0.0)));
viz.spin();

Результат роботи цього коду:
 
Для більш детальної інформації про доступні віджетах читайте нашу документацію .
 
 
Динамічно змінюється сцена
Найчастіше недостатньо просто відобразити об'єкти, щоб користувач міг їх розглянути, а необхідно надати деяку динаміку. Об'єкти можуть рухатися, міняти свої атрибути. Якщо у нас є відеопотік з Kinect, то можна програвати так зване point cloud videо. Для цього можна зробити наступне:
 
cv::VideoCapture capture(CV_CAP_OPENNI)
Viz3d viz(“dynamic”);
//... добавляем содержимое...

// выставляем положение камеры чуть сбоку
viz.setViewerPose(Affine3d().translate(1.0, 0.0, 0.0));

while(!viz.wasStopped())
{
    //... обновляем содержимое...
    //если надо, меняем позы у добавленных виджетов
    //если надо, заменяем облака новыми полученными с Kinect
    //если надо, меняем положение камеры

    capture.grab();
    capture.retrieve(color, CV_CAP_OPENNI_BGR_IMAGE);
    capture.retrieve(depth, CV_CAP_OPENNI_DEPTH_MAP);
    Mat cloud = computeCloud(depth);
    Mat display = normalizeDepth(depth);
    
    viz.showWidget("cloud", WCloud(cloud, color));
    viz.showWidget("image", WImageOverlay(display, Rect(0, 0, 240, 160)));

    // отрисовываем и обрабатываем пользовательский ввод в течении 30 мс
    viz.spinOnce(30 /*ms*/,  true /*force_redraw*/));
}

Даний цикл буде виконуватися поки користувач не закриє вікно. При цьому, на кожній ітерації циклу віджет зі старим хмарою буде замінюватися на новий з новим хмарою.
 
  
 
Інтерфейс управління
На даний момент управління камерою зроблено в так званому стилі trackball camera, зручному для розглядаючи різних 3D об'єктів. Уявіть собі, що перед камерою є деяка точка в 3D, навколо якої ця камера і обертається за допомогою мишки. Скроллер на мишці наближає / видаляє до і від цієї точки. Використовуючи кнопки shift / ctrl і мишку, можна переміщати цю точку обертання в 3D світі. У майбутньому планується реалізувати free-fly режим для навігації по великих просторів. Я також рекомендую натиснути гарячу кнопку 'H' під час роботи Viz, щоб прочитати роздруковану в консоль інформацію про інших гарячих клавішах і можливостях, від збереження скрішнотов і до включення анагліфічного стерео режиму.
 
 

Як побудувати OpenCV Viz модуль

Ну і нарешті, для тих, хто після прочитання цього тексту загорівся бажанням почати використовувати цей модуль, призначений цей розділ. Viz можна використовувати на всіх трьох домінуючих PC платформах — Windows, Linux, і Mac. Вам буде потрібно встановити VTK і скомпілювати OpenCV з підтримкою VTK. Саму OpenCV c модулем Viz можна завантажити тільки з нашого репозиторію на GitHub'е https://github.com/Itseez/opencv в гілках 2.4 і master. Отже, інструкція:
 
 
1. Установка VTK
Під Linux найбільш простим рішенням є установка VTK з apt репозиторію через команду apt-get install libvtk5-dev. Під Windows вам необхідно завантажити VTK з сайту розробника, найкраще версію 5.10, згенерувати CMake-му проект для Visual Studio і скомпілювати в Release і Debug конфігураціях. Я рекомендую зняти галочку в CMake BUILD_SHARED_LIBS, що призведе до компіляції статичних бібліотек VTK. У цьому випадку після компіляції розмір OpenCV Viz модуля без будь-яких залежностей складе всього близько 10Мб.
 
Під Mac для версій OSX 10.8 і раніше підійде будь-яка версія VTK, під 10.9 Mavericks вдасться скомпілювати VTK 6.2 з офіційно репозитория github.com / Kitware / VTK.git . Релізів 6.2 на момент написання даного блогпост ще не було. Під Mac також рекомендується згенерувати за допомогою CMake проект під XCode і побудувати статичні бібліотеки в Release і Debug конфігураціях.
 
 
2. Компіляція OpenCV c VTK
Цей крок простіше і швидше. Я наводжу команди для Linux, під Windows все мало чим відрізняється
 
     
  1. git clone github.com / Itseez / opencv.git
  2.  
  3. [optional] git checkout-b 2.4 origin/2.4
  4.  
  5. mkdir build && cd build
  6.  
  7. cmake-DWITH_VTK = ON-DVTK_DIR = <шлях до білд каталогом VTK>… / opencv
  8.  
 
Якщо ви ставили VTK через apt-get install, то шлях до неї вказувати не треба — вона буде знайдена CMake'ом автоматично. Далі потрібно упевнитися в консольному балці CMake, що він знайшов і підключив VTK. І не відрапортував про будь несумісність його. Наприклад, якщо ви компілюєте OpenCV з підтримкою Qt5, а VTK зібрана з Qt4, лінковка з VTK призведе до падіння додатки ще на етапі ініціалізації до входу в функцію main (). Рішення — вибирати щось одне. Або скомпілювати VTK без Qt4, знявши відповідну галочку в CMake для VTK. Або взяти VTK 6.1 і вище і зібрати її з підтримкою Qt5. Ну і нарешті, для складання OpenCV запускаємо make-j 6
 
 
3. Запуск текстів (опціонально)
Я також рекомендую завантажити ось цей репозиторій: github.com / Itseez / opencv_extra.git , прописати в змінну оточення OPENCV_TEST_DATA_PATH шлях до opencv_extra / testdata. І запустити файл opencv_test_viz з build каталогу OpenCV. На цьому додатку можна ознайомитися з усіма поточними можливостями даного модуля, а його ісходник можна використовувати для вивчення API.
 
 

Висновок

Ну що ж, ось я добрався і до ув'язнення. Сподіваюся, було цікаво. Цим постом мені хотілося показати, який основний тренд, c моєї точки зору, зараз спостерігається в комп'ютерному зорі, і що бібліотека OpenCV рухається в ногу з часом. І що в OpenCV будуть з'являтися алгоритми для роботи з 3D світом. Тому що ми самі будемо їх розробляти або за допомогою Google Summer of Code студентів, або вдячні користувачі використовують нашу базу, будуть брати участь і в створенні і розвитку подібних алгоритмів в OpenCV.
 
А також хотілося зацікавити вас цим розробленим інструментом, або, може бути, навіть цією областю для досліджень. До речі, якщо у вас з'явилося бажання вести подібну розробку для OpenCV — You are welcome! Ми приймаємо pull request'и через GitHub. Інструкція викладена тут . Будемо раді бачити новий добре працюючий підхід :-)
 
І хоча основна необхідна зараз база створена, я думаю, в майбутньому в Viz будуть додаватися нові можливості. Наприклад, модель скелета людської руки і її візуалізація. Або карти 3D світу з таких алгоритмів, як PTAM. А може бути, і мережевий клієнт, щоб можливо було пересилати дані для візуалізації з мобільного пристрою при налагодженні алгоритмів на ньому :) Але це поки божевільні ідеї :-). Якщо цікаво, в наступному блогпост я міг би розповісти про який-небудь алгоритмі, наприклад, ICP або Kinect Fusion, і як використовувався Viz для його налагодження і візуалізації.
 
А для тих хто дочитав до кінця — бонус. Тут лежить мій оптимізований і легкий remake моєї ж реалізації Kinect Fusion в бібліотеці PCL.
  
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.