Ruby
ffmpeg
RMagick
RubyDay 3

rubyで画像処理できるRMagickの紹介をするよ ヽ(゚ー゚*ヽ)(ノ*゚ー゚)ノわぁい

More than 3 years have passed since last update.

みなさん、おはこんばんにちはでス。普段は動画処理とかffmpegまわりの記事を投稿してます。「ス」です。2014年もあとわずか :calendar: 年末年始とのんびり過ごしたいものです :sake: のんびり過ごすなら、やっぱり、画像処理・動画処理が定番ですよね?そこにあるCPUを何故100%まで使い切らないのか?山があるから登るのが登山家の心がけなら、CPUがあるなら使い切るのがプログラマーとしての正しい心がけです。使い切る中で最適化を目指す!では、使い切るにはどうしたらいいのか? :chicken:

画像処理だ!!!

mosaic.png

動画つくるだ!!!

https://www.youtube.com/watch?v=jWD22lrWbD0

最近は使えるCPUの数が増えてきましたね。少し背伸びをすれば、かつては想像できなかったほど使えそうです。Hadoopです :elephant: 分散処理です。プログラミング楽しいですね。夢は広がります。CPUは寝かせただけ計算機会を失っているのです。とにかく計算。え?プログラミングができない?それなら、3Dバリバリのオンラインゲームをどうぞ。え?最近はGPUだって?・・・石があったら熱するということで、キットカットおいしいね :chocolate_bar:

ところで、RMagickに限らず画像・動画処理ツールを使ったことはありますか?ウェブアプリケーションをつくっているなら

  • 画像サイズの変更:サムネイル用とか
  • フォーマットの変更:jpegをpngにするとか
  • デコ:文字入れしたり、スタンプ入れたりとか

といった感じの用途がメジャーかな。たいていのもの(ソフトにしろライブラリにしろ)には

  • ガウスぼかし
  • 2値化(白黒にする)
  • エッジ抽出

といった謎機能がついていて、「これ何に使うんだろう?」って思うことありません?普通のプログラミング生活からは縁遠い、画像認識(顔とか文字とか物体とか)とかやるときに効いてくる処理だったりします。もし、暇があったらその辺の世界も覗いてみてね。面白いよ。りさんふーりえ云々とかうぇーぶれっと云々とか :smile:

RMagickとその他便利なものを使いながら何かつくる :smile: ねたは、Ruby Advent Calendar 2014 。12月1日時点でカレンダーに載っている情報使う。誰かばっくれても消さない、ごめんね。

http://qiita.com/advent-calendar/2014/ruby

書いていくよ :pencil:

つくるもの

Rubyのアドベントカレンダ2014の登場人物を使った何かをつくります。

できあがり

先に出来上がったものを↓こちらです

https://www.youtube.com/watch?v=jWD22lrWbD0

準備

Ubuntu12.4系を使います。Windowsでは時々RMagickのインストールがうまくいかないことがあるらしい。いつものことだね :smile:

$5/月から使える DigitalOcean というクラウドサービス使ってます。今なら紹介プログラムで $10 分無料で使えます。

それでもWindowsに入れたい場合には

64bit windows でも 32bit 用のImageMagick入れてねとか、インストール時のオプション指定とか気を付けた方がいいことがあるようだ。以下 windows7 にインストール際に参考になるページ ※いずれも最新バージョンの情報ではないので自己責任で

なんかブクマで・・・

RMagickはちょっと前に試したけどWindowsでビルド出来なかったんだよなあ。結局ImageMagickコマンドを呼び出して使ったけど、今はビルドできるんだろうか。

こんなコメントが付いた。Windows 7 に入れてみた。ビルドできた。

ImageMagick のインストール

sudo apt-get install imagemagick

RMagick のインストール

sudo gem install rmagick

RMagick のドキュメント
http://www.imagemagick.org/RMagick/doc/

RMagick の github レポジトリ
https://github.com/RMagick/RMagick

つくるよ

つくりかたの手順

こんな感じでつくります

  • Advent Calendar の参加者一覧を取得 Nokogiri でスクレイピングして .json で保存 (記事下部のおまけにサンプルコード有)
  • qiita.com API v2 を使使用し参加者の情報を取得 (記事下部のおまけにサンプルコード有)
  • フレームをどんな感じにするか考える
  • RMagickの効果を使って画像を加工してフレームをつくる (必須)
  • 合成する (必須)
  • YouTubeにアップロードする (必須)

