アスペクト比と面積から縦横の大きさを求めるRubyスクリプト

Imagemagick-logo

ImageMagick の montage コマンドを使うと複数の画像をタイル状に並べて、1枚のサムネイル集合写真のような画像を作成することができます。スマートフォンの「カメラロール」のようなものです。

tiled-thumbs

具体的には次のようなコマンドでサムネ画像を生成します。

montage -background black -tile 11x20 -geometry 48x48+0+0 *.jpg thumb.jpg

ここで問題となるのが -tile オプション。これは、「複数の入力画像を縦横何個ずつ並べるか」を指定するオプションです。入力画像ファイル数が少なかったり、出力画像のアスペクト比が 1:1 であれば単純に入力画像ファイル数の冪根を求めればいいだけなのですが、16:9 などといったスマホの画面で閲覧させるのを前提としたアスペクト比の場合、何個ずつ並べればいいかの計算がちょっと厄介になります。

入力画像が正方形の場合、入力総数を [math]n[/math], 求めたい縦横何個を [math]x, y[/math], 縦横比を [math]w, h[/math], アスペクト比を升目と考えた時の総数に対する比を [math]t[/math] とおくと、[math]n = xy[/math] で [math]xy \leq wt \cdot ht[/math] を満せばいいわけなので、[math]x, y[/math] それぞれについて解くと、

[math]x = w \sqrt{\mathstrut \frac{n}{wh} }, y = h \sqrt{\mathstrut \frac{n}{wh} }[/math]

という具合になります。

これを Ruby に書き直して計算結果を印字するようにしたものが次のコードです。

#!/usr/bin/env ruby

# 総数
n = 333

# 比率
w = 16
h = 9

def calc(n, w, h)
  t = Math.sqrt(n.to_f / (w * h).to_f)
  return (w * t).to_i, (h * t).to_i
end

x, y = calc(n, w, h)
if x * y < n
  x, y = calc(n + x, w, h)
end

puts "#{x}x#{y}"

calc の戻り値は個数を表すため必ず整数値になるわけですが、総数よりも小さい場合は1枚の画像に収まらないため1、再計算させています。 上の例のように、333 ファイルを 16:9 の比でタイル配置したい場合

25x14

という結果が得られます。


  1. montage コマンドはタイル総数よりも入力画像数が多い場合、複数の画像として出力してくれます。また、足りない場合は余白になります。 

ImageMagickを使ったハミング距離による画像の同一性チェック

画像認識に用いられるライブラリといえば OpenCV が一般的ですが、毎回 face detect のデモを動かしてみて安心してしまい、結果的にいつも本腰を入れて使ってみることがありませんでした。(やりたいなぁとは思いつつ、特に目的がない…)

今回、知人と OpenCV の話題が出たのをきっかけに ruby-opencv を使ってみようとしたけど挫折。OpenCV 本体のソースコードを clone してきて 3 時間近くもビルドを走らせて、環境作りにえらく時間がかかっちゃいました。もう少し根気があればいいんですが…。

さて、高機能な画像認識は必要がないような例、例えば、かなりゆるめの画像の同一性チェックなどでは、OpenCV のような本格的なライブラリを用意しなくても既存の環境でなんとかしたくなってしまいます。おっと、挫折したからって逃げではないですよ!ほんとですよ!

ということで、みんな大好き ImageMagick の convert コマンドに丸投げする形で、Ruby で書いていきたいと思います。 こちらの記事によると、縮小した画像を様々な視点からハッシュ化して、その値を比較することで類似度を算出しています。

今回は単純に明度同士の比較用いて、類似度を計算したいと思います。

手順

  1. ImageMagick を使って、比較対象の画像をそれぞれ 16×16 ピクセルの小さな画像に変換。言うなれば、モザイクのように色の平均値のパッチの集合となる。
  2. それぞれを2値化して、XBM で保存。XBM で保存するのは、Ruby 側から大掛かりな画像ライブラリを介すことなく、簡単に各々のピクセルの色情報を取得するため。
  3. 各々の XBM に含まれるピクセルデータをビット列に変換。
  4. 2つのビット列を比較し、類似性スコアを算出。

こういうのを「ハミング距離」を用いた判定と言うらしいです。

スクリプト

入力画像

試しに、次の2つの画像を入力として与えます。256×256 と 512×512 の大きさの違うレナさんです。

lenna256

lenna512

2値化した XBM として出力されるのは、それぞれ次のとおり。

lenna1

lenna2

似ていますが、入力のソースが若干違うために2値化結果にも差異が出ています。

この画像をビット列に変換し、スコア値を印字すると 0.9375 と表示されました。

