在双摄像头相对平行固定,所拍摄图像视差很小,可使用平移运动模型的情形下,我们提到了“柱面投影+模板匹配+渐入渐出拼接”的解决方案。不考虑多线程,参见图像拼接(四):双摄像头实时视频拼接(平移模型)
可以看出,图像或视频拼接其实是一项”流水线”技术,包括:源图像数据获取(两幅源图像)、柱面投影、模板匹配、图像融合、输出或者是显示拼接后的图像。这个过程中每个步骤有先后,可以说是串行的,怎么能使用多线程处理呢?但是想象一下工厂里的流水线,每个工序在进行的过程中,上一个工序产生的物料都已经准备足,当前工序做完之后,也会立刻传递给下一个工序。所以,不必等待最后的步骤做完,才从头开始。通过这样分析,整个流程的完成时间取决于最慢的那个单步,所以整个效率会有很大提高。
按上面所分,整个过程包括5个单步,所以可以分为5个线程,如果每步耗时比较平均的话,时间效率可以提高5倍!可是,怎么完成“流水线”式的多线程呢?怎么控制每步之间的同步呢?还有“图像融合”这一步需要“源图像数据获取”和“模板匹配(计算平移量)”这两步的同步数据。之前找了一些资料,加上多线程学习的不是很充分,这个问题没有得到很好的解决。
为了简化问题,将线程分为2个,这个问题就容易的多了。第一个线程就叫做“数据准备线程”,它包括帧图像读取,通过模板匹配计算平移量(由于我设计的实验中,柱面投影对结果影响不大,故忽略);第二个线程叫做“数据处理线程”,它包括渐入渐出拼接和拼接后的图像显示。
废话不多说,上代码:

#include"opencv2/core/core.hpp"
#include"opencv2/highgui/highgui.hpp"
#include"opencv2/imgproc/imgproc.hpp"
#include<iostream>
#include<queue>
#include<thread>
#include<mutex>
#include<condition_variable>
#include<time.h>

using namespace std;
using namespace cv;

Mat cylinder(Mat imgIn, int f);
Point2i getOffset(Mat img, Mat img1);
Mat linearStitch(Mat img, Mat img1, Point2i a);

mutex mut;
queue<Point2i> data_queue;
condition_variable data_cond;

VideoCapture cap1(0);
VideoCapture cap2(1);
Mat frame1;
Mat frame2;
Mat frame;
Point2i a;//存储偏移量
bool run = true;
double t = (double)getTickCount();//线程2时间
double t1 = (double)getTickCount();//线程1时间
void data_preparation_thread()
{
    while (run)
    {
        if (cap1.read(frame1) && cap2.read(frame2))
        {
            waitKey(1);
            if (waitKey(20) >= 0)
            {
                run = false;
            }
            imshow("cam1", frame1);
            imshow("cam2", frame2);

            //彩色帧转灰度
            cvtColor(frame1, frame1, CV_RGB2GRAY);
            cvtColor(frame2, frame2, CV_RGB2GRAY);

            //匹配
            //cout << "正在匹配..." << endl;
            t1 = ((double)getTickCount() - t1) / getTickFrequency();
            cout <<"匹配时间:"<< t1 << endl;
            t1 = (double)getTickCount();
            a = getOffset(frame1, frame2);
        }

        lock_guard<mutex> lk(mut);
        data_queue.push(a);
        data_cond.notify_one();

    }
}

void data_processing_thread()
{
    while (run)
    {
        unique_lock<mutex> lk(mut);
        data_cond.wait(lk, []{return !data_queue.empty(); });
        Point2i a = data_queue.front();
        data_queue.pop();
        lk.unlock();
        //cout << "正在拼接"<<endl;
        frame = linearStitch(frame1, frame2, a);
        waitKey(20);
        if (waitKey(20) >= 0)
        {
            run = false;
        }
        t = ((double)getTickCount() - t) / getTickFrequency();
        cout <<"拼接时间:"<< t << endl;
        imshow("stitch", frame);
        t = (double)getTickCount();

    }
}

int main()
{
    //初始化
    if (cap1.isOpened() && cap2.isOpened())
    {
        cout << "*** ***" << endl;
        cout << "摄像头已启动!" << endl;
    }
    else
    {
        cout << "*** ***" << endl;
        cout << "警告:请检查摄像头(数量2)是否安装好!" << endl;
        cout << "程序结束!" << endl << "*** ***" << endl;
        return -1;
    }
    cap1.set(CV_CAP_PROP_FOCUS, 0);
    cap2.set(CV_CAP_PROP_FOCUS, 0);

    //匹配对齐线程
    thread t1(data_preparation_thread);
    //拼接线程
    thread t2(data_processing_thread);
    t1.join();
    t2.join();

    return 0;
}

