package { import flash.display.Sprite; [SWF(width = "465", height = "465", frameRate = "60", backgroundColor = "#808080")] /** * 二値化処理 * 判別分析法による閾値の自動計算 * 解説:http://aquioux.blog48.fc2.com/blog-entry-696.html * @author Aquioux(Yoshida, Akio) */ public class Main extends Sprite { public function Main():void { // model var model:Model = new Model(); // controller var controller:Controller = new Controller(model); addChild(controller); // _view var view:View = new View(model); addChild(view); // 参照のセット controller.view = view; view.controller = controller; } } } import com.adobe.images.PNGEncoder; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Loader; import flash.display.Stage; import flash.events.Event; import flash.events.EventDispatcher; import flash.geom.Matrix; import flash.net.FileReference; import flash.utils.ByteArray; /** * Model * @author YOSHIDA, Akio (Aquioux) */ class Model extends EventDispatcher { // ---------- パブリックメンバ ---------- // // 表示する縦 or 横の長い方の最大値 static public const MAX_SIZE:uint = 256; // View へ渡すデータ public function get data():BitmapData { return _data; } private var _data:BitmapData; // ---------- ローカルメンバ ---------- // // ファイルロードに関わる private var fileRef_:FileReference; private var loader_:Loader; // エフェクタ private var grayscale_:EffectorGrayScale; private var smoothing_:EffectorSmoothing; // ---------- パブリックメソッド ---------- // /** * コンストラクタ */ public function Model() { fileRef_ = new FileReference(); loader_ = new Loader(); grayscale_ = new EffectorGrayScale(); smoothing_ = new EffectorSmoothing(); } /** * 画像ロード step 1 ファイル選択 * Controller 向けに開かれたメソッド */ public function loadHandler():void { fileRef_.addEventListener(Event.SELECT, load2Handler); fileRef_.browse(); } // ---------- ローカルメソッド ---------- // // 画像ロード step 2 ファイル読込 private function load2Handler(e:Event):void { fileRef_.removeEventListener(Event.SELECT, arguments.callee); fileRef_.addEventListener(Event.COMPLETE, load3Handler); fileRef_.load(); } // 画像ロード step 3 ファイル読込完了 private function load3Handler(e:Event):void { fileRef_.removeEventListener(Event.COMPLETE, arguments.callee); loader_.loadBytes(fileRef_.data); loader_.contentLoaderInfo.addEventListener(Event.COMPLETE, load4Handler); } // 画像ロード step 4 ファイル読込後の処理 private function load4Handler(e:Event):void { loader_.contentLoaderInfo.removeEventListener(Event.COMPLETE, arguments.callee); update(Bitmap(loader_.content).bitmapData); } // _data のアップデート private function update(bmd:BitmapData):void { // 読み込んだ画像ファイルの BitmapData の処理 var w:uint = bmd.width; var h:uint = bmd.height; var scale:Number = MAX_SIZE / w; if (_data) _data.dispose(); _data = new BitmapData(w * scale, h * scale); _data.draw(bmd, new Matrix(scale, 0, 0, scale)); // エフェクト適用 grayscale_.applyEffect(_data); smoothing_.applyEffect(_data); // CHANGE イベント発行 dispatchEvent(new Event(Event.CHANGE)); } } import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.BlendMode; import flash.display.Graphics; import flash.display.Sprite; import flash.events.Event; /** * 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; // ---------- ローカルメンバ ---------- // // 各種 DisplayObject 等 private var bm_:Bitmap; // メイン表示 Bitmap private var bmd_:BitmapData; // メイン表示 Bitmap 用の BitmapData private var histLayer_:Sprite; // ヒストグラム表示レイヤー private var thresholdLayer_:Sprite; // 閾値ライン表示レイヤー // エフェクタ private var binarize_:EffectorBinarization; // ---------- パブリックメソッド ---------- // /** * コンストラクタ * @param model Model */ public function View(model:Model) { _model = model; _model.addEventListener(Event.CHANGE, changeHandler); // メイン表示 bm_ = new Bitmap(); addChild(bm_); // ヒストグラム表示レイヤー histLayer_ = new Sprite(); addChild(histLayer_); // 閾値ライン表示レイヤー thresholdLayer_ = new Sprite(); thresholdLayer_.blendMode = BlendMode.INVERT; addChild(thresholdLayer_); // エフェクタ binarize_ = new EffectorBinarization(); } // Controller からの通知 public function notifyFromController(value:uint):void { // 閾値ラインの描画 drawThreshold(value); // 表示 bitmapData の更新 changeThreshold(value); } // ---------- ローカルメソッド ---------- // // Model から Event.CHANGE が発行されたときの処理(ロード時の処理) private function changeHandler(e:Event):void { bmd_ = _model.data; bm_.bitmapData = bmd_; bm_.smoothing = true; bm_.x = uint((stage.stageWidth - bm_.width) / 2); bm_.y = uint((stage.stageHeight - bm_.height) / 2); // ヒストグラム取得 var hist:Vector.<Vector.<Number>> = bmd_.histogram(); // BitmapData のピクセル数取得 var numOfPixel:uint = bmd_.width * bmd_.height; // 閾値の自動計算 var threshold:uint = calcThreshold(hist[0], numOfPixel); // 表示 bitmapData の更新 changeThreshold(threshold); // ヒストグラムの描画 drawHistogram(hist[0]); // 閾値ラインの描画 drawThreshold(threshold); // 表示 // メイン画像 bm_.x = uint((stage.stageWidth - bm_.width) / 2); bm_.y = uint((stage.stageHeight - bm_.height) / 2); // ヒストグラム histLayer_.x = bm_.x; histLayer_.y = bm_.y; // 閾値ライン thresholdLayer_.x = bm_.x; thresholdLayer_.y = bm_.y; // contoller との通信(sliderの位置調整) _controller.notifyFromView(bm_.x, bm_.y + bm_.height, threshold); } // 閾値の判別分析 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 h:Number = bm_.height; var rate:Number = numMax / h * 2; var g:Graphics = histLayer_.graphics; g.clear(); g.beginFill(0xFFCC00, 0.5); for (i = 0; i < 256; i++) { var num:uint = hist[i] / rate; g.drawRect(i, h - 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, bm_.height); } } import com.bit101.components.HSlider; import com.bit101.components.Label; import com.bit101.components.PushButton; import flash.display.Sprite; import flash.events.Event; import flash.events.MouseEvent; import flash.geom.ColorTransform; /** * Controller * @author YOSHIDA, Akio (Aquioux) */ class Controller extends Sprite { // ---------- パブリックメンバ ---------- // // 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; // ---------- ローカルメンバ ---------- // private var loadButton_:PushButton; // ロードボタン private var slider_:HSlider; // スライダー private var label_:Label; // スライダー用ラベル // ---------- パブリックメソッド ---------- // /** * コンストラクタ * @param model Model */ public function Controller(model:Model) { _model = model; // ロードボタン loadButton_ = new PushButton(this, 0, 0, "LOAD", loadHandler); loadButton_.width = 50; // スライダー slider_ = new HSlider(this, 0, -50, sliderHandler); slider_.width = Model.MAX_SIZE; slider_.setSliderParams(0, 255, 0); // ラベル label_ = new Label(this, slider_.x, slider_.y + slider_.height); label_.transform.colorTransform = new ColorTransform(0, 0, 0, 1, 255, 255, 255, 0); } // View からの通知 public function notifyFromView(x:Number, y:Number, value:uint):void { // スライダーの位置調整 slider_.x = x; slider_.y = y; slider_.value = value; // ラベルの位置調整 label_.x = x; label_.y = slider_.y + slider_.height; setLabelText(String(value)); } // ---------- ローカルメソッド ---------- // // ロードボタンのイベントハンドラ private function loadHandler(e:MouseEvent):void { _model.loadHandler(); } // スライダーのイベントハンドラ private function sliderHandler(e:Event):void { var value:uint = slider_.value; // View との通信(スライダーの値を渡す) _view.notifyFromController(value); // 自分の表示を更新 setLabelText(String(value)); } // ラベルのテキスト更新 private function setLabelText(str:String):void { label_.text = "Threshold:" + str; } } 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.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; } } 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; } } 判別分析法による閾値の自動計算