您的位置:首页 > 运维架构

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

2018-01-06 16:15 344 查看
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<vector<Rect> >());
}
Stitcher::Status Stitcher::estimateTransform(InputArray images, const vector<vector<Rect> > &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<Mat>(), pano);
}
Stitcher::Status Stitcher::composePanorama(InputArray images, OutputArray pano)
//images表示经过变形处理后的图像
//pano表示最终得到的全景图像
{
LOGLN("Warping images (auxiliary)... ");

vector<Mat> 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<Mat> seam_est_imgs_subset;
vector<Mat> 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<Point> corners(imgs_.size());
vector<Mat> masks_warped(imgs_.size());    //表示映射变换图的掩码
vector<Mat> images_warped(imgs_.size());    //表示映射变换图
vector<Size> sizes(imgs_.size());    //表示映射变换图的尺寸
vector<Mat> 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<detail::RotationWarper> w = warper_->create(float(warped_image_scale_ * seam_work_aspect_));
for (size_t i = 0; i < imgs_.size(); ++i)    //遍历所有图像
{
Mat_<float> 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<Mat> 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<float>(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<vector<Rect> > &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<Rect> 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<Mat> seam_est_imgs_subset;    //表示seam_est_imgs_的子集
vector<Mat> imgs_subset;    //表示imgs_的子集
vector<Size> 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<double> 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<float>(focals[focals.size() / 2]);
else
warped_image_scale_ = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;

if (do_wave_correct_)    //进行波形校正
{
vector<Mat> 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 <iostream>
#include <fstream>
#include <string>
#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<Mat> 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特征匹配算法,光束平差法中的射线发散法,进行了波形水平校正,球面投影变换,曝光补偿法中的分块增益补偿法,直接图割法,以及多频段融合算法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: