opencv-ffi 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+ }