RMagick 以外ではまってたどり着けない可能性を考慮して、RMagickの基本とかその辺説明しつつ・・・

RMagick の基本

↓この元画像を加工しながら、基本を解説

sample.jpg

※画像はshutterstockのものを使用。利用規約上この記事外では利用できないので、試すときはスマホで自分の画像撮影する等で適当な画像を用意してください。

エフェクトかける ブラー

blur.rb
require 'RMagick' # require してライブラリを読み込み

img = Magick::ImageList.new("sample.jpg") # 元画像の sample.jpg を読み込み
new_img = img.blur_image(20.0, 10.0) # ブラーエフェクトを適用 戻り値に新しい画像が戻るよ
new_img.write("blur.jpg") # ファイルに書き出し

エフェクト後画像
blur.jpg

:exclamation: 注意点

メモリの解放

RMagickが取り扱う画像は明示的にメモリ解放の指示destroy!をする必要がある。たとえば今回の作業のように大量に画像をループ内で作成する場合、画像の書き出しが終わった時点でdestroy!するのが望ましい

memory.rb
img = Magick::ImageList.new("sample.jpg") # 元画像の sample.jpg を読み込み
new_img = img.blur_image(20.0, 10.0) # ブラーエフェクトを適用 戻り値に新しい画像が戻るよ
new_img.write("blur.jpg") # ファイルに書き出し

new_img.destroy! # ★ここでメモリ解放

画像の拡大縮小

resize.rb
require 'RMagick'

img = Magick::ImageList.new("sample.jpg")
new_img = img.scale(0.25)
new_img.write("small.jpg")

縮小後の画像
small.jpg

文字入れ

文字入れにはフォントが必要

環境によってはデフォルトのフォントが自動的に選択されるかも・・つまり明示的に指定したほうが無難

今回は、フリーフォントの「源柔ゴシック (げんじゅうゴシック) 」を使用

http://jikasei.me/font/genjyuu/

↑こちらからダウンロード可能

文字入れしてみる

ダウンロードしたフォントファイルの GenJyuuGothic-Normal.ttf を使用

moji.rb
require 'RMagick'

img = Magick::ImageList.new("sample.jpg")
scaled_img = img.scale(300, 300)

font = "GenJyuuGothic-Normal.ttf"
draw = Magick::Draw.new
draw.annotate(scaled_img, 0, 0, 5, 5, '2014年も終わる') do
  self.font      = font
  self.fill      = 'blue'
  self.stroke    = 'transparent'
  self.pointsize = 30
  self.gravity   = Magick::NorthWestGravity
end

scaled_img.write("moji.png") # save to file

文字入れ後の画像
moji.png

見づらいので白で縁取りしてみる

縁取りした場合

moji2.rb
require 'RMagick'

img = Magick::ImageList.new("sample.jpg")
scaled_img = img.scale(300, 300)

font = "GenJyuuGothic-Normal.ttf"
draw = Magick::Draw.new

draw.annotate(scaled_img, 0, 0, 5, 5, '2014年も終わる') do
  self.font      = font
  self.fill      = 'blue'
  self.stroke    = 'white'
  self.stroke_width    = 4
  self.pointsize = 30
  self.gravity   = Magick::NorthWestGravity
end

draw.annotate(scaled_img, 0, 0, 5, 5, '2014年も終わる') do
  self.font      = font
  self.fill      = 'blue'
  self.stroke    = 'transparent'
  self.pointsize = 30
  self.gravity   = Magick::NorthWestGravity
end

scaled_img.write("moji2.png") # save to file

文字入れ後の画像
moji2.png

qiita カラー(緑)で

文字色は blue, white とかの組込みの定義済みの名前も使えるし、16進RGB指定も使える

moji3.rb
require 'RMagick'

img = Magick::ImageList.new("sample.jpg")
scaled_img = img.scale(300, 300)

font = "GenJyuuGothic-Normal.ttf"
draw = Magick::Draw.new

draw.annotate(scaled_img, 0, 0, 5, 5, '2014年も終わる') do
  self.font      = font
  self.fill      = '#428b09'
  self.stroke    = 'white'
  self.stroke_width    = 4
  self.pointsize = 30
  self.gravity   = Magick::NorthWestGravity
end

