如何实时拼接来自视频摄像头的图像?

12

我使用四个静止的摄像头。这些摄像头彼此之间不会移动。而我希望能够将它们的视频图像实时地拼接成一个统一的视频图像。

我使用OpenCV 2.4.10和cv:stitcher类来完成这项任务,代码如下:

// use 4 video-cameras
cv::VideoCapture cap0(0), cap1(1), cap2(2), cap3(3);

bool try_use_gpu = true;    // use GPU
cv::Stitcher stitcher = cv::Stitcher::createDefault(try_use_gpu);
stitcher.setWarper(new cv::CylindricalWarperGpu());
stitcher.setWaveCorrection(false);
stitcher.setSeamEstimationResol(0.001);
stitcher.setPanoConfidenceThresh(0.1);

//stitcher.setSeamFinder(new cv::detail::GraphCutSeamFinder(cv::detail::GraphCutSeamFinderBase::COST_COLOR_GRAD));
stitcher.setSeamFinder(new cv::detail::NoSeamFinder());
stitcher.setBlender(cv::detail::Blender::createDefault(cv::detail::Blender::NO, true));
//stitcher.setExposureCompensator(cv::detail::ExposureCompensator::createDefault(cv::detail::ExposureCompensator::NO));
stitcher.setExposureCompensator(new cv::detail::NoExposureCompensator());


std::vector<cv::Mat> images(4);
cap0 >> images[0];
cap1 >> images[1];
cap2 >> images[2];
cap3 >> images[3];

// call once!
cv::Stitcher::Status status = stitcher.estimateTransform(images);


while(true) {

    // **lack of speed, even if I use old frames**
    // std::vector<cv::Mat> images(4);
    //cap0 >> images[0];
    //cap1 >> images[1];
    //cap2 >> images[2];
    //cap3 >> images[3];

    cv::Stitcher::Status status = stitcher.composePanorama(images, pano_result);
}

我只能获得10帧每秒的速度(FPS),但我需要25 FPS。如何加速这个例子?
当我使用stitcher.setWarper(new cv::PlaneWarperGpu());时,会得到一个非常扩大的图像,而我不需要这个。
我只需要进行平移操作。
例如,我可以不使用:
透视变换
比例变换
甚至旋转
我该怎么做?或者我怎样才能从cv::Stitcher stitcher参数中获取每个图像的平移参数x,y
更新-在Windows 7 x64上的MSVS 2013中进行性能分析:

1
TBB 可以通过多线程处理来提高速度...您是为哪个系统构建,并使用哪些构建工具?另外,可以尝试将图像数组声明/定义放在 while 循环之外。(您在每个循环中进行分配和释放)。我建议您在拼接函数周围放置一些计时器,以检查该函数调用是否是实际的瓶颈。 - Antonio
你可以测试一下这样是否会提高速度吗?在之前提到的计时器中加入很重要,以排除你卡在读取帧上的可能性。如果完全跳过拼接步骤,你能得到 25fps 吗? - Antonio
1
@Antonio 不是缺乏速度,即使我在每次迭代中使用旧帧。我使用640x480的RGB帧。我添加了来自Windows上MSVS 2013的分析信息。 - Alex
非常棒的性能分析!同时,这里有一些有用的信息:如果你只需要翻译,那么拼接可能不是你所需要的。你说翻译就足够了:你的相机是否都在同一轴和平面上,并且垂直于该轴定向? - Antonio
另外一件事:在OpenCV 2.4中,你不是必须使用GpuMat而不是Mat吗?我认为到目前为止你还没有使用gpu... - Antonio
显示剩余6条评论
2个回答

15

cv::Stitcher相对较慢。如果您的相机之间绝对不移动,而且变换像您说的那样简单,您应该能够通过链接单应矩阵将图像叠加到空白画布上。

以下内容有点数学 - 如果不清楚,我可以使用LaTeX正确地编写它,但SO不支持漂亮的数学符号 :)

您有一组4个相机,从左到右为(C_1、C_2、C_3、C_4),给出了一组4个图像(I_1、I_2、I_3、I_4)

要从I_1转换到I_2,您需要一个3x3的变换矩阵,称为单应矩阵。我们将其称为H_12。同样,对于I_2I_3,我们有H_23,对于I_3I_4,您将拥有H_34

你可以使用标准方法(重叠相机之间的点匹配)预先校准这些单应性。需要创建一个空矩阵作为画布。可以猜测其大小(4 * image_size足够),也可以取右上角(称之为P1_tr)并通过三个单应性进行变换,从而在全景图像的右上方得到一个新点PP_tr(假定已将P1_tr转换为矩阵)。
PP_tr = H_34 * H_23 * H_12 * P1_tr'

这个操作的作用是将P1_tr先转换到相机2,然后从C_2C_3,最后从C_3C_4
您需要创建以下内容以组合图像1和2、图像1、2和3以及图像1-4,我将它们称为V_12V_123V_1234
使用以下内容将图像变形到画布上:
cv::warpAffine(I_2, V_12, H_12, V_12.size( ));

然后对下一张图片执行相同的操作:
cv::warpAffine(I_3, V_123, H_23*H_12, V_123.size( ));
cv::warpAffine(I_4, V_1234, H_34*H_23*H_12, V_1234.size( ));

