opencv-ffi-fast 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +7 -0
- data/README.md +75 -0
- data/Rakefile +22 -0
- data/ext/Rakefile +13 -0
- data/ext/fast/.gitignore +4 -0
- data/ext/fast/LICENSE +30 -0
- data/ext/fast/README +43 -0
- data/ext/fast/fast.c +71 -0
- data/ext/fast/fast.h +31 -0
- data/ext/fast/fast_10.c +4666 -0
- data/ext/fast/fast_11.c +3910 -0
- data/ext/fast/fast_12.c +3134 -0
- data/ext/fast/fast_9.c +5910 -0
- data/ext/fast/mkrf_conf.rb +6 -0
- data/ext/fast/nonmax.c +117 -0
- data/ext/mkrf-monkey.rb +85 -0
- data/ext/mkrf-rakehelper-monkey.rb +52 -0
- data/ext/mkrf_conf.rb +3 -0
- data/lib/opencv-ffi-fast.rb +1 -0
- data/lib/opencv-ffi-fast/fast.rb +113 -0
- data/lib/opencv-ffi-fast/version.rb +5 -0
- data/opencv-ffi-fast.gemspec +25 -0
- data/test/test_ext.rb +50 -0
- data/test/test_wrapper.rb +35 -0
- metadata +112 -0
data/ext/fast/nonmax.c
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
#include <stdlib.h>
|
2
|
+
#include "fast.h"
|
3
|
+
|
4
|
+
|
5
|
+
#define Compare(X, Y) ((X)>=(Y))
|
6
|
+
|
7
|
+
xy* nonmax_suppression(const xy* corners, const int* scores, int num_corners, int* ret_num_nonmax)
|
8
|
+
{
|
9
|
+
int num_nonmax=0;
|
10
|
+
int last_row;
|
11
|
+
int* row_start;
|
12
|
+
int i, j;
|
13
|
+
xy* ret_nonmax;
|
14
|
+
const int sz = (int)num_corners;
|
15
|
+
|
16
|
+
/*Point above points (roughly) to the pixel above the one of interest, if there
|
17
|
+
is a feature there.*/
|
18
|
+
int point_above = 0;
|
19
|
+
int point_below = 0;
|
20
|
+
|
21
|
+
|
22
|
+
if(num_corners < 1)
|
23
|
+
{
|
24
|
+
*ret_num_nonmax = 0;
|
25
|
+
return 0;
|
26
|
+
}
|
27
|
+
|
28
|
+
ret_nonmax = (xy*)malloc(num_corners * sizeof(xy));
|
29
|
+
|
30
|
+
/* Find where each row begins
|
31
|
+
(the corners are output in raster scan order). A beginning of -1 signifies
|
32
|
+
that there are no corners on that row. */
|
33
|
+
last_row = corners[num_corners-1].y;
|
34
|
+
row_start = (int*)malloc((last_row+1)*sizeof(int));
|
35
|
+
|
36
|
+
for(i=0; i < last_row+1; i++)
|
37
|
+
row_start[i] = -1;
|
38
|
+
|
39
|
+
{
|
40
|
+
int prev_row = -1;
|
41
|
+
for(i=0; i< num_corners; i++)
|
42
|
+
if(corners[i].y != prev_row)
|
43
|
+
{
|
44
|
+
row_start[corners[i].y] = i;
|
45
|
+
prev_row = corners[i].y;
|
46
|
+
}
|
47
|
+
}
|
48
|
+
|
49
|
+
|
50
|
+
|
51
|
+
for(i=0; i < sz; i++)
|
52
|
+
{
|
53
|
+
int score = scores[i];
|
54
|
+
xy pos = corners[i];
|
55
|
+
|
56
|
+
/*Check left */
|
57
|
+
if(i > 0)
|
58
|
+
if(corners[i-1].x == pos.x-1 && corners[i-1].y == pos.y && Compare(scores[i-1], score))
|
59
|
+
continue;
|
60
|
+
|
61
|
+
/*Check right*/
|
62
|
+
if(i < (sz - 1))
|
63
|
+
if(corners[i+1].x == pos.x+1 && corners[i+1].y == pos.y && Compare(scores[i+1], score))
|
64
|
+
continue;
|
65
|
+
|
66
|
+
/*Check above (if there is a valid row above)*/
|
67
|
+
if(pos.y != 0 && row_start[pos.y - 1] != -1)
|
68
|
+
{
|
69
|
+
/*Make sure that current point_above is one
|
70
|
+
row above.*/
|
71
|
+
if(corners[point_above].y < pos.y - 1)
|
72
|
+
point_above = row_start[pos.y-1];
|
73
|
+
|
74
|
+
/*Make point_above point to the first of the pixels above the current point,
|
75
|
+
if it exists.*/
|
76
|
+
for(; corners[point_above].y < pos.y && corners[point_above].x < pos.x - 1; point_above++)
|
77
|
+
{}
|
78
|
+
|
79
|
+
|
80
|
+
for(j=point_above; corners[j].y < pos.y && corners[j].x <= pos.x + 1; j++)
|
81
|
+
{
|
82
|
+
int x = corners[j].x;
|
83
|
+
if( (x == pos.x - 1 || x ==pos.x || x == pos.x+1) && Compare(scores[j], score))
|
84
|
+
goto cont;
|
85
|
+
}
|
86
|
+
|
87
|
+
}
|
88
|
+
|
89
|
+
/*Check below (if there is anything below)*/
|
90
|
+
if(pos.y != last_row && row_start[pos.y + 1] != -1 && point_below < sz) /*Nothing below*/
|
91
|
+
{
|
92
|
+
if(corners[point_below].y < pos.y + 1)
|
93
|
+
point_below = row_start[pos.y+1];
|
94
|
+
|
95
|
+
/* Make point below point to one of the pixels belowthe current point, if it
|
96
|
+
exists.*/
|
97
|
+
for(; point_below < sz && corners[point_below].y == pos.y+1 && corners[point_below].x < pos.x - 1; point_below++)
|
98
|
+
{}
|
99
|
+
|
100
|
+
for(j=point_below; j < sz && corners[j].y == pos.y+1 && corners[j].x <= pos.x + 1; j++)
|
101
|
+
{
|
102
|
+
int x = corners[j].x;
|
103
|
+
if( (x == pos.x - 1 || x ==pos.x || x == pos.x+1) && Compare(scores[j],score))
|
104
|
+
goto cont;
|
105
|
+
}
|
106
|
+
}
|
107
|
+
|
108
|
+
ret_nonmax[num_nonmax++] = corners[i];
|
109
|
+
cont:
|
110
|
+
;
|
111
|
+
}
|
112
|
+
|
113
|
+
free(row_start);
|
114
|
+
*ret_num_nonmax = num_nonmax;
|
115
|
+
return ret_nonmax;
|
116
|
+
}
|
117
|
+
|
data/ext/mkrf-monkey.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
|
2
|
+
require 'mkrf'
|
3
|
+
|
4
|
+
# I admit it, I'm monkey-patching mkrf to get the behavior I want.
|
5
|
+
module Mkrf
|
6
|
+
|
7
|
+
class Generator
|
8
|
+
|
9
|
+
def rakefile_contents # :nodoc:
|
10
|
+
objext = CONFIG['OBJEXT']
|
11
|
+
cc = CONFIG['CC'] || 'gcc'
|
12
|
+
cpp = CONFIG['CXX'] || 'g++'
|
13
|
+
extension_sym = File.basename( @extension_name, ".#{CONFIG['DLEXT']}" ).to_sym
|
14
|
+
|
15
|
+
|
16
|
+
<<-END_RAKEFILE
|
17
|
+
# Generated by mkrf, monkey patched for opencv-ffi
|
18
|
+
require 'rake/clean'
|
19
|
+
|
20
|
+
|
21
|
+
SRC = FileList[#{sources.join(',')}]
|
22
|
+
OBJ = SRC.ext('#{objext}')
|
23
|
+
CC = '#{cc}'
|
24
|
+
CPP = '#{cpp}'
|
25
|
+
|
26
|
+
CLEAN.include(OBJ)
|
27
|
+
CLOBBER.include('#{@extension_name}', 'mkrf.log', 'Rakefile')
|
28
|
+
|
29
|
+
ADDITIONAL_OBJECTS = '#{objects}'
|
30
|
+
|
31
|
+
LDSHARED = "#{@available.ldshared_string} #{ldshared}"
|
32
|
+
|
33
|
+
LIBPATH = "#{library_path(CONFIG['libdir'])} #{@available.library_paths_compile_string}"
|
34
|
+
|
35
|
+
INCLUDES = "#{@available.includes_compile_string}"
|
36
|
+
|
37
|
+
LIBS = "#{@available.library_compile_string}"
|
38
|
+
|
39
|
+
CFLAGS = "#{cflags} #{defines_compile_string}"
|
40
|
+
|
41
|
+
RUBYARCHDIR = "\#{ENV["RUBYARCHDIR"]}"
|
42
|
+
LIBRUBYARG_SHARED = "#{CONFIG['LIBRUBYARG_SHARED']}"
|
43
|
+
|
44
|
+
task :default => :build_library
|
45
|
+
|
46
|
+
# Add one layer of indirection so I can generically call "rake build_library"
|
47
|
+
# and have it work ... or not work if the wrong rakefile is being run
|
48
|
+
task :build_library => '#{@extension_name}'
|
49
|
+
|
50
|
+
rule '.#{objext}' => '.c' do |t|
|
51
|
+
sh "\#{CC} \#{CFLAGS} \#{INCLUDES} -o \#{t.name} -c \#{t.source}"
|
52
|
+
end
|
53
|
+
|
54
|
+
rule '.#{objext}' => '.cpp' do |t|
|
55
|
+
sh "\#{CPP} \#{CFLAGS} \#{INCLUDES} -o \#{t.name} -c \#{t.source}"
|
56
|
+
end
|
57
|
+
|
58
|
+
DEPS = OBJ.clone.add('Rakefile')
|
59
|
+
desc "Build this extension"
|
60
|
+
file '#{@extension_name}' => DEPS do
|
61
|
+
sh "\#{LDSHARED} \#{LIBPATH} #{@available.ld_outfile(@extension_name)} \#{OBJ} \#{ADDITIONAL_OBJECTS} \#{LIBS} \#{LIBRUBYARG_SHARED}"
|
62
|
+
end
|
63
|
+
|
64
|
+
desc "Rebuild rakefile"
|
65
|
+
file 'Rakefile' => 'mkrf_conf.rb' do |t|
|
66
|
+
ruby 'mkrf_conf.rb'
|
67
|
+
puts "Rebuilt Rakefile. Run \'rake #{extension_sym.to_s}\' again"
|
68
|
+
end
|
69
|
+
|
70
|
+
desc "Install this extension"
|
71
|
+
task :install => '#{@extension_name}' do
|
72
|
+
makedirs "\#{RUBYARCHDIR}"
|
73
|
+
install "#{@extension_name}", "\#{RUBYARCHDIR}"
|
74
|
+
end
|
75
|
+
|
76
|
+
#{additional_code}
|
77
|
+
END_RAKEFILE
|
78
|
+
end
|
79
|
+
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
|
2
|
+
require 'mkrf/rakehelper'
|
3
|
+
|
4
|
+
alias :old_setup_extension :setup_extension
|
5
|
+
|
6
|
+
module Mkrf
|
7
|
+
@all_libs = []
|
8
|
+
|
9
|
+
def self.all_libs
|
10
|
+
@all_libs
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.all_libs=(a)
|
14
|
+
@all_libs = a
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def rake( rakedir, args = nil )
|
19
|
+
Dir.chdir( rakedir ) do
|
20
|
+
if args
|
21
|
+
sh "rake #{args}"
|
22
|
+
else
|
23
|
+
sh 'rake'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def setup_extension(dir, extension)
|
29
|
+
|
30
|
+
old_setup_extension( dir, extension )
|
31
|
+
|
32
|
+
ext_dir = "ext/#{dir}"
|
33
|
+
ext_so = "#{ext_dir}/#{extension}.#{Config::CONFIG['DLEXT']}"
|
34
|
+
|
35
|
+
task ext_so => FileList["#{ext_dir}/**/*.c*", "#{ext_dir}/mkrf_conf.rb"]
|
36
|
+
|
37
|
+
Mkrf::all_libs << extension.to_sym
|
38
|
+
|
39
|
+
namespace extension.to_sym do
|
40
|
+
|
41
|
+
desc "Run \"rake clean\" in #{ext_dir}"
|
42
|
+
task :clean do
|
43
|
+
rake ext_dir, 'clean'
|
44
|
+
end
|
45
|
+
|
46
|
+
desc "Run \"rake clobber\" in #{ext_dir}"
|
47
|
+
task :clobber do
|
48
|
+
rake extd_dir, 'clobber'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/ext/mkrf_conf.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "opencv-ffi-fast/version"
|
@@ -0,0 +1,113 @@
|
|
1
|
+
|
2
|
+
require 'nice-ffi'
|
3
|
+
require 'opencv-ffi'
|
4
|
+
|
5
|
+
module CVFFI
|
6
|
+
module FAST
|
7
|
+
|
8
|
+
class FASTResultsArray
|
9
|
+
include Enumerable
|
10
|
+
|
11
|
+
attr_accessor :points
|
12
|
+
attr_accessor :nPoints
|
13
|
+
|
14
|
+
def initialize( pts, nPts )
|
15
|
+
@points = pts
|
16
|
+
@nPoints = nPts
|
17
|
+
|
18
|
+
# Define a destructor do dispose of the results
|
19
|
+
destructor = Proc.new { pts.free }
|
20
|
+
ObjectSpace.define_finalizer( self, destructor )
|
21
|
+
end
|
22
|
+
|
23
|
+
def each
|
24
|
+
if @nPoints > 0
|
25
|
+
0.upto(@nPoints-1) { |i|
|
26
|
+
yield Xy.new( @points[i] )
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
alias :size :nPoints
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
class Params
|
36
|
+
attr_accessor :points, :threshold
|
37
|
+
|
38
|
+
VALID_SIZES = [ 9, 10, 11, 12 ]
|
39
|
+
|
40
|
+
def size_valid?( sz )
|
41
|
+
VALID_SIZES.include? sz
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize( opts = {} )
|
45
|
+
p opts
|
46
|
+
@points = opts[:points] || 9
|
47
|
+
@threshold = opts[:threshold] || 20
|
48
|
+
|
49
|
+
raise "Hm, invalid size #{@points} specified for FAST keypoint detector" unless size_valid? @points
|
50
|
+
end
|
51
|
+
|
52
|
+
def to_hash
|
53
|
+
{ :points => @points, :threshold => @threshold }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def self.detect( img, params )
|
59
|
+
FASTDetect( params.points, img, params.threshold )
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.FASTDetect( size, img, threshold )
|
63
|
+
nResults = FFI::MemoryPointer.new :int
|
64
|
+
|
65
|
+
if img.is_a?( IplImage )
|
66
|
+
# Ensure the image is b&w
|
67
|
+
img = img.ensure_greyscale
|
68
|
+
|
69
|
+
results = FFI::Pointer.new :pointer, method("fast#{size}_detect").call( img.imageData, img.width, img.height, img.widthStep, threshold, nResults )
|
70
|
+
else
|
71
|
+
raise ArgumentError, "Don't know how to deal with image class #{img.class}"
|
72
|
+
end
|
73
|
+
|
74
|
+
# Dereference the two pointers
|
75
|
+
nPoints = nResults.read_int
|
76
|
+
points = FFI::Pointer.new Xy, results
|
77
|
+
|
78
|
+
FASTResultsArray.new( points, nPoints )
|
79
|
+
end
|
80
|
+
|
81
|
+
def self.FAST9Detect( img, threshold ); FASTDetect( 9, img, threshold ); end
|
82
|
+
def self.FAST10Detect( img, threshold ); FASTDetect( 10, img, threshold ); end
|
83
|
+
def self.FAST11Detect( img, threshold ); FASTDetect( 11, img, threshold ); end
|
84
|
+
def self.FAST12Detect( img, threshold ); FASTDetect( 12, img, threshold ); end
|
85
|
+
|
86
|
+
#
|
87
|
+
## Here's the actual FFI interface
|
88
|
+
#
|
89
|
+
extend NiceFFI::Library
|
90
|
+
|
91
|
+
libs_dir = File.dirname(__FILE__) + "/../../ext/fast/"
|
92
|
+
pathset = NiceFFI::PathSet::DEFAULT.prepend( libs_dir )
|
93
|
+
load_library("cvffi_fast", pathset)
|
94
|
+
|
95
|
+
class Xy < NiceFFI::Struct
|
96
|
+
layout :x, :int,
|
97
|
+
:y, :int
|
98
|
+
end
|
99
|
+
|
100
|
+
# Leave result as pointer, not Xy.typed_pointer as it makes it easier to deference
|
101
|
+
# it to an array of Xy
|
102
|
+
|
103
|
+
def self.fast_detect_function( sz )
|
104
|
+
attach_function "fast#{sz}_detect".to_sym, [ :pointer, :int, :int, :int, :int, :pointer ], :pointer
|
105
|
+
end
|
106
|
+
|
107
|
+
fast_detect_function 9
|
108
|
+
fast_detect_function 10
|
109
|
+
fast_detect_function 11
|
110
|
+
fast_detect_function 12
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "opencv-ffi-fast/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "opencv-ffi-fast"
|
7
|
+
s.version = CVFFI::FAST::VERSION
|
8
|
+
s.authors = ["Aaron Marburg"]
|
9
|
+
s.email = ["aaron.marburg@pg.canterbury.ac.nz"]
|
10
|
+
s.homepage = "http://github.com/amarburg/opencv-ffi-fast"
|
11
|
+
s.summary = %q{Edward Rosten's FAST keypoint detector algorithm, as a plug-in for OpenCV-FFI.}
|
12
|
+
s.description = %q{Edward Rosten's FAST keypoint detector algorithm, as a plug-in for OpenCV-FFI.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "opencv-ffi-fast"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.extensions = "ext/mkrf_conf.rb"
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
|
22
|
+
s.add_dependency "nice-ffi"
|
23
|
+
s.add_dependency "mkrf"
|
24
|
+
s.add_dependency "opencv-ffi"
|
25
|
+
end
|
data/test/test_ext.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
|
2
|
+
require 'test/setup'
|
3
|
+
require 'opencv-ffi-ext/fast'
|
4
|
+
require 'opencv-ffi'
|
5
|
+
|
6
|
+
class TestSURF < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def setup
|
9
|
+
@img = CVFFI::cvLoadImage( TEST_IMAGE_FILE, CVFFI::CV_LOAD_IMAGE_COLOR )
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def test_cvFASTDetector
|
14
|
+
greyImg = CVFFI::cvCreateImage( CVFFI::CvSize.new( { :height => @img.height,
|
15
|
+
:width => @img.width }),
|
16
|
+
:IPL_DEPTH_8U, 1 )
|
17
|
+
CVFFI::cvCvtColor( @img, greyImg, :CV_RGB2GRAY )
|
18
|
+
|
19
|
+
smallGreyImg = CVFFI::cvCreateImage( CVFFI::CvSize.new( { :height => greyImg.height/2,
|
20
|
+
:width => greyImg.width/2 } ),
|
21
|
+
:IPL_DEPTH_8U, 1 )
|
22
|
+
|
23
|
+
CVFFI::cvResize( greyImg, smallGreyImg, :CV_INTER_LINEAR )
|
24
|
+
|
25
|
+
#CVFFI::cvSaveImage( TestSetup::output_filename("greyImage.jpg"), smallGreyImg.to_ptr )
|
26
|
+
|
27
|
+
nResults = FFI::MemoryPointer.new :int
|
28
|
+
|
29
|
+
results = FFI::Pointer.new :pointer, CVFFI::FAST::fast12_detect( smallGreyImg.imageData, smallGreyImg.width, smallGreyImg.height, smallGreyImg.widthStep, 50, nResults )
|
30
|
+
|
31
|
+
# Dereference the two pointers
|
32
|
+
nPoints = nResults.read_int
|
33
|
+
points = FFI::Pointer.new CVFFI::FAST::Xy, results
|
34
|
+
|
35
|
+
#p points.inspect
|
36
|
+
#p nPoints.inspect
|
37
|
+
|
38
|
+
0.upto(nPoints-1) { |i|
|
39
|
+
pt = CVFFI::FAST::Xy.new( points[i] )
|
40
|
+
|
41
|
+
CVFFI::cvCircle( smallGreyImg, CVFFI::CvPoint.new( :x => pt.x.to_i, :y => pt.y.to_i ), 5,
|
42
|
+
CVFFI::CvScalar.new( :w=>255, :x=>255, :y=>255, :z=>0 ), -1, 8, 0 )
|
43
|
+
}
|
44
|
+
CVFFI::cvSaveImage( TestSetup::output_filename("greyImageFASTPts.jpg"), smallGreyImg )
|
45
|
+
|
46
|
+
|
47
|
+
results.free
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|