Hatena::ブログ(Diary)

flashrod このページをアンテナに追加 RSSフィード

2006-10-15 2値化グレースケールエッジ検出

flashrod2006-10-15

[] AS3で画像処理入門(1) 20:16  AS3で画像処理入門(1)を含むブックマーク

MSDNアルゴリズム入門がある。

Microsoft DreamSpark

Microsoft DreamSpark

C#だけど分かりやすくて良い。これを参考にしてC#からAS3への移植なのだ。

2値化

まずは2値化から。変換はソースビットマップと同じ大きさの新しいビットマップを作って戻すことにする。MSDNのサンプルを参考に、ソースRGBのr,g,b値の平均が閾値以下かどうかで黒白に塗り分ける画像処理はこんなの。

        public function threshold_filter(s:BitmapData):BitmapData {
            var d:BitmapData = new BitmapData(s.width, s.height);
            for (var x:int = 0; x < s.width; x++) {
                for (var y:int = 0; y < s.height; y++) {
                    var c:int = s.getPixel(x, y);
                    c = (((c >> 16) & 255) + ((c >> 8) & 255) + (c & 255)) / 3;
                    if (c <= threshold) {
                        d.setPixel(x, y, Color.BLACK);
                    } else {
                        d.setPixel(x, y, Color.WHITE);
                    }
                }
            }
            return d;
        }

こんなことしなくても、AS3のBitmapDataにはthreshold()メソッドがあるので、forループを自分で回さなくて済む。

        // BitmapData#threshold()メソッド使用版
        public function threshold_filter(s:BitmapData):BitmapData {
            var d:BitmapData = new BitmapData(s.width, s.height);
            var r:Rectangle = new Rectangle(0, 0, s.width, s.height);
            d.fillRect(r, 0xFFFFFFFF); // 不透明白で塗りつぶす
            // 閾値以下を不透明黒にする
            d.threshold(s, r, new Point(0, 0), "<=", threshold, 0xFF000000, 255, false);
            return d;
        }

「以下」なんて条件を文字列 "<=" で指定するところなんて素敵。

グレースケール

次はエッジ検出なんだけど、まず前処理としてグレースケール化をやる。MSDNのサンプルを出来るだけ忠実にAS3に書き換えるとこうなる。

        public function grayscale_filter(s:BitmapData):BitmapData {
            var d:BitmapData = new BitmapData(s.width, s.height);
            for (var x:int = 0; x < s.width; x++) {
                for (var y:int = 0; y < s.height; y++) {
                    var c:int = s.getPixel(x, y);
                    c = (((c >> 16) & 255) + ((c >> 8) & 255) + (c & 255)) / 3; // RGB の平均値
                    d.setPixel(x, y, (c << 16) | (c << 8) | c); // 灰色に設定
                }
            }
            return d;
        }

これもAS3だとColorMatrixFilterを使えば applyFilter() 一発で済む。

        // ColorMatrixFilter 使用版
        public function grayscale_filter(s:BitmapData):BitmapData {
            var d:BitmapData = new BitmapData(s.width, s.height);
            d.applyFilter(s, new Rectangle(0, 0, s.width, s.height), new Point(0, 0),
                          new ColorMatrixFilter([1/3, 1/3, 1/3, 0, 0,
                                                 1/3, 1/3, 1/3, 0, 0,
                                                 1/3, 1/3, 1/3, 0, 0,
                                                 0, 0, 0, 255, 0]));
            return d;
        }

4行5列行列がちょっと不思議だけど、要するに以下のことをやってるだけなのだ。

  • デスティネーションR ← (ソースR + ソースG + ソースB)/3
  • デスティネーションG ← (ソースR + ソースG + ソースB)/3
  • デスティネーションB ← (ソースR + ソースG + ソースB)/3

ちなみにネガポジ反転もこの色変換行列でできる。

                          new ColorMatrixFilter([-1,  0,  0, 0, 255,
                                                  0, -1,  0, 0, 255,
                                                  0,  0, -1, 0, 255,
                                                  0,  0,  0, 255, 0])

単純だけど奥が深そうなかんじ。

エッジ検出

