AS3 でヒストグラムを作る (4) - 周辺分布

最近、画像処理について興味をもって勉強しているのですが、ディジタル画像処理の基礎と応用―基本概念から顔画像認識まで (ディジタル信号処理シリーズ) という本を読んでいます。その中で、「周辺分布」の図が面白かったので AS3 で実装してみました。

周辺分布って何?

周辺分布というのは、2値化した画像においてドットの登場頻度を X軸、Y軸のそれぞれについて集計したもののようです。パターン認識の特徴ベクトルとして利用されるらしいです。

サンプルはこんな感じ。

イメージとしては、画像を縦と横のそれぞれにべちゃっと圧縮した感じ。例えば、「し」の縦棒の下は長くなってますよね。

ActionScript で作ってみた

で、これをリアルタイムで解析できたら楽しそうだったので、作ってみたのがこちらの Flash。

  • 枠の中でドラッグして線を描画できます
  • 枠の外でクリックすると絵を消去できます

適当にマウスを動かすと、スプラッター映画の血痕のようにヒストグラムが延びていくのが愉快です。

技術的にはたいしたことしてません。例によって threshold でピクセル数を集計しているだけです。

本の紹介

ちなみに、この ディジタル画像処理の基礎と応用―基本概念から顔画像認識まで (ディジタル信号処理シリーズ) という本、C# を例に説明はしてますが、実践的な画像処理の情報が詰まってるので、なかなか楽しいです。フィルタやラベリングに始まり、パターン認識からニューラルネット、顔認識などの幅広い内容をサンプルコードつきで扱ってます。他の本を読んだわけではないので、「これがベストだ!」と自信をもって言い切ることはできませんが、入門には分かりやすくていい具合です。

それはそうと、誰か「ActionScript で実践する画像処理」という本を書かないかなぁ。Graphics クラスの初歩から始まり、パーティクルアニメーションを解説したり、BitmapData を使った加工ネタを取り上げたあとに Web カメラや FLV 動画の加工に応用、さらには PV3D で3次元…などなど、魅力的な内容になって面白そうなのに。だったらお前が書けよ、といわれそうだけど、ご覧のように画像処理については初心者なのでいかんとも…。

今回のソースコードは以下に(89行)。

package {
    import flash.display.*;
    import flash.events.Event;
    import flash.geom.*;

    [SWF(width="400", height="400")]
    public class Histogram4 extends Sprite {
        private var canvas:Sprite;
        private static var SIZE:uint = 200;

        public function Histogram4() {
            stage.align = "TL";
            stage.scaleMode = "noScale";
            
            // 描画用キャンバス準備
            canvas = new Sprite();
            addChild(canvas);
            initCanvas();

            // ドラッグで描画
            var dragging:Boolean = false;
            var count:int = 0; // 間引き用のカウンタ
            canvas.addEventListener("mouseDown", function(e:*):void {
                canvas.graphics.moveTo(mouseX, mouseY);
                canvas.graphics.lineTo(mouseX, mouseY);
                dragging = true;
            });
            canvas.addEventListener("mouseMove", function(e:*):void {
                if(dragging) {
                    count = count < 12 ? count + 1 : 0;
                    canvas.graphics.lineTo(mouseX, mouseY);

                    // 12回に1回、ヒストグラムを更新する
                    if(count == 0) {
                        updateHistogram();
                    }
                }
            });
            canvas.addEventListener("mouseUp", function(e:*):void {
                dragging = false;
            });

            // キャンバスの外でクリックしたらクリア
            stage.addEventListener("click", function(event:Event):void {
                if(event.target == stage) {
                    initCanvas();
                }
            });
        }

        // キャンバスを初期化する
        private function initCanvas():void {
            canvas.graphics.clear();
            canvas.graphics.lineStyle(1, 0x808080);
            canvas.graphics.beginFill(0xffffff);
            canvas.graphics.drawRect(0, 0, SIZE, SIZE);
            canvas.graphics.endFill();
            canvas.graphics.lineStyle(2, 0);
            updateHistogram();
        }

        // ヒストグラムを描画する
        private function updateHistogram():void {
            graphics.clear();

            // BitmapData に描画
            var bmd:BitmapData = new BitmapData(SIZE, SIZE);
            bmd.draw(canvas);

            var num:uint;
            graphics.lineStyle(1, 0);

            // ヒストグラム分析&描画
            for(var i:int = 0; i < SIZE; i++) {
                // X 成分のヒストグラム
                num = bmd.threshold(bmd, new Rectangle(0, i, bmd.width, 1), new Point(), "==", 0xff000000);
                graphics.moveTo(SIZE, i);
                graphics.lineTo(SIZE + num, i);

                // Y 成分のヒストグラム
                num = bmd.threshold(bmd, new Rectangle(i, 0, 1, bmd.height), new Point(), "==", 0xff000000);
                graphics.moveTo(i, SIZE);
                graphics.lineTo(i, SIZE + num);
            }

            bmd.dispose();
        }
   }
}