LoginSignup
7
3

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-04-28

実は非常に沢山の方法があります。

  • chunky_png および oily_png
  • mini_magick および rmagick
  • magro

などがあります。その他opencvバインディングを使ったり、Ruby/Tkを使ったり、pnmを経由したりする方法もあります。

ここではRuby-Gnomeの一部である、GdkPixbufを使います。
https://github.com/ruby-gnome2/ruby-gnome2

gem install gtk3

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

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

qiitan-icon.png

そうそう、私の落書きしたぶさかわのQiitanっぽい動物の画像もありますので欲しい人はどうぞ(一体何の記事なんだ
image.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

この記事は以上です。

qiitan.png


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

7
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
3