paperclip-facecrop 0.0.6 → 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.md +61 -26
- data/README_example_b.jpg +0 -0
- data/lib/detectors/face_com.rb +27 -0
- data/lib/detectors/opencv.rb +44 -0
- data/lib/{opencv_ext.rb → detectors/opencv_ext.rb} +2 -2
- data/lib/face_crop.rb +86 -0
- data/lib/paperclip-facecrop.rb +2 -149
- data/lib/processor.rb +143 -0
- data/lib/railtie.rb +22 -0
- data/paperclip-facecrop.gemspec +1 -2
- metadata +36 -65
data/README.md
CHANGED
@@ -2,15 +2,30 @@ Paperclip::FaceCrop
|
|
2
2
|
====================
|
3
3
|
`Paperclip::FaceCrop` is a [Paperclip][paperclip] processor that is aware of the faces found on the image
|
4
4
|
so that they aren't cropped or aren't shown too small while generating the thumbnails.
|
5
|
-
It
|
5
|
+
It can use the [OpenCV][opencv] library or the [Face.com][face_com] web service(or both at the same time) for the facial recognition.
|
6
6
|
|
7
7
|

|
8
8
|
|
9
9
|
Requirements:
|
10
10
|
-------------
|
11
|
+
|
12
|
+
### OpenCV
|
13
|
+
|
14
|
+
If you want to use OpenCV on your own server, you need to install:
|
15
|
+
|
11
16
|
- [OpenCV][opencv]
|
12
17
|
- [OpenCV ruby binding][ruby-opencv]
|
13
18
|
|
19
|
+
In case you get the error message `/ext/opencv/cverror.cpp:143: error: ‘CV_GpuCufftCallError’ was not declared in this scope` while installing the ruby binding,
|
20
|
+
checkout the OpenCV_2.2 branch or just remove the line 143 from `/ext/opencv/cverror.cpp`
|
21
|
+
|
22
|
+
|
23
|
+
### Face.com
|
24
|
+
- [rest-client][rest-client]
|
25
|
+
|
26
|
+
In order to use the Face.com service, you will also need to register in order to get your api key and api secret for your application.
|
27
|
+
|
28
|
+
|
14
29
|
Installation:
|
15
30
|
-------------
|
16
31
|
- Add to your application `Gemfile`
|
@@ -21,39 +36,55 @@ Installation:
|
|
21
36
|
|
22
37
|
bundle install
|
23
38
|
|
24
|
-
- Write an initializer setting the path of the haarcascade filters(`initializers/paperclip.rb` for example):
|
25
39
|
|
26
|
-
|
27
|
-
|
40
|
+
- Write an initializer setting the detectors configuration (`initializers/paperclip.rb` for example):
|
41
|
+
|
42
|
+
### OpenCV
|
43
|
+
|
44
|
+
Set the path of the haarcascade filters:
|
45
|
+
|
46
|
+
Paperclip::FaceCrop.detectors = {
|
47
|
+
'OpenCV' => {
|
48
|
+
:face => %w(/usr/local/share/opencv/haarcascades/haarcascade_frontalface_alt_tree.xml)
|
49
|
+
}
|
28
50
|
}
|
51
|
+
|
52
|
+
|
29
53
|
|
30
|
-
|
54
|
+
You can use more than one filter to try more accurate searches:
|
31
55
|
|
32
|
-
Paperclip::FaceCrop.
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
56
|
+
Paperclip::FaceCrop.detectors = {
|
57
|
+
'OpenCV' => {
|
58
|
+
:face => %w(/usr/local/share/opencv/haarcascades/haarcascade_frontalface_alt_tree.xml
|
59
|
+
/usr/local/share/opencv/haarcascades/haarcascade_frontalface_alt.xml
|
60
|
+
/usr/local/share/opencv/haarcascades/haarcascade_profileface.xml)
|
61
|
+
}
|
38
62
|
}
|
39
63
|
|
40
|
-
|
41
|
-
|
64
|
+
|
65
|
+
|
66
|
+
In order to try to avoid some false positives, you can also specify other classifiers to detect other parts of the face. In that case,
|
67
|
+
only the found areas that contain parts like a mouth, an eye or a nose will be considered a face:
|
42
68
|
|
43
|
-
Paperclip::FaceCrop.
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
]
|
69
|
+
Paperclip::FaceCrop.detectors = {
|
70
|
+
'OpenCV' => {
|
71
|
+
:face => %w(/usr/local/share/opencv/haarcascades/haarcascade_frontalface_alt_tree.xml
|
72
|
+
/usr/local/share/opencv/haarcascades/haarcascade_frontalface_alt.xml
|
73
|
+
/usr/local/share/opencv/haarcascades/haarcascade_profileface.xml),
|
74
|
+
|
75
|
+
:parts => %w(/usr/local/share/opencv/haarcascades/haarcascade_mcs_nose.xml
|
76
|
+
/usr/local/share/opencv/haarcascades/haarcascade_mcs_lefteye.xml
|
77
|
+
/usr/local/share/opencv/haarcascades/haarcascade_mcs_righteye.xml)
|
78
|
+
}
|
54
79
|
}
|
80
|
+
|
55
81
|
|
56
|
-
|
82
|
+
### Face.com
|
83
|
+
|
84
|
+
Paperclip::FaceCrop.detectors = {
|
85
|
+
'FaceCom' => { :api_key => "<YOUR API KEY>", :api_secret => "<YOUR API SECRET>"}
|
86
|
+
}
|
87
|
+
|
57
88
|
|
58
89
|
Usage:
|
59
90
|
------
|
@@ -71,15 +102,19 @@ In case no faces were found, it will behave simply as the `Paperclip::Thumbnail`
|
|
71
102
|
|
72
103
|
You can also set the debug mode to draw on the image the detected regions:
|
73
104
|
|
74
|
-
Paperclip::FaceCrop.debug =
|
105
|
+
Paperclip::FaceCrop.debug = Rails.env.development?
|
75
106
|
|
76
107
|

