similie 0.1.0
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/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
|