判別分析法による閾値の自動計算 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;
}
}