|
77
108
|
|
109
|
+
Each detector will draw the found regions in different colors(Face.com detector in red and OpenCV in green)
|
110
|
+
|
78
111
|
Credits:
|
79
112
|
--------
|
80
113
|
Copyright (c) 2011 Borja Martín Sánchez de Vivar <borjamREMOVETHIS@dagi3d.net> - <http://dagi3d.net>, released under the MIT license
|
81
114
|
The photo used as example belongs to [Jesper Rønn-Jensen](http://www.flickr.com/photos/jesper/)
|
82
115
|
|
116
|
+
[face_com]: http://face.com
|
117
|
+
[rest-client]: https://rubygems.org/gems/rest-client
|
83
118
|
[paperclip]: https://github.com/thoughtbot/paperclip
|
84
119
|
[opencv]: http://opencv.willowgarage.com/
|
85
120
|
[ruby-opencv]: https://github.com/ser1zw/ruby-opencv
|
data/README_example_b.jpg
CHANGED
Binary file
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
|
3
|
+
class FaceCrop::Detector::FaceCom < FaceCrop::Detector::Base
|
4
|
+
URL = "http://api.face.com/faces/detect.json"
|
5
|
+
|
6
|
+
def detect_faces(file)
|
7
|
+
query = @options.to_query
|
8
|
+
url = "#{URL}?#{query}"
|
9
|
+
|
10
|
+
response = RestClient.post url, :file => File.new(file)
|
11
|
+
response = JSON.parse(response)
|
12
|
+
|
13
|
+
photo = response['photos'].first
|
14
|
+
photo['tags'].map do |tag|
|
15
|
+
# values are returned as percentual values
|
16
|
+
x = (photo['width'] * (tag['center']['x'] / 100.0)).to_i
|
17
|
+
y = (photo['height'] * (tag['center']['y'] / 100.0)).to_i
|
18
|
+
w = (photo['width'] * (tag['width'] / 100)).to_i
|
19
|
+
h = (photo['height'] * (tag['height'] / 100)).to_i
|
20
|
+
|
21
|
+
region = FaceCrop::Detector::Region.new(x, y, w, h)
|
22
|
+
region.color = "green"
|
23
|
+
region
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'opencv'
|
2
|
+
require File.expand_path('../opencv_ext', __FILE__)
|
3
|
+
|
4
|
+
class FaceCrop::Detector::OpenCV < FaceCrop::Detector::Base
|
5
|
+
|
6
|
+
def detect_faces(file)
|
7
|
+
|
8
|
+
image = OpenCV::IplImage.load(file, 1)
|
9
|
+
|
10
|
+
faces_regions = detect_regions(image, @options[:face])
|
11
|
+
|
12
|
+
#Paperclip::FaceCrop.classifiers[:nose]
|
13
|
+
unless @options[:parts].nil?
|
14
|
+
faces_parts_regions = detect_regions(image, @options[:parts])
|
15
|
+
|
16
|
+
faces_regions.reject! do |face_region|
|
17
|
+
region = faces_parts_regions.detect do |part_region|
|
18
|
+
# part of a face can't be bigger than the face itself
|
19
|
+
face_region.collide?(part_region) && face_region > part_region
|
20
|
+
end
|
21
|
+
|
22
|
+
region.nil?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
faces_regions
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
def detect_regions(image, classifiers, color = OpenCV::CvColor::Blue)
|
32
|
+
regions = []
|
33
|
+
|
34
|
+
classifiers.each do |classifier|
|
35
|
+
detector = OpenCV::CvHaarClassifierCascade::load(classifier)
|
36
|
+
detector.detect_objects(image) do |region|
|
37
|
+
region.color = "red"
|
38
|
+
regions << region
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
regions
|
43
|
+
end
|
44
|
+
end
|
data/lib/face_crop.rb
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
module FaceCrop
|
2
|
+
|
3
|
+
# Detector
|
4
|
+
#
|
5
|
+
module Detector
|
6
|
+
|
7
|
+
autoload :FaceCom, File.expand_path('../detectors/face_com', __FILE__)
|
8
|
+
autoload :OpenCV, File.expand_path('../detectors/opencv', __FILE__)
|
9
|
+
|
10
|
+
# Base
|
11
|
+
#
|
12
|
+
class Base
|
13
|
+
|
14
|
+
# initialize
|
15
|
+
#
|
16
|
+
def initialize(options)
|
17
|
+
@options = options
|
18
|
+
end
|
19
|
+
|
20
|
+
# detect
|
21
|
+
#
|
22
|
+
def detect(file)
|
23
|
+
key = "#{self.class}#{file}"
|
24
|
+
regions = FaceCrop::Detector::Cache[key]
|
25
|
+
return regions unless regions.nil?
|
26
|
+
|
27
|
+
regions = detect_faces(file)
|
28
|
+
FaceCrop::Detector::Cache[key] = regions
|
29
|
+
regions
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
class Cache
|
35
|
+
@@cache = {}
|
36
|
+
|
37
|
+
def self.[]=(key, faces)
|
38
|
+
@@cache[key] = faces
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.[](key)
|
42
|
+
@@cache[key]
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.clear
|
46
|
+
@@cache = {}
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Region
|
51
|
+
#
|
52
|
+
class Region
|
53
|
+
attr_accessor :center
|
54
|
+
attr_accessor :width
|
55
|
+
attr_accessor :height
|
56
|
+
attr_accessor :color
|
57
|
+
|
58
|
+
def initialize(x, y, width, height)
|
59
|
+
@width, @height = width, height
|
60
|
+
@center = Point.new(x, y)
|
61
|
+
end
|
62
|
+
|
63
|
+
def top_left
|
64
|
+
@top_left ||= Point.new(@center.x - (width / 2), @center.y - (height / 2))
|
65
|
+
end
|
66
|
+
|
67
|
+
def bottom_right
|
68
|
+
@bottom_right ||= Point.new(@center.x + (width / 2), @center.y + (height / 2))
|
69
|
+
end
|
70
|
+
|
71
|
+
def to_s
|
72
|
+
"#{center.x},#{center.y}-#{width}x#{height}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Point
|
77
|
+
#
|
78
|
+
class Point
|
79
|
+
attr_accessor :x
|
80
|
+
attr_accessor :y
|
81
|
+
def initialize(x,y)
|
82
|
+
@x, @y = x, y
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/paperclip-facecrop.rb
CHANGED
@@ -1,149 +1,2 @@
|
|
1
|
-
require File.
|
2
|
-
|
3
|
-
module Paperclip
|
4
|
-
class FaceCrop < Paperclip::Thumbnail
|
5
|
-
|
6
|
-
@@debug = false
|
7
|
-
|
8
|
-
cattr_accessor :classifiers
|
9
|
-
cattr_accessor :debug
|
10
|
-
|
11
|
-
def initialize(file, options = {}, attachment = nil)
|
12
|
-
super(file, options, attachment)
|
13
|
-
|
14
|
-
faces_regions = []
|
15
|
-
faces_parts_regions = []
|
16
|
-
|
17
|
-
raise "No classifiers were defined" if self.classifiers.nil?
|
18
|
-
|
19
|
-
image = OpenCV::IplImage.load(file.path, 1)
|
20
|
-
|
21
|
-
faces_regions = detect_regions(image, self.classifiers[:face])
|
22
|
-
|
23
|
-
#Paperclip::FaceCrop.classifiers[:nose]
|
24
|
-
unless self.classifiers[:parts].nil?
|
25
|
-
faces_parts_regions = detect_regions(image, self.classifiers[:parts], OpenCV::CvColor::Red)
|
26
|
-
|
27
|
-
faces_regions.reject! do |face_region|
|
28
|
-
region = faces_parts_regions.detect do |part_region|
|
29
|
-
# part of a face can't be bigger than the face itself
|
30
|
-
face_region.collide?(part_region) && face_region > part_region
|
31
|
-
end
|
32
|
-
|
33
|
-
region.nil?
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
37
|
-
x_coords = []
|
38
|
-
y_coords = []
|
39
|
-
widths = []
|
40
|
-
heights = []
|
41
|
-
|
42
|
-
faces_regions.each do |region|
|
43
|
-
x_coords << region.top_left.x << region.bottom_right.x
|
44
|
-
y_coords << region.top_left.y << region.bottom_right.y
|
45
|
-
widths << region.width
|
46
|
-
heights << region.height
|
47
|
-
end
|
48
|
-
|
49
|
-
@has_faces = faces_regions.size > 0
|
50
|
-
|
51
|
-
if @has_faces
|
52
|
-
@top_left_x = x_coords.min
|
53
|
-
@top_left_y = y_coords.min
|
54
|
-
@bottom_right_x = x_coords.max
|
55
|
-
@bottom_right_y = y_coords.max
|
56
|
-
|
57
|
-
# average faces areas
|
58
|
-
average_face_width = widths.sum / widths.size
|
59
|
-
average_face_height = heights.sum / heights.size
|
60
|
-
|
61
|
-
# calculating the surrounding margin of the area that covers all the found faces
|
62
|
-
#
|
63
|
-
|
64
|
-
# new width
|
65
|
-
@top_left_x -= average_face_width / 2
|
66
|
-
@bottom_right_x += average_face_width / 2
|
67
|
-
|
68
|
-
# new height
|
69
|
-
@top_left_y -= average_face_height / 2
|
70
|
-
@bottom_right_y += average_face_height / 1.6
|
71
|
-
|
72
|
-
calculate_bounds
|
73
|
-
|
74
|
-
# if the new area is smaller than the target geometry, it's scaled so the final image isn't resampled
|
75
|
-
#
|
76
|
-
if @faces_width < @target_geometry.width
|
77
|
-
delta_width = (@target_geometry.width - @faces_width) / 2
|
78
|
-
@top_left_x -= delta_width
|
79
|
-
@bottom_right_x += delta_width
|
80
|
-
calculate_bounds
|
81
|
-
end
|
82
|
-
|
83
|
-
#raise (@target_geometry.height > 0 and @faces_height < @target_geometry.height).to_s
|
84
|
-
|
85
|
-
if (@target_geometry.height > 0 and @faces_height < @target_geometry.height)
|
86
|
-
delta_height = (@target_geometry.height - @faces_height) / 2
|
87
|
-
@top_left_y -= delta_height
|
88
|
-
@bottom_right_y += delta_height
|
89
|
-
calculate_bounds
|
90
|
-
end
|
91
|
-
|
92
|
-
@faces_height = @faces_width if @target_geometry.height == 0
|
93
|
-
|
94
|
-
@current_geometry = Paperclip::Geometry.new(@faces_width, @faces_height)
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
98
|
-
|
99
|
-
|
100
|
-
def transformation_command
|
101
|
-
return super unless @has_faces
|
102
|
-
|
103
|
-
scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
|
104
|
-
faces_crop = "%dx%d+%d+%d" % [@faces_width, @faces_height, @top_left_x, @top_left_y]
|
105
|
-
|
106
|
-
trans = []
|
107
|
-
trans << "-crop" << %["#{faces_crop}"] << "+repage"
|
108
|
-
trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
|
109
|
-
trans << "-crop" << %["#{crop}"] << "+repage" if crop
|
110
|
-
trans
|
111
|
-
end
|
112
|
-
|
113
|
-
private
|
114
|
-
|
115
|
-
# calculate_bounds
|
116
|
-
#
|
117
|
-
def calculate_bounds
|
118
|
-
@top_left_x = 0 if @top_left_x < 0
|
119
|
-
@bottom_right_x = @current_geometry.width if @bottom_right_x > @current_geometry.width
|
120
|
-
|
121
|
-
@top_left_y = 0 if @top_left_y < 0
|
122
|
-
@bottom_right_y = @current_geometry.height if @bottom_right_y > @current_geometry.height
|
123
|
-
|
124
|
-
@faces_width = @bottom_right_x - @top_left_x
|
125
|
-
@faces_height = @bottom_right_y - @top_left_y
|
126
|
-
end
|
127
|
-
|
128
|
-
# detect_regions
|
129
|
-
#
|
130
|
-
def detect_regions(image, classifiers, color = OpenCV::CvColor::Blue)
|
131
|
-
regions = []
|
132
|
-
|
133
|
-
classifiers.each do |classifier|
|
134
|
-
detector = OpenCV::CvHaarClassifierCascade::load(classifier)
|
135
|
-
detector.detect_objects(image) do |region|
|
136
|
-
regions << region
|
137
|
-
image.rectangle!(region.top_left, region.bottom_right, :color => color) if self.debug
|
138
|
-
end
|
139
|
-
end
|
140
|
-
|
141
|
-
if self.debug
|
142
|
-
image.save_image(@file.path)
|
143
|
-
Rails.logger.info(regions)
|
144
|
-
end
|
145
|
-
|
146
|
-
regions
|
147
|
-
end
|
148
|
-
end
|
149
|
-
end
|
1
|
+
require File.expand_path('../processor', __FILE__)
|
2
|
+
require File.expand_path('../railtie', __FILE__)
|
data/lib/processor.rb
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
require File.expand_path('../face_crop', __FILE__)
|
2
|
+
|
3
|
+
module Paperclip
|
4
|
+
class FaceCrop < Paperclip::Thumbnail
|
5
|
+
|
6
|
+
@@debug = false
|
7
|
+
|
8
|
+
#cattr_accessor :classifiers
|
9
|
+
cattr_accessor :debug
|
10
|
+
|
11
|
+
def self.detectors=(detectors)
|
12
|
+
@@detectors = detectors.map do |name, options|
|
13
|
+
#require File.expand_path("../detectors/#{name}", __FILE__)
|
14
|
+
detector_class = "FaceCrop::Detector::#{name}".constantize
|
15
|
+
detector = detector_class.new(options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def initialize(file, options = {}, attachment = nil)
|
20
|
+
super(file, options, attachment)
|
21
|
+
|
22
|
+
raise "No detectors were defined" if @@detectors.nil?
|
23
|
+
|
24
|
+
faces_regions = []
|
25
|
+
faces_parts_regions = []
|
26
|
+
|
27
|
+
@@detectors.each do |detector|
|
28
|
+
begin
|
29
|
+
faces_regions += detector.detect(file.path)
|
30
|
+
rescue Exception => e
|
31
|
+
Rails.logger.error(e)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
x_coords, y_coords, widths, heights = [], [], [], []
|
36
|
+
|
37
|
+
faces_regions.each do |region|
|
38
|
+
x_coords << region.top_left.x << region.bottom_right.x
|
39
|
+
y_coords << region.top_left.y << region.bottom_right.y
|
40
|
+
widths << region.width
|
41
|
+
heights << region.height
|
42
|
+
end
|
43
|
+
|
44
|
+
@has_faces = faces_regions.size > 0
|
45
|
+
|
46
|
+
if @has_faces
|
47
|
+
@top_left_x = x_coords.min
|
48
|
+
@top_left_y = y_coords.min
|
49
|
+
@bottom_right_x = x_coords.max
|
50
|
+
@bottom_right_y = y_coords.max
|
51
|
+
|
52
|
+
|
53
|
+
#puts @top_left_x.to_s
|
54
|
+
|
55
|
+
# average faces areas
|
56
|
+
average_face_width = widths.sum / widths.size
|
57
|
+
average_face_height = heights.sum / heights.size
|
58
|
+
|
59
|
+
# calculating the surrounding margin of the area that covers all the found faces
|
60
|
+
#
|
61
|
+
|
62
|
+
# new width
|
63
|
+
@top_left_x -= average_face_width / 1.2
|
64
|
+
@bottom_right_x += average_face_width / 1.2
|
65
|
+
|
66
|
+
# new height
|
67
|
+
#puts ":::#{@top_left_x}---#{average_face_width}"
|
68
|
+
#return
|
69
|
+
|
70
|
+
@top_left_y -= average_face_height / 1.2
|
71
|
+
@bottom_right_y += average_face_height / 1.6
|
72
|
+
|
73
|
+
calculate_bounds
|
74
|
+
|
75
|
+
# if the new area is smaller than the target geometry, it's scaled so the final image isn't resampled
|
76
|
+
#
|
77
|
+
if @faces_width < @target_geometry.width
|
78
|
+
delta_width = (@target_geometry.width - @faces_width) / 2
|
79
|
+
@top_left_x -= delta_width
|
80
|
+
@bottom_right_x += delta_width
|
81
|
+
calculate_bounds
|
82
|
+
end
|
83
|
+
|
84
|
+
#raise (@target_geometry.height > 0 and @faces_height < @target_geometry.height).to_s
|
85
|
+
|
86
|
+
if (@target_geometry.height > 0 and @faces_height < @target_geometry.height)
|
87
|
+
delta_height = (@target_geometry.height - @faces_height) / 2
|
88
|
+
@top_left_y -= delta_height
|
89
|
+
@bottom_right_y += delta_height
|
90
|
+
calculate_bounds
|
91
|
+
end
|
92
|
+
|
93
|
+
@faces_height = @faces_width if @target_geometry.height == 0
|
94
|
+
|
95
|
+
@current_geometry = Paperclip::Geometry.new(@faces_width, @faces_height)
|
96
|
+
|
97
|
+
if @@debug
|
98
|
+
parameters = []
|
99
|
+
parameters << "-stroke" << "green"
|
100
|
+
parameters << "-fill" << "none"
|
101
|
+
parameters << faces_regions.map {|r| "-stroke #{r.color} -draw 'rectangle #{r.top_left.x},#{r.top_left.y} #{r.bottom_right.x},#{r.bottom_right.y}'"}
|
102
|
+
parameters << ":source"
|
103
|
+
parameters << ":dest"
|
104
|
+
parameters = parameters.flatten.compact.join(" ").strip.squeeze(" ")
|
105
|
+
|
106
|
+
Paperclip.run("convert", parameters, :source => "#{File.expand_path(file.path)}", :dest => "#{File.expand_path(file.path)}")
|
107
|
+
end
|
108
|
+
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
|
114
|
+
def transformation_command
|
115
|
+
return super unless @has_faces
|
116
|
+
|
117
|
+
scale, crop = @current_geometry.transformation_to(@target_geometry, crop?)
|
118
|
+
faces_crop = "%dx%d+%d+%d" % [@faces_width, @faces_height, @top_left_x, @top_left_y]
|
119
|
+
|
120
|
+
trans = []
|
121
|
+
trans << "-crop" << %["#{faces_crop}"] << "+repage"
|
122
|
+
trans << "-resize" << %["#{scale}"] unless scale.nil? || scale.empty?
|
123
|
+
trans << "-crop" << %["#{crop}"] << "+repage" if crop
|
124
|
+
trans
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
|
129
|
+
# calculate_bounds
|
130
|
+
#
|
131
|
+
def calculate_bounds
|
132
|
+
@top_left_x = 0 if @top_left_x < 0
|
133
|
+
@bottom_right_x = @current_geometry.width if @bottom_right_x > @current_geometry.width
|
134
|
+
|
135
|
+
@top_left_y = 0 if @top_left_y < 0
|
136
|
+
@bottom_right_y = @current_geometry.height if @bottom_right_y > @current_geometry.height
|
137
|
+
|
138
|
+
@faces_width = @bottom_right_x - @top_left_x
|
139
|
+
@faces_height = @bottom_right_y - @top_left_y
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
end
|
data/lib/railtie.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
module FaceCrop
|
2
|
+
class Railtie < Rails::Railtie
|
3
|
+
initializer "paperclip-facecrop.extend_has_attachment" do
|
4
|
+
raise "Paperclip needed" unless defined?(Paperclip)
|
5
|
+
ActiveSupport.on_load :active_record do
|
6
|
+
|
7
|
+
class ActiveRecord::Base
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def has_attached_file_with_face_crop_cache(name, args)
|
11
|
+
has_attached_file_without_face_crop_cache(name, args)
|
12
|
+
send("after_#{name}_post_process", lambda { FaceCrop::Detector::Cache.clear })
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method_chain :has_attached_file, :face_crop_cache
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/paperclip-facecrop.gemspec
CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "paperclip-facecrop"
|
6
|
-
s.version = "0.0
|
6
|
+
s.version = "0.1.0"
|
7
7
|
s.authors = ["Borja Martín"]
|
8
8
|
s.description = %q{Paperclip processor that is aware of the faces detected on the image so that they don't get cropped or aren't shown too small while generating the thumbnails}
|
9
9
|
s.summary = %q{Paperclip processor that is aware of the faces found on the image}
|
@@ -13,5 +13,4 @@ Gem::Specification.new do |s|
|
|
13
13
|
s.files = `git ls-files`.split("\n")
|
14
14
|
s.has_rdoc = false
|
15
15
|
s.add_runtime_dependency("paperclip")
|
16
|
-
s.add_runtime_dependency("opencv", "= 0.0.6")
|
17
16
|
end
|
metadata
CHANGED
@@ -1,99 +1,70 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: paperclip-facecrop
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
|
6
|
-
- 0
|
7
|
-
- 0
|
8
|
-
- 6
|
9
|
-
version: 0.0.6
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
10
6
|
platform: ruby
|
11
|
-
authors:
|
12
|
-
-
|
7
|
+
authors:
|
8
|
+
- Borja Martín
|
13
9
|
autorequire:
|
14
10
|
bindir: bin
|
15
11
|
cert_chain: []
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
dependencies:
|
20
|
-
- !ruby/object:Gem::Dependency
|
12
|
+
date: 2011-08-14 00:00:00.000000000Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
21
15
|
name: paperclip
|
22
|
-
|
23
|
-
requirement: &id001 !ruby/object:Gem::Requirement
|
16
|
+
requirement: &2153665720 !ruby/object:Gem::Requirement
|
24
17
|
none: false
|
25
|
-
requirements:
|
26
|
-
- -
|
27
|
-
- !ruby/object:Gem::Version
|
28
|
-
|
29
|
-
- 0
|
30
|
-
version: "0"
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
31
22
|
type: :runtime
|
32
|
-
version_requirements: *id001
|
33
|
-
- !ruby/object:Gem::Dependency
|
34
|
-
name: opencv
|
35
23
|
prerelease: false
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
- - "="
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
segments:
|
42
|
-
- 0
|
43
|
-
- 0
|
44
|
-
- 6
|
45
|
-
version: 0.0.6
|
46
|
-
type: :runtime
|
47
|
-
version_requirements: *id002
|
48
|
-
description: Paperclip processor that is aware of the faces detected on the image so that they don't get cropped or aren't shown too small while generating the thumbnails
|
24
|
+
version_requirements: *2153665720
|
25
|
+
description: Paperclip processor that is aware of the faces detected on the image
|
26
|
+
so that they don't get cropped or aren't shown too small while generating the thumbnails
|
49
27
|
email: borjam@dagi3d.net
|
50
28
|
executables: []
|
51
|
-
|
52
29
|
extensions: []
|
53
|
-
|
54
30
|
extra_rdoc_files: []
|
55
|
-
|
56
|
-
files:
|
31
|
+
files:
|
57
32
|
- .gitignore
|
58
33
|
- Gemfile
|
59
34
|
- README.md
|
60
35
|
- README_example.jpg
|
61
36
|
- README_example_b.jpg
|
62
37
|
- Rakefile
|
63
|
-
- lib/
|
38
|
+
- lib/detectors/face_com.rb
|
39
|
+
- lib/detectors/opencv.rb
|
40
|
+
- lib/detectors/opencv_ext.rb
|
41
|
+
- lib/face_crop.rb
|
64
42
|
- lib/paperclip-facecrop.rb
|
43
|
+
- lib/processor.rb
|
44
|
+
- lib/railtie.rb
|
65
45
|
- paperclip-facecrop.gemspec
|
66
|
-
has_rdoc: true
|
67
46
|
homepage: http://github.com/dagi3d/paperclip-facecrop
|
68
47
|
licenses: []
|
69
|
-
|
70
48
|
post_install_message:
|
71
49
|
rdoc_options: []
|
72
|
-
|
73
|
-
require_paths:
|
50
|
+
require_paths:
|
74
51
|
- lib
|
75
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
76
53
|
none: false
|
77
|
-
requirements:
|
78
|
-
- -
|
79
|
-
- !ruby/object:Gem::Version
|
80
|
-
|
81
|
-
|
82
|
-
version: "0"
|
83
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ! '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
59
|
none: false
|
85
|
-
requirements:
|
86
|
-
- -
|
87
|
-
- !ruby/object:Gem::Version
|
88
|
-
|
89
|
-
- 0
|
90
|
-
version: "0"
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
91
64
|
requirements: []
|
92
|
-
|
93
65
|
rubyforge_project:
|
94
|
-
rubygems_version: 1.
|
66
|
+
rubygems_version: 1.8.6
|
95
67
|
signing_key:
|
96
68
|
specification_version: 3
|
97
69
|
summary: Paperclip processor that is aware of the faces found on the image
|
98
70
|
test_files: []
|
99
|
-
|