ヒューゴー賞2015受賞作品:The Three-Body Problem(三体問題)英語版を読了しました。非常に楽しい経験でした、ぜひ第二部も読みたいと思います。
本のレビューはここで割愛しますが、本のなかに「モンテカルロ法」というアルゴリズムに関する内容がありました。
モンテカルロ法はイレギュラーな形状の面接を求めるのによく使われるそうです。不規則な形状を規則な形状(まる、四角等)の中において、そして、その規則な図形に向けてランダムにボールを投げます。ボールが不規則な図形の中と外に落ちた個数の比例をもって、規則的な形状のトータル面積をベースに、不規則な図形の面積を算出する方法です。
浅い理解かもしれませんが、つまり、「ランダムはある一つの場所に集中することなく、均等だ」ということです。
自分はランダムに対して、数学だけだはなく、哲学的っぽくて、なんか興味があります。モンテカルロ法を勉強・実験するために、小さいプログラムを作りました。こんな感じです:
不規則な図形を四角いキャンバスにおいて、そのキャンバス全体にランダムの点を投げます。
(点を投げるよりは、画像の画素の色を調べることです。)
サンプルの個数とサンプリングする回数が設定されます。以下の例では1000個の点を10回繰り返し、その平均値をとります。この例で、図形の正確な面積=129060画素で、モンテカルロ法で算出された面積=129439画素で、僅差です。
当然、毎回の計算結果が違いますし、おおむねサンプリングの個数が多いほど正確値に近づきます。
メインなMethodだけここに貼ります。完全のソースはここからダウンロードしてください。
/// モンテカルロ法によって面積を算出する
/// ランダムに画像から画素をサンプリングして、、その画素の色を調べる
/// 色が付いている画素は図形内、色がついていない画素は図形外
/// そして、図形内外の画素の比例を通じて、図形の面積を推測する
///</summary>
///<param name="bm"></param>
///<returns></returns>
private async Task<int> GetAreaOfImageByRandomDot(Bitmap bm)
{
int total_W = bm.Width;
int total_H = bm.Height;
int total_A = total_H * total_W; //画像のトータル面積(画素数)求める
int countOfSamples = (int)numericUpDown1.Value;
int countOfInside =0; //図形内に落ちたランダムサンプリング画素数
int countOfOutside =0; //図形外に落ちたランダムサンプリング画素数
List<Point> lstUsedPoints =new List<Point>(); //一度サンプリングした画素は二度とピックアップされないように履歴を記録するリストを用意
for (int i =0; i < countOfSamples; i++)
{//サンプリング数に合わせてサンプリング作業を開始
await Task.Run(() =>
{//画面フリーズを防ぐために別Threadで作業する
Random rd =new Random(); //ランダム数発生器
Point pt =new Point();
while (true&& lstUsedPoints.Count < total_A)
{//まだ使われていない画素が見つかるまでにずっと探す。見つかったらWhileを脱出する。
//最初は「見つかるまでずっと探せ!」という意味だったので、
//条件はtrueだけだったが、すべての画素を採ってしまった場合はLoopから脱出できないので、もう一つの条件を付け加えた
//画像内からランダムに画素一つを選ぶ
pt.X = rd.Next(0, total_W);
pt.Y = rd.Next(0, total_H);
if (!lstUsedPoints.Contains(pt))
{//この画素はまだピックアップされていない場合:
Color color = bm.GetPixel(pt.X, pt.Y);//画素の色を調べる
this.picked++;
Graphics g =null;
if (this.checkBox1.Checked)
{
try
{
g =this.pictureBox1.CreateGraphics();
g.FillRectangle(Brushes.Purple, pt.X, pt.Y, 1, 1); //画像に印を記録する
}
catch { }
}
//if (color != Color.FromArgb(255, 255, 255, 255))
if (color.R <250|| color.G <250|| color.B <250)
{//白でない場合図形内
countOfInside++;
//if (g != null) { g.FillRectangle(Brushes.White, pt.X, pt.Y, 1, 1); }
}
else
{//白の場合は図形外
countOfOutside++;
//if (g != null) { g.FillRectangle(Brushes.Red, pt.X, pt.Y, 1, 1); }
}
lstUsedPoints.Add(pt);//すでに調べ済みの画素はリストに記録する
break; //一つの画素を調べることができたらもうWhileから脱出する
}
}
});
if (picked %200==0)
{//ProgressBarの刻みを決める、あまり頻繁に更新してしまうと、遅くなるため
this.progressBar1.Value = picked;
this.lblProg.Text = progressBar1.Value.ToString() +" / "+ progressBar1.Maximum.ToString();
}
}
//図形内・外の画素の比例で図形の面積(画素数)を推測する
int areaCalc = total_A * countOfInside / countOfSamples;
return areaCalc;
}