判別分析法による閾値の自動計算
二値化処理
判別分析法による閾値の自動計算
解説:http://aquioux.blog48.fc2.com/blog-entry-696.html
@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/hLCz
*/
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;
}
}