原文地址:http://blog.csdn.net/liyuefeilong/article/details/44220851
SURF算法是著名的尺度不变特征检测器SIFT(Scale-Invariant Features Transform)的高效变种,它为每个检测到的特征定义了位置和尺度,其中尺度的值可用于定义围绕特征点的窗口大小,使得每个特征点都与众不同。这里便是使用SURF算法提取两幅图像中的特征点描述子,并调用OpenCV中的函数进行匹配,最后输出一个可视化的结果,开发平台为Qt5.3.2+OpenCV2.4.9。以下给出图像匹配的实现步骤:
一、输入两幅图像,使用OpenCV中的cv::FeatureDetector接口实现SURF特征检测,在实际调试中改变阈值可获得不一样的检测结果:
<code class="hljs cpp has-numbering"> <span class="hljs-comment">// 设置两个用于存放特征点的向量</span> <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span><cv::KeyPoint></span> keypoint1; <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span><cv::KeyPoint></span> keypoint2; <span class="hljs-comment">// 构造SURF特征检测器</span> cv::SurfFeatureDetector surf(<span class="hljs-number">3000</span>); <span class="hljs-comment">// 阈值</span> <span class="hljs-comment">// 对两幅图分别检测SURF特征</span> surf.detect(image1,keypoint1); surf.detect(image2,keypoint2);</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li></ul>二、OpenCV 2.0版本中引入一个通用类,用于提取不同的特征点描述子。在这里构造一个SURF描述子提取器,输出的结果是一个矩阵,它的行数与特征点向量中的元素个数相同。每行都是一个N维描述子的向量。在SURF算法中,默认的描述子维度为64,该向量描绘了特征点周围的强度样式。两个特征点越相似,它们的特征向量也就越接近,因此这些描述子在图像匹配中十分有用:
<code class="hljs ruby has-numbering"> <span class="hljs-symbol">cv:</span><span class="hljs-symbol">:SurfDescriptorExtractor</span> surfDesc; <span class="hljs-regexp">//</span> 对两幅图像提取<span class="hljs-constant">SURF</span>描述子 <span class="hljs-symbol">cv:</span><span class="hljs-symbol">:Mat</span> descriptor1, descriptor2; surfDesc.compute(image1,keypoint1,descriptor1); surfDesc.compute(image2,keypoint2,descriptor2);</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li></ul>提取出两幅图像各自的特征点描述子后,需要进行比较(匹配)。可以调用OpenCV中的类cv::BruteForceMatcher构造一个匹配器。cv::BruteForceMatcher是类cv::DescriptorMatcher的一个子类,定义了不同的匹配策略的共同接口,结果返回一个cv::DMatch向量,它将被用于表示一对匹配的描述子。(关于cv::BruteForceMatcher 请参考: http://blog.csdn.net/panda1234lee/article/details/11094483?utm_source=tuicool)
三、在一批特征点匹配结果中筛选出评分(或者称距离)最理想的25个匹配结果,这通过std::nth_element实现。
<code class="hljs cs has-numbering"><span class="hljs-keyword">void</span> nth_element(_RandomAccessIterator _first, _RandomAccessIterator _nth, _RandomAccessIterator _last) </code><ul style="" class="pre-numbering"><li>1</li></ul>该函数的作用为将迭代器指向的从_first 到 _last 之间的元素进行二分排序,以_nth 为分界,前面都比 _Nth 小(大),后面都比之大(小),因此适用于找出前n个最大(最小)的元素。
四、最后一步,将匹配的结果可视化。OpenCV提供一个绘制函数以产生由两幅输入图像拼接而成的图像,而匹配的点由直线相连:
<code class="hljs lasso has-numbering"> <span class="hljs-comment">// 以下操作将匹配结果可视化</span> cv<span class="hljs-tag">::Mat</span> imageMatches; cv<span class="hljs-tag">::drawMatches</span>(image1,keypoint1, <span class="hljs-comment">// 第一张图片和检测到的特征点</span> image2,keypoint2, <span class="hljs-comment">// 第二张图片和检测到的特征点</span> matches, <span class="hljs-comment">// 输出的匹配结果</span> imageMatches, <span class="hljs-comment">// 生成的图像</span> cv<span class="hljs-tag">::Scalar</span>(<span class="hljs-number">128</span>,<span class="hljs-number">128</span>,<span class="hljs-number">128</span>)); <span class="hljs-comment">// 画直线的颜色</span></code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li></ul>要注意SIFT、SURF的函数在OpenCV的nonfree模块中而不是features2d,cv::BruteForceMatcher类存放在legacy模块中,因此函数中需要包含头文件:
<code class="hljs vala has-numbering"><span class="hljs-preprocessor">#include <opencv2/legacy/legacy.hpp></span> <span class="hljs-preprocessor">#include <opencv2/nonfree/nonfree.hpp></span></code><ul style="" class="pre-numbering"><li>1</li><li>2</li></ul>完整代码如下:
<code class="hljs cpp has-numbering"><span class="hljs-preprocessor">#include <QCoreApplication></span> <span class="hljs-preprocessor">#include <opencv2/core/core.hpp></span> <span class="hljs-preprocessor">#include <opencv2/highgui/highgui.hpp></span> <span class="hljs-preprocessor">#include <opencv2/legacy/legacy.hpp></span> <span class="hljs-preprocessor">#include <opencv2/nonfree/nonfree.hpp></span> <span class="hljs-preprocessor">#include <QDebug></span> <span class="hljs-keyword">int</span> main(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> *argv[]) { QCoreApplication a(argc, argv); <span class="hljs-comment">// 以下两图比之</span> <span class="hljs-comment">// 输入两张要匹配的图</span> cv::Mat image1= cv::imread(<span class="hljs-string">"c:/Fig12.18(a1).jpg"</span>,<span class="hljs-number">0</span>); cv::Mat image2= cv::imread(<span class="hljs-string">"c:/Fig12.18(a2).jpg"</span>,<span class="hljs-number">0</span>); <span class="hljs-keyword">if</span> (!image1.data || !image2.data) qDebug() << <span class="hljs-string">"Error!"</span>; cv::namedWindow(<span class="hljs-string">"Right Image"</span>); cv::imshow(<span class="hljs-string">"Right Image"</span>, image1); cv::namedWindow(<span class="hljs-string">"Left Image"</span>); cv::imshow(<span class="hljs-string">"Left Image"</span>, image2); <span class="hljs-comment">// 存放特征点的向量</span> <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span><cv::KeyPoint></span> keypoint1; <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span><cv::KeyPoint></span> keypoint2; <span class="hljs-comment">// 构造SURF特征检测器</span> cv::SurfFeatureDetector surf(<span class="hljs-number">3000</span>); <span class="hljs-comment">// 阈值</span> <span class="hljs-comment">// 对两幅图分别检测SURF特征</span> surf.detect(image1,keypoint1); surf.detect(image2,keypoint2); <span class="hljs-comment">// 输出带有详细特征点信息的两幅图像</span> cv::Mat imageSURF; cv::drawKeypoints(image1,keypoint1, imageSURF, cv::Scalar(<span class="hljs-number">255</span>,<span class="hljs-number">255</span>,<span class="hljs-number">255</span>), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); cv::namedWindow(<span class="hljs-string">"Right SURF Features"</span>); cv::imshow(<span class="hljs-string">"Right SURF Features"</span>, imageSURF); cv::drawKeypoints(image2,keypoint2, imageSURF, cv::Scalar(<span class="hljs-number">255</span>,<span class="hljs-number">255</span>,<span class="hljs-number">255</span>), cv::DrawMatchesFlags::DRAW_RICH_KEYPOINTS); cv::namedWindow(<span class="hljs-string">"Left SURF Features"</span>); cv::imshow(<span class="hljs-string">"Left SURF Features"</span>, imageSURF); <span class="hljs-comment">// 构造SURF描述子提取器</span> cv::SurfDescriptorExtractor surfDesc; <span class="hljs-comment">// 对两幅图像提取SURF描述子</span> cv::Mat descriptor1, descriptor2; surfDesc.compute(image1,keypoint1,descriptor1); surfDesc.compute(image2,keypoint2,descriptor2); <span class="hljs-comment">// 构造匹配器</span> cv::BruteForceMatcher< cv::L2<<span class="hljs-keyword">float</span>> > matcher; <span class="hljs-comment">// 将两张图片的描述子进行匹配,只选择25个最佳匹配</span> <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span><cv::DMatch></span> matches; matcher.match(descriptor1, descriptor2, matches); <span class="hljs-built_in">std</span>::nth_element(matches.begin(), <span class="hljs-comment">// 初始位置</span> matches.begin()+<span class="hljs-number">24</span>, <span class="hljs-comment">// 排序元素的位置</span> matches.end()); <span class="hljs-comment">// 终止位置</span> <span class="hljs-comment">// 移除25位后的所有元素</span> matches.erase(matches.begin()+<span class="hljs-number">25</span>, matches.end()); <span class="hljs-comment">// 以下操作将匹配结果可视化</span> cv::Mat imageMatches; cv::drawMatches(image1,keypoint1, <span class="hljs-comment">// 第一张图片和检测到的特征点</span> image2,keypoint2, <span class="hljs-comment">// 第二张图片和检测到的特征点</span> matches, <span class="hljs-comment">// 输出的匹配结果</span> imageMatches, <span class="hljs-comment">// 生成的图像</span> cv::Scalar(<span class="hljs-number">128</span>,<span class="hljs-number">128</span>,<span class="hljs-number">128</span>)); <span class="hljs-comment">// 画直线的颜色</span> cv::namedWindow(<span class="hljs-string">"Matches"</span>); <span class="hljs-comment">//, CV_WINDOW_NORMAL);</span> cv::imshow(<span class="hljs-string">"Matches"</span>,imageMatches); <span class="hljs-keyword">return</span> a.exec(); }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li></ul>效果一,由于原图中飞机的边缘有锯齿状,因此只需观察拐角处,匹配效果良好:
效果二,不涉及图像的旋转和变形,只是将一幅图像进行缩放后进行匹配,得出的效果自然是很好:
效果三,用两个不同的角度拍摄的图像进行匹配,其中部分特征点匹配有偏差,总体效果良好,在调试过程中还可以通过参数调整获取更好的匹配效果。
附注:另一种匹配方法是使用 cv::FlannBasedMatcher 接口以及函数 FLANN 实现快速高效匹配(快速最近邻逼近搜索函数库(Fast Approximate Nearest Neighbor Search Library))。网上有源代码例程如下:
<code class="hljs cpp has-numbering"><span class="hljs-preprocessor">#include <stdio.h></span> <span class="hljs-preprocessor">#include <iostream></span> <span class="hljs-preprocessor">#include "opencv2/core/core.hpp"</span> <span class="hljs-preprocessor">#include <opencv2/highgui/highgui.hpp></span> <span class="hljs-preprocessor">#include <opencv2/legacy/legacy.hpp></span> <span class="hljs-preprocessor">#include "opencv2/features2d/features2d.hpp"</span> <span class="hljs-preprocessor">#include "opencv2/highgui/highgui.hpp"</span> <span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> cv; <span class="hljs-keyword">void</span> readme(); <span class="hljs-comment">/** @function main */</span> <span class="hljs-keyword">int</span> main( <span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span>** argv ) { <span class="hljs-keyword">if</span>( argc != <span class="hljs-number">3</span> ) { readme(); <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span>; } Mat img_1 = imread( argv[<span class="hljs-number">1</span>], CV_LOAD_IMAGE_GRAYSCALE ); Mat img_2 = imread( argv[<span class="hljs-number">2</span>], CV_LOAD_IMAGE_GRAYSCALE ); <span class="hljs-keyword">if</span>( !img_1.data || !img_2.data ) { <span class="hljs-built_in">std</span>::<span class="hljs-built_in">cout</span><< <span class="hljs-string">" --(!) Error reading images "</span> << <span class="hljs-built_in">std</span>::endl; <span class="hljs-keyword">return</span> -<span class="hljs-number">1</span>; } <span class="hljs-comment">//-- Step 1: Detect the keypoints using SURF Detector</span> <span class="hljs-keyword">int</span> minHessian = <span class="hljs-number">400</span>; SurfFeatureDetector detector( minHessian ); <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span><KeyPoint></span> keypoints_1, keypoints_2; detector.detect( img_1, keypoints_1 ); detector.detect( img_2, keypoints_2 ); <span class="hljs-comment">//-- Step 2: Calculate descriptors (feature vectors)</span> SurfDescriptorExtractor extractor; Mat descriptors_1, descriptors_2; extractor.compute( img_1, keypoints_1, descriptors_1 ); extractor.compute( img_2, keypoints_2, descriptors_2 ); <span class="hljs-comment">//-- Step 3: Matching descriptor vectors using FLANN matcher</span> FlannBasedMatcher matcher; <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span>< DMatch ></span> matches; matcher.match( descriptors_1, descriptors_2, matches ); <span class="hljs-keyword">double</span> max_dist = <span class="hljs-number">0</span>; <span class="hljs-keyword">double</span> min_dist = <span class="hljs-number">100</span>; <span class="hljs-comment">//-- Quick calculation of max and min distances between keypoints</span> <span class="hljs-keyword">for</span>( <span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < descriptors_1.rows; i++ ) { <span class="hljs-keyword">double</span> dist = matches[i].distance; <span class="hljs-keyword">if</span>( dist < min_dist ) min_dist = dist; <span class="hljs-keyword">if</span>( dist > max_dist ) max_dist = dist; } <span class="hljs-built_in">printf</span>(<span class="hljs-string">"-- Max dist : %f \n"</span>, max_dist ); <span class="hljs-built_in">printf</span>(<span class="hljs-string">"-- Min dist : %f \n"</span>, min_dist ); <span class="hljs-comment">//-- Draw only "good" matches (i.e. whose distance is less than 2*min_dist )</span> <span class="hljs-comment">//-- PS.- radiusMatch can also be used here.</span> <span class="hljs-built_in">std</span>::<span class="hljs-stl_container"><span class="hljs-built_in">vector</span>< DMatch ></span> good_matches; <span class="hljs-keyword">for</span>( <span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < descriptors_1.rows; i++ ) { <span class="hljs-keyword">if</span>( matches[i].distance < <span class="hljs-number">2</span>*min_dist ) { good_matches.push_back( matches[i]); } } <span class="hljs-comment">//-- Draw only "good" matches</span> Mat img_matches; drawMatches( img_1, keypoints_1, img_2, keypoints_2, good_matches, img_matches, Scalar::all(-<span class="hljs-number">1</span>), Scalar::all(-<span class="hljs-number">1</span>), <span class="hljs-stl_container"><span class="hljs-built_in">vector</span><<span class="hljs-keyword">char</span>></span>(), DrawMatchesFlags::NOT_DRAW_SINGLE_POINTS ); <span class="hljs-comment">//-- Show detected matches</span> imshow( <span class="hljs-string">"Good Matches"</span>, img_matches ); <span class="hljs-keyword">for</span>( <span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < good_matches.size(); i++ ) { <span class="hljs-built_in">printf</span>( <span class="hljs-string">"-- Good Match [%d] Keypoint 1: %d -- Keypoint 2: %d \n"</span>, i, good_matches[i].queryIdx, good_matches[i].trainIdx ); } waitKey(<span class="hljs-number">0</span>); <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>; } <span class="hljs-comment">/** @function readme */</span> <span class="hljs-keyword">void</span> readme() { <span class="hljs-built_in">std</span>::<span class="hljs-built_in">cout</span> << <span class="hljs-string">" Usage: ./SURF_FlannMatcher <img1> <img2>"</span> << <span class="hljs-built_in">std</span>::endl; }</code><ul style="" class="pre-numbering"><li>1</li><li>2</li><li>3</li><li>4</li><li>5</li><li>6</li><li>7</li><li>8</li><li>9</li><li>10</li><li>11</li><li>12</li><li>13</li><li>14</li><li>15</li><li>16</li><li>17</li><li>18</li><li>19</li><li>20</li><li>21</li><li>22</li><li>23</li><li>24</li><li>25</li><li>26</li><li>27</li><li>28</li><li>29</li><li>30</li><li>31</li><li>32</li><li>33</li><li>34</li><li>35</li><li>36</li><li>37</li><li>38</li><li>39</li><li>40</li><li>41</li><li>42</li><li>43</li><li>44</li><li>45</li><li>46</li><li>47</li><li>48</li><li>49</li><li>50</li><li>51</li><li>52</li><li>53</li><li>54</li><li>55</li><li>56</li><li>57</li><li>58</li><li>59</li><li>60</li><li>61</li><li>62</li><li>63</li><li>64</li><li>65</li><li>66</li><li>67</li><li>68</li><li>69</li><li>70</li><li>71</li><li>72</li><li>73</li><li>74</li><li>75</li><li>76</li><li>77</li><li>78</li><li>79</li><li>80</li><li>81</li><li>82</li><li>83</li><li>84</li><li>85</li><li>86</li><li>87</li><li>88</li></ul>以上只是记录这种方法的实现例程,并没有验证代码的正确性。
参考资料:
http://blog.sina.com.cn/s/blog_a98e39a201017pgn.html
http://www.cnblogs.com/tornadomeet/archive/2012/08/17/2644903.html (SURF算法的理论介绍)
http://blog.csdn.net/liyuefeilong/article/details/44166069
http://www.opencv.org.cn/opencvdoc/2.3.2/html/doc/tutorials/features2d/feature_flann_matcher/feature_flann_matcher.html
