失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > OpenCV总结3——图像拼接Stitching

OpenCV总结3——图像拼接Stitching

时间:2019-12-28 19:53:39

相关推荐

OpenCV总结3——图像拼接Stitching

之前折腾过一段时间配准发现自己写的一点都不准,最近需要进行图像的拼接,偶然的机会查到了opencv原来有拼接的库,发现opencv处理配准之外还做了许多的操作,就这个机会查找了相关的资料,同时也研究了以下他的源代码,做一个简单的总结。

Stitching

因为OpenCV已经将算法进行了高度的封装,所以用起来跟OpenGL类似,遵循了一条管线进行处理。

上图是OpenCV官方网站中提供的流程图。

从这个图中我们也能看出这是一个很复杂的过程,源代码中提到了七个部分:

Features Finding and Images Matching:功能查找和图像匹配(感觉与配准的内容比较接近,都是计算特征点和匹配的过程)Rotation Estimation:旋转估计Autocalibration:自动校准Images Warping:图像变形Seam Estimation:接缝估计Exposure Compensation:曝光补偿Image Blenders :图像混合器

之前我做的只考虑了特征和变形,OpenCV这里的要素非常多,所以它的效果也很好。

看英文比较费事,自己做了个翻译:

写个最简单的调用:

vector<Mat> ss;Ptr<Stitcher> stitcher = Stitcher::create();ss.push_back(source_image);ss.push_back(target_image);Mat pano;Stitcher::Status status = stitcher->stitch(ss, pano);if (status != Stitcher::OK){cout << "Can't stitch images, error code = " << int(status) << endl;return -1;}

通过上述的调用就可以得到一个效果不错的拼接全景图像。(这里调用的时候采用的是指针,surf和sift也是同样,因为OpenCV在2后有比较大的改版,所以调用全部改成了指针调用的形式),我在调用这个接口的时候图像只能是用三通道的,单通道会报错。

因为我这里使用的是OpenCV4,所以没有使用2的写法,有一个博客关于OpenCV2的拼接部分源码解析的特别好,很详细。/zhaocj/article/details/78798687

这里根据自己的理解对OpenCV4的源代码进行简单的解析:

创建接口

