Opencv2.4.9源码分析——Stitching(九)

9、Stitcher类

图像拼接方法用到的算法较多,内容较复杂,可能对于一些人来说用起来过于繁琐,因此Opencv把拼接算法封装到了Stitcher类中,这样就不必理会拼接算法中的具体实现过程。

下面我们就简单介绍一下Stitcher类中的一些常用的函数:

 

用系统缺省值创建图像拼接器stitcher:

Stitcher Stitcher::createDefault(bool try_use_gpu)
//try_use_gpu表示是否应用图像处理器
{
    Stitcher stitcher;    //实例化Stitcher类
    //设置全局变量registr_resol_为0.6,registr_resol_表示图像配准分辨率
    stitcher.setRegistrationResol(0.6);
    //设置全局变量seam_est_resol_为0.1,seam_est_resol_表示接缝分辨率
    stitcher.setSeamEstimationResol(0.1);
    //ORIG_RESOL = -1,设置全局变量compose_resol_为ORIG_RESOL,compose_resol_表示合成分辨率
    stitcher.setCompositingResol(ORIG_RESOL);
    //设置全局变量conf_thresh_为1,conf_thresh_表示匹配置信度,即式25
    stitcher.setPanoConfidenceThresh(1);
    //设置全局变量do_wave_correct_为true,do_wave_correct_表示是否进行波形校正
    stitcher.setWaveCorrection(true);
    //设置全局变量wave_correct_kind_为WAVE_CORRECT_HORIZ,wave_correct_kind_表示波形校正的方式,这里是用的水平校正
    stitcher.setWaveCorrectKind(detail::WAVE_CORRECT_HORIZ);
    //设置全局变量features_matcher_为BestOf2NearestMatcher(),features_matcher_表示特征匹配方法,这里是用2-NN方法
    stitcher.setFeaturesMatcher(new detail::BestOf2NearestMatcher(try_use_gpu));
    //设置全局变量bundle_adjuster_为BundleAdjusterRay(),bundle_adjuster_表示光束平差法,这里用的是射线发散方法
    stitcher.setBundleAdjuster(new detail::BundleAdjusterRay());

#if defined(HAVE_OPENCV_GPU) && !defined(DYNAMIC_CUDA_SUPPORT)
    if (try_use_gpu && gpu::getCudaEnabledDeviceCount() > 0)
    {
#if defined(HAVE_OPENCV_NONFREE)
        stitcher.setFeaturesFinder(new detail::SurfFeaturesFinderGpu());
#else
        stitcher.setFeaturesFinder(new detail::OrbFeaturesFinder());
#endif
        stitcher.setWarper(new SphericalWarperGpu());
        stitcher.setSeamFinder(new detail::GraphCutSeamFinderGpu());
    }
    else
#endif
    {
#ifdef HAVE_OPENCV_NONFREE    //表示可以使用NONFREE
        //设置全局变量features_finder_为SurfFeaturesFinder(),features_finder_表示特征检测的方法,在这里是用SURF算法
        stitcher.setFeaturesFinder(new detail::SurfFeaturesFinder());
#else    //表示不可以使用NONFREE
        //设置全局变量features_finder_为OrbFeaturesFinder(),在这里用的是ORB算法
        stitcher.setFeaturesFinder(new detail::OrbFeaturesFinder());
#endif
        //设置全局变量warper_为SphericalWarper(),warper_表示图像投影变换的方法,在这里是球面投影方法
        stitcher.setWarper(new SphericalWarper());
        //设置全局变量seam_finder_为GraphCutSeamFinderBase::COST_COLOR,seam_finder_表示寻找接缝线的算法,在这里是图割法,而且误差表面函数是直接法
        stitcher.setSeamFinder(new detail::GraphCutSeamFinder(detail::GraphCutSeamFinderBase::COST_COLOR));
    }
    //设置全局变量exposure_comp_为BlocksGainCompensator(),exposure_comp_表示曝光补偿方法,在这里是分块增益补偿方法
    stitcher.setExposureCompensator(new detail::BlocksGainCompensator());
    //设置全局变量blender_为MultiBandBlender(),blender_表示融合算法,在这里是多频段融合方法
    stitcher.setBlender(new detail::MultiBandBlender(try_use_gpu));

    return stitcher;    //返回Stitcher类
}

estimateTransform函数用于匹配图像,并评估相机的旋转参数:

