CIFARで提供される画像データをRubyで画像ファイルにする

完成品のパフォーマンスがよくなかったので修正しました

require 'RMagick'
require 'fileutils'

labels = %w(airplane automobile bird cat deer dog frog horse ship truck)
FileUtils.rm_rf("output")
labels.each { |l| FileUtils.mkdir_p("output/#{l}") }

open("./data_batch_1.bin", "rb") do |f|
  while b = f.read(3073) do
    datasets = b.unpack("C*") 
    # labelと赤緑青のチャネルにわける
    label_idx = datasets.shift
    rgb_list = datasets.each_slice(1024).to_a.transpose

    # レンダリング
    img = Magick::Image.new(32, 32)
    d = Magick::Draw.new
    32.times do |y|
      32.times do |x|
        rgb = rgb_list.shift
        d.fill("rgb(#{rgb.join(',')})")
        d.point(x, y)
      end
    end
    d.draw(img)

    # ファイル出力
    name = labels[label_idx]
    filepath = "output/#{name}/#{Dir.glob("output/#{name}/*").count + 1}.jpg"
    puts "output: #{filepath}...."
    img.write(filepath)
  end
end

CIFARという画像セットがある。
10クラスのCIFAR-10、100クラスのCIFAR-100があり次のサイトで提供されてます。

CIFAR-10 and CIFAR-100 datasets

画像サイズは32x32で提供されておりRGBの3チャンネルカラー画像として提供されます。
データセットPython, Matlab向けとbinaryの三種類が提供されています。
今回はbinaryをRubyで扱います。

Binaryでのデータセットレイアウト

圧縮されたファイルを解凍するとdata_batch_1.bin, data_batch_2.bin, data_batch_3.bin, data_batch_4.bin, data_batch_5.binというトレーニングデータとtest_batch.binというテストデータが入っています。
これらは次のようなデータフォーマットをしています。

1byteのラベルと3072bytesのピクセル値をひとつのイメージの組として1つのファイルに10000組入っています。
1byte目のラベルは0-9の値をとってその値はbatches.meta.txtに記載されている順番のindexと対応しています。
3072bytesは1024bytesづつ分けて赤、緑、黄の順番で並んでいます。
また隣り合うイメージの境目はないのできっちり3073bytesを取得する必要があります。
まとめると次のようなイメージになります。

f:id:hatappi1225:20180227221353p:plain

ここで1つ問題があります。
ピクセルデータだと目で見た時に何の画像が認識できない!!
そこで今回はこの画像セットをファイルに出力していこうと思います。

完成品

実行が終わると output 配下にそれぞれのラベルのディレクトリが出来て、その中に該当するラベルの画像ファイルが生成されていく。

require 'RMagick'
require 'fileutils'

labels = %w(airplane automobile bird cat deer dog frog horse ship truck)
FileUtils.rm_rf("output")
labels.each { |l| FileUtils.mkdir_p("output/#{l}") }

binary = File.binread("./data_batch_1.bin")
datasets = binary.unpack("C*")

while true do
  label_idx = datasets.shift
  break if label_idx.nil?
  rgb_list = datasets.slice!(0, 3072).each_slice(1024).to_a.transpose

  img = Magick::Image.new(32, 32)
  d = Magick::Draw.new
  32.times do |y|
    32.times do |x|
      rgb = rgb_list.shift
      d.fill("rgb(#{rgb.join(',')})")
      d.point(x, y)
    end
  end

  d.draw(img)
  name = labels[label_idx]
  filepath = "output/#{name}/#{Dir.glob("output/#{name}/*").count + 1}.jpg"
  puts "output: #{filepath}...."
  img.write(filepath)
end

ポイントとなるところをいくつか紹介します。

binary = File.binread("./data_batch_1.bin")
datasets = binary.unpack("C*")

この部分でバイナリのファイルを読み込んでそれを8bit 符号なし整数でアンパックしたものをarrayとして返します。

label_idx = datasets.shift
break if label_idx.nil?
rgb_list = datasets.slice!(0, 3072).each_slice(1024).to_a.transpose

datasets.shiftでラベルのindexを取り出します。
次に赤緑青のそれぞのチャネルの取り出しを行なっているのですが、slice!で赤緑青をあわせて3072をまず切り出して、each_slice(1024).to_aで1024づつにわけて最後のtransposeを使って赤青黄の組みにしています。
図にすると次のようになります。

f:id:hatappi1225:20180227224525p:plain

画像出力にはimagemagickを使いgemとしてrmagickを使います。

github.com

ベースとなる画像があって切り取りとかで使われたりするけど0から画像を生成することも出来ます。

RMagick 2.12.0: How to use RMagick

このgemを使って左上から順番にRGBで色をつけていって画像を生成してファイルに出力します。

最後に

32x32なのでかなり小さいですが、無事画像ファイルを出力することが出来ました。