In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

forked from: 判別分析法による閾値の自動計算 WebCam 版

判別分析法による閾値の自動計算 WebCam 版
二値化処理
毎フレーム閾値の自動計算をおこないます。
"c" キーダウンで自動計算の実行停止をトグルで切り替え
参照:http://wonderfl.net/code/7c870faaea1800e28e15a5a4e559e855dbe94a2d(静止画版)
@author Aquioux(Yoshida, Akio)
/**
 * Copyright j_rome ( http://wonderfl.net/user/j_rome )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/6xa4
 */

// forked from Aquioux's 判別分析法による閾値の自動計算 WebCam 版
package {
    import flash.display.Sprite;
    import net.hires.debug.Stats;
    [SWF(width = "465", height = "465", frameRate = "45", backgroundColor = "#000000")]
    
    /**
     * 判別分析法による閾値の自動計算 WebCam 版
     * 二値化処理
     * 毎フレーム閾値の自動計算をおこないます。
     * "c" キーダウンで自動計算の実行停止をトグルで切り替え
     * 参照:http://wonderfl.net/code/7c870faaea1800e28e15a5a4e559e855dbe94a2d(静止画版)
     * @author Aquioux(Yoshida, Akio)
     */
    public class Main extends Sprite {
        public function Main():void {
            Wonderfl.capture_delay(20);

            // model
            try {
                var model:Model = new Model(stage);
            } catch (err:Error) {
                trace(err.message);
                return;
            }
            
            // controller
            var controller:Controller = new Controller(model);
            controller.stage = stage;
            
            // view
            var view:View = new View(model);
            addChild(view);
            
            controller.view = view;
            view.controller = controller;

            addChild(new Stats());
        }
    }
}


    import flash.display.BitmapData;
    import flash.display.Stage;
    import flash.events.ActivityEvent;
    import flash.events.Event;
    import flash.events.EventDispatcher;
    import flash.geom.Matrix;
    import flash.media.Camera;
    import flash.media.Video;
    /**
     * Model
     * @author YOSHIDA, Akio (Aquioux)
     */
    class Model extends EventDispatcher {
        // ---------- パブリックプロパティ ----------
        //
        // View へ渡すデータ
        public function get data():BitmapData { return _data; }
        private var _data:BitmapData;

        
        // ---------- ローカルプロパティ ----------
        //
        private var stage_:Stage;
        private var cameraWidth_:uint;
        private var cameraHeight_:uint;
        private var camera_:Camera;
        private var video_:Video;
        private var matrix_:Matrix;
        
        private var grayscale_:EffectorGrayScale;
        private var smooth_:EffectorSmoothing;
        

        // ---------- パブリックメソッド ----------
        //
        /**
         * コンストラクタ
         * @param    stage    ステージ
         * @param    cw        カメラ幅(省略時はステージ幅)
         * @param    ch        カメラ高(省略時はステージ高)
         */
        public function Model(stage:Stage, cw:uint = 0, ch:uint = 0) {
            // 引数の処理
            stage_ = stage;
            var w:uint = stage_.stageWidth;
            var h:uint = stage_.stageHeight;
            cameraWidth_  = (cw == 0) ? w : cw;
            cameraHeight_ = (ch == 0) ? h : ch;

            // View へ渡すデータの生成
            _data = new BitmapData(w, h, false, 0x000000);
            
            // 鏡像になるよう、Matrix で左右反転
            var tx:uint = (w - cameraWidth_)  / 2 + cameraWidth_;
            var ty:uint = (h - cameraHeight_) / 2;
            matrix_ = new Matrix( -1, 0, 0, 1, tx, ty);
            
            grayscale_ = new EffectorGrayScale();
            smooth_    = new EffectorSmoothing();
            smooth_.strength = 4;
            
            // カメラ準備
            camera_ = Camera.getCamera();
            if (camera_) {
                // camera のセットアップ
                camera_.setMode(cameraWidth_, cameraHeight_, stage_.frameRate);
                // video のセットアップ
                video_ = new Video(cameraWidth_, cameraHeight_);
                video_.attachCamera(camera_);
                // カメラがアクティブになるまで待つ
                camera_.addEventListener(ActivityEvent.ACTIVITY, activeHandler);
            } else {
                throw new Error("カメラがありません。");
            }
        }
        
        
        // ---------- ローカルメソッド ----------
        //
        // ACTIVITY イベントハンドラ
        private function activeHandler(e:ActivityEvent):void {
            camera_.removeEventListener(ActivityEvent.ACTIVITY, arguments.callee);
            stage_.addEventListener(Event.ENTER_FRAME, enterFrameHandler);
        }
        // ENTER_FRAME イベントハンドラ
        private function enterFrameHandler(event:Event):void {
            _data.draw(video_, matrix_);
            grayscale_.applyEffect(_data);
            smooth_.applyEffect(_data);
            dispatchEvent(new Event(Event.CHANGE));
        }
    }


    import com.bit101.components.Label;
    import com.bit101.components.Panel;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.display.Graphics;
    import flash.display.Sprite;
    import flash.events.Event;
    import flash.events.MouseEvent;
    /**
     * View
     * @author YOSHIDA, Akio (Aquioux)
     */
    class View extends Sprite {
        // ---------- パブリックプロパティ ----------
        //
        // Model の参照
        public function set model(value:Model):void { _model = value; }
        private var _model:Model;
        
        // Controller の参照
        public function set controller(value:Controller):void { _controller = value; }
        private var _controller:Controller;
        
        
        // ---------- ローカルプロパティ ----------
        //
        private var bm_:Bitmap;                    // メイン表示 Bitmap
        private var bmd_:BitmapData;            // メイン表示 Bitmap 用の BitmapData
        private var numOfPixel_:uint;            // bmd_ のピクセル数
        
        private var panel_:Panel;                // ヒストグラムおよび閾値ラインのコンテナ
        private var histLayer_:Sprite;            // ヒストグラム表示レイヤー
        private var thresholdLayer_:Sprite;        // 閾値ライン表示レイヤー
        private var label_:Label;                // 閾値表示ラベル
        private const PANEL_WIDTH:uint  = 256;
        private const PANEL_HEIGHT:uint = 180;
        private const MESSAGE:String = "Toggle switch \"c\" key down,\nAutomatic/Stop calculation of threshold.\n\n";
        
        private var isAutoThreshold_:Boolean = true;
        private var prevHist_:Vector.<Number> = new Vector.<Number>(256, true);
        private var prevThreshold_:uint;

        private var binarize_:EffectorBinarization;

        
        // ---------- パブリックメソッド ----------
        //
        /**
         * コンストラクタ
         * @param    model    Model
         */
        public function View(model:Model) {
            _model = model;
            addEventListener(Event.ADDED_TO_STAGE, addedToStageHandler);
        }
        
        public function notifyFromController():void {
            isAutoThreshold_ = !isAutoThreshold_;
        }

        // ---------- ローカルメソッド ----------
        //
        private function addedToStageHandler(e:Event):void {
            removeEventListener(Event.ADDED_TO_STAGE, arguments.callee);
            
            // メイン表示
            bm_ = new Bitmap();
            addChild(bm_);
            
            // ヒストグラム、閾値ラインコンテナ
            panel_ = new Panel(this, stage.stageWidth - (PANEL_WIDTH + 10), stage.stageHeight - (PANEL_HEIGHT + 10));
            panel_.setSize(PANEL_WIDTH, PANEL_HEIGHT);
            panel_.buttonMode = true;
            panel_.alpha = 0.75;
            panel_.addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);
            panel_.addEventListener(MouseEvent.MOUSE_UP, mouseUpHandler);
            stage.addEventListener(Event.MOUSE_LEAVE, mouseLeaveHandler);
            
            // ヒストグラム表示レイヤー
            histLayer_ = new Sprite();
            panel_.addChild(histLayer_);
            // 閾値ライン表示レイヤー
            thresholdLayer_ = new Sprite();
            panel_.addChild(thresholdLayer_);
            // 閾値表示ラベル
            label_ = new Label(panel_, 5, 5);
            
            // エフェクタ
            binarize_ = new EffectorBinarization();

            // Model からのイベントを捕捉(初回)
            _model.addEventListener(Event.CHANGE, firstChangeHandler);
        }
        
        // Model から Event.CHANGE が発行されたときの処理(初回)
        private function firstChangeHandler(e:Event):void {
            _model.removeEventListener(Event.CHANGE, arguments.callee);

            bmd_ = _model.data;
            // BitmapData のピクセル数取得
            numOfPixel_ = bmd_.width * bmd_.height;
            changeHandler(null);
            
            // Model からのイベントを捕捉(初回)
            _model.addEventListener(Event.CHANGE, changeHandler);
        }
        // Model から Event.CHANGE が発行されたときの処理(2回目以降)
        private function changeHandler(e:Event):void {
            bmd_ = _model.data;
            
            if (isAutoThreshold_) {
                // ヒストグラム取得
                var hist:Vector.<Vector.<Number>> = bmd_.histogram();
                prevHist_ = hist[0];
                // 閾値の自動計算
                var threshold:uint = calcThreshold(hist[0], numOfPixel_);
                prevThreshold_ = threshold;
            }

            // 表示 bitmapData の更新
            changeThreshold(prevThreshold_);
            // ヒストグラムの描画
            drawHistogram(prevHist_);
            // 閾値ラインの描画
            drawThreshold(prevThreshold_);
            // 閾値の表示
            label_.text = MESSAGE + "Threshold : " + String(prevThreshold_);
        }
        
        
        // 閾値の判別分析
        private function calcThreshold(hist:Vector.<Number>, numOfPixel_:uint):uint {
            var maxSeparability:Number  = 0;    // 最大分離値を待避させる変数
            var maxDegree:uint = 0;                // そのときの階調を待避させる変数
            for (var i:int = 1; i < 255; i++) {
                // 1~254 を閾値としたときの分離度を計算し、最大値を待避する
                var Separability:Number = calcSeparability(i, hist, numOfPixel_);
                if (Separability > maxSeparability) {
                    maxSeparability = Separability;
                    maxDegree       = i;
                }
            }
            return maxDegree;
        }
        // 分離度の計算
        private function calcSeparability(threshold:uint, hist:Vector.<Number>, numOfPixel_:uint):Number {
            var i:uint;                                // ループカウンター
            var num1:uint = 0, num2:uint = 0;        // 各領域の画素数
            var con1:Number = 0, con2:Number = 0;    // 各領域の濃度(濃度平均値)
            var con:Number = 0;                        // 濃度中間値
            var dis1:Number, dis2:Number;            // 分散計算用
            var within:Number = 0;                    // クラス内分散値
            var between:Number = 0;                    // クラス間分散値
            
            // 二つの領域の画素数と濃度を計算
            for (i = 0; i < threshold; i++) {
                num1 += hist[i];
                con1 += i * hist[i];
            }
            for (i = threshold; i < 256; i++) {
                num2 += hist[i];
                con2 += i * hist[i];
            }
            con = (con1 + con2) / numOfPixel_;    // 濃度中間値
            con1 /= num1;    // 領域1の濃度平均値
            con2 /= num2;    // 領域2の濃度平均値

            if (num1 == 0 || num2 == 0) return 0;
            
            // 分散を計算
            // クラス内分散
            for (i = 0; i < threshold; i++) {
                dis1 = i - con1;
                within += dis1 * dis1 * hist[i];
            }
            for (i = threshold; i < 256; i++) {
                dis2 = i - con2;
                within += dis2 * dis2 * hist[i];
            }
            within /= numOfPixel_;
            // クラス間分散
            for (i = 0; i < threshold; i++) {
                dis1 = con - con1;
                between += dis1 * dis1 * hist[i];
            }
            for (i = threshold; i < 256; i++) {
                dis2 = con - con2;
                between += dis2 * dis2 * hist[i];
            }
            between /= numOfPixel_;
            
            return between / within;
        }
        
        // 閾値更新による BitmapData の更新
        private function changeThreshold(value:uint):void {
            var bmd:BitmapData = bmd_.clone();
            binarize_.threshold = value;
            binarize_.applyEffect(bmd);
            bm_.bitmapData = bmd;
            bm_.smoothing = true;
        }
        
        // ヒストグラムの描画
        private function drawHistogram(hist:Vector.<Number>):void {
            var numMax:uint = 0;
            for (var i:int = 0; i < 256; i++) {
                numMax = Math.max(numMax, hist[i]);
            }
            var rate:Number = numMax / PANEL_HEIGHT * 2;
            
            var g:Graphics = histLayer_.graphics;
            g.clear();
            g.beginFill(0xFFCC00);
            for (i = 0; i < 256; i++) {
                var num:uint = hist[i] / rate;
                g.drawRect(i, PANEL_HEIGHT - num, 1, num);
            }
            g.endFill();
        }

        // 閾値ラインの描画
        private function drawThreshold(value:uint):void {
            var g:Graphics = thresholdLayer_.graphics;
            g.clear();
            g.lineStyle(0, 0x000000);
            g.moveTo(value, 0);
            g.lineTo(value, PANEL_HEIGHT);
        }
        
        private function mouseDownHandler(e:MouseEvent):void {
            panel_.startDrag();
        }
        private function mouseUpHandler(e:MouseEvent):void {
            panel_.stopDrag();
        }
        private function mouseLeaveHandler(e:Event):void {
            mouseUpHandler(null);
        }
    }


    import flash.display.Stage;
    import flash.events.KeyboardEvent;
    /**
     * Controller
     * @author YOSHIDA, Akio (Aquioux)
     */
    class Controller {
        // ---------- パブリックプロパティ ----------
        //
        // Model の参照
        public function set model(value:Model):void { _model = value; }
        private var _model:Model;

        // View の参照
        public function set view(value:View):void { _view = value; }
        private var _view:View;
        
        // stage
        public function set stage(value:Stage):void {
            _stage = value;
            _stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler);
        }
        private var _stage:Stage;
        

        // ---------- ローカルプロパティ ----------
        //


        // ---------- パブリックメソッド ----------
        //
        /**
         * コンストラクタ
         * @param    model    Model
         */
        public function Controller(model:Model) {
        }
        

        // ---------- ローカルメソッド ----------
        //
        // キーボードイベントのイベントハンドラ
        private function keyDownHandler(e:KeyboardEvent):void {
            if (e.keyCode == 67) _view.notifyFromController();
        }
    }


    import flash.display.BitmapData;
    import flash.geom.Point;
    /**
     * BitmapData エフェクト用抽象クラス
     * @author YOSHIDA, Akio
     */
    class AbstractEffector {
        /*
         * BitmapData.applyFilter で destPoint として使用する Point オブジェクト
         */
        protected const ZERO_POINT:Point = new Point(0, 0);
        
        /*
         * コンストラクタ
         */
        public function AbstractEffector() {}
        
        /*
         * 効果の適用
         * @param    value    効果をかける BitmapData
         */
        public function applyEffect(value:BitmapData):BitmapData {
            return effect(value);
        }
        
        /*
         * 効果内容、具体的なコードはサブクラスで定義する
         * @param    value    効果をかける BitmapData
         */
        protected function effect(value:BitmapData):BitmapData {
            return value;
        }
    }


    import flash.display.BitmapData;
    import flash.geom.Rectangle;
    /**
     * 二値化
     * 閾値より大きな値のピクセルは白、それ以外は黒に置き換える
     * @author YOSHIDA, Akio (Aquioux)
     */
    class EffectorBinarization extends AbstractEffector {
        
        public function set threshold(value:int):void {
            if (value < 0)   value = 0;
            if (value > 255) value = 255;
            _threshold = value;
        }
        private var _threshold:int = 127;    // 閾値:0 ~ 255
        
        /*
         * 二値化実行
         * @param    value    効果をかける BitmapData
         */
        override protected function effect(value:BitmapData):BitmapData {
            var cloneBitmapData:BitmapData = new BitmapData(value.width, value.height);
            var rect:Rectangle = cloneBitmapData.rect;
            cloneBitmapData = value.clone();
            value.fillRect(rect, 0xFF000000);
            value.threshold(cloneBitmapData, rect, ZERO_POINT, ">", _threshold, 0xFFFFFFFF, 0x000000FF, false);
            return value;
        }
    }


    import flash.display.BitmapData;
    import flash.filters.ColorMatrixFilter;
    /**
     * ColorMatrixFilter による BitmapData のグレイスケール化(NTSC 系加重平均による)
     * 参考:Foundation ActionScript 3.0 Image Effects(P106)
     *         http://www.amazon.co.jp/gp/product/1430218711?ie=UTF8&tag=laxcomplex-22
     * @author YOSHIDA, Akio (Aquioux)
     */
    class EffectorGrayScale extends AbstractEffector {
        // ColorMatrixFilter
        private const GRAYSCALE_MATRIX:Array = [
            /**/
            0.3, 0.59, 0.11, 0, 0,
            0.3, 0.59, 0.11, 0, 0,
            0.3, 0.59, 0.11, 0, 0,
            0,   0,    0,    1, 0
        ];
        private const GRAYSCALE_FILTER:ColorMatrixFilter = new ColorMatrixFilter(GRAYSCALE_MATRIX);
        
        
        /*
         * グレイスケール実行
         * @param    value    効果をかける BitmapData
         */
        override protected function effect(value:BitmapData):BitmapData {
            value.applyFilter(value, value.rect, ZERO_POINT, GRAYSCALE_FILTER);
            return value;
        }
    }


    import flash.display.BitmapData;
    import flash.filters.BitmapFilterQuality;
    import flash.filters.BlurFilter;
    /**
     * BlurFilter による平滑化
     * @author YOSHIDA, Akio (Aquioux)
     */
    class EffectorSmoothing extends AbstractEffector {
        /*
         * ぼかしの量
         * @param    value    数値
         */
        public function set strength(value:Number):void {
            blurFilter.blurX = blurFilter.blurY = value;
        }
        /*
         * ぼかしの質
         * @param    value    数値
         */
        public function set quality(value:int):void {
            blurFilter.quality = value;
        }
        // ブラーフィルタ
        private var blurFilter:BlurFilter;


        public function EffectorSmoothing() {
            blurFilter = new BlurFilter(2, 2, BitmapFilterQuality.MEDIUM);
        }
        
        /*
         * 平滑化実行
         * @param    value    効果をかける BitmapData
         */
        override protected function effect(value:BitmapData):BitmapData {
            value.applyFilter(value, value.rect, ZERO_POINT, blurFilter);
            return value;
        }
    }