opencv-ffi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (130) hide show
  1. data/.gitignore +7 -0
  2. data/Gemfile +15 -0
  3. data/README.md +126 -0
  4. data/Rakefile +52 -0
  5. data/docs/DocsIndex.md +1 -0
  6. data/docs/examples/load_image.rb +25 -0
  7. data/ext/Rakefile +13 -0
  8. data/ext/aishack-sift/.gitignore +4 -0
  9. data/ext/aishack-sift/Descriptor.h +34 -0
  10. data/ext/aishack-sift/KeyPoint.h +38 -0
  11. data/ext/aishack-sift/README +20 -0
  12. data/ext/aishack-sift/SIFT.cpp +1036 -0
  13. data/ext/aishack-sift/SIFT.h +84 -0
  14. data/ext/aishack-sift/example/.gitignore +2 -0
  15. data/ext/aishack-sift/example/Makefile +24 -0
  16. data/ext/aishack-sift/example/MySIFT.cpp +29 -0
  17. data/ext/aishack-sift/mkrf_conf.rb +13 -0
  18. data/ext/aishack-sift/siftlib.cpp +85 -0
  19. data/ext/eigen/.gitignore +4 -0
  20. data/ext/eigen/eigen_polynomial.cpp +41 -0
  21. data/ext/eigen/eigen_svd.cpp +100 -0
  22. data/ext/eigen/mkrf_conf.rb +14 -0
  23. data/ext/mkrf-monkey.rb +85 -0
  24. data/ext/mkrf-rakehelper-monkey.rb +52 -0
  25. data/ext/mkrf_conf.rb +3 -0
  26. data/ext/opencv-ffi/.gitignore +4 -0
  27. data/ext/opencv-ffi/matcher_helper.cpp +56 -0
  28. data/ext/opencv-ffi/mkrf_conf.rb +12 -0
  29. data/ext/opencv-ffi/vector_math.cpp +39 -0
  30. data/ext/opensurf/.gitignore +4 -0
  31. data/ext/opensurf/README +38 -0
  32. data/ext/opensurf/fasthessian.cpp +376 -0
  33. data/ext/opensurf/fasthessian.h +108 -0
  34. data/ext/opensurf/integral.cpp +58 -0
  35. data/ext/opensurf/integral.h +55 -0
  36. data/ext/opensurf/ipoint.cpp +108 -0
  37. data/ext/opensurf/ipoint.h +76 -0
  38. data/ext/opensurf/kmeans.h +172 -0
  39. data/ext/opensurf/mkrf_conf.rb +10 -0
  40. data/ext/opensurf/responselayer.h +92 -0
  41. data/ext/opensurf/surf.cpp +317 -0
  42. data/ext/opensurf/surf.h +66 -0
  43. data/ext/opensurf/surflib.cpp +98 -0
  44. data/ext/opensurf/surflib.h +96 -0
  45. data/ext/opensurf/utils.cpp +357 -0
  46. data/ext/opensurf/utils.h +63 -0
  47. data/lib/.gitignore +1 -0
  48. data/lib/opencv-ffi-ext/eigen.rb +84 -0
  49. data/lib/opencv-ffi-ext/features2d.rb +4 -0
  50. data/lib/opencv-ffi-ext/matcher_helper.rb +24 -0
  51. data/lib/opencv-ffi-ext/opensurf.rb +217 -0
  52. data/lib/opencv-ffi-ext/sift.rb +118 -0
  53. data/lib/opencv-ffi-ext/vector_math.rb +115 -0
  54. data/lib/opencv-ffi-wrappers.rb +7 -0
  55. data/lib/opencv-ffi-wrappers/core.rb +24 -0
  56. data/lib/opencv-ffi-wrappers/core/iplimage.rb +50 -0
  57. data/lib/opencv-ffi-wrappers/core/mat.rb +268 -0
  58. data/lib/opencv-ffi-wrappers/core/misc_draw.rb +44 -0
  59. data/lib/opencv-ffi-wrappers/core/point.rb +286 -0
  60. data/lib/opencv-ffi-wrappers/core/rect.rb +40 -0
  61. data/lib/opencv-ffi-wrappers/core/scalar.rb +104 -0
  62. data/lib/opencv-ffi-wrappers/core/size.rb +88 -0
  63. data/lib/opencv-ffi-wrappers/enumerable.rb +10 -0
  64. data/lib/opencv-ffi-wrappers/features2d.rb +17 -0
  65. data/lib/opencv-ffi-wrappers/features2d/image_patch.rb +322 -0
  66. data/lib/opencv-ffi-wrappers/features2d/star.rb +111 -0
  67. data/lib/opencv-ffi-wrappers/features2d/surf.rb +115 -0
  68. data/lib/opencv-ffi-wrappers/highgui.rb +10 -0
  69. data/lib/opencv-ffi-wrappers/imgproc.rb +4 -0
  70. data/lib/opencv-ffi-wrappers/imgproc/features.rb +35 -0
  71. data/lib/opencv-ffi-wrappers/imgproc/geometric.rb +39 -0
  72. data/lib/opencv-ffi-wrappers/matcher.rb +297 -0
  73. data/lib/opencv-ffi-wrappers/matrix.rb +37 -0
  74. data/lib/opencv-ffi-wrappers/misc.rb +41 -0
  75. data/lib/opencv-ffi-wrappers/misc/params.rb +34 -0
  76. data/lib/opencv-ffi-wrappers/sequence.rb +37 -0
  77. data/lib/opencv-ffi-wrappers/vectors.rb +38 -0
  78. data/lib/opencv-ffi.rb +12 -0
  79. data/lib/opencv-ffi/calib3d.rb +26 -0
  80. data/lib/opencv-ffi/core.rb +15 -0
  81. data/lib/opencv-ffi/core/draw.rb +68 -0
  82. data/lib/opencv-ffi/core/dynamic.rb +13 -0
  83. data/lib/opencv-ffi/core/library.rb +5 -0
  84. data/lib/opencv-ffi/core/operations.rb +122 -0
  85. data/lib/opencv-ffi/core/point.rb +22 -0
  86. data/lib/opencv-ffi/core/types.rb +172 -0
  87. data/lib/opencv-ffi/cvffi.rb +8 -0
  88. data/lib/opencv-ffi/features2d.rb +7 -0
  89. data/lib/opencv-ffi/features2d/library.rb +6 -0
  90. data/lib/opencv-ffi/features2d/star.rb +30 -0
  91. data/lib/opencv-ffi/features2d/surf.rb +38 -0
  92. data/lib/opencv-ffi/highgui.rb +31 -0
  93. data/lib/opencv-ffi/imgproc.rb +9 -0
  94. data/lib/opencv-ffi/imgproc/features.rb +37 -0
  95. data/lib/opencv-ffi/imgproc/geometric.rb +42 -0
  96. data/lib/opencv-ffi/imgproc/library.rb +6 -0
  97. data/lib/opencv-ffi/imgproc/misc.rb +39 -0
  98. data/lib/opencv-ffi/version.rb +3 -0
  99. data/opencv-ffi.gemspec +26 -0
  100. data/test/core/test_draw.rb +46 -0
  101. data/test/core/test_operations.rb +135 -0
  102. data/test/core/test_size.rb +14 -0
  103. data/test/core/test_text.rb +52 -0
  104. data/test/ext/test_eigen.rb +105 -0
  105. data/test/ext/test_opensurf.rb +35 -0
  106. data/test/ext/test_sift.rb +26 -0
  107. data/test/ext/test_vector_math.rb +85 -0
  108. data/test/features2d/test_surf.rb +63 -0
  109. data/test/imgproc/test_goodfeatures.rb +18 -0
  110. data/test/setup.rb +65 -0
  111. data/test/test_calib3d.rb +38 -0
  112. data/test/test_core.rb +26 -0
  113. data/test/test_ext.rb +8 -0
  114. data/test/test_features2d.rb +9 -0
  115. data/test/test_files/images/IMG_7088.JPG +0 -0
  116. data/test/test_files/images/IMG_7088_small.JPG +0 -0
  117. data/test/test_files/images/IMG_7089.JPG +0 -0
  118. data/test/test_highgui.rb +26 -0
  119. data/test/test_imgproc.rb +35 -0
  120. data/test/test_wrappers.rb +8 -0
  121. data/test/wrappers/core/test_draw.rb +41 -0
  122. data/test/wrappers/core/test_mat.rb +40 -0
  123. data/test/wrappers/core/test_operations.rb +35 -0
  124. data/test/wrappers/core/test_types.rb +235 -0
  125. data/test/wrappers/features2d/test_image_patch.rb +108 -0
  126. data/test/wrappers/test_imgproc.rb +87 -0
  127. data/test/wrappers/test_matcher.rb +96 -0
  128. data/test/wrappers/test_star.rb +28 -0
  129. data/test/wrappers/test_surf.rb +36 -0
  130. metadata +234 -0
