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.
- 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
|
+
}
|