similie 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -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
+ }
@@ -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