package { import flash.display.Sprite; import net.hires.debug.Stats; [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "#FFFFFF")] /** * [WebCam] Manic Monday * 説明:http://aquioux.blog48.fc2.com/blog-entry-710.html * @author Aquioux(Yoshida, Akio) */ public class Main extends Sprite { public function Main():void { Wonderfl.capture_delay(30); // model try { var model:Model = new Model(stage); } catch (err:Error) { trace(err.message); return; } // view var view:View = new View(model); addChild(view); // controller var controller:Controller = new Controller(model); //controller.view = view; addChild(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.geom.Point; import flash.geom.Rectangle; 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 smooth_:Smooth; // 平滑化 private var grayscale_:GrayScale; // グレイスケール private var nega_:Negative; // 反転 private var binarize_:Binarize; // 二値 private var divider_:SubDivider; // 平面分割 private var gray_:BitmapData; private var rect_:Rectangle; private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT; private var isNegative_:Boolean = false; // ---------- パブリックメソッド ---------- // /** * コンストラクタ * @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, true, 0x00000000); gray_ = new BitmapData(w, h, false, 0x000000); rect_ = _data.rect; // 鏡像になるよう、Matrix で左右反転 var tx:uint = (w - cameraWidth_) / 2 + cameraWidth_; var ty:uint = (h - cameraHeight_) / 2; matrix_ = new Matrix( -1, 0, 0, 1, tx, ty); // カメラ準備 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("カメラがありません。"); } // エフェクタ smooth_ = new Smooth(); // 平滑化 smooth_.strength = 4; grayscale_ = new GrayScale(); // グレイスケール nega_ = new Negative(); // 反転 binarize_ = new Binarize(); // 二値 binarize_.auto = true; divider_ = new SubDivider(); // 平面分割 } /** * isNegative_ の切り替え * Controller とのインターフェース */ public function notifyFromController():void { isNegative_ = !isNegative_; } // ---------- ローカルメソッド ---------- // // 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_); smooth_.applyEffect(_data); // 平滑化 grayscale_.applyEffect(_data); // グレイスケール gray_.copyPixels(_data, rect_, ZERO_POINT); if (isNegative_) nega_.applyEffect(gray_); // 反転 binarize_.applyEffect(gray_); // 二値 _data.fillRect(rect_, 0x00000000); divider_.applyEffect(rect_, gray_, _data, 10, 10, binarize_.threshold); dispatchEvent(new Event(Event.CHANGE)); } } import flash.display.Bitmap; import flash.display.BitmapData; import flash.events.Event; import flash.filters.DropShadowFilter; import flash.geom.Point; import flash.geom.Rectangle; /** * View * @author YOSHIDA, Akio (Aquioux) */ class View extends Bitmap { // ---------- パブリックプロパティ ---------- // // Model の参照 public function set model(value:Model):void { _model = value; } private var _model:Model; // ---------- ローカルプロパティ ---------- // private var filter:DropShadowFilter; // ---------- パブリックメソッド ---------- // /** * コンストラクタ * @param model Model */ public function View(model:Model) { _model = model; _model.addEventListener(Event.CHANGE, changeHandler); filter = new DropShadowFilter(5, 45, 0x666666, 1, 8, 8); filters = [filter]; } // ---------- ローカルメソッド ---------- // // Model から Event.CHANGE が発行されたときの処理 private function changeHandler(e:Event):void { bitmapData = _model.data; } } import flash.display.Sprite; import flash.events.Event; import flash.events.KeyboardEvent; import flash.events.MouseEvent; /** * Controller * @author YOSHIDA, Akio (Aquioux) */ class Controller extends Sprite { // ---------- パブリックプロパティ ---------- // // Model の参照 public function set model(value:Model):void { _model = value; } private var _model:Model; // ---------- パブリックメソッド ---------- // /** * コンストラクタ * @param model Model */ public function Controller(model:Model) { _model = model; addEventListener(Event.ADDED_TO_STAGE, addToStageHandler); } // ---------- ローカルメソッド ---------- // // イベントハンドラ // 自分がステージに登録されたとき private function addToStageHandler(e:Event):void { removeEventListener(Event.ADDED_TO_STAGE, arguments.callee); stage.addEventListener(KeyboardEvent.KEY_DOWN, keyDownHandler); } // イベントハンドラ // キーダウンク時 private function keyDownHandler(e:KeyboardEvent):void { if (e.keyCode == 82) _model.notifyFromController(); // "r" キー } } import flash.display.BitmapData; import flash.display.Graphics; import flash.geom.Rectangle; import frocessing.color.ColorHSV; /** * 画面分割(モノクロ) * @author YOSHIDA, Akio (Aquioux) */ class SubDivider { // ---------- ローカルプロパティ ---------- // // 共用 Rectangle private var nextRect_:Rectangle = new Rectangle(); // 4分木再帰処理に使用 private var drawRect_:Rectangle = new Rectangle(); // 描画に使用 // ヒストグラムから濃度の合計値を求める関数 private const GET_BRIGHTNESS:Function = EffectorUtils.getSumOfBrightness1; public var hsv_:ColorHSV = new ColorHSV(); // ---------- パブリックメソッド ---------- // /* * コンストラクタ */ public function SubDivider() { } /* * 効果適用 * @param rect 現在の対象 Rectangle * @param gray 効果対象 BitmapData * @param canvas 描画 BitmapData * @param divideThreshold 分割閾値 * @param minSize 分割サイズ最小値 * @param drawThreshold 描画閾値 */ public function applyEffect(rect:Rectangle, gray:BitmapData, canvas:BitmapData, divideThreshold:uint, minSize:uint, drawThreshold:uint, callInner:Boolean = false):void { // 外部から呼び出された場合、 hsv_ を初期化 if (!callInner) hsv_.h = 0; // 現在のチェック対象矩形の縦または横の長さが指定より小さい場合、再帰を停止 var isDivide:Boolean = rect.width > minSize && rect.height > minSize; // 走査範囲のピクセル数 var numOfPixel:uint = rect.width * rect.height; // 走査範囲のヒストグラム var hist:Vector.<Vector.<Number>> = gray.histogram(rect); // 再帰判定 if (isDivide) { // 濃度の標準偏差 var deviation:Number = getStandardDeviation(hist[0], numOfPixel); if (deviation > divideThreshold) { // 標準偏差が指定より大きい場合、4分木再帰を続行 // Rectangle の x、y、width、height が uint になるようにする // (numOfPixel と齟齬が出ないようにするため) var nextWidth1:uint = rect.width / 2 >> 0; var nextHeight1:uint = rect.height / 2 >> 0; var nextWidth2:uint = rect.width - nextWidth1; var nextHeight2:uint = rect.height - nextHeight1; var left:Number = rect.x; var top:Number = rect.y; var center:Number = left + nextWidth1; var middle:Number = top + nextHeight1; var xs:Vector.<uint> = Vector.<uint>([left, center, left, center]); var ys:Vector.<uint> = Vector.<uint>([top, top, middle, middle]); var ws:Vector.<uint> = Vector.<uint>([nextWidth1, nextWidth2, nextWidth1, nextWidth2]); var hs:Vector.<uint> = Vector.<uint>([nextHeight1, nextHeight1, nextHeight2, nextHeight2]); // 4分木再帰 for (var i:int = 0; i < 4; i++) { nextRect_.x = xs[i]; nextRect_.y = ys[i]; nextRect_.width = ws[i]; nextRect_.height = hs[i]; applyEffect(nextRect_, gray, canvas, divideThreshold, minSize, drawThreshold, true); } } else { // 標準偏差が指定より小さい場合、再帰を停止し、描画をおこなう isDivide = false; } } // 描画 if (!isDivide) draw(rect, hist[0], numOfPixel, canvas, drawThreshold); } // ---------- ローカルメソッド ---------- // // 対象 BitmapData の指定矩形範囲の標準偏差を求める private function getStandardDeviation(hist:Vector.<Number>, numOfPixel:uint):Number { // 平均値 var average:Number = hist[255] * 255 / numOfPixel; // 平均との差の二乗を累積 var diff:Number = 0; var sum:uint = 0; diff = -average; sum += diff * diff * hist[0]; diff = 255 - average; sum += diff * diff * hist[255]; return Math.sqrt(sum / numOfPixel); } // キャンバスに描画 private function draw(rect:Rectangle, hist:Vector.<Number>, numOfPixel:uint, canvas:BitmapData, threshold:uint):void { var sum:uint = GET_BRIGHTNESS(hist); var brightness:uint = sum / numOfPixel; if (brightness < threshold) { drawRect_.x = rect.x + 1; drawRect_.y = rect.y + 1; drawRect_.width = rect.width - 2; drawRect_.height = rect.height - 2; canvas.fillRect(drawRect_, hsv_.value32); hsv_.h += 0.3; } } } import flash.display.BitmapData; /** * BitmapDataEffector 用 interface * @author YOSHIDA, Akio (Aquioux) */ interface IEffector { function applyEffect(value:BitmapData):BitmapData; } import flash.geom.Point; /** * bitmapDataEffector パッケージ内のクラスで共通に使う定数など * @author YOSHIDA, Akio (Aquioux) */ class EffectorUtils { // ---------- パブリックプロパティ ---------- // // BitmapData が備える各種メソッドの destPoint 用 static public const ZERO_POINT:Point = new Point(0, 0); // グレイスケール用の各チャンネルの重みづけ static public const LUM_R:Number = 0.298912; static public const LUM_G:Number = 0.586611; static public const LUM_B:Number = 0.114478; // ---------- パブリックメソッド ---------- // // 一つのチャンネルにおける濃度の平均値を求める(引数のヒストグラムで調整する) static public function getAverageOfBrightness1(hist:Vector.<Number>):uint { var sum:uint = 0; var numOfPixel:uint = 0; for (var i:int = 0; i < 256; i++) { sum += i * hist[i]; numOfPixel += hist[i]; } return sum / numOfPixel >> 0; } // RGB チャンネルにおける濃度の各平均値を求める static public function getAverageOfBrightness3(hist:Vector.<Vector.<Number>>):Vector.<uint> { var rSum:uint = 0; var gSum:uint = 0; var bSum:uint = 0; var numOfPixel:uint = 0; for (var i:int = 0; i < 256; i++) { rSum += i * hist[0][i]; gSum += i * hist[1][i]; bSum += i * hist[2][i]; numOfPixel += hist[0]; } return Vector.<uint>([rSum / numOfPixel >> 0, gSum / numOfPixel >> 0, bSum / numOfPixel >> 0]); } // 一つのチャンネルにおける濃度の平均値を求める(引数のヒストグラムで調整する) static public function getSumOfBrightness1(hist:Vector.<Number>):uint { var sum:uint = 0; for (var i:int = 0; i < 256; i++) { sum += i * hist[i]; } return sum; } // RGB チャンネルにおける濃度の各平均値を求める static public function getSumOfBrightness3(hist:Vector.<Vector.<Number>>):Vector.<uint> { var rSum:uint = 0; var gSum:uint = 0; var bSum:uint = 0; for (var i:int = 0; i < 256; i++) { rSum += i * hist[0][i]; gSum += i * hist[1][i]; bSum += i * hist[2][i]; } return Vector.<uint>([rSum, gSum, bSum]); } } import flash.display.BitmapData; import flash.filters.BitmapFilterQuality; import flash.filters.BlurFilter; import flash.geom.Point; /** * BlurFilter による平滑化 * @author YOSHIDA, Akio (Aquioux) */ class Smooth implements IEffector { // ---------- パブリックプロパティ ---------- // /* * ぼかしの強さ * @param value 数値 */ public function set strength(value:Number):void { filter_.blurX = filter_.blurY = value; } /* * ぼかしの質 * @param value 数値 */ public function set quality(value:int):void { filter_.quality = value; } // ---------- ローカルプロパティ ---------- // private var filter_:BlurFilter; // ブラーフィルタ private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT; // ---------- パブリックメソッド ---------- // /* * コンストラクタ */ public function Smooth() { filter_ = new BlurFilter(2, 2, BitmapFilterQuality.MEDIUM); } /* * 効果適用 * @param value 効果対象 BitmapData */ public function applyEffect(value:BitmapData):BitmapData { value.applyFilter(value, value.rect, ZERO_POINT, filter_); return value; } } import flash.display.BitmapData; import flash.filters.ColorMatrixFilter; import flash.geom.Point; /** * 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 GrayScale implements IEffector { // ---------- ローカルプロパティ ---------- // private const R:Number = EffectorUtils.LUM_R; private const G:Number = EffectorUtils.LUM_G; private const B:Number = EffectorUtils.LUM_B; private const MATRIX:Array = [ R, G, B, 0, 0, R, G, B, 0, 0, R, G, B, 0, 0, 0, 0, 0, 1, 0 ]; private const FILTER:ColorMatrixFilter = new ColorMatrixFilter(MATRIX); private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT; // ---------- パブリックメソッド ---------- // /* * 効果適用 * @param value 効果対象 BitmapData */ public function applyEffect(value:BitmapData):BitmapData { value.applyFilter(value, value.rect, ZERO_POINT, FILTER); return value; } } import flash.display.BitmapData; import flash.filters.ConvolutionFilter; import flash.geom.Point; /** * ConvolutionFilter による BitmapData の色反転 * 参考:http://www40.atwiki.jp/spellbound/pages/231.html * @author YOSHIDA, Akio (Aquioux) */ class Negative implements IEffector { // ---------- ローカルプロパティ ---------- // private const FILTER:ConvolutionFilter = new ConvolutionFilter(1, 1, [ -1], 1, 255); private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT; // ---------- パブリックメソッド ---------- // /* * 効果適用 * @param value 効果対象 BitmapData */ public function applyEffect(value:BitmapData):BitmapData { value.applyFilter(value, value.rect, ZERO_POINT, FILTER); return value; } } import flash.display.BitmapData; import flash.geom.Point; import flash.geom.Rectangle; /** * 二値化 * 閾値より大きな値のピクセルは白、それ以外は黒に置き換える * @author YOSHIDA, Akio (Aquioux) */ class Binarize implements IEffector { // ---------- パブリックプロパティ ---------- // // 閾値 public function get threshold():int { return _threshold; } 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 // 判別分析をするか否か public function get auto():Boolean { return _auto; } public function set auto(value:Boolean):void { _auto = value; } private var _auto:Boolean = false; // ---------- ローカルプロパティ ---------- // private const ZERO_POINT:Point = EffectorUtils.ZERO_POINT; // ---------- パブリックメソッド ---------- // /* * 効果適用 * @param value 効果対象 BitmapData */ public function applyEffect(value:BitmapData):BitmapData { var bufferBmd:BitmapData = new BitmapData(value.width, value.height); var rect:Rectangle = bufferBmd.rect; bufferBmd = value.clone(); if (_auto) { var hist:Vector.<Vector.<Number>> = value.histogram(); var numOfPixel:uint = rect.width * rect.height; _threshold = calcThresholdRough(hist[2], numOfPixel); } value.fillRect(rect, 0xFF000000); value.threshold(bufferBmd, rect, ZERO_POINT, ">", _threshold, 0xFFFFFFFF, 0x000000FF, false); return value; } // ---------- ローカルメソッド ---------- // // 閾値の判別分析 private function calcThresholdRough(hist:Vector.<Number>, numOfPixel:uint):uint { var total:uint; for (var i:int = 0; i < 256; i++) { total += hist[i] * i; } return total / numOfPixel >> 0; } // 閾値の判別分析 private function calcThresholdStrict(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; } } [WebCam] Manic Monday