Entry

プログラミングメモ - 画像解析関連のサンプルコードでつらつら

2008年11月10日

悪口ばっかだな。まあいいや。今,画像解析がらみの話で GA(Genetic Algorithm; 遺伝的アルゴリズム)に首を突っ込んでるんですけど,参考書に選んだ『画像処理とパターン認識入門』のサンプルコードがひどい。

画像処理とパターン認識入門 - 基礎からVC#/VC++ .NETによるプロジェクト作成まで
酒井 幸市
森北出版
売り上げランキング: 49930

本書は,画像解析の基本から,かなり高度な内容(の一歩手前)まで,そつなくまとまった画像解析本です。OpenCV みたいな便利ライブラリを使っていると,中でどんなことが起きているか分からないままだったりするけれど,一度は中身の話を見ておくといいんだと思います。そゆ意味ではいい本だし,少し骨のある入門本を探している人には,間違いなくお勧めできます。

ただ,それだけにサンプルコードがショボイのはイタい。例えば,GA を使ったテンプレートマッチング。テンプレートを回転・拡大・平行移動させて,適応度を計算する以下のメソッドです(p187)。開発言語は C# です。

private double Fitness(int j)
{
    int i, x, y, xa, ya;
    double sum, ang, xx, yy;

    ang = individual[j].angle;
    sum = 0.0;
    for (i = 0; i < sModel.sum; i++) {
        // rotation
        xx = (double)sModel.x[i]; yy = (double)sModel.y[i];
        xa = (int)(Math.Cos(Math.PI * ang / 180.0) * xx)
           + (int)(Math.Sin(Math.PI * ang / 180.0) * yy);
        ya = - (int)(Math.Sin(Math.PI * ang / 180.0) * xx)
           + (int)(Math.Cos(Math.PI * ang / 180.0) * yy);
        // scaling,translation
        x = individual[j].x + (int)(xa * individual[j].rate) ;
        y = individual[j].y + (int)(ya * individual[j].rate) ;

        sum += f[x,y];//原画像データを重ねる
    {
    return(sum / (double)sModel.sum);
{

つか,実際に試してないけれども,これコンパイルできないだろ。だって,中括弧が開きっぱなしだもん。ちゃんとテストしてるのだろうか。

まぁ,これは多分誤植なんだと思います(このメソッドに限らないんですけど)。現在の版では直ってるかもしれませんしね。けど,もっとひどいのはロジック。ループ内でラジアン値を何度も計算している以下の部分です。

        xa = (int)(Math.Cos(Math.PI * ang / 180.0) * xx)
           + (int)(Math.Sin(Math.PI * ang / 180.0) * yy);
        ya = - (int)(Math.Sin(Math.PI * ang / 180.0) * xx)
           + (int)(Math.Cos(Math.PI * ang / 180.0) * yy);

Math.PI * ang / 180.0 を1回のループで4回計算しています。ループは全部で sModel.sum 回まわるから,sModel.sum * 4 回計算することになる。ループの外側で1回の計算すればいいのに。頼むからやめてくれ……。

あともうひとつ。キャストしすぎ。

        xa = (int)(Math.Cos(Math.PI * ang / 180.0) * xx)
           + (int)(Math.Sin(Math.PI * ang / 180.0) * yy);
        ya = - (int)(Math.Sin(Math.PI * ang / 180.0) * xx)
           + (int)(Math.Cos(Math.PI * ang / 180.0) * yy);
        // scaling,translation
        x = individual[j].x + (int)(xa * individual[j].rate) ;
        y = individual[j].y + (int)(ya * individual[j].rate) ;

なぜ,こうしないのか。これなら1回のキャストで済むし,丸め誤差も最小限になるのに。

        xa = (int)((Math.Cos(Math.PI * ang / 180.0) * xx)
                 + (Math.Sin(Math.PI * ang / 180.0) * yy));
        ya = - (int)((Math.Sin(Math.PI * ang / 180.0) * xx)
                 + (Math.Cos(Math.PI * ang / 180.0) * yy));

それ以前に,そもそも xa, ya なる変数を int 型にキャストする意味が分からない。どうせ最後に individual[j].rate(これは double 型)と掛け算するんだから,これでいいんでねいの?

        xa = (Math.Cos(Math.PI * ang / 180.0) * xx)
           + (Math.Sin(Math.PI * ang / 180.0) * yy);
        ya = - (Math.Sin(Math.PI * ang / 180.0) * xx)
           + (Math.Cos(Math.PI * ang / 180.0) * yy);

individual[j].rate と掛け算するときに,せっかくキャストした int 型の xa, ya を double に戻すことになる。そして,その計算結果をまた int にキャストするという……意味不明なキャストになっています。もしかすると,写像する度に離散平面に戻していることを強調したいのかもしれません。けど,離散平面に戻すのは,最後の重ね合わせ(だけ)で十分です。反面,こんなにキャストしまくってたら,計算は遅くなるし,丸め誤差は無視できなくなるしで,いいことがありません。本書の方法だと,PI を 3 として計算しても,同じような結果が出るかもしれない。

つことで直す。

private double Fitness(int j)
{
  int i, x, y;
  double xa, ya;
  double sum, rad, dcos, dsin;

  rad = Math.PI * (individual[j].angle / 180.0);
  dcos = Math.Cos(rad);
  dsin = Math.Sin(rad);
  sum = 0.0;
  for (i = 0; i < sModel.sum; i++) {
    // rotation
    xa = (dcos * sModel.x[i]) + (dsin * sModel.y[i]);
    ya = (dcos * sModel.y[i]) - (dsin * sModel.x[i]);
    // scaling,translation
    x = individual[j].x + (int)(xa * individual[j].rate);
    y = individual[j].y + (int)(ya * individual[j].rate);

    sum += f[x,y];//原画像データを重ねる
  }
  return (sum / sModel.sum);
}

ついでに,sin と cos もあらかじめ計算しておくことにしました。

基本的に,GA の画像テンプレートマッチングってのは,計算量が膨大になります。個人的には,「効率のいいランダムサーチ」とすら思っているくらい。んなもんで,こゆとこでちまちまと最適化しないと,遅くて仕方ないと思います。

あと,これは好みの問題かもしれないけれども,こういうのはあまり好きじゃない(p186)。

cnt = 0;
while (cnt < numGeneration) {
  // asessment
  // save elite
  // selection
  // crossover
  // mutation
  cnt++;
}

こうしてやりたいのだけれど,for-文を使っちゃ何か問題でもあるんだろうか。

for (cnt = 0; cnt < numGeneration; cnt++) {
  // asessment
  // save elite
  // selection
  // crossover
  // mutation
}

変数 cnt はループ後に参照されるものの,ループの途中では1回も参照されません。途中で値が変わる(逆戻りとか)わけでもなく,淡々と進んだ世代の数を数える変数です。開始条件と終了条件,それと世代を進める演算(cnt++)は同じところにあった方が分かりやすいと思うんですけどね。あたしは。

あたしが言うのもなんなんですけど,本書のサンプルプログラムって,どうもプログラミング慣れしていない感じがします。せっかくいい本なんだけどなぁ……残念。

Trackback
Trackback URL:
Ads
About
Search This Site
Ads
Categories
Recent Entries
Log Archive
Syndicate This Site
Info.
クリエイティブ・コモンズ・ライセンス
Movable Type 3.36
Valid XHTML 1.1!
Valid CSS!
ブログタイムズ

© 2003-2012 AIAN