Opencv2.4.9源码分析——Stitching
Posted zhaocj
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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<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特征匹配算法,光束平差法中的射线发散法,进行了波形水平校正,球面投影变换,曝光补偿法中的分块增益补偿法,直接图割法,以及多频段融合算法。
以上是关于Opencv2.4.9源码分析——Stitching的主要内容,如果未能解决你的问题,请参考以下文章
Opencv2.4.9源码分析——Gradient Boosted Trees
Win7下qt5.3.1+opencv2.4.9编译环境的搭建(好多 Opencv2.4.9源码分析的博客)