Rubyで画像を行列にしたり、行列を画像にしたりする

ここではRuby-Gnomeの一部である、GdkPixbufを使います。

https://github.com/ruby-gnome2/ruby-gnome2

gem install gtk3

Gtkは↓みたいなGUIを作るやつだけど、今回はそれに関しては最後の方にちょこっとだけ出てきます。

image.png

画像素材には、かわいいQiitanを使います1

qiitan-icon.png


画像→ピクセル

require 'gdk_pixbuf2'

image = GdkPixbuf::Pixbuf.new(file: "qiitan-icon.png")
width = image.width
height = image.height
data = image.pixels

image.png

はい、Qiitanが、数字の切れ端になってしまいましたね。


画像 → ピクセル(行列)

このまま、Rubyの配列のまま頑張ってもいいのですが、それだと辛いので、ここでは専用の行列計算ライブラリを使いしょう。Numo::NArrayです。

https://github.com/ruby-numo/numo-narray

わざわざRubyの配列を経由しなくても、バイナリ文字列を経由すれば大丈夫ですね。

require 'gdk_pixbuf2'

require 'numo/narray'

image = GdkPixbuf::Pixbuf.new(file: "qiitan-icon.png")
width = image.width
height = image.height

data = image.pixel_bytes.to_str

narray = Numo::UInt8.from_binary data
narray.reshape!(height, width, 4)

さいごのreshape の4に注目してください。Qiitanは透過性の情報が入っているので、チャンネルが4つあります。


ピクセル(行列) → 画像

逆に行列からQiitan画像を復元してみましょう。

require 'gdk_pixbuf2'

data2 = narray.to_string

image2 = GdkPixbuf::Pixbuf.new(data: data2, width: width, height: height, has_alpha: true)
image2.save("qiitan-reloaded.png")

無事、Qiitanが復活しました!

qiitan-reloaded.png

このQiitanの画像は4チャンネルなので、has_alpha に気をつけてください。


フィルターでもかけてみるか

さて、これでこの記事は終わりでもいいのですが、せっかくなので適当にフィルタをかけてみましょう。

require 'gdk_pixbuf2'

require 'numo/narray'

# フィルターをかける関数
def filter(na, filter)
height, width, _c = na.shape
f_height, f_width = filter.shape
na2 = Numo::UInt8.zeros(height + f_height - 1, width + f_width - 1)
x = (f_height - 1) / 2
y = (f_width - 1) / 2
na2[x...-x, y...-y] = na
na3 = na.clone.fill 0

f_height.times do |i|
f_width.times do |j|
temp = (na2[i..(i - f_height), j..(j - f_width)] * filter[i, j])
na3 += temp
end
end

Numo::UInt8.cast(na3.ceil)
end

image = GdkPixbuf::Pixbuf.new(file: "qiitan-icon.png")
width = image.width
height = image.height

data = image.pixel_bytes.to_str

narray = Numo::UInt8.from_binary data
narray.reshape!(height, width, 4)

# 横ブレを引き起こすフィルタ
myfilter = Numo::SFloat.ones(3,51)
myfilter /= myfilter.sum

# アルファチャンネル以外にフィルターをかける
3.times do |i|
narray[true, true, i] = filter(narray[true, true, i], myfilter)
end

data2 = narray.to_string

image2 = GdkPixbuf::Pixbuf.new(data: data2, width: width, height: height, has_alpha: true)
image2.save("qiitan-vibrated.png")

qiitan-vibrated.png

フィルタを変えてみましょう

myfilter = Numo::SFloat[[0,-1,0],[-1,0,1],[0,1,0]]

qiitan-reloaded.png

なかなかいい感じですね。

ほかにもチャンネルの順番を入れ替えるだけで、簡単にQiitanの色違いができたりします。

qiitan-[0, 1, 2, 3].pngqiitan-[0, 2, 1, 3].pngqiitan-[1, 0, 2, 3].pngqiitan-[2, 1, 0, 3].png


ウィンドウに表示してみよう

さらにおまけで、これらの画像をWindowに表示することを考えます。GdkPixbufを使っているのでGTKを使えば簡単ですね。

image.png

ソースコードは適当ですが、こんな感じでしょうか

require 'gtk3'

win = Gtk::Window.new
win.signal_connect(:destroy) { Gtk.main_quit }
hbox = Gtk::Box.new(:horizontal)
win.add hbox

Dir.glob('*.png').each do |file|
pixbuf = GdkPixbuf::Pixbuf.new file: file
pixbuf = pixbuf.scale(100, 100)
image = Gtk::Image.new(pixbuf: pixbuf)
hbox.add image
end

win.show_all

Gtk.main

この記事は以上です。





  1. 著作権ありそうだけど、たぶんこのサイト内で使うぶんには怒られないであろう…