draw.annotate(scaled_img, 0, 0, 5, 5, '2014年も終わる') do
  self.font      = font
  self.fill      = '#428b09'
  self.stroke    = 'transparent'
  self.pointsize = 30
  self.gravity   = Magick::NorthWestGravity
end

scaled_img.write("moji3.png") # save to file

文字入れ後の画像
moji3.png

リファレンス

http://www.imagemagick.org/RMagick/doc/draw.html#annotate

モザイク画像作成(冒頭のやつ)

この記事の頭に貼り付けていた画像もRMagickで作成

mosaic.rb
require 'RMagick'
require 'yaml'
require "open-uri"

thumbnails = YAML.load(File.open('thumbnails.yml').read)
images = Magick::ImageList.new 
thumbnails.each do |uri|
  puts uri
  images.read(uri) # uri が remote の場合でも、rubyとRMagickどちらも最新版ならこれでもOKのはず。古いとエラー
  # urlimage = open(uri) # ruby バージョンが古くてuriがhttpsの場合SSLでエラーが発生する可能性有
  # images.from_blob(urlimage.read)
end # thumbnails.each

tile = Magick::ImageList.new
page = Magick::Rectangle.new(0,0,0,0)
images.scene = 0

5.times do |i|
  5.times do |j|
    tile << images.scale(80, 80)
    page.x = j * tile.columns
    page.y = i * tile.rows
    tile.page = page
    (images.scene += 1) rescue images.scene = 0
  end
end
mosaic = tile.mosaic
mosaic.write("mosaic.png")

注意点

  • バージョン(ruby, RMagick)によって http https のプロトコル使って直接 read できないことがある
  • バージョン(ruby, RMagick)によって https の場合SSLの証明書エラーになることがある

以下参考URL

RMagick でアニメーション

この先の作業でのポイント

  • 最終的には動画ファイル .mp4
  • 動画ファイルの生成には ffmpeg を使用(餅は餅屋で)
  • RMagick は動画の元となるフレームの作成に使う
  • できるだけ RMagick の機能を使ってフレームをつくる
  • 1280x720 ぐらいを目指す

芸術の素養というか、そういうのが無いので、アイデアを拝借。インスピレーションを得る、これ大事。たとえばこういうの。

http://matome.naver.jp/odai/2139688110872725901

自分の好きな映画も良いと思う。最近は実写系の映画もオープニング、エンディングで遊んでるから参考にするには良い。印象に残っているのは007とか。ときどき、プログラミングの手を休めて↓のような記事を読んだり、動画をみると新しいアイディアが浮かんでくるかもしれない。

魅力的なアニメーションをつくるための12の原則(動画あり) http://wired.jp/2014/05/14/12-principles-of-animation/

クリエイティブなんて向いてないことを再認識⇒参加者全員が表示される動画に落ち着く

:ramen: ここまで書いて空腹になったので・・・休憩

gif アニメーション

休憩後、やっぱり、時間無さそう・・・ 動画にしなくてもgifでアニメーションできるし、RMagickのみでつくれるのは良い!

以下参考リンクのみで、ごめんよ :scream:

参考リンク

動画でアニメーション

フレームをごりごりつくって、ffmpeg 使って動画にしてもらう。

next で次のフレームに移る処理で、draw で画像ファイルを書きだす感じでいく予定

背景とかフレームの枠を準備

できるだけ RMagick の機能を使ってフレームをつくる」のでテンプレートとなる画像等は用意しないでフレームをつくっていく。サイズは 1280x720 で。空の画像をRMagickで準備するには Image.new(width, height) を使う。

empty.rb
require 'RMagick'

f = Magick::Image.new(1280,720) { self.background_color = "white" }
f.write("background.png")

味気ないので、qiita っぽくする。qiita グリーン "#428b09" を拝借。"Ruby Advent Calendar 2014" の文字を入れる。

background.rb
require 'RMagick'

f = Magick::Image.new(1280,720) { self.background_color = "white" } # 背景を白で準備

draw = Magick::Draw.new
# http://www.imagemagick.org/RMagick/doc/draw.html#rectangle
draw.fill('#428b09') # 先に色を指定して
draw.rectangle(0, 0, 1280, 100) # トップのバナーを長方形で
draw.draw(f) # 塗る

