d.sunnyone.org
sunnyone.org

ページ

2012-09-27

ImageMagickで紙の白レベルを判定する

今日は、一部で「美白化」と呼ばれる、もともと白かった紙の色褪せた部分を白くする処理について。白くするには、どれだけ白っぽくするか、すなわち白とする基準を指定する必要があるが、ImageMagickを利用して、どのレベル以降を白と見做すか判断する方法について、この記事で述べる。

結果だけ使いたい方は、判定ロジックのシェルスクリプトを最後に載せているのでどうぞ。
実行すると、このようになる(以下は95%だった例)
$ ./whitelevel.sh test.jpg
95

この方法(+α)で運用しているが、かなりうまく動いている。

背景

ImageMagickを使って美白化をする例はいくつかWeb上にもあり、-levelを使ってパーセンテージ指定で白レベルを指定するか、-linear-stretchを使ってハイライト側のピクセル数から判断する方法があった。しかし、前者は紙によって異なるし、後者は白い部分が少ない画像で悲しい思いをする可能性があるので、実際の画像から白のレベルを決めたい、というのが背景である。

方針

直感的に言えば、白っぽい部分はヒストグラム上は山になっているはずなので、その直感の方法に合わせて、ヒストグラムを作成し、グラフの傾きから計算すればできそうである。しかし、「ImageMagickでヒストグラムを出力し、数値を計算可能な状態にparseする」「数値をもとに山を求める」というののどちらも面倒くさい。しかも、実際に画像を見てみると、白部分がなだらかだったりして、山になっていないこともある。

ちょっと困っていたが、画像を見ていてふと
「はじっこ(枠)って白くね?」
「ImageMagickって枠切りとれるわ」

という2点に気付き、枠を切り出し、その色を白とすることを思い立った。

もう少し具体的な方針を書くと、以下の通りである。
  1. convertコマンドの-trimを利用して、画像全体のサイズと枠の切り出し座標を得る。
  2. 枠のサイズが、白レベルを判定するのに十分であるかを確認する。
  3. 枠を切り出し、色の平均レベル(μ)と、標準偏差(σ)を算出する。
  4. μ-3σ(正規分布における99.73%の点)を得て、白レベルとする。
  5. (得られたレベルと-levelを利用して、美白化する。)

なお、紙の横方向(綴じてあるほうと、その反対側)は、均一でないことがあるので、余白は上下を用いる。

手順

1. convertコマンドの-trimを利用して、画像全体のサイズと切り出し座標を得る

まず、convertコマンドでは、-trimを使うと、余白を切り取ることができる。
-fuzzオプションを併せて利用すると、その範囲を誤差と見做してくれる。
例としては、以下の通り。
$ convert -trim -fuzz 40% hello.jpg hello-trim.jpg

こんな画像が:

こうなる:


さらに、convertコマンドでは、info:を出力先にすると、画像を出力する代わりに、画像の情報を吐き出してくれる。
$ convert -trim -fuzz 40% hello.jpg info:
hello.jpg JPEG 1338x719 1792x1076+184+136 8-bit DirectClass 0.020u 0:00.010

実は、必要な情報は含まれているので、このままでもいいのだが、-formatというオプションを利用すると、出力フォーマットを調整できる。

まずは、以下の情報を使う。
%H   page (canvas) height
%Y   page (canvas) y offset (including sign)
%h   current image height in pixels
それぞれ、「元画像の高さ」「切り出し縦オフセット」「切り出された画像の高さ」である。

$ convert -trim -fuzz 40%  hello.jpg  -format "%H %Y %h" info:
1076 +136 719

これで、切り出しサイズが得られた。

2. 枠のサイズが、白レベルを判定するのに十分であるかを確認する

端から黒かったりすると、白レベル判定に不十分なので、1.で得たサイズが妥当かどうか、確認する。
上記のサイズから計算するだけなので詳細は省くが、今は、高さが全体の1%以下あるいは25%以上の場合は、白レベル判定不能にしている。

3. 枠を切り出し、色の平均レベル(μ)と、標準偏差(σ)を算出する

サイズ情報を得ているので、-cropを使って画像を切り出し、また-formatとinfo:を使って白部分の平均と標準偏差を得る。今回は、以下の指定を使う。

%[mean]                 CALCULATED: average value statistic of image
%[standard-deviation]   CALCULATED: standard-deviation statistic of image

コマンドとしては、ボールド化のときに使った彩度をゼロにする-modulate 100,0を併せて、以下の形(わかりやすさのため改行を入れている)。
$ convert hello.jpg \
  -modulate 100,0 \
  '(' -clone 0 -crop x$TOP_BLANK+0+0 ')' \
  '(' -clone 0 -crop x+0+$BOTTOM_OFFSET ')' \
  -delete 0 \
  -append -format "%[mean] %[standard-deviation]" info:
64209.3 671.804

これは、上下の双方が白レベル判定として有効だったときで、上下をそれぞれ切り出し、もとの画像を削除したあと、くっつけて情報を得ている。$TOP_BLANKは%Yの値で、$BOTTOM_OFFSETは%Y + %hの値である。

4. μ-3σ(正規分布における99.73%の点)を得て、白レベルとする

この得られた白い部分は、ちゃんと白だけの画像であれば、正規分布だろうと想定して、正規分布では99.73%の値をカバーできる、μ-3σの点を白と判断する。
正規分布のイメージはこちら等で: http://www.cap.or.jp/~toukei/kandokoro/html/14/14_2migi.htm

上記の例では、64209.3 - 3 * 671.804 = 62193.888である。
Maxが65535なので、パーセンテージにすると、655.35で割って、62193.888 / 655.35 = 94.901...である。

これで、95%というレベルが得られた。

(5. 得られたレベルと-levelを利用して、美白化する)

端から色がついていたりして、取得できない画像があるので、もう少し工夫が必要なのだが、一応得られたものを適用するには、-levelを利用して、以下のように処理できる。
$ convert -modulate 100,0 -level ,94% hello.jpg hello-white.jpg

実際にこれを使って白くしたものがこちら。

実装

上記を実装したスクリプトは、以下の通り。
#!/bin/sh
# White level detection script
# Copyright (c) 2012 Yoichi Imai <sunnyone41@gmail.com>
if [ -z "$1" ]; then
echo usage: $0 image
exit 1
fi
FILE="$1"
INFOY=`/usr/bin/convert -trim -fuzz 40% $FILE -format "%H %Y %h" info:`
if [ -z "$INFOY" ]; then
echo -1
exit 0
fi
ORIG_HEIGHT=`echo $INFOY | /usr/bin/awk '{print $1}'`
TOP_BLANK=`echo $INFOY | /usr/bin/awk '{print $2}' | tr -d '+'`
CROPPED_HEIGHT=`echo $INFOY | /usr/bin/awk '{print $3}'`
# No blank detected. This page is all blank or gray.
if [ "$TOP_BLANK" -eq -1 ] ; then
echo -1
exit 0
fi
BOTTOM_OFFSET=`expr $TOP_BLANK + $CROPPED_HEIGHT`
BOTTOM_BLANK=`expr $ORIG_HEIGHT - $BOTTOM_OFFSET`
TOP_BLANK_PERCENT=`expr $TOP_BLANK \* 100 / $ORIG_HEIGHT`
BOTTOM_BLANK_PERCENT=`expr $BOTTOM_BLANK \* 100 / $ORIG_HEIGHT`
# Too large: Just white or totally image. Too small: useless
if [ $TOP_BLANK_PERCENT -le 1 -o $TOP_BLANK_PERCENT -ge 25 ] ; then
TOP_INVALID=1
fi
if [ $BOTTOM_BLANK_PERCENT -le 1 -o $BOTTOM_BLANK_PERCENT -le 25 ] ; then
BOTTOM_INVALID=1
fi
# Both side are invalid, then this image is useless.
if [ -n "$TOP_INVALID" -a -n "$BOTTOM_INVALID" ] ; then
echo -1
exit 0
elif [ -n "$TOP_INVALID" ] ; then
RESULT="`/usr/bin/convert $1 -modulate 100,0 -crop x+0+$BOTTOM_OFFSET -format "%[mean] %[standard-deviation]" info:`"
elif [ -n "$BOTTOM_INVALID" ] ; then
RESULT="`/usr/bin/convert $1 -modulate 100,0 -crop x$TOP_BLANK+0+0 -format "%[mean] %[standard-deviation]" info:`"
else
RESULT="`/usr/bin/convert $1 -modulate 100,0 '(' -clone 0 -crop x$TOP_BLANK+0+0 ')' '(' -clone 0 -crop x+0+$BOTTOM_OFFSET ')' -delete 0 -append -format "%[mean] %[standard-deviation]" info:`"
fi
echo $RESULT | /usr/bin/awk '{ print int(($1 - 3 * $2) / 655.35) }'
view raw whitelevel.sh hosted with ❤ by GitHub


よりうまく使うには

よりうまく使うには、単体の画像ではうまくいかず、画像群に対して使うのが妥当なのだけど、それをするとスクリプトを使う前提の説明がしんどいのでここでは省く。

実は何度か紹介してきたような方法を組合せて、決まった置き方をすると自動で本をまとめるスクリプト群ができていて、うまく動いているのだけど、PDF対応中なので、近日公開予定。


0 件のコメント:

コメントを投稿