LoginSignup
38
36

More than 1 year has passed since last update.

「テスト駆動開発+画像処理アルゴリズム開発 → ぐう有能」の法則

Last updated at Posted at 2021-08-27

案件の中で、テスト駆動開発を画像処理開発に応用してみると、かなり上手く機能することが分かった。

その心を語ってみる。

テスト駆動開発の教科書

開発の初期のころ、テスト駆動開発に興味を持ったので、早速**「テスト駆動開発の教科書」**として読んでみた。

教科書はJava, Pythonで記載されている。
とりあえず、実践としては第1章のJavaのケースを、自分なりにC++で書いてみて色々練習したりしていた。

第1章の中身は$、€、£などの外貨のレートの為替を実装するもの。
この時点で「仕様が確定しているアルゴリズムに対してはパワフルな効果を与える」ことが分かった。
(逆に言えばお客さんから仕様聞きを頻繁にやって、ソフトウエアのアップデートをする必要があるものに対しては、テスト駆動開発はあまり向いていないと思う)

テスト駆動開発の信念の中で最も大切なこと

 教科書では、頻繁に**「仕様を実装するな。どうテストをするかをまずは考えよ」**としつこいくらいに伝えてくる。
 そして、それをアルゴリズム開発の背景で考えていくと、以下の視点で語れるかどうかが非常に大切だ。

  1. テスト可能な対象かどうか
  2. そのテストは分割可能か
  3. そのテストは正解を持つか

テスト可能な対象かどうか

 例として、**「入力画像のヒストグラムを抽出して、画素分布を調べるアルゴリズム」**を考えてみよう。

テストが可能な場合

 とあるサンプルの入力画像がある。
 その画像は、画像処理ソフトで調べると、モノクロ画像であり、
 画素平均が120, 画素分散が50.3となっている。

 そこで、ローカルフォルダにある入力画像が正常に読めているかどうかを確認したい。

 これは、テスト可能である。

テストが不可能な場合

 任意の10枚の入力画像がある。
 その画像は、一定の条件で撮った画像であるが、撮像の仕方によっては変化する。

 その画像は、画像処理ソフトで調べると、カラー画像であり、
 正しく撮れていれば、特定のROIで全ての画素は100~150の範囲に入っている。

 そのデータを使うことで、撮った画像のROIが常に正常な特徴を持っているかを確認したい。

 これはテスト出来ない
 一見さっきと同じように見えるが、**「撮像の仕方によっては変化する」**の部分が不可能である。

 統計の話をすると、【正しく撮れていれば】を含めた画像の母集団が無限大に存在するのに、標本数がN=10しか存在しないので、例えば10枚のテストを行なって成功しても、【正しく撮れていれば】を正しく評価出来ない。

 例えば、こうした場合は、**「任意の10枚の入力画像」という表現をやめて、「ローカルに保存されたある一定の画像10枚」**とするとして、「一定の画像では、それらしい結果が出てくること」をテストする方向性で保証するようにする。

 もちろん、アルゴリズム開発の上ではこれだけでは不足であるので、**【正しく撮れていれば】**を保証するための性能テストを別途追加する必要がある。

そのテストは分割可能か

 テスト駆動開発では、うるさいくらいに簡単なテストから作るべしということを伝えてくる。

 これは、アルゴリズム開発においては重要。
 ニューラルネットワークでも扱ってない限りは、何がしかの**【論理/ロジック】**が多数存在し、それを分割できるように設計すると思うので、これをテストする。

良くない例

 「A~Z」「0~9」からなる筆跡の画像があって、そのパターンがどの程度合致しているかを、
 パターンマッチングによって判定し、類似度を算出する。

【36種類の文字数字のテンプレート画像を用意して、それと入力画像を比較して、類似度を算出する】というモデルはパターン認識の教科書によく載っている。

じゃあ、**「テンプレート画像を用意して」「入力画像を比較して」「類似度を算出する」**という処理をまとめて書く必要性はあるだろうか?
まとめて書くと、各工程ごとで処理を考えているときに、他のことを考えられなくて不具合が起きるはずだ。

例えば、実際にまとめて書こうとすると、こういう問題が起きる

  • 例えば、「入力画像の読み取り処理」を設計している間に、「テンプレート画像の読み取り処理」の不具合のことを考えないので、仮に入力画像の読み取り処理の変更で後者の処理が変わっても、それがどこの不具合になるのかが予想できなくなる
  • 例えば、「IとLとJが違う文字である」ことを評価して問題があるから修正している作業の間で、今まで正解だった「OとQが違う文字であること」が変わる恐れがある

従って、もっと、問題を簡単にするべきなのだ。

良い例(?)

 もっといい例はあると思うけども、例えば

 次の順序で、文字認識のアルゴリズム開発の評価のテストを先に作っておき、あとで開発する。
