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)

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
      width -= slice_width

  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
      height -= slice_height

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

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

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

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

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.


Now read this

OhLife and Online Privacy

OhLife is a brilliant site that emails you nightly and lets you keep a journal through email. I’ve been using the site for many months now, and it’s been immensely useful in spurring me to keep a journal. Not that I’ve read over the... Continue →