draw = Magick::Draw.new
font = "GenJyuuGothic-Normal.ttf"
draw.annotate(f, 0, 0, 15, 12, 'Ruby Advent Calendar 2014') do
  self.font      = font
  self.fill      = 'white'
  self.stroke    = 'transparent'
  self.pointsize = 54
  self.gravity   = Magick::NorthWestGravity
end

f.write("background.png")

↓これが背景のイメージ

background_small.png

アニメーションに使えそうなRMagickのエフェクト

motion_blur が良さそう。試しに「ス」のアイコンに motion_blur かけて20枚ほどフレーム画像を作成

motion_blur.rb
require 'RMagick'

img = Magick::ImageList.new("src.png")
img.alpha Magick::DeactivateAlphaChannel # アルファチャネルを無効に
img = img.opaque_channel("#000000", "#FFFFFF") # アルファチャネル部分が黒かったので白く

ctr = 0
-10.upto(10) {|i|
  if i >= 0
    tmp = i != 0 ? img.motion_blur( 0, i*i, 0) : img # 左の方向にぶれる感じ
  else
    tmp = i != 0 ? img.motion_blur( 0, i*i, 180) : img # 右方向にぶれる感じ
  end
  file_name = "frames/frame-%03d.png" % ctr
  tmp.write(file_name)
  ctr += 1
}

21枚の画像ができてこんな感じ

frame_files.png

:exclamation: 注意点

motion_blur はガウスぼかし gausian_blur を内部的に使用しているため、引数sigmaの値が大きくなるほど計算量が増え処理が遅くなる。使用可能なリソースの範囲でパラメーターを考える必要がある。

みんなのアイコンに使ってみる

hoge.rb
require 'RMagick'

# background を準備
bg = Magick::Image.new(1280,720) { self.background_color = "white" } # 背景を白で準備

draw = Magick::Draw.new
# http://www.imagemagick.org/RMagick/doc/draw.html#rectangle
draw.fill('#428b09') # 先に色を指定して
draw.rectangle(0, 0, 1280, 100) # トップのバナーを長方形で
draw.draw(bg) # 塗る

draw = Magick::Draw.new
font = "GenJyuuGothic-Normal.ttf"
draw.annotate(bg, 0, 0, 15, 12, 'Ruby Advent Calendar 2014') do
  self.font      = font
  self.fill      = 'white'
  self.stroke    = 'transparent'
  self.pointsize = 54
  self.gravity   = Magick::NorthWestGravity
end

# ここでエフェクトしたアイコンを張り付けてファイルに出力
base = Magick::Image.new(600,600) { self.background_color = "white" } # 背景を白で準備
overlay = Magick::Image.read("mosaic.png").first # これのサイズは400x400
base.composite!(overlay, 100, 100, Magick::OverCompositeOp) # 中央になるように配置

ctr = 0
-10.upto(10) {|i|
  if i >= 0
    tmp = i != 0 ? base.motion_blur( 0, i*i*0.5, 0) : base
  else
    tmp = i != 0 ? base.motion_blur( 0, i*i*0.5, 180) : base
  end
  file_name = "frames/frame-%03d.png" % ctr

  # background の中央に配置
  bg.composite!(tmp, 340, 120, Magick::OverCompositeOp) # 中央になるように配置
  bg.write(file_name)
  ctr += 1
}

同じように21枚の画像ができてこんな感じ

frame_files2.png

ffmpeg のはなし

複数の画像をつかってアニメーション動画をつくる

1秒間に5枚(5フレーム)の画像を使った動画を作成

ffmpeg -r 5 -i frame-%03d.png -c:v libx264 -pix_fmt yuv420p -y out.mp4

↑こんな感じでコマンドで一発で動画が作成できる!できた動画は↓

https://www.youtube.com/watch?v=Qd1C5v2m76Y

※画質が荒いのは元の画像が80x80のため

みんなの顔⇒ https://www.youtube.com/watch?v=AqHmcZ5qplg

使用したオプションの解説

  • -r 1秒間のフレーム数を指定する(1より小さい数でも 0.5 とかOK)
  • -i 入力に使用するファイル
  • -c:v ビデオコーデックの指定
  • -pix_fmt ピクセルフォーマットの指定 ※これ指定しないとちゃんとした絵にならない
  • -y 同名のファイルがあったら強制的に上書き

