Content aware cropping for Ruby and Carrierwave
Crops images based on entropy: leaving the most interesting part intact.
Best results achieved in combination with scaling: the cropping is then only used to square the image, cutting off the least interesting part. The trimming simply chops off te edge that is least interesting, and continues doing so, untill it reached the requested size.
gem install smartcropper
Or add to you bundle file.
With carrierwave, you use it in a custom manipulate! block. For example, carrierwave in a Rails project:
File uploaders/attachement_uploader.rb:
def smart_crop_and_scale(width, height)
manipulate! do |img|
img = CropToelie.new(img)
img = img.smart_crop_and_scale(width, height)
img = yield(img) if block_given?
img
end
end
# Create different versions of your uploaded files:
# A square and tiny thumbnail
version :thumb do
process :smart_crop_and_scale => [80, 80]
end
# A large version, only cropped
version :preview do
processs :crop => [200,400]
end
# A square version
version :cover do
process :smart_square
end
With custom code, you simply create an instance of SmartCropper
image = Magick::ImageList("carice.jpg")
SmartCropper.new(image).smart_square.write("carice_square.jpg")
# Or, simpler
SmartCropper.from_file("carice.jpg").smart_square.write("carice_square.jpg")
Smartcropper grabs a slice from both outer sides of the image. It compares the entropy of the slices and then removes the slice with the lowest entropy. More differentiating pixels (colors, shadows, depth) indicates a more interesting part of the image; a higher entropy.
It then repeats that for the top-and bottom
As you can see, entropy of a slice is not a perfect measurement of the "interestingness". Some slices have a high entropy, not because they contain the focus of the image, but some other artifact, like a tree, a logo or a lot of reflecting water.
Attempts to scan the image, rather then shaving it, have been made. But the result is not much better, yet the performance is hundreds times worse.
For now, shaving suffices. But I want to re-investigate the scanning-method, combined with some simple statistics to determine hotspots in the image. Then the "hotness" and size of these hotspots can be used to determine the boundries of the "interesting part".
The biggest hurdle, then, is to reduce memory-usage and amount of iterations; since the value of each pixel in an image must be loaded into memory and looped over, multiple times, you can image the performance for large-ish images.