Delphiで画像から顔領域や目の領域を認識検出(Haar Cascades識別器)するクラスを作りましたが、処理速度と精度の両方を高くする畳み込み方法がわからない


mam  URL  2023-01-11 10:26:41  No: 150740  IP: [192.*.*.*]

Delphiで画像から顔領域や目の領域を検出できるクラスを作りました。
https://mam-mam.net/delphi/haar_cascade.html
アルゴリズムは、OpenCVと同じHaar Cascadesを使用しています。

参考まで、このクラスを使用してDelphiでWEBカメラ(USBカメラ)からの画像の顔領域にリアルタイムにスタンプを合成するWindowsアプリを作ってみました。
https://mam-mam.net/download/mamwebcamphoto2.html

しかしながら、精度を上げるようにパラメータを調整すると処理速度が遅くなりますし、処理速度を速くしようとすると精度が下がります。
現行は畳み込み+Haar Cascades処理を以下のように5重ループ行っています。
ループ開始(窓サイズを少しずつ大きくしていく)
  ループ開始(画像に対して窓を少しずつ横に移動していく)
    ループ開始(画像に対して窓を少しずつ縦に移動していく)
      ループ開始(Haar Cascadesのステージ1~25までのループ)
        ループ開始(features Rectのループ)
          積分画像からリーフ値を合計していく
        ループ終わり
        各ステージ毎にカスケード型識別器処理を行い失敗したらループを抜ける
      ループ終わり
    ループ終わり
  ループ終わり
ループ終わり

問題は画像の畳み込みを行っている箇所なのですが、良い畳み込みのアルゴリズムがありませんでしょうか。

編集 削除
mam  URL  2023-01-14 12:40:54  No: 150742  IP: [192.*.*.*]

自己レスです。


とりあえず、TTask/ITaskを使用して並列プログラミングにより処理速度を高速化してみました。

ループ開始(窓サイズを少しずつ大きくしていく)
  ループ開始(画像に対して窓を少しずつ横に移動していく)
    ループ開始(画像に対して窓を少しずつ縦に移動していく)
      tasks[0]~tasks[MaxWorkerThreadsCount-1]・・・窓の縦の位置が少しずつ違う
      ---------------------------------------------------------------
        ループ開始(Haar Cascadesのステージ1~25までのループ)
          ループ開始(features Rectのループ)
            積分画像からリーフ値を合計していく
          ループ終わり
          各ステージ毎にカスケード型識別器処理を行い失敗したらループを抜ける
        ループ終わり
      ---------------------------------------------------------------
      tasks[0]~tasks[MaxWorkerThreadsCount-1]を並列に実行
    ループ終わり
  ループ終わり
ループ終わり

MaxWorkerThreadsCountの値はTThreadPool.Default.MaxWorkerThreadsから取得しました。
コア数×25の値になるそうで、8コア搭載のパソコンを使っている場合は、最大200スレッドを並列に同時実行することになります。
処理速度は随分速くなりましたが、CPUリソースの使用率はかなり高くなりました。


TTask/ITaskの使用には注意点がありまして、エンバカデロ社の説明ページのようにTTask/ITaskの使用を行うと引数がまともに渡せませんでした。
例えば以下のソースコードの場合、
tasks[0]にはvalue=0の値が渡され、tasks[1]にはvalue=1の値が渡されると思っていたのですが
実際には、tasks[0]とtasks[1]に渡される値が0だったり1だったり、まるでランダムのように変化しました。
(デバッグ時のステップ実行で1行ずつ止めると期待通りの値が渡されますが・・・)
タスク(スレッド)の開始・終了順序が保証されないことを考えると、これが「当たり前」なのかもしれません。

function RunStage(v:Integer);
begin
  ・・・
end;

procedure TForm1.MyButtonClick(Sender: TObject);
var tasks: array of ITask; 
    value: Integer; 
begin
  value := 0; 
  tasks[0] := TTask.Create(procedure()
  begin
    RunStage(value);
  end); 
  tasks[0].Start; 

  value := value+1;
  tasks[1] := TTask.Create(procedure()
  begin
    RunStage(value);
  end);
  tasks[1].Start; 

  TTask.WaitForAll(tasks);
end;


