1. 程式人生 > >【OpenCV入門教程之十七】OpenCV重對映 SURF特徵點檢測合輯

【OpenCV入門教程之十七】OpenCV重對映 SURF特徵點檢測合輯

               

本篇文章中,我們一起探討了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];        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, 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 type    CV_WRAP int descriptorType() const;    //! finds the keypoints using fast hessian detector used in SURF    void 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 keypoints    void 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 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 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”,在幫助文字相關程式碼中加入了一句