Point2i getOffset(Mat img, Mat img1)
{
    Mat templ(img1, Rect(0, 0.4*img1.rows, 0.2*img1.cols, 0.2*img1.rows));
    Mat result(img.cols - templ.cols + 1, img.rows - templ.rows + 1, CV_8UC1);//result存放匹配位置信息
    matchTemplate(img, templ, result, CV_TM_CCORR_NORMED);
    normalize(result, result, 0, 1, NORM_MINMAX, -1, Mat());
    double minVal; double maxVal; Point minLoc; Point maxLoc; Point matchLoc;
    minMaxLoc(result, &minVal, &maxVal, &minLoc, &maxLoc, Mat());
    matchLoc = maxLoc;//获得最佳匹配位置
    int dx = matchLoc.x;
    int dy = matchLoc.y - 0.4*img1.rows;//右图像相对左图像的位移
    Point2i a(dx, dy);
    return a;
}


Mat linearStitch(Mat img, Mat img1, Point2i a)
{
    int d = img.cols - a.x;//过渡区宽度
    int ms = img.rows - abs(a.y);//拼接图行数
    int ns = img.cols + a.x;//拼接图列数
    Mat stitch = Mat::zeros(ms, ns, CV_8UC1);
    //拼接
    Mat_<uchar> ims(stitch);
    Mat_<uchar> im(img);
    Mat_<uchar> im1(img1);

    if (a.y >= 0)
    {
        Mat roi1(stitch, Rect(0, 0, a.x, ms));
        img(Range(a.y, img.rows), Range(0, a.x)).copyTo(roi1);
        Mat roi2(stitch, Rect(img.cols, 0, a.x, ms));
        img1(Range(0, ms), Range(d, img1.cols)).copyTo(roi2);
        for (int i = 0; i < ms; i++)
            for (int j = a.x; j < img.cols; j++)
                ims(i, j) = uchar((img.cols - j) / float(d)*im(i + a.y, j) + (j - a.x) / float(d)*im1(i, j - a.x));

    }
    else
    {
        Mat roi1(stitch, Rect(0, 0, a.x, ms));
        img(Range(0, ms), Range(0, a.x)).copyTo(roi1);
        Mat roi2(stitch, Rect(img.cols, 0, a.x, ms));
        img1(Range(-a.y, img.rows), Range(d, img1.cols)).copyTo(roi2);
        for (int i = 0; i < ms; i++)
            for (int j = a.x; j < img.cols; j++)
                ims(i, j) = uchar((img.cols - j) / float(d)*im(i, j) + (j - a.x) / float(d)*im1(i + abs(a.y), j - a.x));
    }


    return stitch;
}

对代码做些解释说明:
关于C++的多线程,linux和windows上都有相关的库,我查找了windows编程的书,里面的有些多线程函数过于古老,感觉杂乱。后又了解到C++11标准库里有多线程库,这个比较新,又是语言层面的,通用性和移植性更高,所以采用了C++11的多线程库。可以看到,有关多线程头文件如下:

#include<thread>
#include<mutex>
#include<condition_variable>

代码中使用了多线程中互斥量、锁、条件变量的概念。如果对这些概念感到茫然,可以学习下C++11线程库的教程。稍有学习后你会发现上述代码很简单。

为了帮助理解,画了张简图,(图中while的意思仅代表每个线程内部都不停在循环处理)

java 多个摄像头画面拼接融合成一个画面 多摄像头视频融合_#include


通俗的理解,互斥量是一块有限的资源,每次只能有一个线程占据它。占据它的方式就是用一个锁,将它锁定。

//定义一个互斥量
mutex mut;
//定义一个锁,锁定互斥量
lock_guard<mutex> lk(mut);

还可以用另一种锁的类型定义锁,这种类型更加灵活

//定义一个锁,锁定互斥量
unique_lock<mutex> lk(mut);
//...
//解锁
lk.unlock();

定义和创建一个线程,要定义它的线程处理函数。运行它,使用join()函数。

//匹配对齐线程
thread t1(data_preparation_thread);
//拼接线程
thread t2(data_processing_thread);
t1.join();
t2.join();

所以整个代码,大部分的工作是完成两个线程处理函数。
线程1(数据准备):执行读帧和计算平移量的工作,工作完成后,要求锁定互斥量,将结果压入队列,并通过条件变量发出通知。
线程2(数据处理):锁定互斥量,通过条件变量等待消息,仅当消息传来,拿到数据后才释放(有些霸道哦)。。拿到数据后,解锁互斥量,然后就可以愉快地执行任务了:图像融合和拼接结果的显示。
注:我现在分析代码,发现仅仅将平移量结果压入队列,而当前帧数据是通过全局变量实现更新的,这样的做法可以work,但有些草率,可以再定义两个Mat类型的队列的.

最后分析实验结果的时间效率,发现符合预期,拼接时间降低大约12。

java 多个摄像头画面拼接融合成一个画面 多摄像头视频融合_#include_02