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

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

OpenCV* и PanoTools*


Я протестировал две самых популярных библиотеки с открытым исходным кодом: OpenCV и PanoTools. Я начал с PanoTools — это достаточно развитая библиотека для сшивания, доступная для Windows*, Mac OS* и Linux*. В ней поддерживается множество расширенных возможностей, обеспечивается стабильное качество. Вторая рассмотренная библиотека — OpenCV. OpenCV — это очень крупный проект, содержащий несколько библиотек по обработке изображений, с обширной базой пользователей. Он выпускается для Windows, Mac OS, Linux, Android* и iOS*. Обе библиотеки включают образцы приложений для сшивания. Приложение в составе PanoTools выполнило работу за 1 минуту 44 секунды. Приложение в составе OpenCV — за 2 минуты 16 секунд. Хотя скорость у PanoTools выше, мы решили использовать OpenCV в качестве отправной точки, учитывая значительную пользовательскую базу этой библиотеки и ее доступность на мобильных платформах.

Описание первоначального приложения и сценария тестирования


Мы будем использовать образец приложения OpenCV cpp-example-stitching_detailed в качестве отправной точки. Приложение запускает конвейер сшивания, который включает несколько раздельных этапов. Вот краткое назначение этих этапов.
  1. Импорт изображений.
  2. Поиск элементов.
  3. Попарное сравнение.
  4. Деформация изображений.
  5. Составление.
  6. Смешение.

Для тестирования мы использовали планшет с четырехъядерной «системой на кристалле» Intel® Atom™ Z3770 с 2 ГБ оперативной памяти под управлением Windows 8.1. Нагрузка состояла в сшивании 16 изображений с разрешением 1280 х 720.

Многопоточный поиск элементов с помощью OpenMP*


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

#pragma omp parallel for
for (int i = 0; i < num_images; ++i)

Теперь цикл будет выполняться в несколько потоков. Но в цикле мы задаем значения переменных fullj'mg и img. Из за этого возникнет конкуренция между потоками, и это повлияет на результат. Самый простой способ решить эту проблему — преобразовать переменные в векторы. Берем следующие объявления переменных:

Mat full_imgj img;

и заменяем их на:

vector<Mat> full_img(num_images);
vector<Mat> img(num_images);

Теперь в цикле мы изменим каждое вхождение каждой переменной на ее новое имя.
full_img становится full_img[i]
img становится img[i]

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

full_img.release();
img.releaseQ;

Затем можно удалить эту строку из этапа составления:

full_img = imread(img_names[img_idx]);

На full_img мы снова ссылаемся при масштабировании в цикле составления. Снова изменяем имена переменных:
full_img становится full_img[img_idx]
img становится img[img_idx]

Итак, первый цикл распараллелен. Теперь нужно распараллелить цикл деформации. Сначала добавляем директиву компилятора, чтобы сделать цикл параллельным:

#pragma omp parallel for
for (int i = 0; i < num_images; ++i)

Это все, что необходимо, чтобы цикл стал параллельным, но можно еще немного оптимизировать этот раздел. Существует второй цикл for сразу после первого. Мы можем перенести работу из него в первый цикл, чтобы сократить количество запускаемых потоков. Переместите эту строку в первый цикл for:

images_warped[i].convertTo(images_warped_f[i], CV_32F);

Также нужно перенести определение переменной images_warped_f выше первого цикла:

vector<Mat> images_warped_f(num_images);

Теперь можно распараллелить цикл составления. Добавляем директиву компилятора перед циклом for:

#pragma omp parallel for
for (int img_idx = 0; img_idx < num_images; ++img_idx)

Третий цикл распараллелен. После всех этих изменений нагрузка выполняется за 2 минуты 8 секунд, то есть на 8 секунд быстрее, чем раньше.

Оптимизация алгоритма попарного сравнения


Попарное сравнение элементов реализовано так, что каждое изображение сравнивается с каждым другим изображением, из-за чего объем работы возрастает по формуле O(n2). В этом нет необходимости, поскольку нам известен порядок, в котором следуют изображения. Надо переписать алгоритм так, чтобы каждое изображение сравнивалось только с соседними по порядку изображениями.
Для этого изменяем вот этот блок:

vector<MatchesInfo> pairwise_matches; Best0f2NearestMatcher matcher(try_gpUj match_conf); matcher(featureSj pairwise_matches); matcher. collectGarbageQ;

на это:

vector<MatchesInfo> pairwise_matches;
Best0f2NearestMatcher matcher(try_gpUj match_conf);
Mat matchMask(feat ures.size()j features.size(),CV_8U, Scalar(0));
for (int i = 0; i < num_images -1; ++i)
{
matchMask.at<char>(i,i+l) =1;
}
matcher(featureSj pairwise_matcheSjmatchMask);
matcher. collectGarbageQ;

