概要
BitmapDataの中身を二値化するコードを書いてみましょう。
左側のように色数が何千、何万色もある画像を、右側のような2色の画像に変換します。
アルゴリズム
色には明度という明るさや暗さを表現する基準があります。
この色は明るい、あの色は暗いという言い方をしますよね。
この色は明るい、あの色は暗いという言い方をしますよね。
上の画像を見てください。
一番明るいところ(0xFF)と一番暗いところ(0x0)の間に矢印を引いています。
一番明るいところ(0xFF)と一番暗いところ(0x0)の間に矢印を引いています。
対象のピクセルが、この矢印が指している明るさよりも暗ければ真っ黒に、明るければ真っ白にするというのが二値化です。
この矢印の指している明るさは(0xFF + 0x0) / 2なので、0x7Fになります。この0x7Fの事を「閾値」といいます。
この閾値より明るければ真っ白に、この閾値より暗ければ真っ黒な色に変換、と言い換えることが出来ますね。
この矢印の指している明るさは(0xFF + 0x0) / 2なので、0x7Fになります。この0x7Fの事を「閾値」といいます。
この閾値より明るければ真っ白に、この閾値より暗ければ真っ黒な色に変換、と言い換えることが出来ますね。
ただ、この閾値と色が入っている変数を比べてはいけません。
0x123456のような書き方は変数で扱いやすいようにしている形式であって、実際の明るさとは関係ないからです。
赤、緑、青それぞれに明るさがあるのだから、それの平均を取るように計算してみましょう。
(赤 + 緑 + 青) / 3なので、0x123456なら(0x12 + 0x34 + 0x56) / 3で平均の明るさが出てくることになります。
0x123456のような書き方は変数で扱いやすいようにしている形式であって、実際の明るさとは関係ないからです。
赤、緑、青それぞれに明るさがあるのだから、それの平均を取るように計算してみましょう。
(赤 + 緑 + 青) / 3なので、0x123456なら(0x12 + 0x34 + 0x56) / 3で平均の明るさが出てくることになります。
その明るさと閾値を比べて二値化処理を行います。
では、実際に二値化するコードを書いてみましょう。
プログラム
private function binarization(bd:BitmapData):BitmapData
{
var dest:BitmapData = bd.clone();
var color:ColorHSV = new ColorHSV();
for (var y:int = 0; y < bd.height; y++)
{
for (var x:int = 0; x < bd.width; x++)
{
color.value = bd.getPixel(x, y);
var gray:Number = (color.r + color.g + color.b) / 3;
if (gray <= 0x7F)
{
dest.setPixel(x, y, 0x0);
}
else
{
dest.setPixel(x, y, 0xFFFFFF);
}
}
}
return dest;
}
コードが長いので一つずつ説明していきましょう。
private function binarization(bd:BitmapData):BitmapData
{
}
二値化することを英語でbinarizationと言うので、そのままメソッド名に採用します。
二値化対象のBitmapDataを受け取り、二値化したBitmapDataを返却します。
二値化対象のBitmapDataを受け取り、二値化したBitmapDataを返却します。
var dest:BitmapData = bd.clone();
var color:ColorHSV = new ColorHSV();
受け取ったBitmapDataをそのまま二値化するのではなく、コピーを作ってそのコピーBitmapDataを二値化するようにします。
ちょっと面倒に思われるかも知れませんが、この方が汎用性が高いと思うのでコピーを作りましょう。
コピーを作るにはBitmapData#clone()を使用します。
ちょっと面倒に思われるかも知れませんが、この方が汎用性が高いと思うのでコピーを作りましょう。
コピーを作るにはBitmapData#clone()を使用します。
後でピクセルをRGBごとに分解するため、ColorHSVオブジェクトを生成しておきます。
for (var y:int = 0; y < bd.height; y++)
{
for (var x:int = 0; x < bd.width; x++)
{
// 中の処理
}
}
画像の一つ一つのピクセルを二値化していくため、高さ * 幅の数だけ回る二重ループを用意します。
color.value = bd.getPixel(x, y);
var gray:Number = (color.r + color.g + color.b) / 3;
if (gray <= 0x7F)
{
dest.setPixel(x, y, 0x0);
}
else
{
dest.setPixel(x, y, 0xFFFFFF);
}
getPixel(x, y)で特定座標のピクセルを取得し、ColorHSVオブジェクトに入れておきます。
取得した色の平均を取り、gray変数に代入します。
そのgray変数が、閾値よりも明るかったらsetPixel()で白に、暗かったら黒に変換します。
元のbdに設定するわけではなく、コピーしたdestが対象なので間違えないようにしてください。
取得した色の平均を取り、gray変数に代入します。
そのgray変数が、閾値よりも明るかったらsetPixel()で白に、暗かったら黒に変換します。
元のbdに設定するわけではなく、コピーしたdestが対象なので間違えないようにしてください。
return dest;
二重ループを抜けた後、二値化処理を行ったdestをreturnで返却します。
addChild(new Bitmap(binarization(bd)));
使用方法はこのようになります。一行で書けるので便利ですね。
閾値の変更
閾値は別に0x7Fで無ければならないというわけではありません。
if (gray <= 0xAF)
閾値を0xAFにしてみました。
だいぶ明るい色が閾値になってしまったので黒の面積が多くなります。
だいぶ明るい色が閾値になってしまったので黒の面積が多くなります。
if (gray <= 0x3F)
閾値を0x3Fにしてみました。
今度は逆に暗い色が閾値になっているので、白の面積が多くなっていますね。
今度は逆に暗い色が閾値になっているので、白の面積が多くなっていますね。
色の変更
白と黒にするだけはなく、別の色を指定してみましょう。
if (gray <= 0x7F) dest.setPixel(x, y, 0xED1A3D);
else dest.setPixel(x, y, 0xFFFFFF);
閾値より暗ければ赤、明るければ白に変換するようにしています。
if (gray <= 0x7F) dest.setPixel(x, y, 0x0);
else dest.setPixel(x, y, 0xFFC702);
閾値より暗ければ暗めの黒、明るければ暗めの黄色に変換するようにしています。
このように閾値や色を変更して二値化の調整を行うと、色々と面白いかもしれません。
このように閾値や色を変更して二値化の調整を行うと、色々と面白いかもしれません。
検証用コード
package
{
import flash.display.Sprite;
import flash.display.BitmapData;
import flash.display.Bitmap;
import flash.display.Loader;
import flash.events.Event;
import flash.geom.Matrix;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import flash.system.Security;
import frocessing.color.ColorHSV;
public class Main extends Sprite
{
private const WIDTH:int = 200;
private const HEIGHT:int = 200;
public function Main()
{
Security.loadPolicyFile("http://farm3.static.flickr.com/crossdomain.xml");
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.INIT, initHandler);
loader.load(new URLRequest("http://farm3.static.flickr.com/2047/2280715132_f0a72c1088.jpg"), new LoaderContext(true)); // 読み込みたい画像URL
}
private function initHandler(event:Event):void
{
var loader:Loader = event.currentTarget.loader;
var matrix:Matrix = new Matrix();
matrix.scale(WIDTH / loader.width, HEIGHT / loader.height);
var bd:BitmapData = new BitmapData(WIDTH, HEIGHT);
bd.draw(loader, matrix);
addChild(new Bitmap(binarization(bd)));
}
private function binarization(bd:BitmapData):BitmapData
{
var dest:BitmapData = bd.clone();
var color:ColorHSV = new ColorHSV();
for (var y:int = 0; y < bd.height; y++)
{
for (var x:int = 0; x < bd.width; x++)
{
color.value = bd.getPixel(x, y);
var gray:Number = (color.r + color.g + color.b) / 3;
if (gray <= 0x7F)
{
dest.setPixel(x, y, 0x0);
}
else
{
dest.setPixel(x, y, 0xFFFFFF);
}
}
}
return dest;
}
}
}
このwikiの更新情報RSS