・ローカルにある入力画像を読み取れるかを、テストする
・テンプレート画像と入力画像が読み込めているかを、テストする
・「0」と「1」という文字の2種類の初期テンプレート画像を用意して、テストする
・「0」と「1」が判別できる関数を作り、テンプレート画像から入力画像の類似度を求められるかを、テストする
・類似度を求めた結果から、画像Aが「0」、画像Bが「1」と判定されるかを、テストする
・文字検出するための位置が微妙にずれているので、位置合わせ機能をテストする
・テンプレート画像に「2」を追加して、類似度を求められるかをテストする
・類似度を求めた結果から、画像Aが「0」、画像Bが「1」、画像Cが「2」と判定されるかを、テストする
・・・・

という形に、具体的なTo Doを1個ずつ積み上げていって、テストを分割しながら、設計する。

このように、簡単な問題に分割していきながら、一つ一つのテストを越していくのがテスト駆動開発の基本的な考えであり、とても有用である。

また、認識精度によっては、36種類の文字を全て見るなんてとてもじゃないけど不可能、みたいなケースもあるだろう。その場合においても、「絶対に正解する画像」を定義出来るので安心感があるし、時間が許せば1から積み上げた時点で36種類の文字すべてをテスト出来ることになるのだ。

そのテストは正解を持つか

 テスト駆動開発においては、**「三角測量」**と呼ばれるやり方が知られている。

 当然、テスト駆動開発で画像処理を設計するにあたっては、三角測量の概念はとても役に立つ。個人的には、開発中期で本当に役に立つのでありがたい。

 この概念が役に立つシチュエーションは、答えが明確なとき。
 例えば、塗りつぶし処理を考える。

 画像において、輪郭内の画素を塗りつぶす処理を考える。

 ローカルに保存されている画像1~5は、8近傍, 4近傍どちらでも、
 塗りつぶそうとすると、塗りつぶしてしまって背景にまで侵食する。

 ローカルに保存されている画像6~8は、
 4近傍であれば、塗りつぶされて背景が侵食されることはないが、
 8近傍では背景にまで侵食する。

 ローカルに保存されている画像9~13は、8近傍, 4近傍どちらでも、
 背景が侵食されることはない。

 黒で塗りつぶして侵食するときは、必ず画像の端が真っ黒になるので、それをテストする。

この場合、例えば(C++でgtestっぽい書き方ですが他の言語でもよく似た感じだと思う)


/* 4近傍で背景塗りつぶされるテスト */
TEST_F(Evaluation_FillBackground, Background_Filled4_ImageTest) {

... (省略) ....

 for( int i = 1 ; i <= 5; i ++ ) {
  auto image = image_list[i].GetImage();
  image->FillBackground(NEIGHBOR::METHOD_4PIXEL);
  EXPECT_TRUE(image->IsFilledEdge());
 }

... (省略) ....

}

/* 4近傍で背景塗りつぶされないテスト */
TEST_F(Evaluation_FillBackground, Background_Unfilled4_ImageTest) {

... (省略) ....

 for( int i = 6 ; i <= 13; i ++ ) {
  auto image = image_list[i].GetImage();
  image->FillBackground(NEIGHBOR::METHOD_4PIXEL);
  EXPECT_FALSE(image->IsFilledEdge());
 }

... (省略) ....

}

みたいな感じで、明確な答えを2つもっていて、その明確な答えごとに画像を紐づけてテストするような考え方がすごく役に立つ。

テスト駆動開発では、こうした開発者からしたら**「明確な答え」があるんだけど、いちいちチェックするのがすごく大変**なアルゴリズムに、この三角測量を使うとすごい効果的という印象がある。

テスト駆動開発が辛いところ

とにかく設計工数がかかること

これは間違いないと思います。
一つ一つテストから埋めていくので、面倒です。

ただ個人的には、フロントエンド、バックエンドなどで役割分担されるのであればこの工数がかかる部分はリスクになりますが、
アルゴリズムの場合、仕様の柔軟性の検討品質や認識率の担保を並列でやる必要があるので、(厳密にはやらなくても)テスト駆動開発を生かした開発はかなり役に立つと思います。

また、最近では非同期処理やGPGPUなどのハードウエア要因や処理要因が複雑になっているのもあるし、ローカル内で動くことを保証できるのはでかい。

昔のように、C++のDLLやライブラリで処理分割されるようなプロジェクト構成になっている場合は、なおさらそうかな。

アルゴリズムの技術開発も当然大事なのですが、
こうしたテスト系統の開発技術を覚えると、
アルゴリズムを得意としている設計者も、

【何か開眼するかも】

と思わせる要素があるので、やってみると結構面白いです。

38
36
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
36