相机镜头所呈现出的景物要比人类的视觉系统所看到的景物要狭小得多,因此一幅图像不可能捕获到我们所看到的整个景物。全景图像拼接给出了这个问题的解决办法,它是把图像间重叠部分拿出来拼接起来,从而得到一幅更大的图像。这种算法也可以用于把一幅图像插入到另一幅图像中。
图1 图像拼接执行过程及方法
要想完成图像拼接,所要用到的算法较多,Opencv把这些算法用一张图呈现了处理,如图1所示。下面我们就详细图像拼接算法。
1、计算特征点
1.1 原理
图像拼接的第一步是计算图像的特征,并得到它们的描述符。好的特征检测的算法有SIFT和SURF,但它们都受到专利的保护,使用上有一定的限制。而ORB算法是一种很好的替代方法,它是利用FAST检测特征,并生成BRIEF描述符。
关于局部特征检测方面的内容,我前面的文章中有一些算法的介绍,在这里就不再做过多的讲解。
1.2 源码
Opencv中应用的是SURF算法和ORB算法,详细的内容为:
class detail::FeaturesFinder表示寻找图像特征的类:
class CV_EXPORTS FeaturesFinder virtual ~FeaturesFinder() {} void operator ()(const Mat &image, ImageFeatures &features); void operator ()(const Mat &image, ImageFeatures &features, const std::vector<cv::Rect> &rois); virtual void collectGarbage() {} //释放已被分配、但还没有被使用的内存 //虚函数,根据用户所选取的特征类别,调用不同子类的find函数,目前只实现了SURF特征和ORB特征 virtual void find(const Mat &image, ImageFeatures &features) = 0;
在FeaturesFinder类中,重载( )运算符主要的任务是调用find函数来检测图像特征。而find是虚函数,具体是执行FeaturesFinder类的两个子类——SurfFeaturesFinder和OrbFeaturesFinder中的find函数。下面我们就来介绍这两个子类,它们分别表示SURF特征和ORB特征。
SurfFeaturesFinder类的构造函数:
SurfFeaturesFinder::SurfFeaturesFinder(double hess_thresh, int num_octaves, int num_layers, int num_octaves_descr, int num_layers_descr) //hess_thress表示Hessian矩阵行列式响应值的阈值,默认值为300 //num_octaves表示特征点所用到的图像堆的组数,默认值为3 //num_layers表示特征点所用到的图像堆中每组中的中间层数,默认值为4 //num_octaves_descr表示描述符所用到的图像堆的组数,默认值为3 //num_layers_descr表示描述符所用到的图像堆中每组中的中间层数,默认值为4 //如果特征点和描述符所用到的组数和层数都相同,则只需一个Feature2D数据类型来表示特征点和描述符即可,默认就是这种情况 if (num_octaves_descr == num_octaves && num_layers_descr == num_layers) //surf为Feature2D数据类型的指针变量,它被创建为SURF特征点 surf = Algorithm::create<Feature2D>("Feature2D.SURF"); if( surf.empty() ) //如果surf没有创建成功,则报错 CV_Error( CV_StsNotImplemented, "OpenCV was built without SURF support" ); surf->set("hessianThreshold", hess_thresh); //赋阈值 surf->set("nOctaves", num_octaves); //赋组数 surf->set("nOctaveLayers", num_layers); //赋层数 //如果特征点和描述符所用到的组数或层数不相同,则各需要一个数据类型来分别表示特征点和描述符,即特征点用FeatureDetector数据类型,描述符用DescriptorExtractor数据类型 //分别创建SURF特征点detector_和SURF描述符extractor_ detector_ = Algorithm::create<FeatureDetector>("Feature2D.SURF"); extractor_ = Algorithm::create<DescriptorExtractor>("Feature2D.SURF"); if( detector_.empty() || extractor_.empty() ) CV_Error( CV_StsNotImplemented, "OpenCV was built without SURF support" ); detector_->set("hessianThreshold", hess_thresh); //为特征点赋阈值 detector_->set("nOctaves", num_octaves); //为特征点赋组数 detector_->set("nOctaveLayers", num_layers); //为特征点赋层数 extractor_->set("nOctaves", num_octaves_descr); //为描述符赋组数 extractor_->set("nOctaveLayers", num_layers_descr); //为描述符赋层数
检测SURF特征:
void SurfFeaturesFinder::find(const Mat &image, ImageFeatures &features) Mat gray_image; //表示待处理的图像 //输入图像image必须为8位字符型数据类型,可以是灰度图像,也可以是彩色图像 CV_Assert((image.type() == CV_8UC3) || (image.type() == CV_8UC1)); if(image.type() == CV_8UC3) //如果输入图像是彩色图像 cvtColor(image, gray_image, CV_BGR2GRAY); //转换为灰度图像 //如果surf为0,说明在SurfFeaturesFinder类的构造函数内,没有创建该变量,也就意味着要分别使用detector_变量和extractor_变量分别表示SURF特征点和SURF描述符 detector_->detect(gray_image, features.keypoints); //得到SURF特征点 extractor_->compute(gray_image, features.keypoints, features.descriptors); //在surf不为0的情况下,说明在SurfFeaturesFinder类的构造函数内,已创建该变量,也就意味着只需要用surf变量来表示SURF特征点和SURF描述符即可 (*surf)(gray_image, Mat(), features.keypoints, descriptors); //变换描述符的数据形式,并赋值给features features.descriptors = descriptors.reshape(1, (int)features.keypoints.size());
OrbFeaturesFinder类的构造函数:
OrbFeaturesFinder::OrbFeaturesFinder(Size _grid_size, int n_features, float scaleFactor, int nlevels) //_grid_size表示根据该值的大小,把输入图像分割成若干个小的区域,分别得到每个小区域的ORB特征,_grid_size默认值为Size(3,1),即把输入图像分割成3个小区域,每个小区域的宽是原输入图像宽的三分之一,小区域的高与原图像相同;而如果不对输入图像进行分割,则_grid_size应该为Size(1,1) //n_features表示最终得到的特征点的数量,默认值为1500 //scaleFactor表示尺度金字塔的各层间的取样率,必须大于1,默认值为1.3 //nlevels表示尺度金字塔的层数,默认值为5 grid_size = _grid_size; //赋值 //实例化ORB类,它表示分割后每个小区域的ORB特征,并且每个小区域的最多的特征点数量为n_features×(90+小区域的数量)/(100×小区域的数量);而如果不对输入图像进行分割,则该类就表示原输入图像的ORB特征,特征点的数量就等于n_features orb = new ORB(n_features * (99 + grid_size.area())/100/grid_size.area(), scaleFactor, nlevels);
检测ORB特征:
void OrbFeaturesFinder::find(const Mat &image, ImageFeatures &features) Mat gray_image; //表示待处理的图像 //输入图像image必须为8位字符型数据类型,可以是灰度图像,也可以是三通道或四通道的彩色图像 CV_Assert((image.type() == CV_8UC3) || (image.type() == CV_8UC4) || (image.type() == CV_8UC1)); if (image.type() == CV_8UC3) { cvtColor(image, gray_image, CV_BGR2GRAY); } else if (image.type() == CV_8UC4) { cvtColor(image, gray_image, CV_BGRA2GRAY); } else if (image.type() == CV_8UC1) { CV_Error(CV_StsUnsupportedFormat, ""); if (grid_size.area() == 1) //表示没有对输入图像进行分割 //直接对原输入图像进行ORB特征检测,得到特征点和描述符 (*orb)(gray_image, Mat(), features.keypoints, features.descriptors); features.keypoints.clear(); //清除变量 features.descriptors.release(); //清除变量 std::vector<KeyPoint> points; //表示每个小区域的特征点 Mat descriptors; //表示每个小区域的描述符 for (int r = 0; r < grid_size.height; ++r) for (int c = 0; c < grid_size.width; ++c) //(xl, yl)表示当前小区域的左上角在原输入图像的坐标 int xl = c * gray_image.cols / grid_size.width; int yl = r * gray_image.rows / grid_size.height; //(xr, yr)表示当前小区域的右下角在原输入图像的坐标 int xr = (c+1) * gray_image.cols / grid_size.width; int yr = (r+1) * gray_image.rows / grid_size.height; // LOGLN("OrbFeaturesFinder::find: gray_image.empty=" << (gray_image.empty()?"true":"false") << ", " // << " gray_image.size()=(" << gray_image.size().width << "x" << gray_image.size().height << "), " // << " yl=" << yl << ", yr=" << yr << ", " // << " xl=" << xl << ", xr=" << xr << ", gray_image.data=" << ((size_t)gray_image.data) << ", " // << "gray_image.dims=" << gray_image.dims << "\n"); Mat gray_image_part=gray_image(Range(yl, yr), Range(xl, xr)); // LOGLN("OrbFeaturesFinder::find: gray_image_part.empty=" << (gray_image_part.empty()?"true":"false") << ", " // << " gray_image_part.size()=(" << gray_image_part.size().width << "x" << gray_image_part.size().height << "), " // << " gray_image_part.dims=" << gray_image_part.dims << ", " // << " gray_image_part.data=" << ((size_t)gray_image_part.data) << "\n"); (*orb)(gray_image_part, Mat(), points, descriptors); features.keypoints.reserve(features.keypoints.size() + points.size()); //遍历当前小区域的所有特征点,得到这些特征点在原输入图像的坐标,并把它们放入feature中 for (std::vector<KeyPoint>::iterator kp = points.begin(); kp != points.end(); ++kp) features.keypoints.push_back(*kp); features.descriptors.push_back(descriptors);
1.3 应用
下面我们用拼接算法中的特征点检测方法进行特征点检测:
#include "opencv2/core/core.hpp" #include "opencv2/imgproc/imgproc.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/nonfree/nonfree.hpp" #include "opencv2/legacy/legacy.hpp" #include "opencv2/stitching/detail/autocalib.hpp" #include "opencv2/stitching/detail/blenders.hpp" #include "opencv2/stitching/detail/camera.hpp" #include "opencv2/stitching/detail/exposure_compensate.hpp" #include "opencv2/stitching/detail/matchers.hpp" #include "opencv2/stitching/detail/motion_estimators.hpp" #include "opencv2/stitching/detail/seam_finders.hpp" #include "opencv2/stitching/detail/util.hpp" #include "opencv2/stitching/detail/warpers.hpp" #include "opencv2/stitching/warpers.hpp" int main(int argc, char** argv) Mat img = imread("1.jpg"); //读入图像 Ptr<FeaturesFinder> finder; //定义FeaturesFinder类 finder = new SurfFeaturesFinder(); //应用SURF方法 //finder = new OrbFeaturesFinder(); //应用ORB方法 ImageFeatures features; //表示特征 (*finder)(img, features); //特征检测 drawKeypoints(img, features.keypoints, output_img, Scalar::all(-1)); imshow("features", output_img);
输出的结果为:
图2 特征检测结果
|