Класс OpenCV Stitcher с перекрывающимися стационарными камерами

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

Используя stitcher.stitch( )

Учитывая стереопару представлений, я запустил следующий код для некоторых кадров (VideoFile — это пользовательская оболочка для объекта OpenCV VideoCapture):

VideoFile f1( ... );
VideoFile f2( ... );
cv::Mat output_frame;
cv::Stitcher stitcher = cv::Stitcher::createDefault(true);

for( int i = 0; i < num_frames; i++ ) {
    currentFrames.push_back(f1.frame( ));
    currentFrames.push_back(f2.frame( ));
    stitcher.stitch( currentFrames, output_mat );

    // Write output_mat, put it in a named window, etc...

    f1.next_frame();
    f2.next_frame();
    currentFrames.clear();
}

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

Использование estimateTransform( ) и composePanorama( )

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

for( int i = 0; i < num_frames; i++ ) {
    currentFrames.push_back(f1.frame( ));
    currentFrames.push_back(f2.frame( ));

    if( ! have_transform ) {
        status = stitcher.estimateTransform( currentFrames );
    }

    status = stitcher.composePanorama(currentFrames, output_frame );

    // ... as above
}

К сожалению, существует ошибка (задокументирована здесь), из-за которой два представления расходятся в очень странным образом, как на изображениях ниже:

Кадр 1: Кадр 1

Кадр 2: Кадр 2

...

Кадр 8: Кадр 8

Очевидно, это бесполезно, но я подумал, что это может быть просто из-за ошибки, которая в основном продолжает умножать матрицу внутренних параметров на константу каждый раз, когда вызывается composePanorama(). Поэтому я сделал небольшой патч для исправления ошибки, чтобы этого не происходило, но тогда результаты сшивания были плохими. Патч ниже (modules/stitching/src/stitcher.cpp), результаты после него:

243             for (size_t i = 0; i < imgs_.size(); ++i)
244             {
245                 // Update intrinsics
246                // change following to *=1 to prevent scaling error, but messes up stitching.
247                 cameras_[i].focal *= compose_work_aspect;
248                 cameras_[i].ppx *= compose_work_aspect;
249                 cameras_[i].ppy *= compose_work_aspect; 

Результаты: Frame 3Кадр 4

Кто-нибудь знает, как я могу решить эту проблему? По сути, мне нужно выполнить преобразование один раз, а затем применить его к оставшимся кадрам (мы говорим о 30 минутах видео).

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


person n00dle    schedule 29.04.2013    source источник


Ответы (1)


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

Изменения в stitcher.hpp

Добавлена ​​новая функция setCameras() в строке 136:

void setCameras( std::vector<detail::CameraParams> c ) {

     this->cameras_ = c;
 }`

Добавлена ​​новая частная переменная-член, чтобы отслеживать, является ли это нашей первой оценкой:

bool _not_first;

Изменения в stitcher.cpp

В estimateTransform() (строка ~100):

this->not_first = 0;
images.getMatVector(imgs_);
// ... 

В composePanorama() (строка ~227):

// ...
compose_work_aspect = compose_scale / work_scale_;

// Update warped image scale
if( !this->not_first ) { 
    warped_image_scale_ *= static_cast<float>(compose_work_aspect);
    this->not_first = 1;
}   

w = warper_->create((float)warped_image_scale_);
// ...

Код, вызывающий объект stitcher:

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

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

cv::Stitcher stitcher = cv::Stitcher::createDefault(true);
std::vector<cv::detail::CameraParams> cams;
bool have_transform = false;

for( int i = 0; i < num_frames; i++ ) {
        currentFrames.push_back(f1.frame( ));
        currentFrames.push_back(f2.frame( ));

        if( ! have_transform ) {
            status = stitcher.estimateTransform( currentFrames );
            have_transform = true;
            cams = stitcher.cameras();

            // some code to check the status of the stitch and handle errors...
        }

        stitcher.setCameras( cams );
        status = stitcher.composePanorama(currentFrames, output_frame );

        // ... Doing stuff with the panorama
}

Имейте в виду, что это в значительной степени взлом кода OpenCV, который сделает обновление до более новой версии болезненным. К сожалению, у меня было мало времени, поэтому все, что я мог сделать, это неприятный взлом!

person n00dle    schedule 21.05.2013
comment
Вы знаете, остается ли это проблемой в более новой версии openCV? Также у вас есть репозиторий, в котором вы поделились своим результатом? - person theAlse; 22.12.2016
comment
Боюсь, я понятия не имею - мне не нужно было его использовать после этого поста. Также боюсь, что это не репозиторий - я работал над проектом, не финансируемым государством, поэтому код хранился внутри. - person n00dle; 22.12.2016