Теперь время обработки уменьшилось до 1 минуты 54 секунд, то есть мы выиграли 14 секунд. Обратите внимание, что изображения необходимо импортировать в последовательном порядке.

Оптимизация параметров


Доступны параметры, позволяющие изменять разрешение, при котором мы сравниваем и смешиваем изображения. Благодаря улучшенному алгоритму сравнения мы увеличили устойчивость к ошибкам и можем снизить значения некоторых из этих параметров, чтобы значительно сократить объем работы.
Мы изменили параметры по умолчанию:

double work_megapix =0.6; double seam_megapix = 0.1; float conf_thresh = l.f; string warp_type = "spherical";
int expos_comp_type = ExposureCompensator::GAIN_BLOCKS; string seam_find_type = "gc_color";

на это:

double work_megapix = 0.08;
double seam_megapix = 0.08;
float conf_thresh = 0.5f;
string warp_type = "cylindrical";
int expos_comp_type = ExposureCompensator::GAIN;
string seam_find_type = "dp_colorgrad";

После изменения этих параметров обработка нагрузки заняла 22 секунды, что на 1 минуту 40 секунд быстрее. Такое ускорение обусловлено главным образом изменением значений параметров work_megapix и seam_megapix. Обработка существенно ускорилась, поскольку теперь мы выполняем сравнение и сшивание для очень маленьких изображений. При этом уменьшается количество отличительных элементов, которые можно найти и сравнить, но за счет усовершенствованного алгоритма сравнения мы можем слегка пожертвовать точностью.
Удаление ненужной работы
В цикле составления находятся два блока кода, которые нет необходимости повторять, поскольку мы изначально работаем с изображениями одинакового размера. Они относятся к изменению размера несовпадающих изображений и к инициализации смешения. Эти блоки кода можно переместить непосредственно перед циклом составления:

if (!is_compose_scale_set)
{
	…
}
if (blender.empty())
{
	…
}

Обратите внимание, что в коде деформации есть одна строка, где нужно заменить full_img[img_idx] на full_img[0]. За счет этого изменения мы можем ускорить обработку на 2 секунды: теперь она занимает 20 секунд.

Перемещение поиска элементов


Мы сделали еще одно изменение, но его реализация зависит от того, как именно работает приложение сшивания. В нашем случае приложение снимает изображения, а затем сшивает их сразу после съемки всех изображений. Если в вашем приложении такая же ситуация, можно перенести ту часть конвейера, которая отвечает за поиск элементов, на этап съемки изображений. Для этого нужно запускать алгоритм поиска элементов сразу же после съемки первого изображения и сохранять данные до того момента, когда они понадобятся. В наших экспериментах это обусловило ускорение сшивания примерно на 20 %, то есть в данном случае время сшивания сокращается до 16 секунд.

Логирование


Изначально логирование не включено, но отметим, что при его включении несколько снижается производительность. Мы обнаружили, что, если включить логирование, время сшивания возрастает примерно на 10 %. Поэтому в итоговой версии приложения важно отключить логирование.

Заключение


Учитывая популярность приложений для панорамной съемки на мобильных платформах, важно располагать решением с открытым исходным кодом, способным быстро сшивать изображения в панораму. Ускорив сшивание изображений, мы сделали приложение более удобным для конечных пользователей. Благодаря всем этим изменениям нам удалось сократить общее время сшивания с 2 минут 18 секунд до 16 секунд, то есть ускорить обработку приблизительно в 8,5 раза. Подробная информация приведена в этой таблице.

Изменение Уменьшение времени
Многопоточность с OpenMP* 0:08
Алгоритм попарного сравнения 0:14
Оптимизация начальных параметров 1:40
Удаление ненужной работы 0:02
Перемещение поиска элементов 0:04

Программное обеспечение и нагрузки, использованные в тестах производительности, могли быть оптимизированы для достижения высокой производительности только на микропроцессорах Intel. Тесты производительности, такие как SYSmark* и MobileMark*, проводятся на определенных компьютерных системах, компонентах, программах, операциях и функциях. Любые изменения любого из этих элементов могут привести к изменению результатов. При выборе приобретаемых продуктов следует обращаться к другой информации и тестам производительности, в том числе к тестам производительности определенного продукта в сочетании с другими продуктами.
Конфигурации: четырехядерная «система на кристалле» Intel® Atom™ Z3770 с 2 ГБ оперативной памяти под управлением Windows 8.1. Дополнительные сведения см. на сайте Intel.

Комментарии (1)


  1. kirillzak
    06.05.2015 17:38

    Гм, для рядовых фотографов это слишком сложно.
    Существует OpenSource проект Hugin, который как раз и основан на panotools, в котором можно в GUI склеить панораму.

    P.S. но всё равно спасибо за описание работы алгоритма.