similie 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +30 -0
- data/VERSION +1 -0
- data/ext/extconf.rb +25 -0
- data/ext/similie.c +165 -0
- data/test/test_basic.rb +17 -0
- data/test/test_distance.rb +16 -0
- metadata +70 -0
data/README.rdoc
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
= Similie
|
2
|
+
|
3
|
+
Similie is a simple DCT based image hashing interface that,
|
4
|
+
|
5
|
+
* computes a fingerprint based on low frequencies of an image.
|
6
|
+
* computes hamming distance between 2 fingerprints.
|
7
|
+
|
8
|
+
== Example
|
9
|
+
|
10
|
+
require 'similie'
|
11
|
+
|
12
|
+
img1 = Similie.new("test/lena1.jpg")
|
13
|
+
img2 = Similie.new("test/lena2.png") # lena1.jpg cropped and scaled
|
14
|
+
img3 = Similie.new("test/lena5.png") # a different image
|
15
|
+
|
16
|
+
img1.hash #=> unsigned 64bit int
|
17
|
+
|
18
|
+
img1.distance(img2) #=> 1
|
19
|
+
img1.distance(img5) #=> 5
|
20
|
+
|
21
|
+
Similie.distance("test/lena1.jpg", "test/lena5.jpg") #=> 5
|
22
|
+
|
23
|
+
== Dependencies
|
24
|
+
|
25
|
+
* ruby 1.9.1+
|
26
|
+
* opencv 2.1+ (libcv-dev and libhighgui-dev on debian systems)
|
27
|
+
|
28
|
+
= License
|
29
|
+
|
30
|
+
{CC BY-SA 3.0}[http://creativecommons.org/licenses/by-sa/3.0/]
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/ext/extconf.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$CFLAGS = "-I/usr/include/opencv -Wall"
|
4
|
+
$LDFLAGS = "-lhighgui -lcxcore"
|
5
|
+
|
6
|
+
dir_config("libcv", ["/usr/local", "/opt/local", "/usr"])
|
7
|
+
|
8
|
+
headers = [ 'stdio.h', 'stdlib.h', 'string.h', 'opencv/cxcore.h', 'opencv/highgui.h' ]
|
9
|
+
lib_1 = [ 'cxcore', 'cvInitFont', headers ]
|
10
|
+
lib_2 = [ 'highgui', 'cvEncodeImage', headers ]
|
11
|
+
|
12
|
+
if have_header('opencv/cxcore.h') && have_library(*lib_1) && have_library(*lib_2)
|
13
|
+
create_makefile 'similie'
|
14
|
+
else
|
15
|
+
puts %q{
|
16
|
+
Cannot find opencv headers or libraries.
|
17
|
+
|
18
|
+
On debian based systems you can install it from apt as,
|
19
|
+
sudo apt-get install libcv-dev libhighgui-dev
|
20
|
+
|
21
|
+
Refer to http://opencv.willowgarage.com/wiki/InstallGuide for other platforms or operating systems.
|
22
|
+
}
|
23
|
+
|
24
|
+
exit 1
|
25
|
+
end
|
data/ext/similie.c
ADDED
@@ -0,0 +1,165 @@
|
|
1
|
+
/*
|
2
|
+
(c) Bharanee Rathna 2011
|
3
|
+
|
4
|
+
CC BY-SA 3.0
|
5
|
+
http://creativecommons.org/licenses/by-sa/3.0/
|
6
|
+
|
7
|
+
Free for every type of use. The author cannot be legally held responsible for
|
8
|
+
any damages resulting from the use of this work. All modifications or derivatives
|
9
|
+
need to be attributed.
|
10
|
+
*/
|
11
|
+
|
12
|
+
#include <ruby.h>
|
13
|
+
#include <ruby/encoding.h>
|
14
|
+
|
15
|
+
#include <stdio.h>
|
16
|
+
#include <stdlib.h>
|
17
|
+
#include <string.h>
|
18
|
+
#include <time.h>
|
19
|
+
#include <unistd.h>
|
20
|
+
#include <opencv/cv.h>
|
21
|
+
#include <opencv/cxcore.h>
|
22
|
+
#include <opencv/highgui.h>
|
23
|
+
|
24
|
+
#define TO_S(v) rb_funcall(v, rb_intern("to_s"), 0)
|
25
|
+
#define CSTRING(v) RSTRING_PTR(TO_S(v))
|
26
|
+
#define ID_CONST_GET rb_intern("const_get")
|
27
|
+
#define CONST_GET(scope, constant) (rb_funcall(scope, ID_CONST_GET, 1, rb_str_new2(constant)))
|
28
|
+
#define cvGetMonoPixel(img,y,x) ((uchar *)(img->imageData + x*img->widthStep))[y*img->nChannels]
|
29
|
+
|
30
|
+
#undef SIZET2NUM
|
31
|
+
#ifdef HAVE_LONG_LONG
|
32
|
+
#define SIZET2NUM(x) ULL2NUM(x)
|
33
|
+
#else
|
34
|
+
#define SIZET2NUM(x) ULONG2NUM(x)
|
35
|
+
#endif
|
36
|
+
|
37
|
+
// http://en.wikipedia.org/wiki/Hamming_weight
|
38
|
+
|
39
|
+
const uint64 m1 = 0x5555555555555555; //binary: 0101...
|
40
|
+
const uint64 m2 = 0x3333333333333333; //binary: 00110011..
|
41
|
+
const uint64 m4 = 0x0f0f0f0f0f0f0f0f; //binary: 4 zeros, 4 ones ...
|
42
|
+
const uint64 m8 = 0x00ff00ff00ff00ff; //binary: 8 zeros, 8 ones ...
|
43
|
+
const uint64 m16 = 0x0000ffff0000ffff; //binary: 16 zeros, 16 ones ...
|
44
|
+
const uint64 m32 = 0x00000000ffffffff; //binary: 32 zeros, 32 ones
|
45
|
+
const uint64 hff = 0xffffffffffffffff; //binary: all ones
|
46
|
+
const uint64 h01 = 0x0101010101010101; //the sum of 256 to the power of 0,1,2,3...
|
47
|
+
|
48
|
+
int popcount(uint64 x) {
|
49
|
+
x -= (x >> 1) & m1; //put count of each 2 bits into those 2 bits
|
50
|
+
x = (x & m2) + ((x >> 2) & m2); //put count of each 4 bits into those 4 bits
|
51
|
+
x = (x + (x >> 4)) & m4; //put count of each 8 bits into those 8 bits
|
52
|
+
return (x * h01)>>56; //returns left 8 bits of x + (x<<8) + (x<<16) + (x<<24) + ...
|
53
|
+
}
|
54
|
+
|
55
|
+
uint64_t image_phash(IplImage *img) {
|
56
|
+
uint64_t phash = 0;
|
57
|
+
int x, y;
|
58
|
+
double avg;
|
59
|
+
|
60
|
+
IplImage *small = cvCreateImage(cvSize(32, 32), 8, img->nChannels);
|
61
|
+
IplImage *mono = cvCreateImage(cvSize(32, 32), 8, 1);
|
62
|
+
|
63
|
+
cvResize(img, small, CV_INTER_CUBIC);
|
64
|
+
cvCvtColor(small, mono, CV_RGB2GRAY);
|
65
|
+
|
66
|
+
CvMat *dct = cvCreateMat(8, 8, CV_32FC1);
|
67
|
+
for (x = 0; x < 8; x++) {
|
68
|
+
for (y = 0; y < 8; y++) {
|
69
|
+
cvSet2D(dct, x, y, cvScalarAll(cvGetMonoPixel(mono, x, y)));
|
70
|
+
}
|
71
|
+
}
|
72
|
+
|
73
|
+
cvDCT(dct, dct, CV_DXT_ROWS);
|
74
|
+
cvSet2D(dct, 0, 0, cvScalarAll(0));
|
75
|
+
|
76
|
+
avg = (double)cvAvg(dct, 0).val[0] * 64.0 / 63.0;
|
77
|
+
|
78
|
+
uint64_t mask = 1;
|
79
|
+
for (x = 0; x < 8; x++) {
|
80
|
+
for (y = 0; y < 8; y++) {
|
81
|
+
if (cvGet2D(dct, x, y).val[0] > avg) phash |= mask;
|
82
|
+
mask = mask << 1;
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
cvReleaseMat(&dct);
|
87
|
+
cvReleaseImage(&mono);
|
88
|
+
cvReleaseImage(&small);
|
89
|
+
return phash;
|
90
|
+
}
|
91
|
+
|
92
|
+
static void rb_image_free(IplImage *handle) {
|
93
|
+
if (handle)
|
94
|
+
cvReleaseImage(&handle);
|
95
|
+
}
|
96
|
+
|
97
|
+
VALUE rb_image_alloc(VALUE klass) {
|
98
|
+
IplImage *handle = 0;
|
99
|
+
return Data_Wrap_Struct(klass, 0, rb_image_free, handle);
|
100
|
+
}
|
101
|
+
|
102
|
+
IplImage* rb_image_handle(VALUE self) {
|
103
|
+
IplImage *handle = 0;
|
104
|
+
Data_Get_Struct(self, IplImage, handle);
|
105
|
+
if (!handle)
|
106
|
+
rb_raise(rb_eRuntimeError, "Invalid object, did you forget to call super() ?");
|
107
|
+
|
108
|
+
return handle;
|
109
|
+
}
|
110
|
+
|
111
|
+
VALUE rb_image_hash(VALUE self) {
|
112
|
+
VALUE hash = rb_iv_get(self, "@hash");
|
113
|
+
if (NIL_P(hash)) {
|
114
|
+
hash = SIZET2NUM(image_phash(rb_image_handle(self)));
|
115
|
+
rb_iv_set(self, "@hash", hash);
|
116
|
+
}
|
117
|
+
return hash;
|
118
|
+
}
|
119
|
+
|
120
|
+
VALUE rb_image_initialize(VALUE self, VALUE file) {
|
121
|
+
IplImage *img = cvLoadImage(CSTRING(file), -1);
|
122
|
+
if (!img)
|
123
|
+
rb_raise(rb_eArgError, "Invalid image or unsupported format: %s", CSTRING(file));
|
124
|
+
|
125
|
+
DATA_PTR(self) = img;
|
126
|
+
return self;
|
127
|
+
}
|
128
|
+
|
129
|
+
|
130
|
+
VALUE rb_image_distance(VALUE self, VALUE other) {
|
131
|
+
VALUE hash1 = rb_image_hash(self);
|
132
|
+
VALUE hash2 = rb_image_hash(other);
|
133
|
+
int dist = popcount(NUM2ULONG(hash1) ^ NUM2ULONG(hash2));
|
134
|
+
return INT2NUM(dist);
|
135
|
+
}
|
136
|
+
|
137
|
+
VALUE rb_image_distance_func(VALUE self, VALUE file1, VALUE file2) {
|
138
|
+
IplImage *img1, *img2;
|
139
|
+
img1 = cvLoadImage(CSTRING(file1), -1);
|
140
|
+
if (!img1)
|
141
|
+
rb_raise(rb_eArgError, "Invalid image or unsupported format: %s", CSTRING(file1));
|
142
|
+
|
143
|
+
img2 = cvLoadImage(CSTRING(file2), -1);
|
144
|
+
if (!img2) {
|
145
|
+
cvReleaseImage(&img1);
|
146
|
+
rb_raise(rb_eArgError, "Invalid image or unsupported format: %s", CSTRING(file2));
|
147
|
+
}
|
148
|
+
|
149
|
+
int dist = popcount(image_phash(img1) ^ image_phash(img2));
|
150
|
+
|
151
|
+
cvReleaseImage(&img1);
|
152
|
+
cvReleaseImage(&img2);
|
153
|
+
|
154
|
+
return INT2NUM(dist);
|
155
|
+
}
|
156
|
+
|
157
|
+
void Init_similie() {
|
158
|
+
VALUE cSimilie = rb_define_class("Similie", rb_cObject);
|
159
|
+
rb_define_alloc_func(cSimilie, rb_image_alloc);
|
160
|
+
rb_define_method(cSimilie, "initialize", RUBY_METHOD_FUNC(rb_image_initialize), 1);
|
161
|
+
rb_define_method(cSimilie, "hash", RUBY_METHOD_FUNC(rb_image_hash), 0);
|
162
|
+
rb_define_method(cSimilie, "distance", RUBY_METHOD_FUNC(rb_image_distance), 1);
|
163
|
+
|
164
|
+
rb_define_singleton_method(cSimilie, "distance", RUBY_METHOD_FUNC(rb_image_distance_func), 2);
|
165
|
+
}
|
data/test/test_basic.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
describe 'Similie image load' do
|
4
|
+
it 'should load image' do
|
5
|
+
assert Similie.new(File.join($testdir, 'lena1.jpg'))
|
6
|
+
end
|
7
|
+
|
8
|
+
it 'should barf on invalid path' do
|
9
|
+
assert_raises(ArgumentError) { Similie.new(File.join($testdir, 'lena2.jpg')) }
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should hash image' do
|
13
|
+
img = Similie.new(File.join($testdir, 'lena1.jpg'))
|
14
|
+
assert img
|
15
|
+
assert_equal 216455360913932544, img.hash
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require_relative 'helper'
|
2
|
+
|
3
|
+
describe 'Similie image distance' do
|
4
|
+
it 'should work for similar images' do
|
5
|
+
images = %w(lena1.jpg lena2.png lena3.png lena4.png lena5.jpg).map {|file| Similie.new(File.join($testdir, file))}
|
6
|
+
|
7
|
+
assert_equal 1, images[0].distance(images[1])
|
8
|
+
assert_equal 2, images[1].distance(images[2])
|
9
|
+
assert_equal 0, images[2].distance(images[3])
|
10
|
+
assert_equal 6, images[3].distance(images[4])
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should work using the singleton method' do
|
14
|
+
assert_equal 6, Similie.distance(File.join($testdir, 'lena4.png'), File.join($testdir, 'lena5.jpg'))
|
15
|
+
end
|
16
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: similie
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Bharanee Rathna
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-06-04 00:00:00 +10:00
|
18
|
+
default_executable:
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: "\n Similie does image fingerprinting using discrete cosine transform\n and similarity comparison using Hamming distance on fingerprints.\n "
|
22
|
+
email: deepfryed@gmail.com
|
23
|
+
executables: []
|
24
|
+
|
25
|
+
extensions:
|
26
|
+
- ext/extconf.rb
|
27
|
+
extra_rdoc_files:
|
28
|
+
- README.rdoc
|
29
|
+
files:
|
30
|
+
- VERSION
|
31
|
+
- ext/similie.c
|
32
|
+
- README.rdoc
|
33
|
+
- test/test_basic.rb
|
34
|
+
- test/test_distance.rb
|
35
|
+
- ext/extconf.rb
|
36
|
+
has_rdoc: true
|
37
|
+
homepage: http://github.com/deepfryed/similie
|
38
|
+
licenses: []
|
39
|
+
|
40
|
+
post_install_message:
|
41
|
+
rdoc_options: []
|
42
|
+
|
43
|
+
require_paths:
|
44
|
+
- lib
|
45
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
46
|
+
none: false
|
47
|
+
requirements:
|
48
|
+
- - ">="
|
49
|
+
- !ruby/object:Gem::Version
|
50
|
+
segments:
|
51
|
+
- 0
|
52
|
+
version: "0"
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
none: false
|
55
|
+
requirements:
|
56
|
+
- - ">="
|
57
|
+
- !ruby/object:Gem::Version
|
58
|
+
segments:
|
59
|
+
- 0
|
60
|
+
version: "0"
|
61
|
+
requirements: []
|
62
|
+
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 1.3.7
|
65
|
+
signing_key:
|
66
|
+
specification_version: 3
|
67
|
+
summary: Compute image fingerprints and similarity
|
68
|
+
test_files:
|
69
|
+
- test/test_basic.rb
|
70
|
+
- test/test_distance.rb
|