A Use for Smartphone Photos

As a smartphone user, I take a lot of photos. Since I bought an iPhone 4 nearly two years ago, I’ve taken just over 6,000 photos with it. 47GB of memories. On average, 10 photos per day, every day, often of nothing in particular.

These photos aren’t good enough, or meaningful enough to anyone else, to post on Flickr. 500px would scoff at them. The few people on Facebook that would recognize the people, places and events in the photos wouldn’t see the point. They’re tiny fragments of my life, and that’s about it.

My homepage, when this was written.

Instead of forcing these thousands of photos to stay hidden in my iPhoto
library, I found an outlet for them - my homepage. Crudely modelled after the stellar TED.com landing page, it’s supplied by a random set of hundreds of images, all of which I’ve taken, and until now, hand-cropped and hand-selected.

Michael Macias, in a submission to a Codebrawl last November, came up with a brilliantly simple method of content-aware image cropping. By measuring the greyscale entropy of a window as it slides over an image, the highest-interest thumbnail can be determined automatically. I took this solution, modified it (faster, uses ImageMagick, etc.), and hacked together a quick Ruby script that now lives on Github:

# Hacky random image thumbnailer.
# by Peter Sobot, April 21, 2012
# Based heavily on code by Michael Macias
#   (https://gist.github.com/a54cd41137b678935c91)

require 'rmagick'

images = Dir.glob(ARGV[0] ? ARGV[0]
                    : '-default-input-paths-')
output_dir = (ARGV[1] ? ARGV[1]
                : '-default-output-directory-')
num = Dir.glob(output_dir + '*.jpg').count
lim = 50

def entropy(image)
  hist = image.quantize(256, Magick::GRAYColorspace).color_histogram
  area = (image.rows * image.columns).to_f

  -hist.values.reduce(0.0) do |e, freq|
    p = freq / area
    e + p * Math.log2(p)
  end
end

def smart_crop(image, crop_width, crop_height)
  x, y, width, height = 0, 0, image.columns, image.rows
  slice_length = 16

  while (width - x) > crop_width
    slice_width = [width - x - crop_width, slice_length].min

    left = image.crop(x, 0, slice_width, image.rows)
    right = image.crop(width - slice_width, 0, slice_width, image.rows)

    if entropy(left) < entropy(right)
      x += slice_width
    else
      width -= slice_width
    end
  end

  while (height - y) > crop_height
    slice_height = [height - y - crop_height, slice_length].min

    top = image.crop(0, y, image.columns, slice_height)
    bottom = image.crop(0, height - slice_height, image.columns, slice_height)

    if entropy(top) < entropy(bottom)
      y += slice_height
    else
      height -= slice_height
    end
  end

  im = image.crop(x, y, crop_width, crop_height)
  if im.columns < crop_width or im.rows < crop_height
    raise "Cropped image too small!"
  end
  im
end

puts "Matched #{images.length} images."

images.shuffle.slice(0, lim).each do |file|
  output = output_dir + (num + 1).to_s + '.jpg'
  begin
    im = Magick::Image.read(file).first.auto_orient.resize(0.2)
    r = smart_crop(im, 248, 248)
    r.resize!(124, 124, Magick::QuadraticFilter)
    r.strip!
    r.write(output) { self.quality = 85 }

    num = num + 1
    puts "#{file} => #{output}"
  rescue StandardError => e
    puts "ERROR: #{file} didn't work: #{e}"
  end
end

This script automatically chooses 50 random images from a given path (or shell glob) and crops them to their most “interesting” thumbnails. The thumbnails are scaled to size, and saved in incrementing order in the destination folder. It’s highly optimized for my personal workflow, but it does seem to work quite well. For example, take the following shot of Zameer Manji:

That's Zameer. Yup.

The original photo was poorly exposed, had no clear subject, and was, well, weird. After automatically cropping it down to a tiny thumbnail, it fits in nicely on my homepage as an artsy shot of a bike rack in the daylight.

Only one thing left to do: take more photos.

 
21
Kudos
 
21
Kudos

Now read this

Using Eight Cores (incorrectly) with Python

One of my web apps, The Wub Machine, is very computationally expensive. Audio decoding, processing, encoding, and streaming, all in Python. Naturally, my first instinct was to turn to the multiprocessing module to spread the CPU-bound... Continue →