FLYING

〈全日本・紀文豆乳飲料シリーズ「麦芽コーヒー」の500ミリリットルパックを扱う小売店が少ないことに遺憾の意を表明する会〉活動記録

画像の肌色率をもとにソートしてHTMLに出力するスクリプト

前からやりたいと思っていたアレ。こういう馬鹿なスクリプトは、できるなら昨日のうちに公開しておきたかった。仕組みは単純で、画像から適当な数のピクセルを抽出し、HSV色空間上で指定した色相空間と合致するかどうかを確かめるだけ。手元の環境で試してみたところ、認識率は微妙だった。ちなみに、スクリプトを実行するためには RMagick を使用できる環境が必要です。何かサンプルになる画像募集中。

うさげ

ruby image_sort.rb '*.{bmp,jpg,png,gif}'

Windowsネイティブ版のRubyで、カレントフォルダ内にあるすべての画像を対象に実行する場合はこんな感じ。シングルクオートでパス指定を括っているのは、ワイルドカードが展開されるのを防ぐため。他のシェルでどうやるかは知らない。スクリプトを実行すると output.html というファイルが生成されるので、ブラウザで眺めてニヨニヨしてください。これを応用するとおもしろいものが作れるかも。

ソース

#!/usr/bin/ruby
# image_sort.rb

require 'rubygems'
require 'RMagick'
include Magick

LineSamples = 64           # 一辺当たりのサンプル数
Samples = LineSamples ** 2 # 全体のサンプル数
path = ARGV.shift          # 引数から受け取ったパス

class CompareColor
  # 色相範囲を指定して初期化
  def initialize(min, max)
    @min, @max = min, max
  end
  
  # 色相範囲の合致率を返す
  def compare(img)
    width = img.columns
    height = img.rows
    if width * height > Samples then
      return compare_big(img, width, height)
    else
      return compare_small(img, width, height)
    end
  end
  
  # 画像が小さいとき
  def compare_small(img, width, height)
    ret = 0.0
    height.times {|y|
      width.times {|x|
        ret += match(img, x, y)
      }
    }
    ret / (width * height)
  end
  
  # 画像が大きいとき
  def compare_big(img, width, height)
    dx = width.to_f / LineSamples
    dy = height.to_f / LineSamples
    ret = 0.0
    LineSamples.times {|y|
      LineSamples.times {|x|
        ret += diff(img, x*dx, y*dy)
      }
    }
    ret / Samples
  end
  
  # ピクセルが色相範囲に合致するかどうか
  def match(img, x, y)
    color = img.pixel_color(x, y)
    color = rgb2hsv(color.red, color.green, color.blue)
    if color[0] >= @min  && color[0] <= @max then
      return 1.0
    else
      return 0.0
    end
  end
  
  # HSVに変換
  def rgb2hsv(red, green, blue)
    min = [red, green, blue].min
    max = [red, green, blue].max
    if max == min then
      sat = 0
      hue = 0
    else
      sat = 255.0 * (max - min) / max
      cr = 1.0 * (max - red) / (max - min)
      cg = 1.0 * (max - green) / (max - min)
      cb = 1.0 * (max - blue) / (max - min)
      case max
      when red
        hue = 60.0 * (cb - cg)
      when green
        hue = 60.0 * (2.0 + cr - cb)
      when blue
        hue = 60.0 * (4.0 + cg - cr)
      end
      hue += 360 if hue < 0
    end
    [hue, sat, max]
  end
end

begin
  puts("comparing...")
  cc = CompareColor.new(6, 38) # 値は適当に
  files = Dir.glob(path)
  files.map! {|name|
    puts name
    img = ImageList.new(name)
    {'name' => name, 'key' => cc.compare(img)}
  }
  puts("sorting...")
  files.sort! {|a, b|
    a['key'] <=> b['key']
  }
  files.reverse!
  puts("putting...")
  File.open("output.html", "w") {|out|
    out.puts("<html><body><p>")
    files.each {|fi|
      out.puts("<h1>#{fi['name']} => #{fi['key']}</h1>")
      out.puts("<img width=\"400\" height=\"300\" src=\"#{fi['name']}\" />")
    }
    out.puts("</p></body></html>")
  }
rescue
  puts $!
end