上へ

開発メモ

2001/07/30 カーニハン本
仕事上のプログラムは予算、納期の制限もあり、納得いくまでつきつめられるわけではないので、 一般に汚くなりがちなんですが、「これはひどい」(多分、作者もいくつか書いた(^^;) というものなかにはあるので、教育上良い本を探していました。

最近ではオブジェクト指向へのパラダイムシフトに伴うごたごたも一段落して、 一昔前はオブジェクト指向プログラムの作り方だったところが、 オブジェクト指向をつかってどうやって楽をするかということに話題が移ってきて、 仕事用コードを書くための本を探すには良い環境になってきました。

そんな中で、教育上良い本として 「リファクタリング,マーチン ファウラー」 に目をつけていました。プログラムをコンパクトに保つ方法を解説している本なんですが、 最近の本ではよくあるように、オブジェクト指向は常識として特に解説もしていないので、 冷静に考えると、兼業プログラマが多い作者周辺でこの本を読める人はほぼいませんでした。 (いるにはいるけど、そういう人は元々良いコードを書く)

もうちょっと敷居の低い本を探して、古典的有名所を中心に GW を利用して いくつか読んでみました。(←いつの話やねん)
  • プログラム書法,ブライアン.W.カーニハン P.J.プローガー,共立出版,1976
  • ソフトウェア作法,ブライアン.W.カーニハン P.J.プローガー,共立出版,1981
  • プログラミング作法,ブライアン.W.カーニハン Rob Pike,2000
  • 人月の神話,ブルックス,Peason Education Japan,1996(ソフトウェア開発の神話 復刻版)
  • プログラマーの復権,E.ヨードン,シイエム・シイ出版、2000
  • デスマーチ,E.ヨードン,E.ヨードン,シイエム・シイ出版、2001
  • 成功するソフトウェア開発−CMMによるガイドライン,Carnegie Mellon University/Software Engineering Institute,オーム社,1998
一番古い部類になりますが、作者的にはプログラム書法がお勧めでした。 1970年代に書かれたこともあって、 例題が Fortran か PL/I ですが、「わかりやすく書こう−うますぎるプログラムはいけない」 ではじまるこの本は、今でも通用することが多く書いてあり、適度に薄く、空白もあって 良い内容でした。
次のお勧めも、同じカーニハンのプログラミング作法でした。プログラム書法より厚くて、 空白も少ないのが難点ですが、そのかわり、例題は C で書かれていて、 内容もプログラム書法を発展させた充実の内容でした。

1970年代から2000年代まで一気に読んだことになりましたが、全体として、 構造化プログラミング、オブジェクト指向など、プログラムについて書かれたものは生き残る、 流行が去っても全面否定まではされない。 それに対して、フォーターフォールモデルや、各種オブジェクト指向方法論、 フローチャート、ISO9800のような文書とか方法論についは後の世になると けちょんけちょんにけなされることもある。といった感じでした。
今回読んだ本の中ではCMMは今のところ一定の評価を得ているようですが、今後どうなるでしょ?
2001/07/23 Kylix 対応 I/O ポートアクセスドライバ
だれが使うんだこんなもの。(^^;
2001/07/14 仕事
重心演算、データによってはオーバーフローを起こすので、一部 64bit 計算に変更。
Kylix 画面更新遅い対策 その2
ゲームの画面更新は速いはずだ。ということで、 Simple DirectMedia Layer(SDL) というのを発見、安定供給が期待できるか、環境依存がどの程度か、実際の性能は、・・・ など評価している時間がないため、今回は採用を見送る。
画像取り込ボードにコマンドを送って、ハードに描かせる方法もためしてみたが、 これはこれで、ハードアクセスに 70ms 程度(2フレーム?)オーバーヘッドがあって、リスクの割りに 得るものが少ない。
前回からさらに思い付きを試した結果、Kylix 標準の範囲では PaintBox Draw が速いようだ。 これで行く。
Kylix 画面更新遅い対策 その2
ScanLine ベースでピクセルセット関数、直線描画、円描画を作る。
フォーカス評価
スポットが小さくて、強度が強いという条件で適当にフォーカス評価関数を作ってみた。 検出精度がいまいち。
google で "focus" "評価関数" で検索する、"Modified Laplacian" とかいうのが引っかかった。もしかして知らないと恥なのか? "Modified Laplacian" でさらに検索・・・引っかかりすぎ・・・。
原理に戻ると、フォーカスの評価で Laplacian を使うのはなんとなく分かる気がするが、 使いたい場面ではエッジの片側がサチっていることが多いので・・・、 そこまで考えて思い付きを実装、どうなるでしょ?
最少二乗円
3点以上を二乗誤差最少で通る円を求める必要があるので式をたててみると、 連立1次方程式の形になった、プログラムを作り計算させてみると、 ちゃんと計算できているようだ。
(X[1],Y[1])〜(X[n],Y[n])を通る円 (X - A)^2 + (Y - B)^2 = R^2
のパラメータ A, B, R を求める。

| 2*X[1]  2*Y[1]  1|      |X[1]^2 + Y[1]^2|
| 2*X[2]  2*Y[2]  1||A|   |X[2]^2 + Y[2]^2|
| 2*X[3]  2*Y[3]  1||B| = |X[3]^2 + Y[3]^2|
|        ...       ||C|   |      ...      |
| 2*X[n]  2*Y[n]  1|      |X[n]^2 + Y[n]^2|

を解いて、R は R = Sqrt(C + A^2 + B^2)
しかし、暑さで思考が止まりがちなのはなんも・・・。
2001/07/11 仕事
Kylix 画面更新遅い対策
画面の更新(Scanline + Invalidate)が 200ms 位と使い物にならないほど遅い (Windows-95 では 10ms位)。調べてみると 100ms が 640 x 480 領域を更新するのにかかる時間で、最初のうちは ScanLine は 10ms 以内で完了して、Invalidate 発行は(イベントのセットだけなので) 0ms で終了 しているが、Canvas を使った後から、ScanLine を使用時と、Invalidate 発行時にそれぞれ 100ms かかるようになっている。

とりあえず Canvas は使えないので、ScanLine ベースで描画コマンドを作る必要がある。 ピクセルセット関数を作って、ピクセルセット関数から直線描画を作って、 直線描画で円描画を作って・・・でなんとかなるかな。

これで ScanLine と Invalidate は瞬時(10ms程度)に終わる。 実際の描画は 100ms かかるけど、Xプロトコルを経由していないはずの Windows でも 100ms 弱はかかるので、これは諦めるしかないかな。
Kylix 画面更新ちらつき対策
ScanLine で画像をセットして、Invalidate (または Repaint) で画面を更新するときに 画面がちらつく、(Delphi ではダブルバッファプロパティを使うのが定石だが、 Kylix では無くなっている) 調べると、TImage の Invalidate は最終的に、TForm や TPanel の InvalidateRect メソッド を呼び出しているように見える。InvalidateRect の第2引数は、画面更新時の初期化有無を 指定できるはずだが、どうも指定が無視されている。 いろいろやって見たところ、TImage を保有している TForm (または TFrame )の Invalidate メソッドを呼び出すことで、ちらつき無しで更新できるようだ。
2001/07/10 仕事
重心演算関係とりあえず出来た。Blob.pas(そのうち削除予定) コード行数 200 行で、Celeron 400MHz で 60ms 程度 (1〜2フレーム落ち、画像にもよる)ならまぁ良しとする。
ビットマスクを使った探索高速化、効果が10% 程度なら却下しようと思っていたのですが、 20% 程度の効果があったので、微妙な所ですが残しました。
キューのメモリ配置上、スレッドセーフではありませんが、とりあえず自分が 必要になるまでは対応しない予定。
昔持っていた8086-386 のクロック表から、386 以降のプロセッサでは乗算、除算のペナルティは 無視できると思っていたのですが、どうも誤解だったことが発覚。 コンパイラの最適化をOFFにして、適当にループをまわしてみると、 手元の Crusoe (プロセッサがまったく違うけど(笑))では加算、減算、ビット演算を1とすると、 乗算が5、除算、剰余が10程度かかっているようでした。 クロック表は目安程度にしかならない時代ですが、 やっぱり乗算、除算はペナルティと考えていたほうが良さそう。
2001/07/08 仕事
画像処理関係のプログラムを、通常は Matrox の画像処理ライブラリ (MIL 50万+ランタイム7万くらい)を使って作っているのですが、 今回は重心演算程度ということや、原価低減の圧力(笑)もあり、 今回は MIL は使わないことにしています。
とりあえず http://www.google.co.jp/ で探したのですが、めぼしいものを見つけられなかったので、 自作することにしました。手順としては
  1. 2値化
  2. ラベリング
  3. 面積計算
  4. 重心計算
  5. (フォーカス評価)
で、速度が要求される部分なので、ラスタスキャンを最初の2値化時のみに抑えるのを目標に 設定しました。
必要データとしては
  • 元画像 640x480 (Byte、ボードによって変わる)
  • ラベリングバッファ 640x480 (Integer)
  • 2値化インデックスバッファ 0 〜 640x480 - 1 (Integer)
  • 粒子インデックスバッファ(速度重視ならテーブル+インデックス、 速度は劣るけどこれは array of array of Integer でも良いかも)
データの格納形式は、var Image: array [0..640-1, 0..480-1] of Byte; 形式 (静的に決定)は後で流用しようとした時に嫌な思いをすることが多いので却下。

GetMem を使って配列へのポインタ配列を作って(以下概略)クラスで隠蔽が、 MIL とか子バッファが取れる市販画像処理ライブラリでも採用されていそうな形式だけど、
type
  TByteArray = array [0 .. MAXINT div 2 div SizeOf(Byte)] of Byte;
  PByteArray = ^TByteArray;
var
  A: PByteArray;
  P: array of PByteArray;
begin
  SetLength(P, 480);
  GetMem(A, 640 * 480 div SizeOf(Byte));	// 画像ボードから直接転送可
  P[0] := @A[0 * 640];
  P[1] := @A[1 * 640];
  P[2] := @A[2 * 640];
  ...
作者はこの分野のニーズをよく把握しているわけでもないので、この状態で大掛かりなものを 作ると、かなりの確率で ”しょうもないもの(*)” を作ってしまうため、 自分のニーズが出てくるまでは当面却下。

(*) クラスを作るとしても型が違う(=インターフェースが違う)から、 アルゴリズムを共用しようと思っても出来ないし、仮想関数呼び出しオーバーヘッドも惜しいし、 クラス使用を強要することになる割りにいまいちメリットが感じられない、 最近 Delphi-ML でテンプレートもどきという技も出ていたし、しばらく様子見。

とりあえずは1次元配列+縦横サイズ形式とする。(ローテク採用)

データ格納形式が決まって、ラベリング探索アルゴリズム、楽なのは再帰呼び出しによる、 深さ優先探索だけど・・・、最悪 640 x 480 関数呼び出しがネストするので、スタック的にも 関数呼び出しオーバーヘッド的にも却下、キューを作って幅優先探索を作ることにする、TQueue を 普通に使うとメモリアロケートのオーバーヘッドが毎回出るし、姑息なことをしてもある程度 メモリアロケートは発生するし・・・姑息分を差し引くとキューも自作かな・・・。

というところで、暑さで思考が停止・・・。う〜ん、ピンチなのに。
2001/07/07 InputForms
仕事が忙しい・・・というわけで、InputForms を Kylix に対応
  • Kylix では TForm の派生クラスはトップレベルウィンドウにしかならない。 ウィンドウに貼り付けられるのは TFrame または TPanel だが、 親ウィンドウが無い TPanel 上のコンボボックスはアイテムの追加が出来ないので、 TFrame を使うしかない。
  • TFrame には TForm の CreateNew 相当の関数が無いので、派生クラスを作るためには DFM(xfm)ファイルが必要になるが、TFrame 派生クラスの DFM ファイルからは記述してある インスタンスしか作れない。
  • TFrame のインスタンスは DFM 無しでいくらでも作れる。
ということで、InputForms を TForm の派生クラスから、内部で TFrame を持たせる構造に変更。
EUC コードの同一ソースで、Delphi5 と Kylix の両方のコンパイルは通るけど、 印刷ボタンは化けるので、一応 SJIS 版と EUC 版の2つを配布。