さてようやく本題のエッジ検出なのだ。MSDNにある「ラプラシアン 2 を用いた2 次微分」をできるだけそのまま素直にAS3化するとこうなる。

        public function laplacian_filter(s:BitmapData):BitmapData {
            var d:BitmapData = new BitmapData(s.width, s.height);
            var l:Array = [-1, -1, -1,
                           -1, +8, -1,
                           -1, -1, -1]; // ラプラシアンフィルタ
            for (var x:int = 0; x < s.width; x++) {
                for (var y:int = 0; y < s.height; y++) {
                    var c:int = l[0] * (s.getPixel(x - 1, y - 1) & 255)
                        + l[1] * (s.getPixel(x, y - 1) & 255)
                        + l[2] * (s.getPixel(x + 1, y - 1) & 255)
                        + l[3] * (s.getPixel(x - 1, y) & 255)
                        + l[4] * (s.getPixel(x, y) & 255)
                        + l[5] * (s.getPixel(x + 1, y) & 255)
                        + l[6] * (s.getPixel(x - 1, y + 1) & 255)
                        + l[7] * (s.getPixel(x, y + 1) & 255)
                        + l[8] * (s.getPixel(x + 1, y + 1) & 255);
                    c *= amp; // 出力レベルの設定
                    if (c < 0) { // c が負の場合正の値に変換
                        c = -c;
                    }
                    if (c > 255) { // cが255より大きい場合c を 255 に設定
                        c = 255;
                    }
                    d.setPixel(x, y, (c << 16) | (c << 8) | c);
                }
            }
            return d;
        }

cが負の場合って必要なのかな? AS3だとNumberにしとけばいいような気がするのだけど。

これも例によってAS3だとConvolutionFilterを使えば一発で済む。

        // ConvolutionFilter使用版
        public function laplacian_filter(s:BitmapData):BitmapData {
            var d:BitmapData = new BitmapData(s.width, s.height);
            var l:Array = [-1, -1, -1,
                           -1, +8, -1,
                           -1, -1, -1]; // ラプラシアンフィルタ
            d.applyFilter(s, new Rectangle(0, 0, s.width, s.height), new Point(0, 0),
                          new ConvolutionFilter(3, 3, l));
            return d;
        }

このやりかただと、ストリーミングSIMD拡張なCPUで高速化が期待できるとAS3ランゲージリファレンスに書いてある。

ノイズ除去

MSDNの「モノクロ画像に対してメディアンフィルタを利用した雑音処理」というものだ。

        public function median_smooth_filter(s:BitmapData):BitmapData {
            var d:BitmapData = new BitmapData(s.width, s.height);
            var a:Array = new Array(9);
            for (var x:int = 0; x < s.width; x++) {
                for (var y:int = 0; y < s.height; y++) {
                    a[0] = s.getPixel(x - 1, y - 1) & 255;
                    a[1] = s.getPixel(x - 1, y) & 255;
                    a[2] = s.getPixel(x - 1, y + 1) & 255;
                    a[3] = s.getPixel(x, y - 1) & 255;
                    a[4] = s.getPixel(x, y) & 255;
                    a[5] = s.getPixel(x, y + 1) & 255;
                    a[6] = s.getPixel(x + 1, y - 1) & 255;
                    a[7] = s.getPixel(x + 1, y) & 255;
                    a[8] = s.getPixel(x + 1, y + 1) & 255;
                    a.sort(Array.NUMERIC); // ソートして
                    var c:int = a[4]; // 真ん中を取る
                    d.setPixel(x, y, (c << 16) | (c << 8) | c); // 中央値による色の設定
                }
            }
            return d;
        }

MSDNの例の Median_sub は配列バブルソートしてるけど、AS3だとa.sort(Array.NUMERIC)一発で済む。まーでも要素8個くらいのソートならどうでもいい。

ソートして真ん中を取る、というのと同じことをAS3の既存のフィルタでやる方法を探したのだけど、いい手が見つからなかった。中央値じゃなくて平均値なら ConvolutionFilter が使えたのに。

さてこんな感じで前回のカメラからの映像を画像処理してみると上に貼り付けたようなものができる。もうちょっとパラメータを実行時に変更できるようにしたいな。