Виртуальная реальность не раз описывалась в книгах, фильмах и СМИ. Но на практике полная виртуализация ощущений неудобна, ведь человек не может абсолютно отгородиться от реального мира. Сегодня мы поговорим о популярной концепции интерфейсов, объединяющей в себе реальные объекты с виртуальными — о дополненной реальности.

Хорошо забытое старое

На самом деле идея дополненной реальности далеко не так нова, как кажется. В боевых самолетах и танках уже давно применяется нашлемная индикация, быстро и удобно совмещающая реальную панораму обзора со служебной информацией.

Да что тут говорить — раритетные VHS-видеомагнитофоны и то были способны выводить направление перемотки и хронометраж поверх изображения. Бегущие информационные строки в новостях и всплывающие плашки с фамилиями при интервью — все это дополненная реальность в том или ином виде. Идея такого интерфейса — не заменить реальный мир на поддельный, а дополнить его элементами, облегчающими пользование и навигацию. Благодаря доступности компьютеров, web-камер и GPS-навигаторов в XXI веке модели подобных интерфейсов получили широкое распространение в коммерческой и развлекательной сфере.

Оснащенность мобильных телефонов необходимыми техническими средствами существенно расширила область применения QR-кодов, сегодня их не печатают разве что на туалетной бумаге. Прижилось и активно используется сокращение AR — от английского Augmented Reality.

Необходимое оборудование

Дополненная реальность — общий термин, охватывающий широкий круг приложений. Каждое из них преследует свои цели и реализуется с помощью различных технологий. В современной индустрии можно условно выделить два технологических направления. Первое — совмещение изображения, получаемого с камеры, с информацией, генерируемой на основе показаний различных датчиков, взаимодействующих с реальным миром: например, компаса и GPS навигатора.

Такой тандем позволяет рассчитывать координаты объектов, попадающих в объектив, и получать для них дополнительные параметры. Это могут быть как простые отображения координат, направления движения, положения на карте и прочее, так и вещи куда более сложные. К примеру, существует сервис Layar, предоставляющий мобильным AR-браузерам информацию об объектах с определенными координатами. Это позволяет отображать на экране смартфона разнообразные подсказки — от названий туристических достопримечательностей до объявлений о продаже недвижимости и рейтингах ресторанов, составленных их посетителями. Подобный сервис уже больше похож на кадры из фантастических фильмов о будущем.

Второе направление обычно не требует от периферии ничего, кроме наличия камеры, полагаясь на технологии распознавания изображений. Такие системы не могут решать задачи информационного геоориентирования, обычно они используются в локальном пространстве. Расшифровка информации, которую несут в себе штрих-коды и бар-коды – их задача, они дополняют изображение товара его стоимостью и другими параметрами.

Выделение необходимых технологических элементов на изображении попадает в эту же нишу. К примеру, подсветка объектов, похожих по форме на оружие, или графическая индикация при распознавании человеческих лиц в кадре (функция, часто встречающаяся в современных фотоаппаратах). Все это, безусловно, тоже AR. В киноиндустрии и сфере развлечений применяется реконструкция трехмерных координат опорных маркеров с целью дополнения изображения виртуальными объектами, привязанными к этим координатам. Так как найти компьютер с веб-камерой или просто несколько файлов с подходящими фотографиями сегодня не представляет никакого труда, то дальше мы будем вести речь о создании систем дополненной реальности «второго направления».

How it’s made

В основе любого AR-приложения, использующего анализ поступающей с камеры картинки, безусловно, лежит система компьютерного зрения. Можно создавать такую систему самому, но проще взять готовую. Одной из наиболее известных библиотек, реализующих подобный функционал, является OpenCV (Open Source Computer Vision Library — библиотека компьютерного зрения с открытым исходным кодом). Это весьма серьезный фреймворк. В первую очередь он предоставляет RTL в виде типов, базовых примитивов, математических и конфигурационных утилит.

Затем — средства пост-обработки изображений, не сильно уступающие по возможностям графическим редакторам; непосредственно сами алгоритмы компьютерного зрения, позволяющие выделять на изображении геометрические объекты и работать с ними; а также высокоуровневые обвязки для доступа к камере, отображения GUI и прочего. Ну и на закуску — библиотека хорошо документирована и имеет крайне демократичную BSD-лицензию. Список платформ, где работает OpenCV, также весьма радует — это как минимум Windows, Linux, FreeBSD и MacOS X.