@@ -0,0 +1,7 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .yardoc
6
+ coverage
7
+ yardoc
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+ gem "rake"
3
+
4
+ # Specify your gem's dependencies in opencv-ffi.gemspec
5
+ gemspec
6
+
7
+ group :development do
8
+ gem "redcarpet"
9
+ gem "simplecov", :require => false
10
+ gem "yard"
11
+
12
+ # gem "nice-ffi", :git=>"git@github.com:amarburg/nice-ffi.git"
13
+ # gem "ffi", :git=>'git@github.com:amarburg/ffi.git'
14
+ end
15
+
@@ -0,0 +1,126 @@
1
+ OpenCV-FFI
2
+ ==========
3
+
4
+ Status
5
+ ------
6
+
7
+ I freely admit this is project is still in-development. I am using it
8
+ for day-to-day work and it will continue to get better, more stable, etc.
9
+ It does not cover the whole OpenCV C API, by an stretch -- it needs more
10
+ and better tests, really.
11
+
12
+ I'm very happy to discuss how this project might grow and expand to
13
+ become more useful to the Ruby community...
14
+
15
+
16
+ Introduction
17
+ ------------
18
+
19
+ An initial attempt at using [Ruby
20
+ FFI](https://github.com/ffi/ffi) (actually, relying heavily on
21
+ [Nice-FFI](https://github.com/jacius/nice-ffi)) to wrap OpenCV.
22
+
23
+ Currently developing against
24
+ [OpenCV](http://opencv.willowgarage.com/wiki/) 2.3.x pulled from
25
+ [SVN](https://code.ros.org/svn/opencv/branches/2.3/opencv/).
26
+
27
+ Also requires [Eigen](http://eigen.tuxfamily.org/index.php?title=Main_Page) 3.0.x. At present I'm developing against the 3.0.2 source tarball available [here](http://bitbucket.org/eigen/eigen/get/3.0.2.tar.gz).
28
+
29
+ This is admittedly a pet project at the moment, so I'm doing a poor job
30
+ separating out my immediate needs from the "best practices" structure
31
+ for the Gem. For example, I recently added a dependency on Eigen mostly
32
+ as an experiment. In the future I think it would be smart to remove
33
+ the Eigen dependency and make it a separate gem for those who need the
34
+ functionality.
35
+
36
+ At present this is a three level API:
37
+
38
+ + `opencv-ffi` is a "pure" FFI wrapper around the OpenCV C API. That is,
39
+ for each relevant OpenCV struct there is an FFI struct. For each function
40
+ (or at least the one's I've gotten to), there's an `attach_function`
41
+ with relatively little else. Coding with this API is basically 1-to-1
42
+ with coding the OpenCV API in C.
43
+
44
+ + `opencv-ffi-wrappers` is an attempt to create a nicer "more Ruby" API
45
+ on top of the pure level, in two ways. In some cases, OpenCV structs are extended to make them
46
+ full Ruby objects with more object-like APIs.
47
+ In other cases new objects are created
48
+ which wrap around OpenCV objects.
49
+
50
+ New objects are often created to hide the typed-ness of OpenCV
51
+ structs. For example, a `CvPoint2D32F` is different from a
52
+ `CvPoint2D32U`. A `Point` wrapper, however, could behave more
53
+ interchangably.
54
+
55
+ In many cases, OpenCV function are wrapped with helper functions which handle defaults and type checking incoming data in a more Ruby way.
56
+
57
+ For performance reasons, every effort is made to keep data (particularly
58
+ CvMat, IplImage, etc) in OpenCV structs as much as possible.
59
+
60
+ + `opencv-ffi-ext` is a compiled C extension library which adds new
61
+ functionality to the Gem, as I need it. In some cases this might be an
62
+ accelerated backend for a Ruby function. At present, it also includes
63
+ a copy of Edward Rosten's FAST feature detector, compiled from his code,
64
+ and the aforementioned OpenCV-to-Eigen translation layer.
65
+
66
+ Motivation and Project Goals
67
+ ---
68
+
69
+ This project was largely inspired by my experiments with OpenCV's C++
70
+ interface. I found experimentation difficult, and always ended up with
71
+ verbose code which spent more time converting between data types than
72
+ actually performing calculations, particularly given the frequent shaping
73
+ and matrix construction used in multiple view geometry and stereo vision.
74
+ I was constantly refactoring for readability.
75
+
76
+ I also needed to interface with third-party algorithm libraries like
77
+ Eigen, and needed to convert Cv constructs to Eigen constructs, and back.
78
+ Inevitably I wrote whole families of interface and helper functions,
79
+ but wondered at their long-term utility.
80
+
81
+ In the end, I decided if I was going to write helper and conversion
82
+ functions, I might as well get all of the benefits of coding in Ruby
83
+ for free.
84
+
85
+ The project goals are:
86
+
87
+ * Bring OpenCV's algorithms and data types into Ruby.
88
+ * Allow prototyping of expressive, low-code-overhead computer vision algorithms in Ruby.
89
+ * Keep things as time efficient as possible.
90
+
91
+ Obviously, working in Ruby, time/CPU efficiency isn't your first goal,
92
+ but it should be possible to quickly sketch and test an algorithm in Ruby,
93
+ then slowly push the computationally expensive elements into C.
94
+
95
+ Naming
96
+ ---
97
+
98
+ Everything is in the `CVFFI` namespace. OpenCV structures and functions
99
+ are named as in OpenCV, with function using the `cvFunctionName`
100
+ convention, and structure using the `CvStruct` convention.
101
+
102
+ Wrappers will generally dispense with the `Cv`/`cv` prefix. So a
103
+ `CvPoint` is an OpenCV CvPoint structures, while a `Point` is its wrapper.
104
+
105
+
106
+ Resources
107
+ ---
108
+
109
+ See also {file:docs/DocsIndex.md} for an index of other documentation.
110
+
111
+ Example code is in the `docs/examples` directory.
112
+
113
+
114
+ Caveats and Second thoughts
115
+ ---
116
+
117
+ Arguably, the "wrappers" layer could be split into two layers --
118
+ one which adds as much functionality as possible while just extending the OpenCV
119
+ structs, and a second which really breaks out into new high-level objects.
120
+
121
+
122
+ License
123
+ ---
124
+
125
+ TBD
126
+
@@ -0,0 +1,52 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake'
3
+ require 'rake/testtask'
4
+ require 'rake/clean'
5
+ require 'yard'
6
+ require './ext/mkrf-rakehelper-monkey'
7
+
8
+ Rake::TestTask.new(:test) do |t|
9
+ # t.libs << 'lib/opencv-ffi' << '.'
10
+ t.libs << '.'
11
+ t.verbose = true
12
+ t.test_files = FileList['test/test_*.rb']
13
+ end
14
+
15
+ CLEAN.include "lib/*.so"
16
+
17
+ #Rake::ExtensionTask.new('fast')
18
+ setup_extension "opencv-ffi", "libcvffi"
19
+ setup_extension "eigen", "libcvffi_eigen"
20
+ setup_extension "opensurf", "libcvffi_opensurf"
21
+ setup_extension "aishack-sift", "libcvffi_sift"
22
+
23
+
24
+ task :default => 'test'
25
+
26
+ task :test => Mkrf::all_libs
27
+
28
+ # Hm, let YARD take care of documention
29
+ #desc "Build the local Markdown docs to html"
30
+ #task :docs => 'html/'
31
+ #task :docs => :yard
32
+ #task :docs => FileList['README.md', 'docs/*.md'].ext('.html').pathmap("html/%f")
33
+
34
+ #directory 'html/'
35
+
36
+ #rule( ".html" => [
37
+ # proc {|task_name| a = task_name.pathmap("%n.md")
38
+ # if FileTest.exists? a
39
+ # a
40
+ # else
41
+ # task_name.pathmap("docs/%n.md")
42
+ # end } ] ) do |t|
43
+ # sh "redcarpet #{t.source} > #{t.name}"
44
+ #end
45
+
46
+ YARD::Rake::YardocTask.new do |t|
47
+ t.files = ['lib/**/*.rb', '-', 'docs/*.md']
48
+ t.options = [ '--output-dir','yardoc/' ]
49
+ end
50
+
51
+
52
+
@@ -0,0 +1 @@
1
+ Foo foo
@@ -0,0 +1,25 @@
1
+ #
2
+ # A simple example of opencv-ffi
3
+ #
4
+ # Opens a file and displays its size.
5
+ #
6
+ # Usage: ruby load_image.rb file_name
7
+ #
8
+
9
+ require 'opencv-ffi'
10
+ require 'opencv-ffi-wrappers'
11
+
12
+ if ARGV.length == 0
13
+ puts "Usage: #{__FILE__} image_file_name"
14
+ abort
15
+ end
16
+
17
+ img_name = ARGV[0]
18
+ abort "Can't open file \"#{img_name}\"" unless FileTest::readable?( img_name )
19
+
20
+ img = CVFFI::cvLoadImage( img_name )
21
+
22
+ puts "Image \"#{img_name}\" is #{img.width} x #{img.height}"
23
+
24
+
25
+
@@ -0,0 +1,13 @@
1
+
2
+ libs = [ "opencv-ffi", "eigen", "opensurf", "aishack-sift" ]
3
+
4
+ task :default => libs
5
+
6
+ libs.each { |l|
7
+ task l do
8
+ Dir.chdir( [File.dirname(__FILE__),l].join('/') )
9
+ ruby 'mkrf_conf.rb' unless File.exists?( 'Rakefile' )
10
+ sh 'rake build_library'
11
+ end
12
+ }
13
+
@@ -0,0 +1,4 @@
1
+ *.o
2
+ Rakefile
3
+ *.so
4
+ mkrf.log
@@ -0,0 +1,34 @@
1
+ /******************************************************
2
+ * Code by Utkarsh Sinha
3
+ * Based on JIFT by Jun Liu
4
+ * Visit http://aishack.in/ for more indepth articles and tutorials
5
+ * on artificial intelligence
6
+ * Use, reuse, modify, hack, kick. Do whatever you want with
7
+ * this code :)
8
+ ******************************************************/
9
+
10
+ #ifndef DESCRIPTOR_H
11
+ #define DESCRIPTOR_H
12
+
13
+ using namespace std;
14
+
15
+ class Descriptor
16
+ {
17
+ public:
18
+ float xi, yi; // The location
19
+ vector<double> fv; // The feature vector
20
+
21
+ Descriptor()
22
+ {
23
+ }
24
+
25
+ Descriptor(float x, float y, vector<double> const& f)
26
+ {
27
+ xi = x;
28
+ yi = y;
29
+ fv = f;
30
+ }
31
+ };
32
+
33
+ #endif
34
+
@@ -0,0 +1,38 @@
1
+ /******************************************************
2
+ * Code by Utkarsh Sinha
3
+ * Based on JIFT by Jun Liu
4
+ * Visit http://aishack.in/ for more indepth articles and tutorials
5
+ * on artificial intelligence
6
+ * Use, reuse, modify, hack, kick. Do whatever you want with
7
+ * this code :)
8
+ ******************************************************/
9
+
10
+ #ifndef _KEYPOINT_H
11
+ #define _KEYPOINT_H
12
+
13
+ #include <vector>
14
+
15
+ using namespace std;
16
+
17
+ class Keypoint
18
+ {
19
+ public:
20
+ float xi;
21
+ float yi; // It's location
22
+ vector<double> mag; // The list of magnitudes at this point
23
+ vector<double> orien; // The list of orientations detected
24
+ unsigned int scale; // The scale where this was detected
25
+
26
+ Keypoint() { }
27
+ Keypoint(float x, float y) { xi=x; yi=y; }
28
+ Keypoint(float x, float y, vector<double> const& m, vector<double> const& o, unsigned int s)
29
+ {
30
+ xi = x;
31
+ yi = y;
32
+ mag = m;
33
+ orien = o;
34
+ scale = s;
35
+ }
36
+ };
37
+
38
+ #endif
@@ -0,0 +1,20 @@
1
+ This code is derived directly from Utkarsh Sinha's very good tutorial
2
+ and demo at:
3
+
4
+ http://www.aishack.in/2010/07/implementing-sift-in-opencv/
5
+
6
+ As noted SIFT is patented, and as such this implementation cannot be
7
+ used commercially.
8
+
9
+
10
+ Utkarsh's oridinal ReadMe.txt follows:
11
+
12
+
13
+ /******************************************************
14
+ * Code by Utkarsh Sinha
15
+ * Based on JIFT by Jun Liu
16
+ * Visit http://aishack.in/ for more indepth articles and tutorials
17
+ * on artificial intelligence
18
+ * Use, reuse, modify, hack, kick. Do whatever you want with
19
+ * this code :)
20
+ ******************************************************/
@@ -0,0 +1,1036 @@
1
+ /******************************************************
2
+ * Code by Utkarsh Sinha
3
+ * Based on JIFT by Jun Liu
4
+ * Visit http://aishack.in/ for more indepth articles and tutorials
5
+ * on artificial intelligence
6
+ * Use, reuse, modify, hack, kick. Do whatever you want with
7
+ * this code :)
8
+ ******************************************************/
9
+
10
+ #include "SIFT.h"
11
+
12
+ #include <stdio.h>
13
+ #include <assert.h>
14
+
15
+ #include <opencv2/imgproc/imgproc.hpp>
16
+ #include <opencv2/imgproc/imgproc_c.h>
17
+
18
+ using namespace cv;
19
+
20
+ // SaveFloatingPointImage()
21
+ // The standard HighGUI functions can save only 8bit images. This
22
+ // function converts a floating point image (with values 0..1) into
23
+ // a normal 8bit image (with values 0..255), and then saves it.
24
+ void SaveFloatingPointImage(const char *filename, IplImage* img)
25
+ {
26
+ IplImage* dup = cvCreateImage(cvGetSize(img), 8, 1);
27
+ cvCvtScale(img, dup, 255.0);
28
+
29
+ cvSaveImage(filename, dup);
30
+ cvReleaseImage(&dup);
31
+ }
32
+
33
+ // Constructor: Give a preloaded image and provide the desired number
34
+ // of octaves and intervals
35
+ SIFT::SIFT(IplImage* img, int octaves, int intervals)
36
+ {
37
+ // Store the image internally
38
+ m_srcImage = cvCloneImage(img);
39
+
40
+ // Set the number of octaves and intervals
41
+ m_numOctaves = octaves;
42
+ m_numIntervals = intervals;
43
+
44
+ // Proceed to initialize the algorithm
45
+ GenerateLists();
46
+ }
47
+
48
+ // Constructor: Give a filename and the desired number of octaves
49
+ // and intervals
50
+ SIFT::SIFT(const char* filename, int octaves, int intervals)
51
+ {
52
+ // Load the image
53
+ m_srcImage = cvLoadImage(filename);
54
+
55
+ // Set the number of octaves and intervals
56
+ m_numOctaves = octaves;
57
+ m_numIntervals = intervals;
58
+
59
+ // Proceed to initialize the algorithm
60
+ GenerateLists();
61
+ }
62
+
63
+ // AllocateMemory()
64
+ // This function allocates memory for the scale space (the multiple gaussian images,
65
+ // the difference of guassian images)
66
+ void SIFT::GenerateLists()
67
+ {
68
+ // A variable for the loops
69
+ unsigned int i=0;
70
+
71
+ // Create a 2D array of gaussian blurred images
72
+ m_gList = new IplImage**[m_numOctaves];
73
+ for(i=0;i<m_numOctaves;i++)
74
+ m_gList[i] = new IplImage*[m_numIntervals+3];
75
+
76
+ // Create a 2D array to store images generated after the
77
+ // DoG operation
78
+ m_dogList = new IplImage**[m_numOctaves];
79
+ for(i=0;i<m_numOctaves;i++)
80
+ m_dogList[i] = new IplImage*[m_numIntervals+2];
81
+
82
+ // Create a 2D array that will hold if a particular point
83
+ // is an extrema or not
84
+ m_extrema = new IplImage**[m_numOctaves];
85
+ for(i=0;i<m_numOctaves;i++)
86
+ m_extrema[i] = new IplImage*[m_numIntervals];
87
+
88
+ // Create a 2D array of decimal numbers. It holds the sigma
89
+ // used to blur the gaussian images.
90
+ m_absSigma = new double*[m_numOctaves];
91
+ for(i=0;i<m_numOctaves;i++)
92
+ m_absSigma[i] = new double[m_numIntervals+3];
93
+ }
94
+
95
+ // Destructor
96
+ // Cleanup after you're done
97
+ SIFT::~SIFT()
98
+ {
99
+ unsigned int i, j;
100
+ for(i=0;i<m_numOctaves;i++)
101
+ {
102
+ // Release all images in that particular octave
103
+ for(j=0;j<m_numIntervals+3;j++) cvReleaseImage(&m_gList[i][j]);
104
+ for(j=0;j<m_numIntervals+2;j++) cvReleaseImage(&m_dogList[i][j]);
105
+ for(j=0;j<m_numIntervals;j++) cvReleaseImage(&m_extrema[i][j]);
106
+
107
+ // Delete memory for that array
108
+ delete [] m_gList[i];
109
+ delete [] m_dogList[i];
110
+ delete [] m_extrema[i];
111
+ delete [] m_absSigma[i];
112
+ }
113
+
114
+ // Delete the 2D arrays
115
+ delete [] m_gList;
116
+ delete [] m_dogList;
117
+ delete [] m_extrema;
118
+ delete [] m_absSigma;
119
+ }
120
+
121
+ // DoSift()
122
+ // This function does everything in sequence.
123
+ void SIFT::DoSift()
124
+ {
125
+ DetectKeypoints();
126
+ DescribeKeypoints();
127
+ }
128
+
129
+ void SIFT::DetectKeypoints()
130
+ {
131
+ BuildScaleSpace();
132
+ DetectExtrema();
133
+ AssignOrientations();
134
+ }
135
+
136
+ void SIFT::DescribeKeypoints()
137
+ {
138
+ ExtractKeypointDescriptors();
139
+ }
140
+
141
+ // BuildScaleSpace()
142
+ // This function generates all the blurred out images for each octave
143
+ // and also the DoG images
144
+ void SIFT::BuildScaleSpace()
145
+ {
146
+ printf("Generating scale space...\n");
147
+ // For loops
148
+ unsigned int i,j;
149
+
150
+ // floating point grayscale image
151
+ IplImage* imgGray = cvCreateImage(cvGetSize(m_srcImage), IPL_DEPTH_32F , 1);
152
+ IplImage* imgTemp = cvCreateImage(cvGetSize(m_srcImage), 8 , 1);
153
+
154
+ // Create a duplicate. We don't want to mess the original
155
+ // If the image is colour, it is converted to grayscale
156
+ if(m_srcImage->nChannels==3)
157
+ {
158
+ cvCvtColor(m_srcImage, imgTemp, CV_BGR2GRAY);
159
+ }
160
+ else
161
+ {
162
+ cvCopy(m_srcImage, imgTemp);
163
+ }
164
+
165
+ // Finally, generate the floating point image... convert 0..255 range into 0..1
166
+ for(int x=0;x<imgTemp->width;x++)
167
+ {
168
+ for(int y=0;y<imgTemp->height;y++)
169
+ {
170
+ cvSetReal2D(imgGray, y, x, cvGetReal2D(imgTemp, y, x)/255.0);
171
+ }
172
+ }
173
+
174
+ // Lowe claims blur the image with a sigma of 0.5 and double it's dimensions
175
+ // to increase the number of stable keypoints
176
+ cvSmooth(imgGray, imgGray, CV_GAUSSIAN, 0, 0, SIGMA_ANTIALIAS);
177
+
178
+ // Create an image double the dimensions, resize imgGray and store it in m_gList[0][0]
179
+ m_gList[0][0] = cvCreateImage(cvSize(imgGray->width*2, imgGray->height*2), IPL_DEPTH_32F , 1);
180
+ cvPyrUp(imgGray, m_gList[0][0]);
181
+
182
+ // Preblur this base image
183
+ cvSmooth(m_gList[0][0], m_gList[0][0], CV_GAUSSIAN, 0, 0, SIGMA_PREBLUR);
184
+
185
+ // SaveFloatingPointImage("C:\\SIFT Test\\Gaussian\\g_octave_0_scale_0.jpg", m_gList[0][0]);
186
+
187
+ double initSigma = sqrt(2.0f);
188
+
189
+ // Keep a track of the sigmas
190
+ m_absSigma[0][0] = initSigma * 0.5;
191
+
192
+ // Now for the actual image generation
193
+ for(i=0;i<m_numOctaves;i++)
194
+ {
195
+ // Reset sigma for each octave
196
+ double sigma = initSigma;
197
+ CvSize currentSize = cvGetSize(m_gList[i][0]);
198
+
199
+ for(j=1;j<m_numIntervals+3;j++)
200
+ {
201
+ // Allocate memory
202
+ m_gList[i][j] = cvCreateImage(currentSize, 32, 1);
203
+
204
+ // Calculate a sigma to blur the current image to get the next one
205
+ double sigma_f = sqrt(pow(2.0,2.0/m_numIntervals)-1) * sigma;
206
+ sigma = pow(2.0,1.0/m_numIntervals) * sigma;
207
+
208
+ // Store sigma values (to be used later on)
209
+ m_absSigma[i][j] = sigma * 0.5 * pow(2.0f, (float)i);
210
+
211
+ // Apply gaussian smoothing)
212
+ cvSmooth(m_gList[i][j-1], m_gList[i][j], CV_GAUSSIAN, 0, 0, sigma_f);
213
+
214
+ // Calculate the DoG image
215
+ m_dogList[i][j-1] = cvCreateImage(currentSize, 32, 1);
216
+ cvSub(m_gList[i][j-1], m_gList[i][j], m_dogList[i][j-1]);
217
+
218
+ // Save the images generated for fun :)
219
+ /*char* filename = new char[200];
220
+ sprintf(filename, "C:\\SIFT Test\\Gaussian\\g_octave_%d_scale_%d.jpg", i, j);
221
+ SaveFloatingPointImage(filename, m_gList[i][j]);
222
+
223
+ sprintf(filename, "C:\\SIFT TEST\\DOG\\dog_octave_%d_scale_%d.jpg", i, j-1);
224
+ SaveFloatingPointImage(filename, m_dogList[i][j-1]);*/
225
+ }
226
+
227
+ // If we're not at the last octave
228
+ if(i<m_numOctaves-1)
229
+ {
230
+ // Reduce size to half
231
+ currentSize.width/=2;
232
+ currentSize.height/=2;
233
+
234
+ // Allocate memory and resample the image
235
+ m_gList[i+1][0] = cvCreateImage(currentSize, 32, 1);
236
+ cvPyrDown(m_gList[i][0], m_gList[i+1][0]);
237
+ m_absSigma[i+1][0] = m_absSigma[i][m_numIntervals];
238
+
239
+ // Store the image
240
+ /*char* filename = new char[200];
241
+ sprintf(filename, "C:\\SIFT Test\\Gaussian\\g_octave_%d_scale_0.jpg", i+1);
242
+ SaveFloatingPointImage(filename, m_gList[i+1][0]);*/
243
+ }
244
+ }
245
+ }
246
+
247
+ // DetectExtrema()
248
+ // Locates extreme points (maxima and minima)
249
+ // Relatively simple stuff
250
+ void SIFT::DetectExtrema()
251
+ {
252
+ printf("Detecting extrema...\n");
253
+
254
+ // Looping variables
255
+ unsigned int i, j, xi, yi;
256
+
257
+ // Some variables we'll use later on
258
+ double curvature_ratio, curvature_threshold;
259
+ IplImage *middle, *up, *down;
260
+ int scale;
261
+ double dxx, dyy, dxy, trH, detH;
262
+
263
+ unsigned int num=0; // Number of keypoins detected
264
+ unsigned int numRemoved=0; // The number of key points rejected because they failed a test
265
+
266
+ curvature_threshold = (CURVATURE_THRESHOLD+1)*(CURVATURE_THRESHOLD+1)/CURVATURE_THRESHOLD;
267
+
268
+ // Detect extrema in the DoG images
269
+ for(i=0;i<m_numOctaves;i++)
270
+ {
271
+ scale = (int)pow(2.0, (double)i);
272
+
273
+ for(j=1;j<m_numIntervals+1;j++)
274
+ {
275
+ // Allocate memory and set all points to zero ("not key point")
276
+ m_extrema[i][j-1] = cvCreateImage(cvGetSize(m_dogList[i][0]), 8, 1);
277
+ cvZero(m_extrema[i][j-1]);
278
+
279
+ // Images just above and below, in the current octave
280
+ middle = m_dogList[i][j];
281
+ up = m_dogList[i][j+1];
282
+ down = m_dogList[i][j-1];
283
+
284
+ for(xi=1;xi<m_dogList[i][j]->width-1;xi++)
285
+ {
286
+ for(yi=1;yi<m_dogList[i][j]->height-1;yi++)
287
+ {
288
+ // true if a keypoint is a maxima/minima
289
+ // but needs to be tested for contrast/edge thingy
290
+ bool justSet = false;
291
+
292
+ double currentPixel = cvGetReal2D(middle, yi, xi);
293
+
294
+ // Check for a maximum
295
+ if (currentPixel > cvGetReal2D(middle, yi-1, xi ) &&
296
+ currentPixel > cvGetReal2D(middle, yi+1, xi ) &&
297
+ currentPixel > cvGetReal2D(middle, yi , xi-1) &&
298
+ currentPixel > cvGetReal2D(middle, yi , xi+1) &&
299
+ currentPixel > cvGetReal2D(middle, yi-1, xi-1) &&
300
+ currentPixel > cvGetReal2D(middle, yi-1, xi+1) &&
301
+ currentPixel > cvGetReal2D(middle, yi+1, xi+1) &&
302
+ currentPixel > cvGetReal2D(middle, yi+1, xi-1) &&
303
+ currentPixel > cvGetReal2D(up, yi , xi ) &&
304
+ currentPixel > cvGetReal2D(up, yi-1, xi ) &&
305
+ currentPixel > cvGetReal2D(up, yi+1, xi ) &&
306
+ currentPixel > cvGetReal2D(up, yi , xi-1) &&
307
+ currentPixel > cvGetReal2D(up, yi , xi+1) &&
308
+ currentPixel > cvGetReal2D(up, yi-1, xi-1) &&
309
+ currentPixel > cvGetReal2D(up, yi-1, xi+1) &&
310
+ currentPixel > cvGetReal2D(up, yi+1, xi+1) &&
311
+ currentPixel > cvGetReal2D(up, yi+1, xi-1) &&
312
+ currentPixel > cvGetReal2D(down, yi , xi ) &&
313
+ currentPixel > cvGetReal2D(down, yi-1, xi ) &&
314
+ currentPixel > cvGetReal2D(down, yi+1, xi ) &&
315
+ currentPixel > cvGetReal2D(down, yi , xi-1) &&
316
+ currentPixel > cvGetReal2D(down, yi , xi+1) &&
317
+ currentPixel > cvGetReal2D(down, yi-1, xi-1) &&
318
+ currentPixel > cvGetReal2D(down, yi-1, xi+1) &&
319
+ currentPixel > cvGetReal2D(down, yi+1, xi+1) &&
320
+ currentPixel > cvGetReal2D(down, yi+1, xi-1) )
321
+ {
322
+ cvSetReal2D(m_extrema[i][j-1], yi, xi, 255);
323
+ num++;
324
+ justSet = true;
325
+ }
326
+ // Check if it's a minimum
327
+ else if (currentPixel < cvGetReal2D(middle, yi-1, xi ) &&
328
+ currentPixel < cvGetReal2D(middle, yi+1, xi ) &&
329
+ currentPixel < cvGetReal2D(middle, yi , xi-1) &&
330
+ currentPixel < cvGetReal2D(middle, yi , xi+1) &&
331
+ currentPixel < cvGetReal2D(middle, yi-1, xi-1) &&
332
+ currentPixel < cvGetReal2D(middle, yi-1, xi+1) &&
333
+ currentPixel < cvGetReal2D(middle, yi+1, xi+1) &&
334
+ currentPixel < cvGetReal2D(middle, yi+1, xi-1) &&
335
+ currentPixel < cvGetReal2D(up, yi , xi ) &&
336
+ currentPixel < cvGetReal2D(up, yi-1, xi ) &&
337
+ currentPixel < cvGetReal2D(up, yi+1, xi ) &&
338
+ currentPixel < cvGetReal2D(up, yi , xi-1) &&
339
+ currentPixel < cvGetReal2D(up, yi , xi+1) &&
340
+ currentPixel < cvGetReal2D(up, yi-1, xi-1) &&
341
+ currentPixel < cvGetReal2D(up, yi-1, xi+1) &&
342
+ currentPixel < cvGetReal2D(up, yi+1, xi+1) &&
343
+ currentPixel < cvGetReal2D(up, yi+1, xi-1) &&
344
+ currentPixel < cvGetReal2D(down, yi , xi ) &&
345
+ currentPixel < cvGetReal2D(down, yi-1, xi ) &&
346
+ currentPixel < cvGetReal2D(down, yi+1, xi ) &&
347
+ currentPixel < cvGetReal2D(down, yi , xi-1) &&
348
+ currentPixel < cvGetReal2D(down, yi , xi+1) &&
349
+ currentPixel < cvGetReal2D(down, yi-1, xi-1) &&
350
+ currentPixel < cvGetReal2D(down, yi-1, xi+1) &&
351
+ currentPixel < cvGetReal2D(down, yi+1, xi+1) &&
352
+ currentPixel < cvGetReal2D(down, yi+1, xi-1) )
353
+ {
354
+ cvSetReal2D(m_extrema[i][j-1], yi, xi, 255);
355
+ num++;
356
+ justSet = true;
357
+ }
358
+
359
+ // The contrast check
360
+ if(justSet && fabs(cvGetReal2D(middle, yi, xi)) < CONTRAST_THRESHOLD)
361
+ {
362
+ cvSetReal2D(m_extrema[i][j-1], yi, xi, 0);
363
+ num--;
364
+ numRemoved++;
365
+
366
+ justSet=false;
367
+ }
368
+
369
+ // The edge check
370
+ if(justSet)
371
+ {
372
+ dxx = (cvGetReal2D(middle, yi-1, xi) +
373
+ cvGetReal2D(middle, yi+1, xi) -
374
+ 2.0*cvGetReal2D(middle, yi, xi));
375
+
376
+ dyy = (cvGetReal2D(middle, yi, xi-1) +
377
+ cvGetReal2D(middle, yi, xi+1) -
378
+ 2.0*cvGetReal2D(middle, yi, xi));
379
+
380
+ dxy = (cvGetReal2D(middle, yi-1, xi-1) +
381
+ cvGetReal2D(middle, yi+1, xi+1) -
382
+ cvGetReal2D(middle, yi+1, xi-1) -
383
+ cvGetReal2D(middle, yi-1, xi+1)) / 4.0;
384
+
385
+ trH = dxx + dyy;
386
+ detH = dxx*dyy - dxy*dxy;
387
+
388
+ curvature_ratio = trH*trH/detH;
389
+ //printf("Threshold: %f - Ratio: %f\n", curvature_threshold, curvature_ratio);
390
+ if(detH<0 || curvature_ratio>curvature_threshold)
391
+ {
392
+ cvSetReal2D(m_extrema[i][j-1], yi, xi, 0);
393
+ num--;
394
+ numRemoved++;
395
+
396
+ justSet=false;
397
+ }
398
+ }
399
+
400
+
401
+ // A bit roundabout
402
+ if( cvGetReal2D( m_extrema[i][j-1], yi, xi ) != 0 )
403
+ extrema.push_back( Extrema( i, j, xi, yi ) );
404
+ }
405
+ }
406
+
407
+ // Save the image
408
+ /*char* filename = new char[200];
409
+ sprintf(filename, "C:\\SIFT Test\\Extrema\\extrema_oct_%d_scale_%d.jpg", i, j-1);
410
+ cvSaveImage(filename, m_extrema[i][j-1]);*/
411
+ }
412
+ }
413
+
414
+ m_numKeypoints = num;
415
+ printf("Found %d keypoints\n", num);
416
+ printf("m_extrema contains %d keypoints\n", (int)extrema.size() );
417
+ printf("Rejected %d keypoints\n", numRemoved);
418
+ }
419
+
420
+ IplImage *SIFT::magnitude_mat( int i, int j )
421
+ {
422
+ IplImage *magnitude = cvCreateImage(cvGetSize(m_gList[i][j]), 32, 1);
423
+ cvZero(magnitude);
424
+
425
+ // Iterate over the gaussian image with the current octave and interval
426
+ for(unsigned int xi=1;xi<m_gList[i][j]->width-1;xi++)
427
+ {
428
+ for(unsigned int yi=1;yi<m_gList[i][j]->height-1;yi++)
429
+ {
430
+ // Calculate gradient
431
+ double dx = cvGetReal2D(m_gList[i][j], yi, xi+1) - cvGetReal2D(m_gList[i][j], yi, xi-1);
432
+ double dy = cvGetReal2D(m_gList[i][j], yi+1, xi) - cvGetReal2D(m_gList[i][j], yi-1, xi);
433
+
434
+ // Store magnitude
435
+ cvSetReal2D(magnitude, yi, xi, sqrt(dx*dx + dy*dy));
436
+
437
+ // Store orientation as radians
438
+ //double ori=atan(dy/dx);
439
+ //cvSet2D(orientation[i][j-1], yi, xi, cvScalar(ori));
440
+ }
441
+ }
442
+
443
+ return magnitude;
444
+ }
445
+
446
+ double SIFT::orientation_at( int i, int j, int xi, int yi )
447
+ {
448
+ // Calculate gradient
449
+ double dx = cvGetReal2D(m_gList[i][j], yi, xi+1) - cvGetReal2D(m_gList[i][j], yi, xi-1);
450
+ double dy = cvGetReal2D(m_gList[i][j], yi+1, xi) - cvGetReal2D(m_gList[i][j], yi-1, xi);
451
+
452
+ // Store orientation as radians
453
+ //double ori=atan(dy/dx);
454
+ //cvSet2D(orientation[i][j-1], yi, xi, cvScalar(ori));
455
+ return atan(dy/dx);
456
+ }
457
+
458
+
459
+ // AssignOrientations()
460
+ // For all the key points, generate an orientation.
461
+ void SIFT::AssignOrientations()
462
+ {
463
+ printf("Assigning orientations...\n");
464
+ unsigned int i, j, k, xi, yi;
465
+ int kk, tt;
466
+
467
+ // These images hold the magnitude and direction of gradient
468
+ // for all blurred out images
469
+ IplImage*** magnitude = new IplImage**[m_numOctaves];
470
+
471
+ // Allocate some memory
472
+ for(i=0;i<m_numOctaves;i++)
473
+ {
474
+ magnitude[i] = new IplImage*[m_numIntervals];
475
+
476
+ for( j = 1; j <= m_numIntervals; j++ ) {
477
+ magnitude[i][j-1] = NULL;
478
+ }
479
+ }
480
+
481
+ // The histogram
482
+ double* hist_orient = new double[NUM_BINS];
483
+
484
+ for( vector<Extrema>::iterator itr = extrema.begin(); itr != extrema.end(); itr++ ) {
485
+ Extrema ex = (*itr);
486
+
487
+ printf("Calculating magnitude and orientation for extrema at octave %d, interval %d, (%d,%d)\n",
488
+ ex.octave, ex.interval, ex.xi, ex.yi );
489
+ int i = ex.octave;
490
+ int j = ex.interval;
491
+ int xi = ex.xi;
492
+ int yi = ex.yi;
493
+
494
+ // Store current scale, width and height
495
+ unsigned int scale = (unsigned int)pow(2.0, (double)i);
496
+ unsigned int width = m_gList[i][0]->width;
497
+ unsigned int height= m_gList[i][0]->height;
498
+
499
+ double abs_sigma = m_absSigma[i][j];
500
+
501
+ // Generate the magnitude arrays just-in-time, but cache them...
502
+ if( magnitude[i][j-1] == NULL ) {
503
+ magnitude[i][j-1] = magnitude_mat( i, j );
504
+ }
505
+ assert( magnitude[i][j-1] );
506
+ IplImage* imgWeight = cvCreateImage(cvSize(width, height), 32, 1);
507
+ cvSmooth(magnitude[i][j-1], imgWeight, CV_GAUSSIAN, 0, 0, 1.5*abs_sigma);
508
+
509
+ // Get the kernel size for the Guassian blur
510
+ int hfsz = GetKernelSize(1.5*abs_sigma)/2;
511
+
512
+ // Temporarily used to generate a mask of region used to calculate
513
+ // the orientations
514
+ //IplImage* imgMask = cvCreateImage(cvSize(width, height), 8, 1);
515
+ //cvZero(imgMask);
516
+
517
+ // Reset the histogram thingy
518
+ for(k=0;k<NUM_BINS;k++)
519
+ hist_orient[k]=0.0;
520
+
521
+ // Go through all pixels in the window around the extrema
522
+ for(kk=-hfsz;kk<=hfsz;kk++)
523
+ {
524
+ for(tt=-hfsz;tt<=hfsz;tt++)
525
+ {
526
+ // Ensure we're within the image
527
+ if(xi+kk<0 || xi+kk>=width || yi+tt<0 || yi+tt>=height)
528
+ continue;
529
+
530
+ double sampleOrient = orientation_at( i,j, xi+kk, yi+tt ); //cvGetReal2D(orientation[i][j-1], yi+tt, xi+kk);
531
+
532
+ if(sampleOrient <=-M_PI || sampleOrient>M_PI)
533
+ printf("Bad Orientation: %f\n", sampleOrient);
534
+
535
+ sampleOrient+=M_PI;
536
+
537
+ // Convert to degrees
538
+ unsigned int sampleOrientDegrees = sampleOrient * 180 / M_PI;
539
+ hist_orient[(int)sampleOrientDegrees / (360/NUM_BINS)] += cvGetReal2D(imgWeight, yi+tt, xi+kk);
540
+ //cvSetReal2D(imgMask, yi+tt, xi+kk, 255);
541
+ }
542
+ }
543
+
544
+ // We've computed the histogram. Now check for the maximum
545
+ double max_peak = hist_orient[0];
546
+ unsigned int max_peak_index = 0;
547
+ for(k=1;k<NUM_BINS;k++)
548
+ {
549
+ if(hist_orient[k]>max_peak)
550
+ {
551
+ max_peak=hist_orient[k];
552
+ max_peak_index = k;
553
+ }
554
+ }
555
+
556
+ // List of magnitudes and orientations at the current extrema
557
+ vector<double> orien;
558
+ vector<double> mag;
559
+ for(k=0;k<NUM_BINS;k++)
560
+ {
561
+ // Do we have a good peak?
562
+ if(hist_orient[k]> 0.8*max_peak)
563
+ {
564
+ // Three points. (x2,y2) is the peak and (x1,y1)
565
+ // and (x3,y3) are the neigbours to the left and right.
566
+ // If the peak occurs at the extreme left, the "left
567
+ // neighbour" is equal to the right most. Similarly for
568
+ // the other case (peak is rightmost)
569
+ double x1 = k-1;
570
+ double y1;
571
+ double x2 = k;
572
+ double y2 = hist_orient[k];
573
+ double x3 = k+1;
574
+ double y3;
575
+
576
+ if(k==0)
577
+ {
578
+ y1 = hist_orient[NUM_BINS-1];
579
+ y3 = hist_orient[1];
580
+ }
581
+ else if(k==NUM_BINS-1)
582
+ {
583
+ y1 = hist_orient[NUM_BINS-1];
584
+ y3 = hist_orient[0];
585
+ }
586
+ else
587
+ {
588
+ y1 = hist_orient[k-1];
589
+ y3 = hist_orient[k+1];
590
+ }
591
+
592
+ // Next we fit a downward parabola aound
593
+ // these three points for better accuracy
594
+
595
+ // A downward parabola has the general form
596
+ //
597
+ // y = a * x^2 + bx + c
598
+ // Now the three equations stem from the three points
599
+ // (x1,y1) (x2,y2) (x3.y3) are
600
+ //
601
+ // y1 = a * x1^2 + b * x1 + c
602
+ // y2 = a * x2^2 + b * x2 + c
603
+ // y3 = a * x3^2 + b * x3 + c
604
+ //
605
+ // in Matrix notation, this is y = Xb, where
606
+ // y = (y1 y2 y3)' b = (a b c)' and
607
+ //
608
+ // x1^2 x1 1
609
+ // X = x2^2 x2 1
610
+ // x3^2 x3 1
611
+ //
612
+ // OK, we need to solve this equation for b
613
+ // this is done by inverse the matrix X
614
+ //
615
+ // b = inv(X) Y
616
+
617
+ double *b = new double[3];
618
+ CvMat *X = cvCreateMat(3, 3, CV_32FC1);
619
+ CvMat *matInv = cvCreateMat(3, 3, CV_32FC1);
620
+
621
+ cvSetReal2D(X, 0, 0, x1*x1);
622
+ cvSetReal2D(X, 1, 0, x1);
623
+ cvSetReal2D(X, 2, 0, 1);
624
+
625
+ cvSetReal2D(X, 0, 1, x2*x2);
626
+ cvSetReal2D(X, 1, 1, x2);
627
+ cvSetReal2D(X, 2, 1, 1);
628
+
629
+ cvSetReal2D(X, 0, 2, x3*x3);
630
+ cvSetReal2D(X, 1, 2, x3);
631
+ cvSetReal2D(X, 2, 2, 1);
632
+
633
+ // Invert the matrix
634
+ cvInv(X, matInv);
635
+
636
+ b[0] = cvGetReal2D(matInv, 0, 0)*y1 + cvGetReal2D(matInv, 1, 0)*y2 + cvGetReal2D(matInv, 2, 0)*y3;
637
+ b[1] = cvGetReal2D(matInv, 0, 1)*y1 + cvGetReal2D(matInv, 1, 1)*y2 + cvGetReal2D(matInv, 2, 1)*y3;
638
+ b[2] = cvGetReal2D(matInv, 0, 2)*y1 + cvGetReal2D(matInv, 1, 2)*y2 + cvGetReal2D(matInv, 2, 2)*y3;
639
+
640
+ double x0 = -b[1]/(2*b[0]);
641
+
642
+ // Anomalous situation
643
+ if(fabs(x0)>2*NUM_BINS)
644
+ x0=x2;
645
+
646
+ while(x0<0)
647
+ x0 += NUM_BINS;
648
+ while(x0>= NUM_BINS)
649
+ x0-= NUM_BINS;
650
+
651
+ // Normalize it
652
+ double x0_n = x0*(2*M_PI/NUM_BINS);
653
+
654
+ assert(x0_n>=0 && x0_n<2*M_PI);
655
+ x0_n -= M_PI;
656
+ assert(x0_n>=-M_PI && x0_n<M_PI);
657
+
658
+ orien.push_back(x0_n);
659
+ mag.push_back(hist_orient[k]);
660
+ }
661
+ }
662
+
663
+ printf("Saving keypoint at %f,%f\n", xi*scale/2.0, yi*scale/2.0 );
664
+ // Save this keypoint into the list
665
+ m_keyPoints.push_back(Keypoint(xi*scale/2, yi*scale/2, mag, orien, i*m_numIntervals+j-1));
666
+
667
+ // Save the regions!
668
+ /*char* filename = new char[200];
669
+ sprintf(filename, "C:\\SIFT Test\\Orientation Region\\ori_region_oct_%d_scl_%d.jpg", i, j-1);
670
+ cvSaveImage(filename, imgMask);*/
671
+ //cvReleaseImage(&imgMask);
672
+ cvReleaseImage(&imgWeight);
673
+ }
674
+
675
+ // Finally, we're done with all the magnitude and orientation images.
676
+ // Erase them from RAM
677
+ assert(m_keyPoints.size() == m_numKeypoints);
678
+ for(i=0;i<m_numOctaves;i++)
679
+ {
680
+ for(j=0 ; j<m_numIntervals ; j++)
681
+ {
682
+ if( magnitude[i][j] != NULL ) {
683
+ cvReleaseImage(&magnitude[i][j]);
684
+ }
685
+ }
686
+
687
+ delete [] magnitude[i];
688
+ }
689
+
690
+ delete [] magnitude;
691
+ }
692
+
693
+ // ExtractKeypointDescriptors()
694
+ // Generates a unique descriptor for each keypoint descriptor
695
+ void SIFT::ExtractKeypointDescriptors()
696
+ {
697
+ printf("Extract keypoint descriptors...\n");
698
+
699
+ // For loops
700
+ unsigned int i, j;
701
+
702
+ // Interpolated thingy. We're dealing with "inbetween" gradient
703
+ // magnitudes and orientations
704
+ IplImage*** imgInterpolatedMagnitude = new IplImage**[m_numOctaves];
705
+ IplImage*** imgInterpolatedOrientation = new IplImage**[m_numOctaves];
706
+ for(i=0;i<m_numOctaves;i++)
707
+ {
708
+ imgInterpolatedMagnitude[i] = new IplImage*[m_numIntervals];
709
+ imgInterpolatedOrientation[i] = new IplImage*[m_numIntervals];
710
+ }
711
+
712
+ // These two loops calculate the interpolated thingy for all octaves
713
+ // and subimages
714
+ for(i=0;i<m_numOctaves;i++)
715
+ {
716
+ for(j=1;j<m_numIntervals+1;j++)
717
+ {
718
+ unsigned int width = m_gList[i][j]->width;
719
+ unsigned int height =m_gList[i][j]->height;
720
+
721
+ // Create an image and zero it out.
722
+ IplImage* imgTemp = cvCreateImage(cvSize(width*2, height*2), 32, 1);
723
+ cvZero(imgTemp);
724
+
725
+ // Scale it up. This will give us "access" to in betweens
726
+ cvPyrUp(m_gList[i][j], imgTemp);
727
+
728
+ // Allocate memory and zero them
729
+ imgInterpolatedMagnitude[i][j-1] = cvCreateImage(cvSize(width+1, height+1), 32, 1);
730
+ imgInterpolatedOrientation[i][j-1] = cvCreateImage(cvSize(width+1, height+1), 32, 1);
731
+ cvZero(imgInterpolatedMagnitude[i][j-1]);
732
+ cvZero(imgInterpolatedOrientation[i][j-1]);
733
+
734
+ // Do the calculations
735
+ for(float ii=1.5;ii<width-1.5;ii++)
736
+ {
737
+ for(float jj=1.5;jj<height-1.5;jj++)
738
+ {
739
+ // "inbetween" change
740
+ double dx = (cvGetReal2D(m_gList[i][j], jj, ii+1.5) + cvGetReal2D(m_gList[i][j], jj, ii+0.5))/2 - (cvGetReal2D(m_gList[i][j], jj, ii-1.5) + cvGetReal2D(m_gList[i][j], jj, ii-0.5))/2;
741
+ double dy = (cvGetReal2D(m_gList[i][j], jj+1.5, ii) + cvGetReal2D(m_gList[i][j], jj+0.5, ii))/2 - (cvGetReal2D(m_gList[i][j], jj-1.5, ii) + cvGetReal2D(m_gList[i][j], jj-0.5, ii))/2;
742
+
743
+ unsigned int iii = ii+1;
744
+ unsigned int jjj = jj+1;
745
+ assert(iii<=width && jjj<=height);
746
+
747
+ // Set the magnitude and orientation
748
+ cvSetReal2D(imgInterpolatedMagnitude[i][j-1], jjj, iii, sqrt(dx*dx + dy*dy));
749
+ cvSetReal2D(imgInterpolatedOrientation[i][j-1], jjj, iii, (atan2(dy,dx)==M_PI)? -M_PI:atan2(dy,dx) );
750
+ }
751
+ }
752
+
753
+ // Pad the edges with zeros
754
+ for(unsigned int iii=0;iii<width+1;iii++)
755
+ {
756
+ cvSetReal2D(imgInterpolatedMagnitude[i][j-1], 0, iii, 0);
757
+ cvSetReal2D(imgInterpolatedMagnitude[i][j-1], height, iii, 0);
758
+ cvSetReal2D(imgInterpolatedOrientation[i][j-1], 0, iii, 0);
759
+ cvSetReal2D(imgInterpolatedOrientation[i][j-1], height, iii, 0);
760
+ }
761
+
762
+ for(unsigned int jjj=0;jjj<height+1;jjj++)
763
+ {
764
+ cvSetReal2D(imgInterpolatedMagnitude[i][j-1], jjj, 0, 0);
765
+ cvSetReal2D(imgInterpolatedMagnitude[i][j-1], jjj, width, 0);
766
+ cvSetReal2D(imgInterpolatedOrientation[i][j-1], jjj, 0, 0);
767
+ cvSetReal2D(imgInterpolatedOrientation[i][j-1], jjj, width, 0);
768
+ }
769
+
770
+ // Now we have the imgInterpolated* ready. Store and get started
771
+ /*char* filename = new char[200];
772
+ sprintf(filename, "C:\\SIFT Test\\Interpolated Mag\\intmag_oct_%d_scl_%d.jpg", i, j-1);
773
+ cvSaveImage(filename, imgInterpolatedMagnitude[i][j-1]);
774
+
775
+ sprintf(filename, "C:\\SIFT Test\\Interpolated Ori\\intori_oct_%d_scl_%d.jpg", i, j-1);
776
+ cvSaveImage(filename, imgInterpolatedOrientation[i][j-1]);*/
777
+
778
+ cvReleaseImage(&imgTemp);
779
+
780
+ }
781
+ }
782
+
783
+ // Build an Interpolated Gaussian Table of size FEATURE_WINDOW_SIZE
784
+ // Lowe suggests sigma should be half the window size
785
+ // This is used to construct the "circular gaussian window" to weight
786
+ // magnitudes
787
+ CvMat *G = BuildInterpolatedGaussianTable(FEATURE_WINDOW_SIZE, 0.5*FEATURE_WINDOW_SIZE);
788
+
789
+ vector<double> hist(DESC_NUM_BINS);
790
+
791
+ // Loop over all keypoints
792
+ for(unsigned int ikp = 0;ikp<m_numKeypoints;ikp++)
793
+ {
794
+ unsigned int scale = m_keyPoints[ikp].scale;
795
+ float kpxi = m_keyPoints[ikp].xi;
796
+ float kpyi = m_keyPoints[ikp].yi;
797
+
798
+ float descxi = kpxi;
799
+ float descyi = kpyi;
800
+
801
+ unsigned int ii = (unsigned int)(kpxi*2) / (unsigned int)(pow(2.0, (double)scale/m_numIntervals));
802
+ unsigned int jj = (unsigned int)(kpyi*2) / (unsigned int)(pow(2.0, (double)scale/m_numIntervals));
803
+
804
+ unsigned int width = m_gList[scale/m_numIntervals][0]->width;
805
+ unsigned int height = m_gList[scale/m_numIntervals][0]->height;
806
+
807
+ vector<double> orien = m_keyPoints[ikp].orien;
808
+ vector<double> mag = m_keyPoints[ikp].mag;
809
+
810
+ // Find the orientation and magnitude that have the maximum impact
811
+ // on the feature
812
+ double main_mag = mag[0];
813
+ double main_orien = orien[0];
814
+ for(unsigned int orient_count=1;orient_count<mag.size();orient_count++)
815
+ {
816
+ if(mag[orient_count]>main_mag)
817
+ {
818
+ main_orien = orien[orient_count];
819
+ main_mag = mag[orient_count];
820
+ }
821
+ }
822
+
823
+ unsigned int hfsz = FEATURE_WINDOW_SIZE/2;
824
+ CvMat *weight = cvCreateMat(FEATURE_WINDOW_SIZE, FEATURE_WINDOW_SIZE, CV_32FC1);
825
+ vector<double> fv(FVSIZE);
826
+
827
+ for(i=0;i<FEATURE_WINDOW_SIZE;i++)
828
+ {
829
+ for(j=0;j<FEATURE_WINDOW_SIZE;j++)
830
+ {
831
+ if(ii+i+1<hfsz || ii+i+1>width+hfsz || jj+j+1<hfsz || jj+j+1>height+hfsz)
832
+ cvSetReal2D(weight, j, i, 0);
833
+ else
834
+ cvSetReal2D(weight, j, i, cvGetReal2D(G, j, i)*cvGetReal2D(imgInterpolatedMagnitude[scale/m_numIntervals][scale%m_numIntervals], jj+j+1-hfsz, ii+i+1-hfsz));
835
+ }
836
+ }
837
+
838
+ // Now that we've weighted the required magnitudes, we proceed to generating
839
+ // the feature vector
840
+
841
+ // The next two two loops are for splitting the 16x16 window
842
+ // into sixteen 4x4 blocks
843
+ for(i=0;i<FEATURE_WINDOW_SIZE/4;i++) // 4x4 thingy
844
+ {
845
+ for(j=0;j<FEATURE_WINDOW_SIZE/4;j++)
846
+ {
847
+ // Clear the histograms
848
+ for(unsigned int t=0;t<DESC_NUM_BINS;t++)
849
+ hist[t]=0.0;
850
+
851
+ // Calculate the coordinates of the 4x4 block
852
+ int starti = (int)ii - (int)hfsz + 1 + (int)(hfsz/2*i);
853
+ int startj = (int)jj - (int)hfsz + 1 + (int)(hfsz/2*j);
854
+ int limiti = (int)ii + (int)(hfsz/2)*((int)(i)-1);
855
+ int limitj = (int)jj + (int)(hfsz/2)*((int)(j)-1);
856
+
857
+ // Go though this 4x4 block and do the thingy :D
858
+ for(int k=starti;k<=limiti;k++)
859
+ {
860
+ for(int t=startj;t<=limitj;t++)
861
+ {
862
+ if(k<0 || k>width || t<0 || t>height)
863
+ continue;
864
+
865
+ // This is where rotation invariance is done
866
+ double sample_orien = cvGetReal2D(imgInterpolatedOrientation[scale/m_numIntervals][scale%m_numIntervals], t, k);
867
+ sample_orien -= main_orien;
868
+
869
+ while(sample_orien<0)
870
+ sample_orien+=2*M_PI;
871
+
872
+ while(sample_orien>2*M_PI)
873
+ sample_orien-=2*M_PI;
874
+
875
+ // This should never happen
876
+ if(!(sample_orien>=0 && sample_orien<2*M_PI))
877
+ printf("BAD: %f\n", sample_orien);
878
+ assert(sample_orien>=0 && sample_orien<2*M_PI);
879
+
880
+ unsigned int sample_orien_d = sample_orien*180/M_PI;
881
+ assert(sample_orien_d<360);
882
+
883
+ unsigned int bin = sample_orien_d/(360/DESC_NUM_BINS); // The bin
884
+ double bin_f = (double)sample_orien_d/(double)(360/DESC_NUM_BINS); // The actual entry
885
+
886
+ assert(bin<DESC_NUM_BINS);
887
+ assert(k+hfsz-1-ii<FEATURE_WINDOW_SIZE && t+hfsz-1-jj<FEATURE_WINDOW_SIZE);
888
+
889
+ // Add to the bin
890
+ hist[bin]+=(1-fabs(bin_f-(bin+0.5))) * cvGetReal2D(weight, t+hfsz-1-jj, k+hfsz-1-ii);
891
+ }
892
+ }
893
+
894
+ // Keep adding these numbers to the feature vector
895
+ for(unsigned int t=0;t<DESC_NUM_BINS;t++)
896
+ {
897
+ fv[(i*FEATURE_WINDOW_SIZE/4+j)*DESC_NUM_BINS+t] = hist[t];
898
+ }
899
+ }
900
+ }
901
+
902
+ // Now, normalize the feature vector to ensure illumination independence
903
+ double norm=0;
904
+ for(unsigned int t=0;t<FVSIZE;t++)
905
+ norm+=pow(fv[t], 2.0);
906
+ norm = sqrt(norm);
907
+
908
+ for(unsigned int t=0;t<FVSIZE;t++)
909
+ fv[t]/=norm;
910
+
911
+ // Now, threshold the vector
912
+ for(unsigned int t=0;t<FVSIZE;t++)
913
+ if(fv[t]>FV_THRESHOLD)
914
+ fv[t] = FV_THRESHOLD;
915
+
916
+ // Normalize yet again
917
+ norm=0;
918
+ for(unsigned int t=0;t<FVSIZE;t++)
919
+ norm+=pow(fv[t], 2.0);
920
+ norm = sqrt(norm);
921
+
922
+ for(unsigned int t=0;t<FVSIZE;t++)
923
+ fv[t]/=norm;
924
+
925
+ // We're done with this descriptor. Store it into a list
926
+ m_keyDescs.push_back(Descriptor(descxi, descyi, fv));
927
+ }
928
+
929
+ assert(m_keyDescs.size()==m_numKeypoints);
930
+
931
+ // Get rid of memory we don't need anylonger
932
+ for(i=0;i<m_numOctaves;i++)
933
+ {
934
+ for(j=1;j<m_numIntervals+1;j++)
935
+ {
936
+ cvReleaseImage(&imgInterpolatedMagnitude[i][j-1]);
937
+ cvReleaseImage(&imgInterpolatedOrientation[i][j-1]);
938
+ }
939
+
940
+ delete [] imgInterpolatedMagnitude[i];
941
+ delete [] imgInterpolatedOrientation[i];
942
+ }
943
+
944
+ delete [] imgInterpolatedMagnitude;
945
+ delete [] imgInterpolatedOrientation;
946
+ }
947
+
948
+ // GetKernelSize()
949
+ // Returns the size of the kernal for the Gaussian blur given the sigma and
950
+ // cutoff value.
951
+ unsigned int SIFT::GetKernelSize(double sigma, double cut_off)
952
+ {
953
+ unsigned int i;
954
+ for (i=0;i<MAX_KERNEL_SIZE;i++)
955
+ if (exp(-((double)(i*i))/(2.0*sigma*sigma))<cut_off)
956
+ break;
957
+ unsigned int size = 2*i-1;
958
+ return size;
959
+ }
960
+
961
+ // BuildInterpolatedGaussianTable()
962
+ // This function actually generates the bell curve like image for the weighted
963
+ // addition earlier.
964
+ CvMat* SIFT::BuildInterpolatedGaussianTable(unsigned int size, double sigma)
965
+ {
966
+ unsigned int i, j;
967
+ double half_kernel_size = size/2 - 0.5;
968
+
969
+ double sog=0;
970
+ CvMat* ret = cvCreateMat(size, size, CV_32FC1);
971
+
972
+ assert(size%2==0);
973
+
974
+ double temp=0;
975
+ for(i=0;i<size;i++)
976
+ {
977
+ for(j=0;j<size;j++)
978
+ {
979
+ temp = gaussian2D(i-half_kernel_size, j-half_kernel_size, sigma);
980
+ cvSetReal2D(ret, j, i, temp);
981
+ sog+=temp;
982
+ }
983
+ }
984
+
985
+ for(i=0;i<size;i++)
986
+ {
987
+ for(j=0;j<size;j++)
988
+ {
989
+ cvSetReal2D(ret, j, i, 1.0/sog * cvGetReal2D(ret, j, i));
990
+ }
991
+ }
992
+
993
+ return ret;
994
+ }
995
+
996
+ // gaussian2D
997
+ // Returns the value of the bell curve at a (x,y) for a given sigma
998
+ double SIFT::gaussian2D(double x, double y, double sigma)
999
+ {
1000
+ double ret = 1.0/(2*M_PI*sigma*sigma) * exp(-(x*x+y*y)/(2.0*sigma*sigma));
1001
+
1002
+
1003
+ return ret;
1004
+ }
1005
+
1006
+ // ShowKeypoints()
1007
+ // Displays keypoints as calculated by the algorithm
1008
+ void SIFT::ShowKeypoints()
1009
+ {
1010
+ IplImage* img = cvCloneImage(m_srcImage);
1011
+
1012
+ for(int i=0;i<m_numKeypoints;i++)
1013
+ {
1014
+ Keypoint kp = m_keyPoints[i];
1015
+
1016
+ cvLine(img, cvPoint(kp.xi, kp.yi), cvPoint(kp.xi, kp.yi), CV_RGB(255,255,255), 3);
1017
+ cvLine(img, cvPoint(kp.xi, kp.yi), cvPoint(kp.xi+10*cos(kp.orien[0]), kp.yi+10*sin((double)kp.orien[0])), CV_RGB(255,255,255), 1);
1018
+ }
1019
+
1020
+ cvNamedWindow("Keypoints");
1021
+ cvShowImage("Keypoints", img);
1022
+ }
1023
+
1024
+ // ShowAbsSigma()
1025
+ // This function shows the sigmas used for various images.
1026
+ void SIFT::ShowAbsSigma()
1027
+ {
1028
+ for(int i=0;i<m_numOctaves;i++)
1029
+ {
1030
+ for(int j=1;j<m_numIntervals+4;j++)
1031
+ {
1032
+ printf("%f\t", m_absSigma[i][j-1]);
1033
+ }
1034
+ printf("\n");
1035
+ }
1036
+ }