仕方なく、かなり面倒ですが、TTask/ITaskから派生したクラス
THaarStagesTask = class(TTask, ITask)
を作ってコンストラクタでプライベート変数に値を保持するようにしました。

さらに、TTask/ITaskから派生したクラスの複数のインスタンスから同時に同じオブジェクトにアクセスするので
System.MonitorEnter と System.MonitorExit を使って同時アクセスを保護しました。

並列プログラミングがこんなに大変とは思っていませんでした・・・。




上記を踏まえたTTask/ITaskを使用したHaar Cascadesのクラスのソースコードを公開しました。
https://mam-mam.net/delphi/haar_cascade.html#fast

編集 削除
むにゃ  2023-01-15 23:29:45  No: 150743  IP: [192.*.*.*]

>>精度を上げるようにパラメータを調整すると処理速度が遅くなりますし、処理速度を速くしようとすると精度が下がります。
普通は、精度を下げて全体をスキャンし、目や顔に相当するかもと思われる領域をいくつか取得した後、この領域を精度を上げてスキャンして目や顔か判断するんじゃないの?その方が速いんじゃない?
知らんけど
精度を下げると目や顔に相当するかもと思われる領域を取得しそこなうのかな?
取得しそこなわないようにパラメータを調節とかできないのかな?

編集 削除
mam  URL  2023-01-16 00:54:14  No: 150744  IP: [192.*.*.*]

>むにゃ 様
レスありがとうございます。
基本的にはむにゃ様の仰るとおり、目の領域を検出する場合は、顔の検出の後に行います。

私が作っているフリーソフトの場合は顔の検出だけで、その精度と速度向上を行いたく思っております。

haar cascadeのxmlファイルは、ファイル毎に窓サイズ(featureWとfeatureH)は決まっていて、例えば顔の位置(haarcascade_frontalface_default.xml)は、24x24です。(目は20x20みたいです)

THaar.Cascadeを呼び出すときの引数「MinScale」で、検出される最小のサイズが決まります。例えばデフォルトの2を与えると、48x48ピクセル以上の大きさの顔領域しか検出出来ないです。
しかしながら、例えばWebカメラからリアルタイムな画像で検出する場合にMinScale=2を与えて検出させたとき、それぞれのパソコンの環境によってカメラの距離やカメラの画角が違うため、MinScale=2ではほとんど検出出来ないような場合があります。
(haarcascade_frontalface_default.xmlとWEBカメラをデジタルサイネージ等の広告の前で使うと、広告を見た人の人数を数えたりするなど、応用も沢山ありそうです。)

Webカメラからの画像をhaarcascade_fullbody.xmlで使うと、その時の通行人の人数を数えることが出来ますが、これも同じく、これ単体でしか使わなくて、MinScale=1で出来れば使いたいですが、環境によってはかなり重かったりします。
vclのTBitmapを使っていますが、FMXのTBitmapを使うように、ほんの少し改造するだけで、Androidでも動くようになるので、ますます搭載しているカメラやCPUの環境依存が強くなります。

なるべく環境依存しないようにMinScaleとScaleFactorとWindowMoveRateの上手い設定割合や、その他高速化アルゴリズム等があるとありがたいです。
この内容を書きながら思いついたのですが、WEBカメラからの画像は、ゴマ塩ノイズが多いので、
https://mam-mam.net/delphi/fmx_basic_filter.html
にあるメディアンフィルタを使ってノイズを取り除いてからHaar Cascadesを使うのも有りかもしれないですね。(重くなるだけかもですが。)

趣味でDelphiを使ってフリーソフトを作って自己満足しているだけの素人なので、内容が曖昧で申し訳御座いません。
それにしても、Delphiはもう少しヘルプやサンプルソースを素人にもわかるように充実してもらいたいと思いました。

編集 削除
むにゃ  2023-01-16 01:08:09  No: 150745  IP: [192.*.*.*]

無理して環境依存しないように作る必要があるの?
環境に合わせられるように設定できるようにするとか。
その方が性能が上がるならそれがいいんじゃない?
こんなことを書いたら怒られそうだけど。

編集 削除
nam  2023-01-16 01:22:06  No: 150746  IP: [192.*.*.*]

>むにゃ 様

>こんなことを書いたら怒られそうだけど。
そんなことは御座いません。貴重なご意見をいただけるだけでもありがたいです。

編集 削除