opencv-ffi 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/Gemfile +15 -0
- data/README.md +126 -0
- data/Rakefile +52 -0
- data/docs/DocsIndex.md +1 -0
- data/docs/examples/load_image.rb +25 -0
- data/ext/Rakefile +13 -0
- data/ext/aishack-sift/.gitignore +4 -0
- data/ext/aishack-sift/Descriptor.h +34 -0
- data/ext/aishack-sift/KeyPoint.h +38 -0
- data/ext/aishack-sift/README +20 -0
- data/ext/aishack-sift/SIFT.cpp +1036 -0
- data/ext/aishack-sift/SIFT.h +84 -0
- data/ext/aishack-sift/example/.gitignore +2 -0
- data/ext/aishack-sift/example/Makefile +24 -0
- data/ext/aishack-sift/example/MySIFT.cpp +29 -0
- data/ext/aishack-sift/mkrf_conf.rb +13 -0
- data/ext/aishack-sift/siftlib.cpp +85 -0
- data/ext/eigen/.gitignore +4 -0
- data/ext/eigen/eigen_polynomial.cpp +41 -0
- data/ext/eigen/eigen_svd.cpp +100 -0
- data/ext/eigen/mkrf_conf.rb +14 -0
- data/ext/mkrf-monkey.rb +85 -0
- data/ext/mkrf-rakehelper-monkey.rb +52 -0
- data/ext/mkrf_conf.rb +3 -0
- data/ext/opencv-ffi/.gitignore +4 -0
- data/ext/opencv-ffi/matcher_helper.cpp +56 -0
- data/ext/opencv-ffi/mkrf_conf.rb +12 -0
- data/ext/opencv-ffi/vector_math.cpp +39 -0
- data/ext/opensurf/.gitignore +4 -0
- data/ext/opensurf/README +38 -0
- data/ext/opensurf/fasthessian.cpp +376 -0
- data/ext/opensurf/fasthessian.h +108 -0
- data/ext/opensurf/integral.cpp +58 -0
- data/ext/opensurf/integral.h +55 -0
- data/ext/opensurf/ipoint.cpp +108 -0
- data/ext/opensurf/ipoint.h +76 -0
- data/ext/opensurf/kmeans.h +172 -0
- data/ext/opensurf/mkrf_conf.rb +10 -0
- data/ext/opensurf/responselayer.h +92 -0
- data/ext/opensurf/surf.cpp +317 -0
- data/ext/opensurf/surf.h +66 -0
- data/ext/opensurf/surflib.cpp +98 -0
- data/ext/opensurf/surflib.h +96 -0
- data/ext/opensurf/utils.cpp +357 -0
- data/ext/opensurf/utils.h +63 -0
- data/lib/.gitignore +1 -0
- data/lib/opencv-ffi-ext/eigen.rb +84 -0
- data/lib/opencv-ffi-ext/features2d.rb +4 -0
- data/lib/opencv-ffi-ext/matcher_helper.rb +24 -0
- data/lib/opencv-ffi-ext/opensurf.rb +217 -0
- data/lib/opencv-ffi-ext/sift.rb +118 -0
- data/lib/opencv-ffi-ext/vector_math.rb +115 -0
- data/lib/opencv-ffi-wrappers.rb +7 -0
- data/lib/opencv-ffi-wrappers/core.rb +24 -0
- data/lib/opencv-ffi-wrappers/core/iplimage.rb +50 -0
- data/lib/opencv-ffi-wrappers/core/mat.rb +268 -0
- data/lib/opencv-ffi-wrappers/core/misc_draw.rb +44 -0
- data/lib/opencv-ffi-wrappers/core/point.rb +286 -0
- data/lib/opencv-ffi-wrappers/core/rect.rb +40 -0
- data/lib/opencv-ffi-wrappers/core/scalar.rb +104 -0
- data/lib/opencv-ffi-wrappers/core/size.rb +88 -0
- data/lib/opencv-ffi-wrappers/enumerable.rb +10 -0
- data/lib/opencv-ffi-wrappers/features2d.rb +17 -0
- data/lib/opencv-ffi-wrappers/features2d/image_patch.rb +322 -0
- data/lib/opencv-ffi-wrappers/features2d/star.rb +111 -0
- data/lib/opencv-ffi-wrappers/features2d/surf.rb +115 -0
- data/lib/opencv-ffi-wrappers/highgui.rb +10 -0
- data/lib/opencv-ffi-wrappers/imgproc.rb +4 -0
- data/lib/opencv-ffi-wrappers/imgproc/features.rb +35 -0
- data/lib/opencv-ffi-wrappers/imgproc/geometric.rb +39 -0
- data/lib/opencv-ffi-wrappers/matcher.rb +297 -0
- data/lib/opencv-ffi-wrappers/matrix.rb +37 -0
- data/lib/opencv-ffi-wrappers/misc.rb +41 -0
- data/lib/opencv-ffi-wrappers/misc/params.rb +34 -0
- data/lib/opencv-ffi-wrappers/sequence.rb +37 -0
- data/lib/opencv-ffi-wrappers/vectors.rb +38 -0
- data/lib/opencv-ffi.rb +12 -0
- data/lib/opencv-ffi/calib3d.rb +26 -0
- data/lib/opencv-ffi/core.rb +15 -0
- data/lib/opencv-ffi/core/draw.rb +68 -0
- data/lib/opencv-ffi/core/dynamic.rb +13 -0
- data/lib/opencv-ffi/core/library.rb +5 -0
- data/lib/opencv-ffi/core/operations.rb +122 -0
- data/lib/opencv-ffi/core/point.rb +22 -0
- data/lib/opencv-ffi/core/types.rb +172 -0
- data/lib/opencv-ffi/cvffi.rb +8 -0
- data/lib/opencv-ffi/features2d.rb +7 -0
- data/lib/opencv-ffi/features2d/library.rb +6 -0
- data/lib/opencv-ffi/features2d/star.rb +30 -0
- data/lib/opencv-ffi/features2d/surf.rb +38 -0
- data/lib/opencv-ffi/highgui.rb +31 -0
- data/lib/opencv-ffi/imgproc.rb +9 -0
- data/lib/opencv-ffi/imgproc/features.rb +37 -0
- data/lib/opencv-ffi/imgproc/geometric.rb +42 -0
- data/lib/opencv-ffi/imgproc/library.rb +6 -0
- data/lib/opencv-ffi/imgproc/misc.rb +39 -0
- data/lib/opencv-ffi/version.rb +3 -0
- data/opencv-ffi.gemspec +26 -0
- data/test/core/test_draw.rb +46 -0
- data/test/core/test_operations.rb +135 -0
- data/test/core/test_size.rb +14 -0
- data/test/core/test_text.rb +52 -0
- data/test/ext/test_eigen.rb +105 -0
- data/test/ext/test_opensurf.rb +35 -0
- data/test/ext/test_sift.rb +26 -0
- data/test/ext/test_vector_math.rb +85 -0
- data/test/features2d/test_surf.rb +63 -0
- data/test/imgproc/test_goodfeatures.rb +18 -0
- data/test/setup.rb +65 -0
- data/test/test_calib3d.rb +38 -0
- data/test/test_core.rb +26 -0
- data/test/test_ext.rb +8 -0
- data/test/test_features2d.rb +9 -0
- data/test/test_files/images/IMG_7088.JPG +0 -0
- data/test/test_files/images/IMG_7088_small.JPG +0 -0
- data/test/test_files/images/IMG_7089.JPG +0 -0
- data/test/test_highgui.rb +26 -0
- data/test/test_imgproc.rb +35 -0
- data/test/test_wrappers.rb +8 -0
- data/test/wrappers/core/test_draw.rb +41 -0
- data/test/wrappers/core/test_mat.rb +40 -0
- data/test/wrappers/core/test_operations.rb +35 -0
- data/test/wrappers/core/test_types.rb +235 -0
- data/test/wrappers/features2d/test_image_patch.rb +108 -0
- data/test/wrappers/test_imgproc.rb +87 -0
- data/test/wrappers/test_matcher.rb +96 -0
- data/test/wrappers/test_star.rb +28 -0
- data/test/wrappers/test_surf.rb +36 -0
- metadata +234 -0
data/.gitignore
ADDED
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
|
+
|
data/README.md
ADDED
@@ -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
|
+
|
data/Rakefile
ADDED
@@ -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
|
+
|
data/docs/DocsIndex.md
ADDED
@@ -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
|
+
|
data/ext/Rakefile
ADDED
@@ -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,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
|
+
}
|