Forked from: Aquioux's 判別分析法による閾値の自動計算 WebCam 版 diff:1 forked from: 判別分析法による閾値の自動計算 WebCam 版 sukekiyo.pri.. forked:0favorite:0lines:322license : MIT License modified : 2011-03-28 17:02:11 Embed Tweet // 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; } } Code Fullscreen Preview Fullscreen