OpenCV重映射及特征点检测

文章转自:http://www.tuicool.com/articles/uIJvia

一、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,它有两种可能的表示对象。
    1. 表示点(x,y)的第一个映射。
    2. 表示CV_16SC2 , CV_32FC1 或CV_32FC2类型的X值。
  • 第四个参数,InputArray类型的map2,同样,它也有两种可能的表示对象,而且他是根据map1来确定表示那种对象。
    1. 若map1表示点(x,y)时。这个参数不代表任何值。
    2. 表示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函数的使用方法。

     

    //-----------------------------------【头文件包含部分】---------------------------------------
    //	     描述:包含程序所依赖的头文件
    //----------------------------------------------------------------------------------------------
    #include"opencv2/highgui/highgui.hpp"
    #include"opencv2/imgproc/imgproc.hpp"
    #include <iostream>
     
    //-----------------------------------【命名空间声明部分】--------------------------------------
    //	  描述:包含程序所使用的命名空间
    //-----------------------------------------------------------------------------------------------
    using namespace cv;
     
    //-----------------------------------【main( )函数】--------------------------------------------
    //	  描述:控制台应用程序的入口函数,我们的程序从这里开始执行
    //-----------------------------------------------------------------------------------------------
    int main( )
    {
      //【0】变量定义
      MatsrcImage, dstImage;
      Matmap_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>(srcImage.cols - i);
          map_y.at<float>(j,i)= static_cast<float>(j);
        }
      }
     
      //【4】进行重映射操作
      remap(srcImage, dstImage, map_x, map_y, CV_INTER_LINEAR, BORDER_CONSTANT, Scalar(0,0,0) );
     
      //【5】显示效果图
      imshow("【程序窗口】", dstImage );
      waitKey();
     
      return0;
    }

    显示效果图:



     最近在举行世界杯,这里的图片素材就是巴西队的球星们~

     

    1.4 OpenCV2.X中remap函数源代码

     

    这里我们放出remap函数的源码,供需要了解其实现细节的朋友们观看,浅墨在这里不花时间对其进行剖析。

    void cv::remap( InputArray _src,OutputArray _dst,
               InputArray _map1, InputArray_map2,
               int interpolation, intborderType, 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];
         else
            CV_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 constructor
      CV_WRAP SURF();
      //! the full constructor taking all the necessary parameters
      explicit CV_WRAP SURF(double hessianThreshold,
                int nOctaves=4, intnOctaveLayers=2,
                bool extended=true, boolupright=false);
     
      //! returns the descriptor size in float's (64 or 128)
      CV_WRAP int descriptorSize() const;
     
      //! returns the descriptor type
      CV_WRAP int descriptorType() const;
     
      //! finds the keypoints using fast hessian detector used in SURF
      void operator()(InputArray img, InputArray mask,
                  CV_OUTvector<KeyPoint>& keypoints) const;
      //! finds the keypoints and computes their descriptors. Optionally itcan compute descriptors for the user-provided keypoints
      void operator()(InputArray img, InputArray mask,
                  CV_OUTvector<KeyPoint>& keypoints,
                  OutputArray descriptors,
                  booluseProvidedKeypoints=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 detectedkeypoints.
        * mask         Mask specifyingwhere to look for keypoints (optional). Must be a char
        *              matrix withnon-zero values in the region of interest.
        * useProvidedKeypoints If true, the method will skip the detection phaseand will compute
        *                      descriptorsfor the provided keypoints
        */
        CV_WRAP_AS(detectAndCompute) virtual voidoperator()( InputArray image, InputArray mask,
                                         CV_OUTvector<KeyPoint>& keypoints,
                                        OutputArray descriptors,
                                         bool useProvidedKeypoints=false ) const =0;
     
       CV_WRAP void compute( const Mat& image, CV_OUT CV_IN_OUTstd::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类:

    /************************************ BaseClasses ************************************/
     
    /*
     *Abstract base class for 2D image feature detectors.
     */
    class CV_EXPORTS_W FeatureDetector : publicvirtual Algorithm
    {
    public:
       virtual ~FeatureDetector();
     
       /*
        * Detect keypoints in an image.
        * image        The image.
        * keypoints    The detectedkeypoints.
        * mask         Mask specifyingwhere to look for keypoints (optional). Must be a char
        *              matrix withnon-zero values in the region of interest.
        */
       CV_WRAP void detect( const Mat& image, CV_OUTvector<KeyPoint>& keypoints, const Mat& mask=Mat() ) const;
     
       /*
        * Detect keypoints in an image set.
        * images       Image collection.
        * keypoints    Collection ofkeypoints detected in an input images. keypoints[i] is a set of keypointsdetected in an images[i].
        * masks        Masks for imageset. masks[i] is a mask for images[i].
        */
       void detect( const vector<Mat>& images,vector<vector<KeyPoint> >& keypoints, constvector<Mat>& masks=vector<Mat>() ) const;
     
       // Return true if detector object is empty
       CV_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 keypointdetection 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 detectedkeypoints.
      * mask		 Mask specifyingwhere to look for keypoints (optional). Must be a char
      *			  matrix withnon-zero values in the region of interest.
      */
       CV_WRAP void detect( const Mat& image, CV_OUTvector<KeyPoint>& keypoints, const Mat& mask=Mat() ) const;
     
       /*
      * Detect keypoints in an image set.
      * images	   Image collection.
       * keypoints	Collection of keypoints detected in aninput images. keypoints[i] is a set of keypoints detected in an images[i].
      * masks		Masks for imageset. masks[i] is a mask for images[i].
      */
       void detect( const vector<Mat>& images,vector<vector<KeyPoint> >& keypoints, constvector<Mat>& masks=vector<Mat>() ) const;

    同样,看看SURF类的另一个“爷爷”DescriptorExtractor类的声明

     

    /*
     *Abstract base class for computing descriptors for image keypoints.
     *
     * Inthis interface we assume a keypoint descriptor can be represented as a
     *dense, fixed-dimensional vector of some basic type. Most descriptors used
     * inpractice follow this pattern, as it makes it very easy to compute
     *distances between descriptors. Therefore we represent a collection of
     * descriptorsas 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 inputkeypoints. Keypoints for which a descriptor cannot be computed are removed.
        * descriptors  Copmputeddescriptors. Row i is the descriptor for keypoint i.
        */
       CV_WRAP void compute( const Mat& image, CV_OUT CV_IN_OUTvector<KeyPoint>& keypoints, CV_OUT Mat& descriptors ) const;
     
       /*
        * Compute the descriptors for a keypoints collection detected in imagecollection.
        * images       Image collection.
        * keypoints    Input keypointscollection. keypoints[i] is keypoints detected in images[i].
        *              Keypoints for whicha descriptor cannot be computed are removed.
        * descriptors  Descriptorcollection. 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,
                                          SizeimageSize, 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_typeget(const string& name) const;
      template<typename _Tp> typename ParamType<_Tp>::member_typeget(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, constPtr<_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, constvector<Mat>& value);
      CV_WRAP void setAlgorithm(const string& name, constPtr<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, constPtr<_Tp>& value);
     
      void setInt(const char* name, int value);
       void setDouble(const char* name, doublevalue);
      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, constPtr<_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(conststring& 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 ofoutput image may be reused.
             // Two source images,matches, and single keypoints
             // will be drawn.
             // For each keypoint, onlythe center point will be
             // drawn (without a circlearound the keypoint with the
             // keypoint size andorientation).
           DRAW_OVER_OUTIMG = 1, // Output image matrix will not be
               // created (usingMat::create). Matches will be drawn
               // on existing contentof output image.
           NOT_DRAW_SINGLE_POINTS = 2, // Single keypoints will not be drawn.
           DRAW_RICH_KEYPOINTS = 4 // For each keypoint, the circle around
               // keypoint withkeypoint 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 浅墨
    //		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 浅墨
    //		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");  
    
    }

    运行效果图。

    第一组图片对比效果:

     

    第二组图片对比效果:

     



     


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值