现在你有四个画布,所有的画布都是四张图像组合后的宽度,并且每个画布中的一张图像都被转换到了相应的位置。
剩下的就是将转换后的图像合并到彼此上。这可以很容易地通过使用感兴趣区域来实现。
创建ROI掩模可以提前完成,在捕获帧之前完成。
从一个与你的画布大小相同的空白(零)图像开始。将最左边的矩形设置为I_1的大小,变成白色。这是第一张图像的掩模。我们将其称为M_1
接下来,要获取第二个转换图像的掩模,我们执行:
cv::warpAffine(M_1, M_2, H_12, M_1.size( ));
cv::warpAffine(M_2, M_3, H_23*H_12, M_1.size( ));
cv::warpAffine(M_3, M_4, H_34*H_23*H_12, M_1.size( ));

为了将所有图像合并成一个全景图,您需要执行以下操作:
cv::Mat pano = zeros(M_1.size( ), CV_8UC3);
I_1.copyTo(pano, M_1);
V_12.copyTo(pano, M_2): 
V_123.copyTo(pano, M_3): 
V_1234.copyTo(pano, M_4): 

你所做的是将每个画布的相关区域复制到输出图像上,这是一个快速的操作。
你应该能够在GPU上完成所有这些操作,用cv::gpu::Mat替换cv::Mats,用cv::gpu::warpAffine代替非GPU版本。

2
为了使这种方法更加高效,您可以预先计算仿射变换映射,因为它们是恒定的,并使用cv :: remap(参见[doc](http://docs.opencv.org/modules/imgproc/doc/geometric_transformations.html#remap)),它比`cv :: warpAffine`快得多。 - BConic
@n00dle非常感谢!但是你说我必须找到一个名为“单应性”的3x3变换矩阵,并使用:cv::warpAffine();,但是据我所知,warpAffine();使用的是仿射变换矩阵(2x3)。这是否意味着我必须在estimateRigidTransform();之后使用warpAffine();?还是我必须在findHomography();之后使用warpPerspective();?我如何从使用findHomography();找到的单应性中获取ramap();map1, map2 - Alex
抱歉,我混淆了这两个问题。在这种情况下,您说您只需要翻译,那么使用estimateRigidTransform估计的仿射矩阵就可以解决问题。如果您发现这不能完全表示变换(即叠加的图像以非均匀方式有些不对齐),则可能需要使用单应性矩阵。我对remap问题不是100%确定 - 我想我在工作中有代码,但由于英国正在庆祝复活节假期,我要到周三才能回去。我建议查看initUndistortRectifyMap()因为我觉得它可能与此有关。 - n00dle
嗨,感谢你的解释。我正在尝试非常相似的东西。让我困扰的是,在进行单应性变换时,图像周围会出现大量无用的黑色区域。有没有办法只获取图像的有用部分?(出于性能原因,我不想使用for循环)。这就是我的意思:原始图像:http://imgur.com/a/wUlwE,然后我应用单应性变换来获得鸟瞰图:http://imgur.com/a/49Ee5(请注意,窗口中存在大量不需要的黑色区域),我想要的是:http://imgur.com/a/gCoEC(我手动裁剪了它),而不会丢失太多数据。 - LandonZeKepitelOfGreytBritn
一旦您获得了3x3的单应性矩阵(H),就可以很容易地计算出原始图像(称为A)在新图像空间(B)中角落的位置。对于某个角落A =(x,y),将其表示为齐次坐标A' =(x,y,1),然后执行B' = HA'。使用此方法将为您提供新图像中的四个角点,因此您可以根据这些选择裁剪它。 - n00dle
显示剩余2条评论

1
注意:我将这个答案留下来作为尝试的记录,因为我提出的方法似乎不起作用,而当使用cv :: Mat时显然GPU已经在使用。
尝试使用gpu::GpuMat
std::vector<cv::Mat> images(4);
std::vector<gpu::GpuMat> gpuImages(4);
gpu::GpuMat pano_result_gpu;
cv::Mat pano_result; 
bool firstTime = true;

[...] 

cap0 >> images[0];
cap1 >> images[1];
cap2 >> images[2];
cap3 >> images[3];
for (int i = 0; i < 4; i++)
   gpuImages[i].upload(images[i]);
if (firstTime) {
    cv::Stitcher::Status status = stitcher.estimateTransform(gpuImages);
    firstTime = false;
    }
cv::Stitcher::Status status = stitcher.composePanorama(gpuImages, pano_result_gpu);
pano_result_gpu.download(pano_result);

当我在estimateTransform();composePanorama();中使用cv :: gpu :: GpuMat而不是cv :: Mat时,我会在Windows 7x64 + OpenCV 2.4.9上遇到错误:* OpenCV错误:在cv :: resize中断言失败(func!= 0),文件C:\ opencv_2.4.9 \ opencv \ sources \ modules \ imgproc \ src \ imgwarp.cpp,第1980行* - Alex
@Alex 至少证明了GPU以前没有被使用 :) 请更新你的问题,并发布你在 C:\opencv_2.4.9\opencv\sources\modules\imgproc\src\imgwarp.cpp 大约在第1980行发现的代码。 - Antonio
但是我在我的问题中添加的MSVS 2013分析结果显示GPU被多次使用 :) 错误代码在这里:http://pastebin.com/VVN1gMK2 - Alex
@Alex 我看到我有一个2.4.9的副本,但那很难调试...是谁在调用cv::resize并且显然使用了错误的插值属性?也许最终使用gpu::GpuMat并不是正确的方法。 - Antonio

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接