Первое OpenCV-приложение

Вот и настало время приступить к написанию приложения с использованием OpenCV. Наше первое приложение будет искать четырехугольный базис маркера на заданной картинке, чтобы потом (в теории) передать его на дальнейшую обработку. На этом простом примере мы разберем основные функции и правила построения вызовов. В первую очередь нам нужна картинка, с которой мы будем работать.

Картинки в OpenCV либо создаются пустыми с помощью функции cvCreateImage, либо загружаются из файлов с помощью cvLoadImage, либо копируются из указателей на уже существующие изображения с помощью cvCloneImage.

Когда ресурсы, выделенные под хранение изображения, больше не нужны, необходимо освободить их с помощью вызова cvReleaseImage. Мы будем загружать первичное изображение из файла:

IplImage *img_orig = cvLoadImage("image.jpg");

Чтобы контролировать процесс, будем отображать каждый этап на экране и ожидать нажатия клавиши, прежде чем перейти к следующему. Для отображения нам необходимо окно, которое тоже придется создать. Окна в OpenCV адресуются по символьным именам. Своеобразно, но жить можно.

cvNamedWindow("XaKeP OpenCV Window", CV_WINDOW_AUTOSIZE);
cvShowImage("XaKeP OpenCV Window", img_orig);
cvWaitKey(0);

Далее необходимо сделать препроцессинг изображения, чтобы подготовить его к поиску контуров. Я выбрал алгоритм, состоящий из трех шагов. Сначала мы просто обесцвечиваем изображение. Если бы у нас стояла задача искать прямоугольник какого-то определенного цвета, то вместо этого пришлось бы делать выделение цветового канала. Вторым шагом мы делаем эквалайзинг гистограммы изображения, чтобы заполнить всю яркостную шкалу и таким образом исправить слишком темное (либо наоборот, пересвеченное) изображение. И, наконец, мы делаем пороговую бинаризацию — все, что темнее 50%, становится черным, а все, что светлее – белым. Так как у нас 8-битная шкала, то это будут границы 0-127-255.

Основные моменты кода за пропуском несущественных деталей будут выглядеть вот так:

cvCvtColor(img_orig, img_gray, CV_RGB2GRAY);
cvEqualizeHist(img_gray, img_hist);
cvThreshold(img_hist, img_thr, 127, 255, CV_THRESH_BINARY);
IplImage *img_thr_bkp = cvCloneImage(img_thr);

В конце я создаю бэкап картинки, так как она понадобится мне позже, но исходный вариант «испортится» в процессе поиска контуров. Теперь, когда препроцессинг проведен, можно приступить непосредственно к оконтуриванию.

Тут стоит обратить внимание на то, что связный список, в котором хранятся контуры, относится к примитивам, находящимся в специальных пулах памяти (в одном пуле может быть больше одного примитива), и эти пулы необходимо создавать, а потом, при ненадобности, удалять. В принципе, это удобно, так как позволяет реализовать концепцию сборки мусора.

CvMemStorage *storage = cvCreateMemStorage(0);
CvSeq *contours = NULL;
cvFindContours(img_thr, storage, &contours);

Получив список контуров, нужно его отфильтровать. Сначала мы проводим аппроксимацию с необходимой точностью (я взял 2% отклонения), чтобы учитывать только резкие углы, а не каждый незначительный изгиб. Следующим шагом мы проверяем, что аппроксимированный контур имеет четыре угла, достаточную площадь и является выпуклым многоугольником. Если контур удовлетворяет условию, мы рисуем его на исходном изображении:

while (contours) {
CvSeq result = cvApproxPoly(... cvContourPerimeter(contours)0.02 ...);
if (result->total==4 && cvContourArea(result) >= 100 && cvCheckContourConvexity(result))
{
cvDrawContours(img_orig, result, ...);

Также я хочу реконструировать изображения найденных (потенциально) маркеров, рассчитав им матрицы аффинных преобразований и создав новые картинки посредством применения этих матриц. Применять их, кстати, я буду к тому самому бэкапу, сделанному в самом начале. В качестве расчетных значений мы возьмем координаты четырехугольника, прошедшего проверку, и новой квадратной картинки 200х200, назвав их srcQuad и dstQuad соответственно:

CvMat warp_mat = cvCreateMat(3, 3, CV_32FC1);
cvGetPerspectiveTransform(srcQuad, dstQuad, warp_mat);
IplImage
mrk = cvCreateImage(cvSize(200, 200), 8, 1);
cvWarpPerspective(img_thr_bkp, mrk, warp_mat);

Получившиеся картинки я сохраню в массиве, а позже покажу на экране. Вот, в принципе, и все. Результат работы этой небольшой программки можно увидеть на иллюстрации. Я считаю, что для ужасного качества исходного изображения результат превосходен — мы нашли и реконструировали наш маркер с лямбдой.

Гюльчатай, открой личико

Анализ найденного контура на предмет того, является ли он выпуклым четырехугольником — дело немудреное. Но поиск лица или человеческой фигуры такими алгоритмами сделать нереально, тут нужен системный подход и математическая теория. Традиционно для поиска сложных объектов на изображении пользуются так называемыми каскадами Хаара. Они названы в честь венгерского математика Альфреда Хаара, придумавшего дискретные вейвлет-преобразования, использующиеся в данном алгоритме.

Если вкратце, то производится поиск шаблонов, описанных специфическими примитивами. Сначала ищется грубый шаблон, состоящий из небольшого количества элементов, после чего каждый элемент сравнивается с необходимым набором уже более мелких элементов, и так до полного совпадения. Подобная рекурсия и называется «каскадами».

OpenCV предоставляет необходимые инструменты для работы с каскадами Хаара и даже содержит несколько уже обученных классификаторов — в основном это части человеческого тела. Сейчас мы напишем небольшую программу, которая, пользуясь заданным классификатором, будет отмечать на изображении найденные объекты. Я произвожу предварительную обработку исходного изображения, чтобы облегчить работу классификатору.

Эта обработка заключается в обесцвечивании и эквалайзинге гистограммы. Процедура аналогична описанной ранее, поэтому не будем заострять на ней внимание. Если картинка слишком большая, то неплохо бы ее уменьшить, чтобы время поиска не было слишком большим (я пренебрег этим моментом). После чего нам необходимо загрузить один из уже обученных каскадных классификаторов, поставляемых вместе с библиотекой:

CvHaarClassifi erCascade *cascade=(CvHaarClassifi erCascade*) cvLoad("/usr/share/opencv/haarcascades/haarcascade_frontalface_default.xml");

Теперь можно переходить непосредственно к поиску. Обращаю твое внимание, что результатом поиска будет являться связный список прямоугольников, который хранится в пуле памяти, так что этот пул надо не забыть создать, а затем очистить. По окончании поиска мы производим перебор всех найденных прямоугольников, и рисуем их на исходной картинке:

CvSeq faces = cvHaarDetectObjects(img_gray, cascade, storage);
for (size_t i=0; i<faces->total; ++i)
{
CvRect *r = (CvRect
)cvGetSeqElem(faces, i);
cvRectangle(img, cvPoint(r->x, r->y), cvPoint(r->x + r->width, r->y + r->height));
}

После этого остается только создать окно и отобразить в нем исходную картинку, разрисованную прямоугольниками. Как это сделать, я описал ранее. Помимо пользования уже готовыми, обученными каскадными классификаторами, можно обучать и свои — OpenCV предоставляет для этого необходимый набор средств. К сожалению, это весьма объемная тема, выходящая за рамки данной статьи.

ARToolkit

Библиотека OpenCV, безусловно, предоставляет достаточное количество низкоуровневых возможностей и очень хороша для извлечения максимума информации из изображения. Но иногда требуется быстро и качественно найти в кадре ограниченный набор заранее известных объектов. Именно этой задачей в 1999 году заинтересовался японец Хироказу Като, профессор научно-технического института Нары.

Совместно с лабораторией HIT университета Вашингтона он выпустил библиотеку, названную ARToolkit. Основная задача этой библиотеки — отслеживание в кадре заранее известных квадратных маркеров и реконструкция их расположения в пространстве относительно камеры. Эти данные позволяют рассчитывать трехмерные координаты элементов, отрисовываемых поверх кадра с привязкой к этим самым маркерам. Таким образом создается интерфейс дополненной реальности.

Библиотека заточена под координатное пространство OpenGL, что крайне удобно. Также поддерживается работа с захватом видео, тонкой калибровкой камер, обучению работе со своими маркерами и прочие необходимые утилитарные механизмы.

Библиотека предоставляет крайне гибкое API, позволяющее вести как высокоуровневое крупноблочное моделирование, буквально в несколько строк, так и тонкую настройку каждого алгоритма при необходимости реализации нестандартных решений. Проект очень популярен, лежит в основе более чем десяти библиотек, развивающих его идеологию, и портирован в том или ином виде даже на такие нецелевые платформы, как Flash и SilverLight.

Используем ARToolkit на практике

Думаю, настало время написать свое приложение, использующее ARToolkit. В комплекте с библиотекой идут шаблоны четырех тестовых маркеров — Hiro, Kanji, Sample1 и Sample2; pdf’ки для печати можно найти в каталоге patterns. Маркер Sample1 у нас будет являться платформой для синего куба, Hiro — для красного шара, а остальные два не будут являться ничем, мы не будем их обрабатывать. В качестве фреймворка для OpenGL используем GLUT, с его инициализации и начнем:

glutInit(&argc, argv);

Теперь можно запускать подсистемы ARToolkit’а. Начнем с видео: откроем видеокамеру, загрузим стандартные настройки для нее, узнаем разрешение, инициализируем его размерами подсистемы камеры и GUI, запустим захват кадров:

arVideoOpen("");
arVideoInqSize(&frame_width, &frame_height);
arParamLoad("Data/camera_para.dat", 1, &param1);
arParamChangeSize(&param1, frame_width, frame_height, &param2);
arInitCparam(&param2); // Webcam
argInit(&param2, 1.0, 0, 0, 0, 0); // GUI

В каталоге bin/Data лежат файлы с данными тестовых маркеров, их нужно загрузить и получить дескрипторы для дальнейшей работы. На самом деле можно пользоваться не только тестовыми маркерами, на шаблоне blankPatt.gif легко нарисовать любой маркер и оцифровать его с помощью утилиты mk_patt, идущей в комплекте с библиотекой.

mrk1_id = arLoadPatt("Data/patt.sample1");
mrk2_id = arLoadPatt("Data/patt.hiro");

После этого нужно запустить основной цикл ARToolkit’а, который инициализируется тремя callback’ами, первый — для передачи событий с мышки, его мы реализовать не будем и оставим пустым, второй — для событий с клавиатуры (я реализовал там выход при нажатии ESC), и третий — основная функция, в которой и будет происходить обработка изображения:

argMainLoop(NULL, keyFunc, mainFunc);

В функции mainFunc и происходит основное шаманство. Сначала мы берем кадр с камеры и выводим его на экран. Потом ищем на нем маркеры и сличаем дескрипторы найденных с теми, что мы загрузили в самом начале:

ARUint8 *frame = (ARUint8 *)arVideoGetImage();
argDispImage(frame, 0, 0);
arDetectMarker(frame, 100, &mrk_info, &mrk_count);
for (int i=0; i<mrk_count; ++i)
if (mrk1_id==mrk_info[i].id) index=i;

Теперь самое интересное. По найденному маркеру мы можем рассчитать матрицу трансформации для OpenGL. После применения этой матрицы нужные параметры смещений, поворотов и масштабирования настроятся таким образом, что начало координат окажется прямо в центре нашего маркера. Все нарисованное будет вполне натурально спроецировано поверх кадра и окажется на своем месте:

arGetTransMat(&mrk_info[index], mrk1_center, rk1_width, mrk1_trans);
argConvGlpara(mrk1_trans, gl_para);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixd(gl_para);
glutSolidCube(50.0);

Заключение

Надеюсь, мне удалось наглядно показать, что для того, чтобы начать разрабатывать системы Augmented Reality, вполне достаточно твоего старенького ноутбука со встроенной веб-камерой. Библиотеки OpenCV и ARToolkit к твоим услугам, тебе осталось только самое простое и приятное — написать мегасофтину с интерфейсом будущего :).

Links

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии