画像加工の例とその考え方

気に入っている猫の写真を加工してみたので、
ソースコードを置いておきます。
そのソースコードが具体的に何をしているのかということを推測してみたので、
それも書いておきます。

画像加工やPHP言語はまるで専門ではないので、
信憑性はあまりありませんが、
基本的な考え方や方向性は外していないと思います。

やってみたこと

猫の写真を加工しました。
何年か前にN252iで撮った近所の野良猫です。

これを

こんなふうに、
「ラフにトレースしたものをスキャナで取り込んだような感じ」
にしてみました。

本当のところは輪郭線だけを取り込みたかったのですが、
まあ、これはこれで味があるからいいかと思いました。

ソースコード

あまり真面目にじろじろ見なくていいです。

<?php
//ヘッダ処理
header ("content-type: image/png");

//元画像読み込み
$filename = "cat.jpg";
$image = ImageCreateFromJPEG ( $filename );

//加工
imagefilter ( $image , IMG_FILTER_EDGEDETECT );
imagefilter ( $image , IMG_FILTER_GRAYSCALE );
imagefilter ( $image , IMG_FILTER_SMOOTH , 8 );
imagefilter ( $image , IMG_FILTER_BRIGHTNESS , 20 );
imagefilter ( $image , IMG_FILTER_CONTRAST , -255 );

//表示
imagepng ( $image );
imagedestroy ( $image );
?>

  • ヘッダ処理。
  • 元画像読み込み。
  • 加工。
  • 表示。
となっています。
ここから先、「加工」の部分を説明します。

作業工程

まずは、順を追って作業工程を図示します。
専門的な用語も出てくるかもしれませんが、
あとで説明します。

最初はこれです。

imagefilter ( $image , IMG_FILTER_EDGEDETECT );

エッジ抽出をすることにより、

こうなります。

imagefilter ( $image , IMG_FILTER_GRAYSCALE );

念のためにグレースケールに変換しておきます。

あまり変化したようには見えませんが、
たまに変化する画像もあります。

imagefilter ( $image , IMG_FILTER_SMOOTH , 8 );

スムージングをして、ゴミを取り除く努力をします。

少しぼやけてくれました。

imagefilter ( $image , IMG_FILTER_BRIGHTNESS , 20 );
imagefilter ( $image , IMG_FILTER_CONTRAST , -255 );

この二行で二値化をします。

白と黒に多分きっぱり分かれてくれています。

1.エッジ抽出

さて、まずはエッジ抽出の話です。
エッジというのは「ふち」とか「へり」とかいう意味です。
それを抽出します。

imagefilter ( $image , IMG_FILTER_EDGEDETECT );

この一行だけで最終出力が出てくるかと期待したのですが、
駄目でした。
PHPの中身はよく分かりません。

画像を見るかぎりでは、こんなフィルタをかけているのかなと思いました。

ちょっとこの「フィルタ」という概念について、
細かく説明します。

「フィルタ」という概念

一般的に、信号処理で入力と出力の間にあるごちゃごちゃしたものは、
ほとんど「フィルタ」と呼ばれます。
ただ、画像処理の分野で、下の図のような表みたいなのが出てきたら、
これから説明するような演算を示すようです。

コンピュータ上での画像は、ピクセルごとに輝度値が与えられています。
赤・緑・青ごとに値が割り当てられています。
(6×6ピクセルというとても小さな画像だと思ってください。)

この画像の赤で示した部分に、フィルタをかけるとすると、
こんな計算をすることになります。

この計算結果の値を
「この赤い部分にフィルタをかけたときの出力」
とします。

なんのことか分からないと思いますが、
まあ、とにかく、
「かけ算をして」「合計している」
ということを頭に入れておいてください。
(内積の求め方と同じです。)
具体的な意味はこれから説明していきます。

エッジを抽出するために

エッジというのは変化度が強い部分です。
だから、典型的なエッジの輝度はこんな感じです。

縦線です。
横線や斜めも考えられますが、とりあえずは縦です。

そして、エッジは決してのっぺりとはしていません。
だから、典型的にエッジでない画像の輝度はこんな感じになります。

ここで、さきほどのような3×3のフィルタをかけて、
「典型的なエッジ」の出力値を大きくして、
「典型的にエッジでないやつ」の出力値を小さくしたいのです。
そうすると、慣れてくるとこんなフィルタが思いつきます。

このフィルタを実際に適用してみるとこうなります。

見事に、縦線は大きな出力になり、
のっぺりな画像はゼロになってくれました。

縦線については逆向きも考えねばなりませんが、
これは、絶対値をとって解決します。

さらに横線はこういうフィルタを使って絶対値をとります。
90度傾けただけです。