Ptr<Stitcher> Stitcher::create(Mode mode){Ptr<Stitcher> stitcher = makePtr<Stitcher>();//获取指针stitcher->setRegistrationResol(0.6);//配准分辨率,最大是1,如果想要获得较多的特征点要将这个值调大,因为它是缩放时乘以的系数之一stitcher->setSeamEstimationResol(0.1);//接缝处的分辨率stitcher->setCompositingResol(ORIG_RESOL);//合成分辨率stitcher->setPanoConfidenceThresh(1);//匹配置信度阈值stitcher->setSeamFinder(makePtr<detail::GraphCutSeamFinder>(detail::GraphCutSeamFinderBase::COST_COLOR));//基于最小图割的接缝查找stitcher->setBlender(makePtr<detail::MultiBandBlender>(false));//采用多段混合算法进行混合,细节(高频)采用较小的平滑渐变,宏观(低频)采用较大的平滑渐变stitcher->setFeaturesFinder(ORB::create());//设置特征查找的方法stitcher->setInterpolationFlags(INTER_LINEAR);//设置插值的方法stitcher->work_scale_ = 1;//计算是的尺度stitcher->seam_scale_ = 1;//接缝尺度stitcher->seam_work_aspect_ = 1;//接缝与计算的尺度比例stitcher->warped_image_scale_ = 1;//图像变换的尺度switch (mode){case PANORAMA: // PANORAMA is the default// mostly already setupstitcher->setEstimator(makePtr<detail::HomographyBasedEstimator>());//单应矩阵估计stitcher->setWaveCorrection(true);//波校正stitcher->setWaveCorrectKind(detail::WAVE_CORRECT_HORIZ);//波校正的方法stitcher->setFeaturesMatcher(makePtr<detail::BestOf2NearestMatcher>(false));//特征点匹配方法knnstitcher->setBundleAdjuster(makePtr<detail::BundleAdjusterRay>());//相机光线修正,估计焦距stitcher->setWarper(makePtr<SphericalWarper>());//变换模式:球stitcher->setExposureCompensator(makePtr<detail::BlocksGainCompensator>());//曝光补偿break;case SCANS:stitcher->setEstimator(makePtr<detail::AffineBasedEstimator>());//仿射变换stitcher->setWaveCorrection(false);//不采用波修正stitcher->setFeaturesMatcher(makePtr<detail::AffineBestOf2NearestMatcher>(false, false));//knnstitcher->setBundleAdjuster(makePtr<detail::BundleAdjusterAffinePartial>());//估计仿射变换的参数stitcher->setWarper(makePtr<AffineWarper>());//仿射变换stitcher->setExposureCompensator(makePtr<detail::NoExposureCompensator>());//不进行曝光补偿break;default:CV_Error(Error::StsBadArg, "Invalid stitching mode. Must be one of Stitcher::Mode");break;}return stitcher;}

原本在之前的版本中会有createDefault这个函数,但是在新版中没有了,只有一个调用的接口,所以以前的调用方式不能再使用。

以上代码中,接缝查找器具有不同的四种算法:NoSeamFinder(无需接缝查找)、PairwiseSeamFinder(逐点查找)、DpSeamFinder(动态规划查找)、GraphCutSeamFinder(最小图割查找),其中逐点查找只实现了voronoi图法(泰森多边形),接缝的具体内容查看接缝部分的解析。

混合采用多段混合,将图像按照频率展开金字塔,先按照不同频率平滑加权,然后把各个频率的分量加权,得到最后的结果,具体查看混合部分的解析。

特征查找的方法也有许多中:BRAISK(一种二进制的特征描述算子,适合较大模糊图像的配准)、ORB(快速特征点提取的算法,是FAST和BRIEF的结合,在此基础上进行优化改进,比surf快十倍,是sift的百倍)、MSER(最大极值稳定区域,利用分水岭算法得到形状稳定的区域)、FAST(如果像素与邻域内的大量像素点的差距较大,则认为是特征点)、AGAST(自适应通用加速分割检测,比surf和sift都快)、shi_tomas(为避免harris出现聚簇现象特出的一种算法)、blob(斑点特征,blob是一块联通区域,通过对前后背景的二值化,进行联通区域的提取与标记)、KAZE、AKAZE(非线性尺度空间检测,保证图像边缘尺度变化中信息损失较少,保留细节信息),具体查看配准相关信息。

图像的变换方式分为两种:单应变换和仿射变换,单应变换会将一张图中的特征点映射到另一张图的特征点上面,仿射变换是整张图像的变换,并且仿射变换会保留线的并行性。

波校正分为水平和垂直方向。

拼接接口

Stitcher::Status Stitcher::stitch(InputArrayOfArrays images, OutputArray pano){//默认是需要遮罩的,如果没有遮罩,则传入空的图像return stitch(images, noArray(), pano);}Stitcher::Status Stitcher::stitch(InputArrayOfArrays images, InputArrayOfArrays masks, OutputArray pano){CV_INSTRUMENT_REGION();Status status = estimateTransform(images, masks);//估计旋转矩阵也就是计算特征进行匹配以及旋转等相关内容,也就是流程图中的上半部分if (status != OK)return status;return composePanorama(pano);}

上面的代码是我们调用的接口代码,将整个过程分为了两个大的部分,一部分进行配准等操作,另一部分进行融合操作。

配准

Stitcher::Status Stitcher::estimateTransform(InputArrayOfArrays images, InputArrayOfArrays masks){CV_INSTRUMENT_REGION();images.getUMatVector(imgs_);//这里将图像转化为GPU中可以使用的图像,也就是说使图像支持并行处理,遵循opencl标准masks.getUMatVector(masks_);Status status;if ((status = matchImages()) != OK)//匹配图像return status;if ((status = estimateCameraParams()) != OK)//估计相机参数return status;return OK;}

这里的估计变换部分又分成了两部分,一部分用于匹配图像,另一部分用于相机参数的估计。

匹配图像

以下是图像匹配部分的代码:

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;//匹配尺度和接缝尺度的比值seam_scale_ = 1;//接缝尺度bool is_work_scale_set = false;//匹配尺度是否被设置bool is_seam_scale_set = false;//接缝尺度是否被设置features_.resize(imgs_.size());//features是保存所有图像特征的一个数组,所以数量上是一一对应的,所以此处对特征数组的数量进行设定seam_est_imgs_.resize(imgs_.size());//接缝估计数组,因为在整个处理过程中还有接缝误差的估计,所以这里有接缝相关的内容full_img_sizes_.resize(imgs_.size());//保存每个图像的尺寸LOGLN("Finding features...");#if ENABLE_LOGint64 t = getTickCount();//返回系统启动经过的时间#endifstd::vector<UMat> feature_find_imgs(imgs_.size());//保存进行过尺度处理的图像std::vector<UMat> feature_find_masks(masks_.size());//保存图像的遮罩for (size_t i = 0; i < imgs_.size(); ++i){full_img_sizes_[i] = imgs_[i].size();//保存图像的大小if (registr_resol_ < 0)//判断配准分辨率,默认值是-1{//分辨率-1应该表示尺度不变feature_find_imgs[i] = imgs_[i];//保存图像work_scale_ = 1;//修改配准尺度is_work_scale_set = true;}else{if (!is_work_scale_set)//没有设置配准尺度,需要设置{work_scale_ = std::min(1.0, std::sqrt(registr_resol_ * 1e6 / full_img_sizes_[i].area()));//为配准分辨率乘以10的6次方,除以图像的面积,计算一个新的配准尺度is_work_scale_set = true;}resize(imgs_[i], feature_find_imgs[i], Size(), work_scale_, work_scale_, INTER_LINEAR_EXACT);//缩放至配准尺度,精确双线性插值}if (!is_seam_scale_set)//没有接缝尺度,设置接缝尺度{seam_scale_ = std::min(1.0, std::sqrt(seam_est_resol_ * 1e6 / full_img_sizes_[i].area()));//同样计算接缝尺度seam_work_aspect_ = seam_scale_ / work_scale_;//改变比例大小is_seam_scale_set = true;}if (!masks_.empty())//如果所有的区域都寻找特征,没有遮罩{resize(masks_[i], feature_find_masks[i], Size(), work_scale_, work_scale_, INTER_NEAREST);//将遮罩缩放至配准图像大小,最邻近插值}features_[i].img_idx = (int)i;//标识当前特征属于哪张图像LOGLN("Features in image #" << i+1 << ": " << features_[i].keypoints.size());resize(imgs_[i], seam_est_imgs_[i], Size(), seam_scale_, seam_scale_, INTER_LINEAR_EXACT);//设置接缝图像,缩放至接缝尺度}// find features possibly in paralleldetail::computeImageFeatures(features_finder_, feature_find_imgs, features_, feature_find_masks);//查找图像的特征信息,这是一个并行的过程// Do it to save memoryfeature_find_imgs.clear();//清空图像节省内存feature_find_masks.clear();LOGLN("Finding features, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");LOG("Pairwise matching");#if ENABLE_LOGt = getTickCount();#endif(*features_matcher_)(features_, pairwise_matches_, matching_mask_);//对特征进行进行成对的匹配,其中mask代表的是哪些图像是需要进行匹配的,不是抠图是用的mask,其中会用到一些并行的加速方法features_matcher_->collectGarbage();//回收内存LOGLN("Pairwise matching, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");// Leave only images we are sure are from the same panoramaindices_ = detail::leaveBiggestComponent(features_, pairwise_matches_, (float)conf_thresh_);//通过置信度判断,小于置信度的图像认为不在同一幅全景图中,被舍弃std::vector<UMat> seam_est_imgs_subset;std::vector<UMat> imgs_subset;std::vector<Size> full_img_sizes_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)//子集只有两张以上才可以拼接{LOGLN("Need more images");return ERR_NEED_MORE_IMGS;}return OK;}

特征结构体:ImageFeatures

上面代码中用来保存特征的数组的类型是下面的这个结构体。

struct CV_EXPORTS ImageFeatures{int img_idx;//图像标号Size img_size;//图像大小std::vector<KeyPoint> keypoints;//特征点UMat descriptors;//特征描述};

图像特征并行查找:computeImageFeatures

有两个重载的函数,一个是用于单张图像的,另一个是用于多张图像的并行处理,主要使用的就是配准过程中用到的函数

void computeImageFeatures(const Ptr<Feature2D> &featuresFinder,InputArrayOfArrays images,std::vector<ImageFeatures> &features,InputArrayOfArrays masks){//对于多张图像进行一个加速处理// compute all featuresstd::vector<std::vector<KeyPoint>> keypoints;//特征点std::vector<UMat> descriptors;//描述// TODO replace with 1 call to new over load of detectAndComputefeaturesFinder->detect(images, keypoints, masks);//查找所有图像特征点featuresFinder->compute(images, keypoints, descriptors);//计算所有图像的特征描述// store to ImageFeaturessize_t count = images.total();features.resize(count);//保存计算后的特征信息ImageFeaturesCV_Assert(count == keypoints.size() && count == descriptors.size());for (size_t i = 0; i < count; ++i){features[i].img_size = images.size(int(i));features[i].keypoints = std::move(keypoints[i]);features[i].descriptors = std::move(descriptors[i]);}}void computeImageFeatures(const Ptr<Feature2D> &featuresFinder,InputArray image,ImageFeatures &features,InputArray mask){//单张图像的处理features.img_size = image.size();featuresFinder->detectAndCompute(image, mask, features.keypoints, features.descriptors);}

去除置信度低的图像

std::vector<int> leaveBiggestComponent(std::vector<ImageFeatures> &features, std::vector<MatchesInfo> &pairwise_matches,float conf_threshold){const int num_images = static_cast<int>(features.size());//获得图像的数量DisjointSets comps(num_images);//不相交集,一种用于求最小生成树等的通用数据结构for (int i = 0; i < num_images; ++i){for (int j = 0; j < num_images; ++j){if (pairwise_matches[i*num_images + j].confidence < conf_threshold)//判断置信度continue;int comp1 = comps.findSetByElem(i);//查找元素int comp2 = comps.findSetByElem(j);if (comp1 != comp2)comps.mergeSets(comp1, comp2);//将rank小的集合合并到大的集合}}int max_comp = static_cast<int>(std::max_element(comps.size.begin(), comps.size.end()) - comps.size.begin());//查找元素中的最大值的标号std::vector<int> indices;std::vector<int> indices_removed;for (int i = 0; i < num_images; ++i)if (comps.findSetByElem(i) == max_comp)// 是否与置信度最大值有关indices.push_back(i);//保存标号elseindices_removed.push_back(i);std::vector<ImageFeatures> features_subset;//特征子集std::vector<MatchesInfo> pairwise_matches_subset;//匹配子集for (size_t i = 0; i < indices.size(); ++i){features_subset.push_back(features[indices[i]]);//将置信度高的特征提取出来for (size_t j = 0; j < indices.size(); ++j){pairwise_matches_subset.push_back(pairwise_matches[indices[i]*num_images + indices[j]]);//抽取对应的匹配子集pairwise_matches_subset.back().src_img_idx = static_cast<int>(i);pairwise_matches_subset.back().dst_img_idx = static_cast<int>(j);}}if (static_cast<int>(features_subset.size()) == num_images)return indices;//返回需要匹配的图像标号LOG("Removed some images, because can't match them or there are too similar images: (");LOG(indices_removed[0] + 1);for (size_t i = 1; i < indices_removed.size(); ++i)LOG(", " << indices_removed[i]+1);LOGLN(").");LOGLN("Try to decrease the match confidence threshold and/or check if you're stitching duplicates.");//改变特征集合匹配集features = features_subset;pairwise_matches = pairwise_matches_subset;return indices;}

DisjointSets

class CV_EXPORTS DisjointSets{public:DisjointSets(int elem_count = 0) {createOneElemSets(elem_count); }void createOneElemSets(int elem_count);int findSetByElem(int elem);int mergeSets(int set1, int set2);//低rank的合并在高rank中std::vector<int> parent;std::vector<int> size;private:std::vector<int> rank_;};

不相交集,用于计算最小树,联通集等,具体的之后再研究,这就是一个比较通用的数据结构。

以上是图像配准部分的代码。

相机参数估计

Stitcher::Status Stitcher::estimateCameraParams()//相机参数估计{//全局框架下估计单应性// estimate homography in global frameif (!(*estimator_)(features_, pairwise_matches_, cameras_))return ERR_HOMOGRAPHY_EST_FAIL;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_);//光束平差法设置置信度if (!(*bundle_adjuster_)(features_, pairwise_matches_, cameras_))//利用光束平差法精确相机参数return ERR_CAMERA_PARAMS_ADJUST_FAIL;//找到焦距,并且使它作为图像最后的尺度// Find median focal length and use it as final image scalestd::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());//焦距排序if (focals.size() % 2 == 1)//将焦距中间值作为图像的尺度warped_image_scale_ = static_cast<float>(focals[focals.size() / 2]);elsewarped_image_scale_ = static_cast<float>(focals[focals.size() / 2 - 1] + focals[focals.size() / 2]) * 0.5f;if (do_wave_correct_)//波形修正{std::vector<Mat> rmats;for (size_t i = 0; i < cameras_.size(); ++i)rmats.push_back(cameras_[i].R.clone());//得到相机的旋转矩阵detail::waveCorrect(rmats, wave_correct_kind_);//对相机旋转矩阵进行校正从而优化相机的参数for (size_t i = 0; i < cameras_.size(); ++i)cameras_[i].R = rmats[i];//将优化后的旋转矩阵重新保存}return OK;}

以上是对相机参数的估计以及优化,波校正和不相交集之后再说。

相机参数:CameraParams

struct CV_EXPORTS CameraParams{CameraParams();CameraParams(const CameraParams& other);CameraParams& operator =(const CameraParams& other);Mat K() const;double focal; // 焦距double aspect; // 纵横比double ppx; // 主要点xdouble ppy; // 主要点yMat R; // 旋转Mat t; // 平移};

以上的所有代码完成了对于图像特征的估计以及相机的估计,得到了有关变换的参数。

全景融合

Stitcher::Status Stitcher::composePanorama(InputArrayOfArrays images, OutputArray pano){CV_INSTRUMENT_REGION();LOGLN("Warping images (auxiliary)... ");std::vector<UMat> imgs;images.getUMatVector(imgs);//获得可以并行计算的图像if (!imgs.empty()){CV_Assert(imgs.size() == imgs_.size());UMat img;seam_est_imgs_.resize(imgs.size());//接缝数量for (size_t i = 0; i < imgs.size(); ++i){imgs_[i] = imgs[i];resize(imgs[i], img, Size(), seam_scale_, seam_scale_, INTER_LINEAR_EXACT);//将图像缩放至接缝尺度seam_est_imgs_[i] = img.clone();//保存缩放后的图像}std::vector<UMat> seam_est_imgs_subset;//用于保存需要融合的图像子集std::vector<UMat> 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]]);}seam_est_imgs_ = seam_est_imgs_subset;imgs_ = imgs_subset;//重新赋值}UMat pano_;//保存全景图#if ENABLE_LOGint64 t = getTickCount();#endifstd::vector<Point> corners(imgs_.size());//保存图像的左上角的位置std::vector<UMat> masks_warped(imgs_.size());//遮罩变换std::vector<UMat> images_warped(imgs_.size());//图像变换std::vector<Size> sizes(imgs_.size());//尺寸std::vector<UMat> masks(imgs_.size());//遮罩// Prepare image masksfor (size_t i = 0; i < imgs_.size(); ++i)//准备遮罩图像{masks[i].create(seam_est_imgs_[i].size(), CV_8U);masks[i].setTo(Scalar::all(255));}// Warp images and their masksPtr<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);//转化为浮点类型K(0,0) *= (float)seam_work_aspect_;//缩放至接缝尺度K(0,2) *= (float)seam_work_aspect_;K(1,1) *= (float)seam_work_aspect_;K(1,2) *= (float)seam_work_aspect_;//对图像进行投影变换,得到变换后的图像以及左上角点的坐标corners[i] = w->warp(seam_est_imgs_[i], K, cameras_[i].R, interp_flags_, BORDER_REFLECT, images_warped[i]);//边缘使用反向重复sizes[i] = images_warped[i].size();//获得变换后的图像大小w->warp(masks[i], K, cameras_[i].R, INTER_NEAREST, BORDER_CONSTANT, masks_warped[i]);//变换掩码}LOGLN("Warping images, time: " << ((getTickCount() - t) / getTickFrequency()) << " sec");//在找到接缝之前进行曝光补偿// Compensate exposure before finding seamsexposure_comp_->feed(corners, images_warped, masks_warped);//计算曝光补偿的系数for (size_t i = 0; i < imgs_.size(); ++i)exposure_comp_->apply(int(i), corners[i], images_warped[i], masks_warped[i]);//曝光补偿// Find seamsstd::vector<UMat> images_warped_f(imgs_.size());for (size_t i = 0; i < imgs_.size(); ++i)images_warped[i].convertTo(images_warped_f[i], CV_32F);seam_finder_->find(images_warped_f, corners, masks_warped);//查找缝隙,并判断缝隙处的像素值// Release unused memoryseam_est_imgs_.clear();images_warped.clear();images_warped_f.clear();masks.clear();LOGLN("Compositing...");#if ENABLE_LOGt = getTickCount();#endifUMat img_warped, img_warped_s;UMat 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;std::vector<detail::CameraParams> cameras_scaled(cameras_);//加载相机UMat full_img, img;for (size_t img_idx = 0; img_idx < imgs_.size(); ++img_idx){LOGLN("Compositing image #" << indices_[img_idx] + 1);#if ENABLE_LOGint64 compositing_t = getTickCount();#endif// Read image and resize it if necessaryfull_img = imgs_[img_idx];//遍历图像if (!is_compose_scale_set)//是否设置了合成尺度{if (compose_resol_ > 0)//合成分辨率compose_scale = std::min(1.0, std::sqrt(compose_resol_ * 1e6 / full_img.size().area()));//设置合成分辨率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 scalefloat warp_scale = static_cast<float>(warped_image_scale_ * compose_work_aspect);/更新变换尺度w = warper_->create(warp_scale);//设置新的映射尺度// Update corners and sizesfor (size_t i = 0; i < imgs_.size(); ++i){// Update intrinsicscameras_scaled[i].ppx *= compose_work_aspect;//根据尺度更新参数cameras_scaled[i].ppy *= compose_work_aspect;cameras_scaled[i].focal *= compose_work_aspect;// Update corner and sizeSize sz = full_img_sizes_[i];//更新大小if (std::abs(compose_scale - 1) > 1e-1)//图像的尺度1+-0.1{sz.width = cvRound(full_img_sizes_[i].width * compose_scale);sz.height = cvRound(full_img_sizes_[i].height * compose_scale);}Mat K;cameras_scaled[i].K().convertTo(K, CV_32F);Rect roi = w->warpRoi(sz, K, cameras_scaled[i].R);//计算大小为sz的局部区域变换后的结果corners[i] = roi.tl();//保存新的边缘位置sizes[i] = roi.size();}}if (std::abs(compose_scale - 1) > 1e-1)//合成尺度在一定的范围内{#if ENABLE_LOGint64 resize_t = getTickCount();#endifresize(full_img, img, Size(), compose_scale, compose_scale, INTER_LINEAR_EXACT);//图像缩放至合成尺寸LOGLN(" resize time: " << ((getTickCount() - resize_t) / getTickFrequency()) << " sec");}elseimg = full_img;full_img.release();Size img_size = img.size();LOGLN(" after resize time: " << ((getTickCount() - compositing_t) / getTickFrequency()) << " sec");Mat K;cameras_scaled[img_idx].K().convertTo(K, CV_32F);//将相机内参数矩阵转化为浮点数#if ENABLE_LOGint64 pt = getTickCount();#endif// Warp the current imagew->warp(img, K, cameras_[img_idx].R, interp_flags_, BORDER_REFLECT, img_warped);//重新计算变换LOGLN(" warp the current image: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");#if ENABLE_LOGpt = getTickCount();#endif// Warp the current image maskmask.create(img_size, CV_8U);mask.setTo(Scalar::all(255));w->warp(mask, K, cameras_[img_idx].R, INTER_NEAREST, BORDER_CONSTANT, mask_warped);//重新计算遮罩的变化LOGLN(" warp the current image mask: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");#if ENABLE_LOGpt = getTickCount();#endif// Compensate exposureexposure_comp_->apply((int)img_idx, corners[img_idx], img_warped, mask_warped);//进行曝光补偿LOGLN(" compensate exposure: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");#if ENABLE_LOGpt = getTickCount();#endifimg_warped.convertTo(img_warped_s, CV_16S);img_warped.release();img.release();mask.release();// Make sure seam mask has proper sizedilate(masks_warped[img_idx], dilated_mask, Mat());//确保接缝遮罩有适当的尺寸resize(dilated_mask, seam_mask, mask_warped.size(), 0, 0, INTER_LINEAR_EXACT);//膨胀后的图像要缩放回原有的大小bitwise_and(seam_mask, mask_warped, mask_warped);//元遮罩与接缝遮罩取交集,过滤出接缝的所在地LOGLN(" other: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");#if ENABLE_LOGpt = getTickCount();#endifif (!is_blender_prepared)//是否准备好了混合{blender_->prepare(corners, sizes);//设置混合区域is_blender_prepared = true;}LOGLN(" other2: " << ((getTickCount() - pt) / getTickFrequency()) << " sec");LOGLN(" feed...");#if ENABLE_LOGint64 feed_t = getTickCount();#endif// Blend the current imageblender_->feed(img_warped_s, mask_warped, corners[img_idx]);//混合当前图像LOGLN(" feed time: " << ((getTickCount() - feed_t) / getTickFrequency()) << " sec");LOGLN("Compositing ## time: " << ((getTickCount() - compositing_t) / getTickFrequency()) << " sec");}#if ENABLE_LOGint64 blend_t = getTickCount();#endifUMat result;blender_->blend(result, result_mask_);//得到最终图像LOGLN("blend time: " << ((getTickCount() - blend_t) / getTickFrequency()) << " sec");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 confusingresult.convertTo(pano, CV_8U);return OK;}

从上面的代码中可以看到,配准和融合是的尺度是不同的,并且对相机的参数进行了两次的优化,并没有使用findHomography这个函数获得单应矩阵,而是通过相机的方式进行投影变换。

未完,待续。

如果觉得《OpenCV总结3——图像拼接Stitching》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。