Stitcher::Status Stitcher::estimateTransform(InputArray images)
//images表示待拼接的输入图像
{
    //调用另一种形式的estimateTransform函数
    return estimateTransform(images, vector >());
}
Stitcher::Status Stitcher::estimateTransform(InputArray images, const vector > &rois)
//images表示待拼接的输入图像
//rois表示输入图像中感兴趣的矩形区域,即只对该区域进行拼接
{
    images.getMatVector(imgs_);    //图像赋值
    rois_ = rois;    //赋值

    Status status;
    //调用matchImages函数,用于匹配图像,该函数在后面给出介绍
    if ((status = matchImages()) != OK)
        return status;
    //调用estimateCameraParams函数,用于评估相机参数,该函数在后面给出介绍
    estimateCameraParams();

    return OK;
}

composePanorama函数用于合并拼接图像:

Stitcher::Status Stitcher::composePanorama(OutputArray pano)
//pano表示最终得到的全景图像
{
    //调用另一种形式的composePanorama函数
    return composePanorama(vector(), pano);
}
Stitcher::Status Stitcher::composePanorama(InputArray images, OutputArray pano)
//images表示经过变形处理后的图像
//pano表示最终得到的全景图像
{
    LOGLN("Warping images (auxiliary)... ");

    vector imgs;
    images.getMatVector(imgs);    //赋值
    //如果imgs不为空,即输入参数images有值,则需要把images的相关值赋予imgs_
    if (!imgs.empty())    //如果imgs不为空,即有值
    {
        CV_Assert(imgs.size() == imgs_.size());    //确保图像数量相同

        Mat img;
        //定义seam_est_imgs_的数量,seam_est_imgs_表示在进行接缝线算法后得到的图像集合,由于尺度变换,该图像的尺寸与源图尺寸会不一样
        seam_est_imgs_.resize(imgs.size());

        for (size_t i = 0; i < imgs.size(); ++i)    //遍历各个图像,改变图像尺寸
        {
            imgs_[i] = imgs[i];    //赋值
            //根据尺度seam_scale_变换当前图像的尺寸
            resize(imgs[i], img, Size(), seam_scale_, seam_scale_); 
            seam_est_imgs_[i] = img.clone();    //图像赋值
        }
        //下面两个变量分别表示图像seam_est_imgs_和图像imgs_的子集,这两个集合的图像只是图像尺寸不同
        vector seam_est_imgs_subset; 
        vector imgs_subset; 
        //indices_表示真正可以拼接在一幅全景图像的待拼接图像的数量,即可能有些图像是不属于拼接图像的,indices_由leaveBiggestComponent得到
        for (size_t i = 0; i < indices_.size(); ++i)    //遍历图像
        {
            //分别得到图像seam_est_imgs_和图像imgs_的子集
            imgs_subset.push_back(imgs_[indices_[i]]);    //放入队列
            seam_est_imgs_subset.push_back(seam_est_imgs_[indices_[i]]);    //放入队列
        }

        seam_est_imgs_ = seam_est_imgs_subset;    //赋值
        imgs_ = imgs_subset;    //赋值
    }

    Mat &pano_ = pano.getMatRef();    //赋值

#if ENABLE_LOG
    int64 t = getTickCount();    //用于计时
#endif
    //表示经过映射变换后,各个图像在全景图像坐标系上的左上角坐标
    vector corners(imgs_.size());
    vector masks_warped(imgs_.size());    //表示映射变换图的掩码
    vector images_warped(imgs_.size());    //表示映射变换图
    vector sizes(imgs_.size());    //表示映射变换图的尺寸
    vector masks(imgs_.size());    //表示映射变换前的图像掩码

    // Prepare image masks
    for (size_t i = 0; i < imgs_.size(); ++i)    //遍历图像,准备掩码
    {
        masks[i].create(seam_est_imgs_[i].size(), CV_8U);    //定义尺寸大小
        masks[i].setTo(Scalar::all(255));    //掩码矩阵先赋值为255,表示不掩码
    }

    // Warp images and their masks
    //定义图像映射变换类,这里的warped_image_scale_就是焦距,seam_work_aspect_表示接缝尺度与匹配尺度的比值,warped_image_scale_和seam_work_aspect_的乘积表示映射变换的尺度
    Ptr w = warper_->create(float(warped_image_scale_ * seam_work_aspect_));
    for (size_t i = 0; i < imgs_.size(); ++i)    //遍历所有图像
    {
        Mat_ K;
        cameras_[i].K().convertTo(K, CV_32F);    //得到当前相机的内参数
        //依据seam_work_aspect_的大小调整相机的内参数,之所以要调整内参数,是因为得到内参数的图像与要映射变换的图像的尺寸不一样
        K(0,0) *= (float)seam_work_aspect_;    //表示fu
        K(0,2) *= (float)seam_work_aspect_;    //表示cx
        K(1,1) *= (float)seam_work_aspect_;    //表示fv
        K(1,2) *= (float)seam_work_aspect_;    //表示cy
        //对图像进行映射变换,得到图像映射图images_warped
        corners[i] = w->warp(seam_est_imgs_[i], K, cameras_[i].R, INTER_LINEAR, BORDER_REFLECT, images_warped[i]);
        sizes[i] = images_warped[i].size();    //得到尺寸
        //对掩码进行映射变换,得到掩码映射图masks_warped
        w->warp(masks[i], K, cameras_[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);
    }

    vector images_warped_f(imgs_.size());
    for (size_t i = 0; i < imgs_.size(); ++i)    //变换图像的数据类型
        images_warped[i].convertTo(images_warped_f[i], CV_32F);

    LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");

    // Find seams
    //生成曝光补偿器
    exposure_comp_->feed(corners, images_warped, masks_warped);
    seam_finder_->find(images_warped_f, corners, masks_warped);    //寻找接缝线

    // Release unused memory
    //释放一些内存空间
    seam_est_imgs_.clear();
    images_warped.clear();
    images_warped_f.clear();
    masks.clear();

    LOGLN("Compositing...");
#if ENABLE_LOG
    t = getTickCount();
#endif

    Mat img_warped, img_warped_s;    //表示不同数据类型的映射变换图像
    //分别表示不同过程中的掩码图像
    Mat dilated_mask, seam_mask, mask, mask_warped;

    //double compose_seam_aspect = 1;
    double compose_work_aspect = 1;    //表示合成尺度与匹配尺度之比
    bool is_blender_prepared = false;    //表示融合器是否准备好

    double compose_scale = 1;    //表示合成尺度
    bool is_compose_scale_set = false;    //表示合成尺度是否设置好

    Mat full_img, img;
    for (size_t img_idx = 0; img_idx < imgs_.size(); ++img_idx)    //遍历所有图像
    {
        LOGLN("Compositing image #" << indices_[img_idx] + 1);

        // Read image and resize it if necessary
        full_img = imgs_[img_idx];    //得到当前图像
        if (!is_compose_scale_set)    //compose_scale还没有被设置
        {
            //compose_resol_被初始化为-1,因此compose_scale并没有被重新赋值
            if (compose_resol_ > 0)
                compose_scale = min(1.0, sqrt(compose_resol_ * 1e6 / full_img.size().area()));
            //表示compose_scale已被设置,则在下次遍历图像时,无需再调整映射变换图的尺寸和左上角坐标
            is_compose_scale_set = true; 

            // Compute relative scales
            //compose_seam_aspect = compose_scale / seam_scale_;
            //得到合成尺度与匹配尺度之比
            compose_work_aspect = compose_scale / work_scale_;

            // Update warped image scale
            //调整映射变换尺度
            warped_image_scale_ *= static_cast(compose_work_aspect);
            //根据调整后的映射变换尺度,重新得到映射变换器
            w = warper_->create((float)warped_image_scale_);

            // Update corners and sizes
            //更新映射变换图像的参数:左上角坐标和尺寸
            for (size_t i = 0; i < imgs_.size(); ++i)    //遍历所有图像
            {
                // Update intrinsics
                //更新相机的内参数
                cameras_[i].focal *= compose_work_aspect;
                cameras_[i].ppx *= compose_work_aspect;
                cameras_[i].ppy *= compose_work_aspect;

                // Update corner and size
                Size sz = full_img_sizes_[i];    //表示当前图像尺寸
                if (std::abs(compose_scale - 1) > 1e-1)    //调整图像尺寸
                {
                    sz.width = cvRound(full_img_sizes_[i].width * compose_scale);
                    sz.height = cvRound(full_img_sizes_[i].height * compose_scale);
                }

                Mat K;
                cameras_[i].K().convertTo(K, CV_32F);    //转换相机参数的数据格式
                Rect roi = w->warpRoi(sz, K, cameras_[i].R);    //得到映射变换图的矩形
                corners[i] = roi.tl();    //映射变换图在全景图像坐标系上的左上角坐标
                sizes[i] = roi.size();    //得到映射变换图的尺寸
            }
        }
        //依据合成尺度compose_scale,调整图像的尺寸
        if (std::abs(compose_scale - 1) > 1e-1)
            resize(full_img, img, Size(), compose_scale, compose_scale);
        else
            img = full_img;
        full_img.release();    //释放一些内存空间
        Size img_size = img.size();

        Mat K;
        cameras_[img_idx].K().convertTo(K, CV_32F);    //转换相机参数的数据格式

        // Warp the current image
        //对改变了尺寸的当前图像进行映射变换
        w->warp(img, K, cameras_[img_idx].R, INTER_LINEAR, BORDER_REFLECT, img_warped);

        // Warp the current image mask
        mask.create(img_size, CV_8U);
        mask.setTo(Scalar::all(255));
        //映射变换当前图像的掩码,得到映射变换图掩码
        w->warp(mask, K, cameras_[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped);

        // Compensate exposure
        //完成曝光补偿
        exposure_comp_->apply((int)img_idx, corners[img_idx], img_warped, mask_warped);

        img_warped.convertTo(img_warped_s, CV_16S);    //改变映射变换图的数据类型
        img_warped.release();    //释放一些内存空间
        img.release();
        mask.release();

        // Make sure seam mask has proper size
        //对因合成而改变图像尺寸之前的映射变换掩码(即对该函数内seam_finder_->find函数得到的掩码)进行膨胀运算,使掩码面积缩小,从而扩展了接缝线附近的区域
        dilate(masks_warped[img_idx], dilated_mask, Mat());
        //再把掩码图像的尺寸调整为mask_warped(即调整到因合成而改变图像尺寸下的映射变换掩码,由该函数内exposure_comp_->apply函数得到)的大小尺寸, 
        resize(dilated_mask, seam_mask, mask_warped.size());
        //“与”操作,得到更准确的掩码图像,即mask_warped仅仅是扩展了接缝线附近的区域
        mask_warped = seam_mask & mask_warped;

        //在该循环体的第一次循环的时候,准备融合器,即定义好全景图像的大小
        if (!is_blender_prepared)
        {
            blender_->prepare(corners, sizes);    //准备融合器
            is_blender_prepared = true;    //置1,避免重复调用blender_->prepare
        }

        // Blend the current image
        //把当前图像的数据添加进融合器内
        blender_->feed(img_warped_s, mask_warped, corners[img_idx]);
    }

    Mat result, result_mask;
    blender_->blend(result, result_mask);    //得到全景图像result及它的掩码result_mask

    LOGLN("Compositing, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");

    // Preliminary result is in CV_16SC3 format, but all values are in [0,255] range,
    // so convert it to avoid user confusing
    result.convertTo(pano_, CV_8U);    //得到全景图像pano_

    return OK;
}

如果对拼接的执行过程不熟悉,那么也是不清楚estimateTransform函数和composePanorama函数的具体含义和执行顺序的。好在Opencv还定义了stitch函数,它完全回避了拼接的具体执行过程,也就是直接调用该函数即可:

Stitcher::Status Stitcher::stitch(InputArray images, OutputArray pano)
//images表示输入的待拼接图像
//pano表示最终得到的全景图像
{
    Status status = estimateTransform(images);    //调用estimateTransform函数
    if (status != OK)
        return status;
    return composePanorama(pano);    //调用composePanorama函数
}
Stitcher::Status Stitcher::stitch(InputArray images, const vector > &rois, OutputArray pano)
//images表示输入的待拼接图像
//rois表示输入图像中感兴趣的矩形区域,即只对该区域进行拼接
//pano表示最终得到的全景图像
{
    Status status = estimateTransform(images, rois);    //调用estimateTransform函数
    if (status != OK)
        return status;
    return composePanorama(pano);    //调用composePanorama函数
}

下面我们再给出前面函数用到的两个重要函数:

Stitcher::Status Stitcher::matchImages()    //图像匹配
{
    if ((int)imgs_.size() < 2)    //只有两幅以上的图像才能拼接
    {
        LOGLN("Need more images");
        return ERR_NEED_MORE_IMGS;
    }

    work_scale_ = 1;    //表示匹配尺度
    seam_work_aspect_ = 1;    //表示work_scale_和seam_scale_的比值
    seam_scale_ = 1;    //表接缝尺度
    bool is_work_scale_set = false;    //表示work_scale_是否被设置
    bool is_seam_scale_set = false;    //表示seam_scale_是否被设置
    Mat full_img, img;
    features_.resize(imgs_.size());    //定义特征点的矢量数量
    seam_est_imgs_.resize(imgs_.size());    //定义seam_est_imgs_的矢量数量
    full_img_sizes_.resize(imgs_.size());    //full_img_sizes_表示图像尺寸

    LOGLN("Finding features...");
#if ENABLE_LOG
    int64 t = getTickCount();
#endif

    for (size_t i = 0; i < imgs_.size(); ++i)    //遍历所有图像,寻找特征点
    {
        full_img = imgs_[i];    //得到当前图像
        full_img_sizes_[i] = full_img.size();    //得到当前图像的尺寸
        //registr_resol_被设置为0.6,所以执行的是else内容
        if (registr_resol_ < 0)
        {
            img = full_img;
            work_scale_ = 1;
            is_work_scale_set = true;
        }
        else
        {
            //没有设置work_scale_,则此时需要设置work_scale_
            if (!is_work_scale_set)
            {
                //由图像配准分辨率和图像面积设置匹配尺度
                work_scale_ = min(1.0, sqrt(registr_resol_ * 1e6 / full_img.size().area()));
                is_work_scale_set = true;    //避免再次循环重复设置work_scale_
            }
            //由work_scale_调整图像尺寸
            resize(full_img, img, Size(), work_scale_, work_scale_);
        }
        //没有设置seam_scale_,则此时需要设置seam_scale_
        if (!is_seam_scale_set)
        {
            //由接缝分辨率和图像面积设置接缝尺度
            seam_scale_ = min(1.0, sqrt(seam_est_resol_ * 1e6 / full_img.size().area()));
            //设置seam_work_aspect_
            seam_work_aspect_ = seam_scale_ / work_scale_;
            is_seam_scale_set = true;    //避免再次循环重复设置seam_scale_
        }

        if (rois_.empty())    //表示图像的所有区域都是寻找特征点的区域
            (*features_finder_)(img, features_[i]);    //寻找图像img的特征点
        else    //否则在指定区域内寻找特征点
        {
            vector rois(rois_[i].size());    //定义区域矩形
            for (size_t j = 0; j < rois_[i].size(); ++j)    //得到该区域在当前图像的位置
            {
                Point tl(cvRound(rois_[i][j].x * work_scale_), cvRound(rois_[i][j].y * work_scale_));
                Point br(cvRound(rois_[i][j].br().x * work_scale_), cvRound(rois_[i][j].br().y * work_scale_));
                rois[j] = Rect(tl, br);    //得到区域矩形
            }
            //在图像img中的rois区域内寻找特征点
            (*features_finder_)(img, features_[i], rois);
        }
        features_[i].img_idx = (int)i;    //赋值索引
        LOGLN("Features in image #" << i+1 << ": " << features_[i].keypoints.size());

        resize(full_img, img, Size(), seam_scale_, seam_scale_);    //改变图像尺寸
        seam_est_imgs_[i] = img.clone();    //赋值
    }

    // Do it to save memory
    features_finder_->collectGarbage();    //收集内存碎片
    full_img.release();    //释放内存
    img.release();    //释放内存

    LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");

    LOG("Pairwise matching");
#if ENABLE_LOG
    t = getTickCount();
#endif
    (*features_matcher_)(features_, pairwise_matches_, matching_mask_);    //特征点匹配
    features_matcher_->collectGarbage();    //收集内存碎片
    LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");

    // Leave only images we are sure are from the same panorama
    //调用leaveBiggestComponent函数,得到全景图像集合(即输入图像中有可能有些图像并不能构成全景图像),indices_为全景图像集合的图像索引
    indices_ = detail::leaveBiggestComponent(features_, pairwise_matches_, (float)conf_thresh_);
    vector seam_est_imgs_subset;    //表示seam_est_imgs_的子集
    vector imgs_subset;    //表示imgs_的子集
    vector full_img_sizes_subset;    //表示imgs_subset集合中图像的尺寸
    for (size_t i = 0; i < indices_.size(); ++i)    //遍历全景图像集合中的所有图像
    {
        imgs_subset.push_back(imgs_[indices_[i]]);    //赋值
        seam_est_imgs_subset.push_back(seam_est_imgs_[indices_[i]]);
        full_img_sizes_subset.push_back(full_img_sizes_[indices_[i]]);
    }
    seam_est_imgs_ = seam_est_imgs_subset;    //赋值
    imgs_ = imgs_subset;
    full_img_sizes_ = full_img_sizes_subset;

    if ((int)imgs_.size() < 2)    //图像数量必须要大于2
    {
        LOGLN("Need more images");
        return ERR_NEED_MORE_IMGS;
    }

    return OK;
}
void Stitcher::estimateCameraParams()    //相机参数评估
{
    detail::HomographyBasedEstimator estimator;    //表示相机参数评估器
    estimator(features_, pairwise_matches_, cameras_);    //得到相机参数cameras_

    for (size_t i = 0; i < cameras_.size(); ++i)    //遍历所有的相机
    {
        Mat R;
        cameras_[i].R.convertTo(R, CV_32F);    //变换旋转参数的数据类型
        cameras_[i].R = R;    //得到旋转参数
        LOGLN("Initial intrinsic parameters #" << indices_[i] + 1 << ":\n " << cameras_[i].K());
    }

    bundle_adjuster_->setConfThresh(conf_thresh_);    //设置匹配置信度
    //利用光束平差法精确化相机参数cameras_
    (*bundle_adjuster_)(features_, pairwise_matches_, cameras_);

    // Find median focal length and use it as final image scale
    vector focals;    //表示焦距
    for (size_t i = 0; i < cameras_.size(); ++i)    //遍历所有相机,得到焦距
    {
        LOGLN("Camera #" << indices_[i] + 1 << ":\n" << cameras_[i].K());
        focals.push_back(cameras_[i].focal);
    }

    std::sort(focals.begin(), focals.end());    //焦距排序
    //把焦距的中间值作为图像映射变换尺度warped_image_scale_
    if (focals.size() % 2 == 1)
        warped_image_scale_ = static_cast(focals[focals.size() / 2]);
    else
        warped_image_scale_ = static_cast(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;

    if (do_wave_correct_)    //进行波形校正
    {
        vector rmats;
        for (size_t i = 0; i < cameras_.size(); ++i)
            rmats.push_back(cameras_[i].R);    //得到相机旋转参数
        detail::waveCorrect(rmats, wave_correct_kind_);    //波形校正
        for (size_t i = 0; i < cameras_.size(); ++i)
            cameras_[i].R = rmats[i];    //校正后的旋转参数
    }
}

通过以上分析,我们会发现,Stitcher类实现的拼接与第8节给出的实现方法最大的不同之处在于,Stitcher类在拼接的各个阶段,借助于尺度不断缩小图像的尺寸:特征检测与匹配是在work_scale_(匹配尺度)下进行的,映射变换是在seam_scale_(接缝尺度)下进行的,而图像合成又是在compose_scale(合成尺度)下进行的。这么做的优点显而易见,既提高了效率,又节省了内存开销,但最终得到的全景图像的尺寸缩小了。

 

下面我们给出利用Stitcher类实现的图像拼接:

#include   
#include   
#include   
#include "opencv2/opencv_modules.hpp"  
#include "opencv2/highgui/highgui.hpp"  
#include "opencv2/stitching/stitcher.hpp"
using namespace std;  
using namespace cv;  
using namespace cv::detail;  

int main( ) 
{   
   vector imgs;    //输入9幅图像
   Mat img;
   img = imread("4.jpg");	
   imgs.push_back(img);
   img = imread("5.jpg");	
   imgs.push_back(img);
   img = imread("6.jpg");	
   imgs.push_back(img);
   img = imread("7.jpg");	
   imgs.push_back(img);
   img = imread("8.jpg");	
   imgs.push_back(img);
   img = imread("9.jpg");	
   imgs.push_back(img);	
   img = imread("10.jpg");	
   imgs.push_back(img);
   img = imread("11.jpg");	
   imgs.push_back(img);
   img = imread("12.jpg");	
   imgs.push_back(img);

   int num_images = 9;
   
   Mat pano;    //全景图像
   Stitcher stitcher = Stitcher::createDefault(false);    //定义全景图像拼接器
   Stitcher::Status status = stitcher.stitch(imgs, pano);    //图像拼接
    
   if (status != Stitcher::OK) 
   {   
      cout << "Can't stitch images, error code = " << int(status) << endl;   
      return -1;   
   }   
   
   imwrite("pano.jpg", pano);    //存储图像
   return 0;   
}

该程序输入的9幅图像与第8节中的9幅图像完全相同,最终用时不到4分钟。

 

我们最后总结一下Stitcher类所使用的各种默认算法:SURF特征检测算法(NONFREE条件下),2-NN特征匹配算法,光束平差法中的射线发散法,进行了波形水平校正,球面投影变换,曝光补偿法中的分块增益补偿法,直接图割法,以及多频段融合算法。

你可能感兴趣的:(opencv)