さて、斜めはどうするかという問題ですが、
ピタゴラスの定理で斜辺の長さを求めるような感じで解決します。
(数学的には「L2ノルム」などというと通じます。)
これで、縦横を利用して斜めも解決されています。
この式だと絶対値をとる必要はなくなります。

というわけで、こういうフィルタを「Sobelフィルタ」というらしいです。

エッジの部分で輝度が高くなるはずですが、
PHPのエッジ抽出フィルタでは、
それをさらに白黒反転させているように見えます。
そして、エッジでないと判断した部分を
灰色にしているようにも見受けられます。
(つまりSobelフィルタよりも複雑なことをしているようです。)
PHPの中身まではよく分かりません。
それから、PHPがSobelフィルタを使っているかどうかも知りません。
また、RGBのカラーごとにフィルタを使っているのか、
グレースケールに直してからフィルタを使っているのかも分かりません。
ただ、考える方向性はだいたい似たようなもののはずです。

とにかくエッジ抽出で、猫の画像はこうなりました。

2.グレースケール

すぐ上の文章で、
エッジ抽出のときにカラーのまま処理しているかどうかが分からない、
と書きましたが、多分、カラーのままです。
なので、カラーから白黒の濃淡画像にします。

imagefilter ( $image , IMG_FILTER_GRAYSCALE );

白黒の濃淡画像にする方法は多分誰でも分かると思います。

赤と緑と青の輝度値を足して3で割ります。

猫の画像はこうなりましたが、
あまり変わっているようには見えません。
(別の画像では変わりました。)

3.スムージング

エッジを抽出しただけでは、
点々のようなノイズが残ります。
そういうごましおのようなノイズをとりたいと思います。

imagefilter ( $image , IMG_FILTER_SMOOTH , 8 );

ここでもまた、フィルタを使います。
このフィルタはPHPがどういうものを使っているのか推測ができません。
スムージングのためのフィルタは種類がたくさんあるからです。
おそらく、最も簡単なものは、以下のものです。

3×3ピクセルの平均をとることになります。
平均をとるとなだらかになって、
ごましおのようなノイズがとれるということは、
直観的に分かると思います。

また、「メディアンフィルタ」というフィルタもあります。
これは注目ピクセルの近くのピクセルの中央値をとるフィルタです。
先ほどまでとは違い、かけ算や足し算をしません。

スムージングをした結果、ちょっとぼやけました。

4.二値化

さて、仕上げです。
エッジを抽出してノイズをぼやかしたので、
あとは白と黒だけで中間の灰色のない画像にするだけです。
中間の灰色を「白」か「黒」にすることを「二値化」といいます。

実は、PHPには二値化専用の関数があるのではないかと期待したのですが、
見当たりませんでした。
もしかしたら、あるかもしれません。

ちなみに、二値化の基本は「閾値(しきいち・いきち)」を設定して、
それよりも上なら白く、それよりも下なら黒くする、というものです。
この閾値は人間が入力することもありますし、
機械が自動設定することもあります。
今回は自動設定をしませんでした。

とにかく、二値化のための関数が見当たらなかったので、
コントラストを調節する関数で代用しました。
コントラストというのは「輝度のくっきり具合」です。
コントラストを最大に調整すると白と黒にはっきり分かれてくれます。

imagefilter ( $image , IMG_FILTER_CONTRAST , -255 );

と思ってこの行を書いてみたのですが、
どうにも灰色の部分が残ります。
おそらく、エッジ抽出のときにPHPが勝手に作った灰色だろうと思います。

この灰色を消すべく、輝度を調整しました。
(このあたり、泥臭いです。)

imagefilter ( $image , IMG_FILTER_BRIGHTNESS , 20 );
imagefilter ( $image , IMG_FILTER_CONTRAST , -255 );

輝度をいくらか明るくすれば、灰色が白に近づいてくれるので、
二値化したときに白くなってくれるだろうという算段です。
(数学的には二値化のための閾値を変えるという意味になります。)

結果の画像はこの輝度調節の値にかなり左右されるのですが、
何枚か試してみた感じでは、
この値でそこそこうまく動いてくれるっぽいです。

というわけで、二値化した結果はこうなりました。

「トレースしてスキャナで取り込んだ感じ」の完成です。

まとめ

猫の画像を「トレースしてスキャナで取り込んだ感じ」に加工しました。
PHPのコードを示しました。
そのコードで何がおこなわれているのかということに関して、
算数の観点から説明しました。

参考文献とページ作成者プロフィール

参考文献など(ブログのエントリ)
ページ作成者プロフィール

更新履歴

2008/03/01:ページ作成

inserted by FC2 system