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

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

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

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;
		}
	}