【OpenCV入门教程之十七】OpenCV重映射 SURF特征点检测合辑
本系列文章由@浅墨_毛星云 出品,转载请注明出处。
文章链接: http://blog.csdn.net/poem_qianmo/article/details/30974513
作者:毛星云(浅墨) 微博:http://weibo.com/u/1723155442
知乎:http://www.zhihu.com/people/mao-xing-yun
写作当前博文时配套使用的OpenCV版本: 2.4.9
本篇文章中,我们一起探讨了OpenCV中重映射和SURF特征点检测相关的知识点,主要一起了解OpenCV中重映射相关的函数remap,SURF算法在OpenCV中的体现与应用。此博文一共有三个配套的麻雀虽小但五脏俱全的示例程序,其经过浅墨详细注释过的代码都在文中贴出,且文章最后提供了综合示例程序的下载。
依然是先看看程序运行截图。
重映射:
SURF特征点检测:
一、OpenCV重映射
1.1 重映射的概念简析
重映射,就是把一幅图像中某位置的像素放置到另一个图片指定位置的过程。为了完成映射过程, 我们需要获得一些插值为非整数像素的坐标,因为源图像与目标图像的像素坐标不是一一对应的。一般情况下,我们通过重映射来表达每个像素的位置 (x,y),像这样 :
g(x,y) = f ( h(x,y) )
在这里, g( ) 是目标图像, f() 是源图像, 而h(x,y) 是作用于 (x,y) 的映射方法函数。
来看个例子。 若有一幅图像 I ,想满足下面的条件作重映射:
h(x,y) = (I.cols - x, y )
这样的话,图像会按照 x 轴方向发生翻转。那么,源图像和效果图分别如下:
在OpenCV中,我们用函数remap( )来实现简单重映射,下面我们就一起来看看这个函数。
1.2 remap( )函数解析
remap( )函数会根据我们指定的映射形式,将源图像进行重映射几何变换,基于的式子如下:
需要注意,此函数不支持就地(in-place)操作。看看其原型和参数。
C++: void remap(InputArray src, OutputArraydst, InputArray map1, InputArray map2, int interpolation, intborderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())
- 第一个参数,InputArray类型的src,输入图像,即源图像,填Mat类的对象即可,且需为单通道8位或者浮点型图像。
- 第二个参数,OutputArray类型的dst,函数调用后的运算结果存在这里,即这个参数用于存放函数调用后的输出结果,需和源图片有一样的尺寸和类型。
- 第三个参数,InputArray类型的map1,它有两种可能的表示对象。
- 表示点(x,y)的第一个映射。
- 表示CV_16SC2 , CV_32FC1 或CV_32FC2类型的X值。
- 第四个参数,InputArray类型的map2,同样,它也有两种可能的表示对象,而且他是根据map1来确定表示那种对象。
- 若map1表示点(x,y)时。这个参数不代表任何值。
- 表示CV_16UC1 , CV_32FC1类型的Y值(第二个值)。
- 第五个参数,int类型的interpolation,插值方式,之前的resize( )函数中有讲到,需要注意,resize( )函数中提到的INTER_AREA插值方式在这里是不支持的,所以可选的插值方式如下:
- INTER_NEAREST - 最近邻插值
- INTER_LINEAR – 双线性插值(默认值)
- INTER_CUBIC – 双三次样条插值(逾4×4像素邻域内的双三次插值)
- INTER_LANCZOS4 -Lanczos插值(逾8×8像素邻域的Lanczos插值)
- 第六个参数,int类型的borderMode,边界模式,有默认值BORDER_CONSTANT,表示目标图像中“离群点(outliers)”的像素值不会被此函数修改。
- 第七个参数,const Scalar&类型的borderValue,当有常数边界时使用的值,其有默认值Scalar( ),即默认值为0。
1.3 详细注释的重映射示例程序
下面放出精简后的以remap函数为核心的示例程序,方便大家快速掌握remap函数的使用方法。
//-----------------------------------【程序说明】---------------------------------------------- // 程序名称::《【OpenCV入门教程之十七】OpenCV重映射 & SURF特征点检测合辑 》 博文配套源码 // 开发所用IDE版本:Visual Studio 2010 // 开发所用OpenCV版本: 2.4.9 // 2014年5月26日 Created by 浅墨 // 配套博文链接: http://blog.csdn.net/poem_qianmo/article/details/26977557 // PS:程序结合配合博文学习效果更佳 // 浅墨的微博:@浅墨_毛星云 http://weibo.com/1723155442 // 浅墨的知乎:http://www.zhihu.com/people/mao-xing-yun // 浅墨的豆瓣:http://www.douban.com/people/53426472/ //----------------------------------------------------------------------------------------------//-----------------------------------【头文件包含部分】--------------------------------------- // 描述:包含程序所依赖的头文件 //---------------------------------------------------------------------------------------------- #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream>//-----------------------------------【命名空间声明部分】-------------------------------------- // 描述:包含程序所使用的命名空间 //----------------------------------------------------------------------------------------------- using namespace cv;//-----------------------------------【main( )函数】-------------------------------------------- // 描述:控制台应用程序的入口函数,我们的程序从这里开始执行 //----------------------------------------------------------------------------------------------- int main( ) {//【0】变量定义Mat srcImage, dstImage;Mat map_x, map_y;//【1】载入原始图srcImage = imread( "1.jpg", 1 );if(!srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; } imshow("原始图",srcImage);//【2】创建和原始图一样的效果图,x重映射图,y重映射图dstImage.create( srcImage.size(), srcImage.type() );map_x.create( srcImage.size(), CV_32FC1 );map_y.create( srcImage.size(), CV_32FC1 );//【3】双层循环,遍历每一个像素点,改变map_x & map_y的值for( int j = 0; j < srcImage.rows;j++){ for( int i = 0; i < srcImage.cols;i++){//改变map_x & map_y的值. map_x.at<float>(j,i) = static_cast<float>(i);map_y.at<float>(j,i) = static_cast<float>(srcImage.rows - j);} }//【4】进行重映射操作remap( srcImage, dstImage, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) );//【5】显示效果图imshow( "【程序窗口】", dstImage );waitKey();return 0; }
显示效果图:
最近世界杯正如火如荼地进行着,这里的图片素材就是巴西队的球星们~
1.4 OpenCV2.X中remap函数源代码
这里我们放出remap函数的源码,供需要了解其实现细节的朋友们观看,浅墨在这里不花时间对其进行剖析。
void cv::remap( InputArray _src, OutputArray _dst,InputArray _map1, InputArray _map2,int interpolation, int borderType, const Scalar& borderValue ) {static RemapNNFunc nn_tab[] ={remapNearest<uchar>, remapNearest<schar>, remapNearest<ushort>, remapNearest<short>,remapNearest<int>, remapNearest<float>, remapNearest<double>, 0};static RemapFunc linear_tab[] ={remapBilinear<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, RemapVec_8u, short>, 0,remapBilinear<Cast<float, ushort>, RemapNoVec, float>,remapBilinear<Cast<float, short>, RemapNoVec, float>, 0,remapBilinear<Cast<float, float>, RemapNoVec, float>,remapBilinear<Cast<double, double>, RemapNoVec, float>, 0};static RemapFunc cubic_tab[] ={remapBicubic<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,remapBicubic<Cast<float, ushort>, float, 1>,remapBicubic<Cast<float, short>, float, 1>, 0,remapBicubic<Cast<float, float>, float, 1>,remapBicubic<Cast<double, double>, float, 1>, 0};static RemapFunc lanczos4_tab[] ={remapLanczos4<FixedPtCast<int, uchar, INTER_REMAP_COEF_BITS>, short, INTER_REMAP_COEF_SCALE>, 0,remapLanczos4<Cast<float, ushort>, float, 1>,remapLanczos4<Cast<float, short>, float, 1>, 0,remapLanczos4<Cast<float, float>, float, 1>,remapLanczos4<Cast<double, double>, float, 1>, 0};Mat src = _src.getMat(), map1 = _map1.getMat(), map2 = _map2.getMat();CV_Assert( map1.size().area() > 0 );CV_Assert( !map2.data || (map2.size() == map1.size()));_dst.create( map1.size(), src.type() );Mat dst = _dst.getMat();if( dst.data == src.data )src = src.clone();int depth = src.depth();RemapNNFunc nnfunc = 0;RemapFunc ifunc = 0;const void* ctab = 0;bool fixpt = depth == CV_8U;bool planar_input = false;if( interpolation == INTER_NEAREST ){nnfunc = nn_tab[depth];CV_Assert( nnfunc != 0 );}else{if( interpolation == INTER_AREA )interpolation = INTER_LINEAR;if( interpolation == INTER_LINEAR )ifunc = linear_tab[depth];else if( interpolation == INTER_CUBIC )ifunc = cubic_tab[depth];else if( interpolation == INTER_LANCZOS4 )ifunc = lanczos4_tab[depth];elseCV_Error( CV_StsBadArg, "Unknown interpolation method" );CV_Assert( ifunc != 0 );ctab = initInterTab2D( interpolation, fixpt );}const Mat *m1 = &map1, *m2 = &map2;if( (map1.type() == CV_16SC2 && (map2.type() == CV_16UC1 || map2.type() == CV_16SC1 || !map2.data)) ||(map2.type() == CV_16SC2 && (map1.type() == CV_16UC1 || map1.type() == CV_16SC1 || !map1.data)) ){if( map1.type() != CV_16SC2 )std::swap(m1, m2);}else{CV_Assert( ((map1.type() == CV_32FC2 || map1.type() == CV_16SC2) && !map2.data) ||(map1.type() == CV_32FC1 && map2.type() == CV_32FC1) );planar_input = map1.channels() == 1;}RemapInvoker invoker(src, dst, m1, m2, interpolation,borderType, borderValue, planar_input, nnfunc, ifunc,ctab);parallel_for_(Range(0, dst.rows), invoker, dst.total()/(double)(1<<16)); }
好了,重映射先就讲这么多,在文章末尾还有一个综合一点的示例程序供大家学习。下面我们开始讲解SURF相关的内容。
二.SURF特征点检测
SURF算法有一些不错的内容和用法,OpenCV中使用颇多,浅墨会花一些篇幅对其进行讲解。今天的这篇文章只是一个小小的开头,主要介绍SURF特征点检测。
先简单了解一下SURF算法的大概内容吧。
2.1 SURF算法概览
SURF,我们简单介绍一下,英语全称为SpeededUp Robust Features,直译的话就是“加速版的具有鲁棒性的特征“算法,由Bay在2006年首次提出。SURF是尺度不变特征变换算法(SIFT算法)的加速版。一般来说,标准的SURF算子比SIFT算子快好几倍,并且在多幅图片下具有更好的稳定性。SURF最大的特征在于采用了harr特征以及积分图像的概念,这大大加快了程序的运行时间。SURF可以应用于计算机视觉的物体识别以及3D重构中。
PS: 由于我们的专栏侧重点是教大家如何快速入门OpenCV编程,不是来进行图像处理科普的,所以原理部分不会花笔墨多讲。一方面是浅墨也不喜欢讲这些枯燥的概念,另一方面是大家肯定应该也不喜欢看这些枯燥的原理,大家是喜欢看代码的〜( ̄▽ ̄〜)。就像小魏CPU童鞋在博客上写的,“Talk is cheap. Show me thecode.”
所以原理部分大家就自行用搜索引擎去学习吧,浅墨会将更多的笔墨用来分享网络上独一无二的干货。
2.2 前世今生——SURF类相关OpenCV源码剖析
OpenCV中关于SURF算法的部分,常常涉及到的是SURF、SurfFeatureDetector、SurfDescriptorExtractor这三个类,这一小节我们就来对他们进行人肉,挖挖其背景,看看他们究竟是什么来头。
在D:\Program Files (x86)\opencv\sources\modules\nonfree\include\opencv2\nonfree下的features2d.hpp头文件中,我们可以发现这样两句定义:
typedef SURF SurfFeatureDetector; typedef SURF SurfDescriptorExtractor;
我们都知道,typedef声明是为现有类型创建一个新的名字,类型别名。这就表示,SURF类忽然同时有了两个新名字SurfFeatureDetector以及SurfDescriptorExtractor。
也就是说,我们平常使用的SurfFeatureDetector类和SurfDescriptorExtractor类,其实就是SURF类,他们三者等价。
然后在这两句定义的上方,我们可以看到SURF类的类声明全貌:
class CV_EXPORTS_W SURF : public Feature2D { public://! the default constructorCV_WRAP SURF();//! the full constructor taking all the necessary parametersexplicit CV_WRAP SURF(double hessianThreshold,int nOctaves=4, int nOctaveLayers=2,bool extended=true, bool upright=false);//! returns the descriptor size in float's (64 or 128)CV_WRAP int descriptorSize() const;//! returns the descriptor typeCV_WRAP int descriptorType() const;//! finds the keypoints using fast hessian detector used in SURFvoid operator()(InputArray img, InputArray mask,CV_OUT vector<KeyPoint>& keypoints) const;//! finds the keypoints and computes their descriptors. Optionally it can compute descriptors for the user-provided keypointsvoid operator()(InputArray img, InputArray mask,CV_OUT vector<KeyPoint>& keypoints,OutputArray descriptors,bool useProvidedKeypoints=false) const;AlgorithmInfo* info() const;CV_PROP_RW double hessianThreshold;CV_PROP_RW int nOctaves;CV_PROP_RW int nOctaveLayers;CV_PROP_RW bool extended;CV_PROP_RW bool upright;protected:void detectImpl( const Mat& image, vector<KeyPoint>& keypoints, const Mat& mask=Mat() ) const;void computeImpl( const Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors ) const; };
可以观察到,SURF类公共继承自Feature2D类,我们再次进行转到,可以在路径d:\Program Files(x86)\opencv\build\include\opencv2\features2d\features2d.hpp看到Feature2D类的声明:
class CV_EXPORTS_W Feature2D : public FeatureDetector, public DescriptorExtractor { public:/** Detect keypoints in an image.* image The image.* keypoints The detected keypoints.* mask Mask specifying where to look for keypoints (optional). Must be a char* matrix with non-zero values in the region of interest.* useProvidedKeypoints If true, the method will skip the detection phase and will compute* descriptors for the provided keypoints*/CV_WRAP_AS(detectAndCompute) virtual void operator()( InputArray image, InputArray mask,CV_OUT vector<KeyPoint>& keypoints,OutputArray descriptors,bool useProvidedKeypoints=false ) const = 0;CV_WRAP void compute( const Mat& image, CV_OUT CV_IN_OUT std::vector<KeyPoint>& keypoints, CV_OUT Mat& descriptors ) const;// Create feature detector and descriptor extractor by name.CV_WRAP static Ptr<Feature2D> create( const string& name ); };
显然,Feature2D类又是公共继承自FeatureDetector以及 DescriptorExtractor类。继续刨根问底,我们看看其父类FeatureDetector以及 DescriptorExtractor类的定义。
首先是FeatureDetector类:
/************************************ Base Classes ************************************//** Abstract base class for 2D image feature detectors.*/ class CV_EXPORTS_W FeatureDetector : public virtual Algorithm { public:virtual ~FeatureDetector();/** Detect keypoints in an image.* image The image.* keypoints The detected keypoints.* mask Mask specifying where to look for keypoints (optional). Must be a char* matrix with non-zero values in the region of interest.*/CV_WRAP void detect( const Mat& image, CV_OUT vector<KeyPoint>& keypoints, const Mat& mask=Mat() ) const;/** Detect keypoints in an image set.* images Image collection.* keypoints Collection of keypoints detected in an input images. keypoints[i] is a set of keypoints detected in an images[i].* masks Masks for image set. masks[i] is a mask for images[i].*/void detect( const vector<Mat>& images, vector<vector<KeyPoint> >& keypoints, const vector<Mat>& masks=vector<Mat>() ) const;// Return true if detector object is emptyCV_WRAP virtual bool empty() const;// Create feature detector by detector name.CV_WRAP static Ptr<FeatureDetector> create( const string& detectorType );protected:virtual void detectImpl( const Mat& image, vector<KeyPoint>& keypoints, const Mat& mask=Mat() ) const = 0;/** Remove keypoints that are not in the mask.* Helper function, useful when wrapping a library call for keypoint detection that* does not support a mask argument.*/static void removeInvalidPoints( const Mat& mask, vector<KeyPoint>& keypoints ); };
这里,我们看到了我们以后经常会用到的detect( )方法重载的两个原型,原来是SURF类经过两层的继承,从FeatureDetector类继承而来的。
/** Detect keypoints in an image.* image The image.* keypoints The detected keypoints.* mask Mask specifying where to look for keypoints (optional). Must be a char* matrix with non-zero values in the region of interest.*/CV_WRAP void detect( const Mat& image, CV_OUT vector<KeyPoint>& keypoints, const Mat& mask=Mat() ) const;/** Detect keypoints in an image set.* images Image collection.* keypoints Collection of keypoints detected in an input images. keypoints[i] is a set of keypoints detected in an images[i].* masks Masks for image set. masks[i] is a mask for images[i].*/void detect( const vector<Mat>& images, vector<vector<KeyPoint> >& keypoints, const vector<Mat>& masks=vector<Mat>() ) const;
同样,看看SURF类的另一个“爷爷”DescriptorExtractor类的声明。
/** Abstract base class for computing descriptors for image keypoints.** In this interface we assume a keypoint descriptor can be represented as a* dense, fixed-dimensional vector of some basic type. Most descriptors used* in practice follow this pattern, as it makes it very easy to compute* distances between descriptors. Therefore we represent a collection of* descriptors as a Mat, where each row is one keypoint descriptor.*/ class CV_EXPORTS_W DescriptorExtractor : public virtual Algorithm { public:virtual ~DescriptorExtractor();/** Compute the descriptors for a set of keypoints in an image.* image The image.* keypoints The input keypoints. Keypoints for which a descriptor cannot be computed are removed.* descriptors Copmputed descriptors. Row i is the descriptor for keypoint i.*/CV_WRAP void compute( const Mat& image, CV_OUT CV_IN_OUT vector<KeyPoint>& keypoints, CV_OUT Mat& descriptors ) const;/** Compute the descriptors for a keypoints collection detected in image collection.* images Image collection.* keypoints Input keypoints collection. keypoints[i] is keypoints detected in images[i].* Keypoints for which a descriptor cannot be computed are removed.* descriptors Descriptor collection. descriptors[i] are descriptors computed for set keypoints[i].*/void compute( const vector<Mat>& images, vector<vector<KeyPoint> >& keypoints, vector<Mat>& descriptors ) const;CV_WRAP virtual int descriptorSize() const = 0;CV_WRAP virtual int descriptorType() const = 0;CV_WRAP virtual bool empty() const;CV_WRAP static Ptr<DescriptorExtractor> create( const string& descriptorExtractorType );protected:virtual void computeImpl( const Mat& image, vector<KeyPoint>& keypoints, Mat& descriptors ) const = 0;/** Remove keypoints within borderPixels of an image edge.*/static void removeBorderKeypoints( vector<KeyPoint>& keypoints,Size imageSize, int borderSize ); };
上述代码表明FeatureDetector 类和DescriptorExtractor类都虚继承自Algorithm基类。
呼,历经千辛万苦,终于,我们找到SURF类德高望重的祖先——OpenCV中的Algorithm基类。看看其原型声明:
/*!Base class for high-level OpenCV algorithms */ class CV_EXPORTS_W Algorithm { public:Algorithm();virtual ~Algorithm();string name() const;template<typename _Tp> typename ParamType<_Tp>::member_type get(const string& name) const;template<typename _Tp> typename ParamType<_Tp>::member_type get(const char* name) const;CV_WRAP int getInt(const string& name) const;CV_WRAP double getDouble(const string& name) const;CV_WRAP bool getBool(const string& name) const;CV_WRAP string getString(const string& name) const;CV_WRAP Mat getMat(const string& name) const;CV_WRAP vector<Mat> getMatVector(const string& name) const;CV_WRAP Ptr<Algorithm> getAlgorithm(const string& name) const;void set(const string& name, int value);void set(const string& name, double value);void set(const string& name, bool value);void set(const string& name, const string& value);void set(const string& name, const Mat& value);void set(const string& name, const vector<Mat>& value);void set(const string& name, const Ptr<Algorithm>& value);template<typename _Tp> void set(const string& name, const Ptr<_Tp>& value);CV_WRAP void setInt(const string& name, int value);CV_WRAP void setDouble(const string& name, double value);CV_WRAP void setBool(const string& name, bool value);CV_WRAP void setString(const string& name, const string& value);CV_WRAP void setMat(const string& name, const Mat& value);CV_WRAP void setMatVector(const string& name, const vector<Mat>& value);CV_WRAP void setAlgorithm(const string& name, const Ptr<Algorithm>& value);template<typename _Tp> void setAlgorithm(const string& name, const Ptr<_Tp>& value);void set(const char* name, int value);void set(const char* name, double value);void set(const char* name, bool value);void set(const char* name, const string& value);void set(const char* name, const Mat& value);void set(const char* name, const vector<Mat>& value);void set(const char* name, const Ptr<Algorithm>& value);template<typename _Tp> void set(const char* name, const Ptr<_Tp>& value);void setInt(const char* name, int value);void setDouble(const char* name, double value);void setBool(const char* name, bool value);void setString(const char* name, const string& value);void setMat(const char* name, const Mat& value);void setMatVector(const char* name, const vector<Mat>& value);void setAlgorithm(const char* name, const Ptr<Algorithm>& value);template<typename _Tp> void setAlgorithm(const char* name, const Ptr<_Tp>& value);CV_WRAP string paramHelp(const string& name) const;int paramType(const char* name) const;CV_WRAP int paramType(const string& name) const;CV_WRAP void getParams(CV_OUT vector<string>& names) const;virtual void write(FileStorage& fs) const;virtual void read(const FileNode& fn);typedef Algorithm* (*Constructor)(void);typedef int (Algorithm::*Getter)() const;typedef void (Algorithm::*Setter)(int);CV_WRAP static void getList(CV_OUT vector<string>& algorithms);CV_WRAP static Ptr<Algorithm> _create(const string& name);template<typename _Tp> static Ptr<_Tp> create(const string& name);virtual AlgorithmInfo* info() const /* TODO: make it = 0;*/ { return 0; } };
关于这几个类缠绵悱恻的关系,画个图就一目了然了,也就是这样的过程:
3.3 drawKeypoints函数详解
因为接下来的示例程序需要用到drawKeypoints函数,我们在这里顺便讲一讲。
顾名思义,此函数用于绘制关键点。
C++: void drawKeypoints(const Mat&image, const vector<KeyPoint>& keypoints, Mat& outImage, constScalar& color=Scalar::all(-1), int flags=DrawMatchesFlags::DEFAULT )
- 第一个参数,const Mat&类型的src,输入图像。
- 第二个参数,const vector<KeyPoint>&类型的keypoints,根据源图像得到的特征点,它是一个输出参数。
- 第三个参数,Mat&类型的outImage,输出图像,其内容取决于第五个参数标识符falgs。
- 第四个参数,const Scalar&类型的color,关键点的颜色,有默认值Scalar::all(-1)。
- 第五个参数,int类型的flags,绘制关键点的特征标识符,有默认值DrawMatchesFlags::DEFAULT。可以在如下这个结构体中选取值。
struct DrawMatchesFlags {enum{DEFAULT = 0, // Output image matrix will be created (Mat::create),// i.e. existing memory of output image may be reused.// Two source images, matches, and single keypoints// will be drawn.// For each keypoint, only the center point will be// drawn (without a circle around the keypoint with the// keypoint size and orientation).DRAW_OVER_OUTIMG = 1, // Output image matrix will not be// created (using Mat::create). Matches will be drawn// on existing content of output image.NOT_DRAW_SINGLE_POINTS = 2, // Single keypoints will not be drawn.DRAW_RICH_KEYPOINTS = 4 // For each keypoint, the circle around// keypoint with keypoint size and orientation will// be drawn.}; };
三、综合示例部分
因为这次的两个知识点关联度不大,所以不方便组织起来成为一个综合示例程序。在这里我们分开将其放出。
3.1 重映射综合示例程序
先放出以remap为核心的综合示例程序,可以用按键控制四种不同的映射模式。且利用了OpenCV版本标识宏“CV_VERSION”,在帮助文字相关代码中加入了一句:
printf("\t当前使用的OpenCV版本为 OpenCV "CV_VERSION);
便可以智能检测出当前使用的OpenCV版本,并输出。如图:
按键说明也可以由上图看出。
放出这个程序详细注释的源代码:
//-----------------------------------【程序说明】---------------------------------------------- // 程序名称::《【OpenCV入门教程之十七】OpenCV重映射 & SURF特征点检测合辑 》 博文配套源码 // 开发所用IDE版本:Visual Studio 2010 // 开发所用OpenCV版本: 2.4.9 // 2014年6月15日 Created by 浅墨 // 配套博文链接: http://blog.csdn.net/poem_qianmo/article/details/30974513 // PS:程序结合配合博文学习效果更佳 // 浅墨的微博:@浅墨_毛星云 http://weibo.com/1723155442 // 浅墨的知乎:http://www.zhihu.com/people/mao-xing-yun // 浅墨的豆瓣:http://www.douban.com/people/53426472/ //----------------------------------------------------------------------------------------------//-----------------------------------【头文件包含部分】--------------------------------------- // 描述:包含程序所依赖的头文件 //---------------------------------------------------------------------------------------------- #include "opencv2/highgui/highgui.hpp" #include "opencv2/imgproc/imgproc.hpp" #include <iostream>//-----------------------------------【命名空间声明部分】-------------------------------------- // 描述:包含程序所使用的命名空间 //----------------------------------------------------------------------------------------------- using namespace cv; using namespace std;//-----------------------------------【宏定义部分】-------------------------------------------- // 描述:定义一些辅助宏 //------------------------------------------------------------------------------------------------ #define WINDOW_NAME "【程序窗口】" //为窗口标题定义的宏 //-----------------------------------【全局变量声明部分】-------------------------------------- // 描述:全局变量的声明 //----------------------------------------------------------------------------------------------- Mat g_srcImage, g_dstImage; Mat g_map_x, g_map_y;//-----------------------------------【全局函数声明部分】-------------------------------------- // 描述:全局函数的声明 //----------------------------------------------------------------------------------------------- int update_map( int key); static void ShowHelpText( );//输出帮助文字//-----------------------------------【main( )函数】-------------------------------------------- // 描述:控制台应用程序的入口函数,我们的程序从这里开始执行 //----------------------------------------------------------------------------------------------- int main( int argc, char** argv ) {//改变console字体颜色system("color 2F"); //显示帮助文字ShowHelpText();//【1】载入原始图g_srcImage = imread( "1.jpg", 1 );if(!g_srcImage.data ) { printf("读取图片错误,请确定目录下是否有imread函数指定的图片存在~! \n"); return false; } imshow("原始图",g_srcImage);//【2】创建和原始图一样的效果图,x重映射图,y重映射图g_dstImage.create( g_srcImage.size(), g_srcImage.type() );g_map_x.create( g_srcImage.size(), CV_32FC1 );g_map_y.create( g_srcImage.size(), CV_32FC1 );//【3】创建窗口并显示namedWindow( WINDOW_NAME, CV_WINDOW_AUTOSIZE );imshow(WINDOW_NAME,g_srcImage);//【4】轮询按键,更新map_x和map_y的值,进行重映射操作并显示效果图while( 1 ){//获取键盘按键 int key = waitKey(0); //判断ESC是否按下,若按下便退出 if( (key & 255) == 27 ) { cout << "程序退出...........\n"; break; } //根据按下的键盘按键来更新 map_x & map_y的值. 然后调用remap( )进行重映射update_map(key);remap( g_srcImage, g_dstImage, g_map_x, g_map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0, 0) );//显示效果图imshow( WINDOW_NAME, g_dstImage );}return 0; }//-----------------------------------【update_map( )函数】-------------------------------- // 描述:根据按键来更新map_x与map_x的值 //---------------------------------------------------------------------------------------------- int update_map( int key ) {//双层循环,遍历每一个像素点for( int j = 0; j < g_srcImage.rows;j++){ for( int i = 0; i < g_srcImage.cols;i++){switch(key){case '1': // 键盘【1】键按下,进行第一种重映射操作if( i > g_srcImage.cols*0.25 && i < g_srcImage.cols*0.75 && j > g_srcImage.rows*0.25 && j < g_srcImage.rows*0.75){g_map_x.at<float>(j,i) = static_cast<float>(2*( i - g_srcImage.cols*0.25 ) + 0.5);g_map_y.at<float>(j,i) = static_cast<float>(2*( j - g_srcImage.rows*0.25 ) + 0.5);}else{ g_map_x.at<float>(j,i) = 0;g_map_y.at<float>(j,i) = 0;}break;case '2':// 键盘【2】键按下,进行第二种重映射操作g_map_x.at<float>(j,i) = static_cast<float>(i);g_map_y.at<float>(j,i) = static_cast<float>(g_srcImage.rows - j);break;case '3':// 键盘【3】键按下,进行第三种重映射操作g_map_x.at<float>(j,i) = static_cast<float>(g_srcImage.cols - i);g_map_y.at<float>(j,i) = static_cast<float>(j);break;case '4':// 键盘【4】键按下,进行第四种重映射操作g_map_x.at<float>(j,i) = static_cast<float>(g_srcImage.cols - i);g_map_y.at<float>(j,i) = static_cast<float>(g_srcImage.rows - j);break;} }}return 1; }//-----------------------------------【ShowHelpText( )函数】---------------------------------- // 描述:输出一些帮助信息 //---------------------------------------------------------------------------------------------- static void ShowHelpText() { //输出一些帮助信息 printf("\n\n\n\t欢迎来到重映射示例程序~\n\n"); printf("\t当前使用的OpenCV版本为 OpenCV "CV_VERSION); printf( "\n\n\t按键操作说明: \n\n" "\t\t键盘按键【ESC】- 退出程序\n" "\t\t键盘按键【1】- 第一种映射方式\n" "\t\t键盘按键【2】- 第二种映射方式\n" "\t\t键盘按键【3】- 第三种映射方式\n" "\t\t键盘按键【4】- 第四种映射方式\n" "\n\n\t\t\t\t\t\t\t\t by浅墨\n\n\n" ); }
运行效果图。首先是原始图:
第一种重映射:
第二种重映射:
第三种重映射:
第四种重映射:
3.2 SURF特征点检测综合示例程序
这个示例程涉及到如下三个方面:
- 使用 FeatureDetector 接口来发现感兴趣点。
- 使用 SurfFeatureDetector 以及其函数 detect 来实现检测过程
- 使用函数 drawKeypoints 绘制检测到的关键点。
详细注释的源代码:
//-----------------------------------【程序说明】---------------------------------------------- // 程序名称::《【OpenCV入门教程之十七】OpenCV重映射 & SURF特征点检测合辑 》 博文配套源码 之【SURF特征点检测】 // 开发所用IDE版本:Visual Studio 2010 // 开发所用OpenCV版本: 2.4.9 // 2014年6月15日 Created by 浅墨 // 配套博文链接: http://blog.csdn.net/poem_qianmo/article/details/30974513 // PS:程序结合配合博文学习效果更佳 // 浅墨的微博:@浅墨_毛星云 http://weibo.com/1723155442 // 浅墨的知乎:http://www.zhihu.com/people/mao-xing-yun // 浅墨的豆瓣:http://www.douban.com/people/53426472/ //----------------------------------------------------------------------------------------------//-----------------------------------【头文件包含部分】--------------------------------------- // 描述:包含程序所依赖的头文件 //---------------------------------------------------------------------------------------------- #include "opencv2/core/core.hpp" #include "opencv2/features2d/features2d.hpp" #include "opencv2/highgui/highgui.hpp" #include "opencv2/nonfree/nonfree.hpp" #include <iostream>//-----------------------------------【命名空间声明部分】-------------------------------------- // 描述:包含程序所使用的命名空间 //----------------------------------------------------------------------------------------------- using namespace cv;//-----------------------------------【全局函数声明部分】-------------------------------------- // 描述:全局函数的声明 //----------------------------------------------------------------------------------------------- static void ShowHelpText( );//输出帮助文字//-----------------------------------【main( )函数】-------------------------------------------- // 描述:控制台应用程序的入口函数,我们的程序从这里开始执行 //----------------------------------------------------------------------------------------------- int main( int argc, char** argv ) {//【0】改变console字体颜色 system("color 2F"); //【0】显示帮助文字 ShowHelpText( ); //【1】载入源图片并显示Mat srcImage1 = imread("1.jpg", 1 );Mat srcImage2 = imread("2.jpg", 1 );if( !srcImage1.data || !srcImage2.data )//检测是否读取成功{ printf("读取图片错误,请确定目录下是否有imread函数指定名称的图片存在~! \n"); return false; } imshow("原始图1",srcImage1);imshow("原始图2",srcImage2);//【2】定义需要用到的变量和类int minHessian = 400;//定义SURF中的hessian阈值特征点检测算子SurfFeatureDetector detector( minHessian );//定义一个SurfFeatureDetector(SURF) 特征检测类对象std::vector<KeyPoint> keypoints_1, keypoints_2;//vector模板类是能够存放任意类型的动态数组,能够增加和压缩数据//【3】调用detect函数检测出SURF特征关键点,保存在vector容器中detector.detect( srcImage1, keypoints_1 );detector.detect( srcImage2, keypoints_2 );//【4】绘制特征关键点Mat img_keypoints_1; Mat img_keypoints_2;drawKeypoints( srcImage1, keypoints_1, img_keypoints_1, Scalar::all(-1), DrawMatchesFlags::DEFAULT );drawKeypoints( srcImage2, keypoints_2, img_keypoints_2, Scalar::all(-1), DrawMatchesFlags::DEFAULT );//【5】显示效果图imshow("特征点检测效果图1", img_keypoints_1 );imshow("特征点检测效果图2", img_keypoints_2 );waitKey(0);return 0; }//-----------------------------------【ShowHelpText( )函数】---------------------------------- // 描述:输出一些帮助信息 //---------------------------------------------------------------------------------------------- void ShowHelpText() { //输出一些帮助信息 printf("\n\n\n\t欢迎来到【SURF特征点检测】示例程序~\n\n"); printf("\t当前使用的OpenCV版本为 OpenCV "CV_VERSION); printf( "\n\n\t按键操作说明: \n\n" "\t\t键盘按键任意键- 退出程序\n\n""\n\n\t\t\t\t\t\t\t\t by浅墨\n\n\n"); }
这里的图片素材是浅墨自己用手机拍的自己写的书:)
第一组图片对比效果:
第二组图片对比效果:
本篇文章的配套源代码请点击这里下载:
【浅墨OpenCV入门教程之十七】配套源代码之【重映射】 下载
------------------------------------------------------------------------
【浅墨OpenCV入门教程之十七】配套源代码之【SURF特征点检测】下载
OK,今天的内容大概就是这些,我们下篇文章见:)
【OpenCV入门教程之十七】OpenCV重映射 SURF特征点检测合辑相关推荐
- OpenCV仿射变换 SURF特征点描述合辑
原文:http://blog.csdn.net/poem_qianmo/article/details/33320997?utm_source=tuicool&utm_medium=refer ...
- 【OpenCV入门教程之一】 OpenCV 2.4.8 +VS2010的开发环境配置
目录(?)[-] 因为读研期间的研究方向是图像处理所以浅墨这段时间闭门研究了很多OpenCV和图像处理相关的知识与内容眼看自己积累到一定的程度了于是决定开始开设这个OpenCV系列专栏总结自己所学也分 ...
- OpenCV:remap()简单重映射
学习自:[OpenCV入门教程之十七]OpenCV重映射 重映射,就是把一幅图像中某位置的像素放置到另一个图片指定位置的过程. 为了完成映射过程, 我们需要获得一些插值为非整数像素的坐标,因为源图像与 ...
- 转载:【opencv入门教程之六】创建Trackbar图片对比度、亮度值调整
[OpenCV入门教程之六] 创建Trackbar & 图像对比度.亮度值调整 浅墨_毛星云 2014-03-18 21:43:18 103746 收藏 21 最后发布:2014-03-18 ...
- 转载:【OpenCV入门教程之四】 ROI区域图像叠加初级图像混合 全剖析
[OpenCV入门教程之四] ROI区域图像叠加&初级图像混合 全剖析 浅墨_毛星云 2014-03-10 12:48:05 157425 收藏 19 最后发布:2014-03-10 12:4 ...
- 转载:【opencv入门教程之三】:图片的载入|显示|输出
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/20537737 作者:毛星云(浅墨) ...
- 转载:【opencv入门教程之三】:组件结构
本系列文章由zhmxy555(毛星云)编写,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/19925819 作者:毛星 ...
- 转载:【opencv入门教程之一】:配置
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/19809337 作者:毛星云(浅 ...
- 【OpenCV入门教程之十三】OpenCV图像金字塔:高斯金字塔、拉普拉斯金字塔与图片尺寸缩放
本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接: http://blog.csdn.net/poem_qianmo/article/details/26157633 作者:毛星云(浅墨) ...
最新文章
- 程序员感叹一年只能存下15万太少了……网友:潸然泪下
- javascript worker 多线程 简单示例
- 如何用 SpringBoot 实现并发登录人数控制(附代码)
- 某业务自助开通账户问题排查
- 2017-2018-1 Java演绎法 第三周 作业
- 树形结构 —— 并查集 —— 并查集的删除操作
- 生成用于ASP.NET Web API的TypeScript客户端API
- eclipse生成变量的快捷键
- JVM本地方法栈及native方法
- iOS 实时录音和播放
- 2023南京工业大学计算机考研信息汇总
- linux在服务器上下载文件,上传下载文件到Linux服务器
- HTML+CSS期末大作业:家乡旅游网站设计——山东菏泽(6页) 简单个人网页设计作业 静态HTML旅行主题网页作业 DW个人网站模板下载 大学生简单个人网页作品代码
- 可靠性计算python库
- 怎样设置电脑桌面共享计算机,局域网共享设置,小编教你电脑怎么设置局域网共享...
- wifidog 配置文件
- Mac电脑:您的安全性偏好设置仅允许安装来自App Store和被认可的开发者的应用(解决方法)
- IDEA:Lambda expression are not supported at language level ‘5‘
- python mse函数_Python 线性回归处理糖尿病数据 计算MSE等
- H264 AVCC 格式转 ANNEX B格式