動画ファイルに音声(BGM)を加える

  • BGMはフリーBGM素材として定評のある「魔王魂」http://maoudamashii.jokersounds.com/list/bgm6.html さんから
  • ラスボスっぽい書き手が多いのでラスボスっぽい曲をチョイス
  • ffmpeg でできた動画の音声を入れ替え
ffmpeg -i video.mp4 -i bgm.aac -bsf:a aac_adtstoasc -map 0:0 -map 1 -vcodec libx264 -acodec libvo_aacenc -y ruby_advent_calendar_2014.mp4

制作を終えての感想

ヽ(゚ー゚ヽ)(ノ゚ー゚)ノわぁい

ruby を使った意味について・・・

おまけ

使用させていただいたユーザーの皆様

日付順

※12月1日時点(一覧作成中に変更があったりで・・・)

qiitaのプロフィール画像

  • gravatar の場合もあれば twimg の場合もあって、面倒
  • gravatar 系のときプログラムから取得するとエラーになりがち

qiitaのAPI

  • ユーザー情報は http://qiita.com/api/v2/users/joker1007 で取得可能
  • Contribution の数字は入らない(Contributionはストックされた合計数だろうか?)
  • 投稿数、フォロワー数、フォロー数は取得できる

qiita の Contribution のスクレイピング with Nokogiri

get_contribution.rb
require 'nokogiri'
require 'open-uri'

url = 'http://qiita.com/joker1007'
html = Nokogiri::HTML(open(url), nil, 'utf-8')
ctb = html.xpath('//div[@class="second-line"]/span[@class="count"]/text()')
puts ctb

Advent Caendar から Nokogiri 使ってメッセージを取得

get_messages.rb
require 'nokogiri'
require 'open-uri'
require 'json'

messages = []
url = "http://qiita.com/advent-calendar/2014/ruby"
html = Nokogiri::HTML(open(url), nil, 'utf-8')
# カレンダーの枠tdを探す
tds = html.xpath('//td[@class="adventCalendar_calendar_day"]')
tds.each do |td| 
  author = td.xpath('p[@class="adventCalendar_calendar_author"]')
  if author.empty? # author 無いなら無視

  else
    user_id = author.xpath('a/@href').to_s[1, 100] # 先頭スラッシュ無視
    puts user_id

    message = nil
    m_a = td.xpath('p[@class="adventCalendar_calendar_entry"]/a')
    if m_a.empty? # 未投稿の場合はこっち
      message = td.xpath('p[@class="adventCalendar_calendar_entry"]/text()')
      else # 投稿済みの場合
      message = td.xpath('p[@class="adventCalendar_calendar_entry"]/a/text()')
    end
    puts message
    messages.push(:user_id=>user_id, :message=>message)
  end
end # tds.each
json_file = "messages.json"
File.open(json_file,"w") do |f|
  f.write(messages.to_json)
end

qiita API v2 ユーザー情報取得

api/v2/users/:id

{
  "description": "Qiita, Qiita:Team(RoR)やKobito(Objective-C)の開発をしています.",
  "facebook_id": "yaotti",
  "followees_count": 118,
  "followers_count": 181,
  "github_login_name": "yaotti",
  "id": "yaotti",
  "items_count": 101,
  "linkedin_id": "yaotti",
  "location": "Tokyo, Japan",
  "name": "Hiroshige Umino",
  "organization": "Increments Inc",
  "profile_image_url": "https://si0.twimg.com/profile_images/2309761038/1ijg13pfs0dg84sk2y0h_normal.jpeg",
  "twitter_screen_name": "yaotti",
  "website_url": "http://yaotti.hatenablog.com"
}

Gravatar のAPI

gravatar なんて使っている人少ないと思ってたのだがけっこういるのね。メールアドレスをハッシュしたこんな感じのURLで画像を取得できます。パラメーターsでサイズ指定できるのがポイント :thumbsup:

http://www.gravatar.com/avatar/205e460b479e2e5b48aec07710c08d50?s=200

ruby + RMagick でやった意味について

  • やったことはシェルで ImageMagick + ffmpeg 使うのと変わらないではないか
    • はいその通りです
  • ruby でやる意味はどこにあったのか
    • RMagick 周辺を綺麗にラップした感じのものをつくれたら意味あるかも ※RMagickが処理するエフェクトメソッドの内、self戻すのもあれば、new image戻すものもあったりとややこしいから
  • 何が得られたのか
    • 作業中はCPU使用率が100%近くを維持できた :chart_with_upwards_trend: