【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

邮箱: [email protected]

写作当前博文时配套使用的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( )函数会依据我们指定的映射形式,将源图像进行重映射几何变换,基于的式子例如以下:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" style="font-family: ‘Microsoft YaHei‘;font-size:14px;" width="421" height="26" />

须要注意,此函数不支持就地(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”,在帮助文字相关代码中添加了一句:

 printf("\t当前使用的OpenCV版本号为 OpenCV "CV_VERSION); 

便能够智能检測出当前使用的OpenCV版本号,并输出。如图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

按键说明也能够由上图看出。

放出这个程序具体凝视的源码:

//-----------------------------------【程序说明】----------------------------------------------
//		程序名称::《【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"
		);
}  

执行效果图。首先是原始图:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

第一种重映射:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

另外一种重映射:

第三种重映射:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" />

第四种重映射:

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");  

}

这里的图片素材是浅墨自己用手机拍的自己写的书:)

第一组图片对照效果:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="420" height="629" /> 

第二组图片对照效果:

 

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcG9lbV9xaWFubW8=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" width="420" height="629" style="font-family: ‘Microsoft YaHei‘;font-size:14px;" />

本篇文章的配套源码请点击这里下载:

【浅墨OpenCV新手教程之十七】配套源码之【重映射】 下载

------------------------------------------------------------------------

【浅墨OpenCV新手教程之十七】配套源码之【SURF特征点检測】下载

OK,今天的内容大概就是这些。我们下篇文章见:)

时间: 2024-10-15 09:50:09

【OpenCV新手教程之十七】OpenCV重映射 &amp; SURF特征点检測合辑的相关文章

【OpenCV新手教程之十八】OpenCV仿射变换 &amp;amp; SURF特征点描写叙述合辑

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/33320997 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 知乎:http://www.zhihu.com/people/mao-xing-yun 邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本号: 2.4.9 本篇文章中.我们一起探讨了OpenCV

【OpenCV入门教程之十七】OpenCV重映射 &amp; SURF特征点检测合辑

本系列文章由@浅墨_毛星云 出品,转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/30974513 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 知乎:http://www.zhihu.com/people/mao-xing-yun 邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本: 2.4.9 本篇文章中,我们一起探讨了OpenCV中

【OpenCV新手教程第14】OpenCVHough变换:霍夫变换线,霍夫变换圆汇编

本系列文章由@浅墨_毛星云 出品.转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/26977557 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 知乎:http://www.zhihu.com/people/mao-xing-yun 邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本号: 2.4.9 本篇文章中.我们一起探讨了OpenCV

OpenCV特征点检測------Surf(特征点篇)

Surf(Speed Up Robust Feature) Surf算法的原理                                                                           1.构建Hessian矩阵构造高斯金字塔尺度空间 事实上surf构造的金字塔图像与sift有非常大不同,就是由于这些不同才加快了其检測的速度. Sift採用的是DOG图像.而surf採用的是Hessian矩阵行列式近似值图像.Hessian矩阵是Surf算法的核心,为了方

OpenCV探索之路(八):重映射与仿射变换

重映射 重映射就是把一幅图像中某个位置的像素放置到另一个图片中指定位置的过程. 用一个数学公式来表示就是: 其中的 f 就是映射方式,也就说,像素点在另一个图像中的位置是由 f 来计算的. 在OpenCV中,用的是remap函数实现重映射. 基本重映射 #include <iostream> #include <opencv2\opencv.hpp> #include <opencv2\imgproc\imgproc.hpp> #include <opencv2\

【OpenCV入门教程之一】 OpenCV 2.4.8 +VS2010的开发环境配置

目录(?)[-] 因为读研期间的研究方向是图像处理所以浅墨这段时间闭门研究了很多OpenCV和图像处理相关的知识与内容眼看自己积累到一定的程度了于是决定开始开设这个OpenCV系列专栏总结自己所学也分享知识给大家 还是先放出待会儿的测试用图 下载和安装OpenCV SDK sources里面是源代码想查看完整的源代码需要用cmake来解包如何解包大家百度一下就可以或者下次浅墨来专门讲一讲这里就先不多说了 配置环境变量 工程包含include目录的配置 工程库lib目录的配置 链接库的配置 在Wi

【OpenCV十六新手教程】OpenCV角检测Harris角点检测

本系列文章由@浅墨_毛星云 出品.转载请注明出处. 文章链接:http://blog.csdn.net/poem_qianmo/article/details/29356187 作者:毛星云(浅墨)    微博:http://weibo.com/u/1723155442 知乎:http://www.zhihu.com/people/mao-xing-yun 邮箱: [email protected] 写作当前博文时配套使用的OpenCV版本号: 2.4.9 本篇文章中,我们一起探讨了OpenCV

系列文章 -- OpenCV入门教程

<OpenCV3编程入门>内容简介&勘误&配套源代码下载 [OpenCV入门教程之十八]OpenCV仿射变换 & SURF特征点描述合辑 [OpenCV入门教程之十七]OpenCV重映射 & SURF特征点检测合辑 [OpenCV入门教程之十六]OpenCV角点检测之Harris角点检测 [OpenCV入门教程之十五]水漫金山:OpenCV漫水填充算法(Floodfill) [OpenCV入门教程之十四]OpenCV霍夫变换:霍夫线变换,霍夫圆变换合辑 [Ope

OpenCV 入门教程 之环境配置 + 图片匹配 matchTemplate

1.什么是OpenCV OpenCV的全称是:Open Source Computer Vision Library.OpenCV是一个基于(开源)发行的跨平台计算机视觉库,可以运行在Linux.Windows和Mac OS操作系统上.它轻量级而且高效--由一系列 C 函数和少量 C++ 类构成. 总结特点: 1.开源, 商业用途也不必公开自己的源代码或者改善后的代码. 2.效率高,简单的图像处理就算了,涉及到复杂的处理一般的类库无法满足比如CXImage 3.有巨头维护(Intel) 有这三个