面白い傾向として、最初に小さな画像にサンプリングする際の解像度を上げれば、より精度の高い類似性検出ができるのかと思いがちですが、実際にはその逆のパターンが多く見受けられました。

出力されるスコア値は 0.0 から 1.0 の値を取り、1.0 に近くなればなるほど類似度が高くなります。また、0.0 に近くなればなるほど、比較対象の明度が元画像に対して反転している可能性が高くなります。

この例では明度を2値化しているので、例えばほとんどのピクセルの明度が閾値以下(または、以上)になるような画像同士の比較には向いていません。また、部分マッチやトランスフォーメーション(回転・移動)に対する類似性を検出もできません。高度な検出を可能にするならば、やはり OpenCV を使うべきですが、明度だけではなく彩度やレベル・トーンカーブなど色々な側面を数列化して比較するとコストをかけずに検出精度を上げることも可能だと思われます。

PDFを連番画像ファイルを含むZIP圧縮ファイルに変換

安価な Android 端末が出回っています。
Amazon キンドルが発売されて、投げ売り状態だった楽天の Kobo が、新品でも3000円程度で流通するようになりました。
ようやく、電子書籍を読むというライフスタイルが日本にも定着するんでしょうか。今後が楽しみです。

さて、私が持っている Huawei の IDEOS のような低スペックな端末では、PDF の文書を読むのにもひと苦労です。
低スペックな環境では、ファイルサイズが小さく高圧縮&レンダリングにマシンパワーが必要なものより、ファイルサイズが大きく冗長でも負荷をかけずに表示できるものの方が重宝される場合があります。

特にほとんどが文字の場合、ページをめくるたびに何秒も待たされるより、少々画質が荒くても(もちろん、文字が読める程度に、です)サクサクとページをめくれる方が良い事もあります。

という事で、既存の PDF ファイルの各ページを連番の JPEG ファイルにして、ZIP 形式で圧縮するスクリプトを書いてみましょう。

1. まず基本的な事

ImageMagick の convert コマンドで、次のようにタイプすると PDF のページを JPEG ファイルとして保存することができます。

[bash]
convert src.pdf[1] dst1.jpg
[/bash]

鉤括弧の中は、ページ番号を指定します。0 ページ目から開始します。存在しないページを指定すると

[bash]
convert src.pdf[999] dst999.jpg

Requested FirstPage is greater than the number of pages in the file: 645
No pages will be processed (FirstPage > LastPage).
[/bash]

とエラーになります。

2. シェルのブラケット展開で繰りかえす

エラーが出るまで繰替えせばいいので、

[bash]
for n in {0..999}; do convert src.pdf[$n] dst$n.jpg || break; done
[/bash]

と書く事ができます。0から999ページまで指定して変換を試みて、失敗したら break で抜ける、という動作をします。

3. 圧縮ファイルにつっこむ

変換し終わったファイルから ZIP ファイルに追加していくには

[bash]
for n in {0..999}; do convert src.pdf[$n] dst$n.jpg || break; zip target.zip dst$n.jpg; done
[/bash]

という風に、そのまま zip コマンドに投げてやれば良いです。

[bash]
for n in {0..999}; do convert src.pdf[$n] dst$n.jpg || break; zip target.zip dst$n.jpg && rm dst$n.jpg; done
[/bash]

ZIP ファイルへの追加に成功した後、JPEG ファイルは不要ですので、削除しておきましょう。

4. 変換オプションを指定する

必須ではありませんが、convert のオプションを利用する事によって、画像サイズの指定、グレースケール化、フォントの指定など様々なことができます。詳しくは

[bash]convert –help[/bash]

をご覧ください。

5. スクリプト化

以上の事を踏まえて、スクリプトにしてみます。

[bash]

/!usr/bin/env bash

src=$1
dst=$2

opt=’-density 600 -resize 640 -font kochigothic’

dir=/tmp/$$

(test ! -d $dir && mkdir $dir) || exit 1
test ! -f $src && exit 1

tmp=basename $dst

for n in {0..999}
do
pict=printf '%04d.jpg' $n
convert $opt $src[$n] $dir/$pict 2>/dev/null || break
pushd $dir >/dev/null
zip $tmp $pict && rm $pict
popd >/dev/null
done

mv $dir/$tmp $dst
rmdir $dir
[/bash]

このスクリプトは、/tmp 以下に一時ディレクトリを作成し、そこに画像ファイルとZIPファイルを貯め込むようにしています。
実行は、以下のように使います。

[bash]
pdf2cbz src.pdf dst.zip
[/bash]

dst.zip の名前を自動生成するようにすれば、複数ファイルの一括処理や Nautilus などのファイラから右クリックで変換、など、さらに便利に使えそうですね。