このブログは、株式会社フィックスターズのエンジニアが、あらゆるテーマについて自由に書いているブログです。
初めまして、アルバイトの藤本です。特徴記述は、画像における特徴点を「記述」、すなわち特徴ベクトルという数値に変換する処理です。今回のプロジェクトにおいてはSfMという3次元復元アルゴリズムで使用することを想定しています。
数ある特徴記述のアルゴリズムの中でもHashSIFTは精度と計算コストのバランスに優れており、私はインターン生の時から、GPU環境におけるさらなる高速化に取り組んでいました。
ちなみにその成果はfixstars/cuda-efficient-featuresの一部として公開しています。cuda-efficient-featuresについてはCUDAによる局所特徴量計算の高速化とソースコード公開にて紹介がされているのでご覧いただけると幸いです。
今回はHashSIFTの概要とその高速化手法について紹介していきます。
HashSIFT[1]は特徴記述において長く使われているSIFTの改良版です。以下の4つの手順からなります。
1~3の操作は上図にあるように、従来のSIFTと同じ操作です。
グレイスケール変換や行列演算など基本的な処理はすでにopenCVに高速化が実装されています。これを用いれば、CUDAの知識がなくとも簡単に高速化ができます。
HashSIFTにおいて大部分を占める特徴ベクトル計算は、まず各特徴点ごとに並列化できます。また、1特徴点について32×32の周辺画素を処理しますが、これも画素ごとに並列化できます。
1つの特徴点に対するパッチ(周辺画素データ)、ヒストグラム、特徴ベクトルをshared memory上で共有するため、下図のように1特徴点に1ブロックを、1画素に1スレッドを割り当てることにしました。 (実際は、32×32画素に同数のスレッドを割り当てるとメモリや同期といった点でパフォーマンスが低下するので、一番効率が良くなるようにスレッドの次元は調節しました。)
作成した特徴ベクトルは結果として返す前に正規化するのですが、この際に特徴ベクトルの2乗和を計算する必要があります。
ここでshared memoryに置かれた特徴ベクトルの要素を単に集計(リダクション)しようとすると、1つのメモリに対して大量のスレッドがアトミックなアクセスをする必要が出てきて非効率です。そこで
これによって各特徴点に対する計算を効率化しました。
SfMは大量の画像を処理する前提なので、今回実装した特徴記述は繰り返し呼び出されることを想定しています。
https://proc-cpuinfo.fixstars.com/wp-content/uploads/2023/12/reduction.pngこのとき、呼び出しの度にデバイスメモリを確保/解放するのはオーバーヘッドとなり、できれば避けたいです。
そこで、それらのメモリを関数内のローカル変数からクラス変数にし、そのクラスのインスタンス自体は処理の最初から最後まで保持し続けるようにしました。これにより画像間でのcudaAPIの処理時間を大幅に減少させることができました。
しかしプロファイルを見るとcv::cuda::gemm
という行列演算を実行した後に、まだcudaAPIが呼び出されてしまっています。そこでopenCVの実装を確認したところ、cublasのハンドル作成が原因でした。
cublasHandle_t handle;
cublasSafeCall( cublasCreate_v2(&handle) );
handle
についてもクラス変数にしたところ完全に解消。良かったです。
ここまで高速化を進めて、まだ処理が重い部分はヒストグラムの作成でした。
今回は64スレッドが4×4のヒストグラムに対して同時に書き込みを行っていくので、アトミック演算であるatomicAddが必要でした。これはreduction命令におけるwarp shuffleで言及した通り非効率です。
理想としてはatomicAddを完全になくしたかったのですが、そのためにはどうしてもスレッド数を減らす必要があり、結果として実行時間が長くなってしまいます。そこで、各スレッドがアクセスするヒストグラムの場所を分散させることで待機状態になる時間をなるべく減らすようにしました。
最初は上図のように青色のスレッドが黄色の枠で囲まれたヒストグラム内の画素を上から下に走査していく実装でしたが、これだとヒストグラムの1セル内に16スレッドが衝突する可能性があります。そこで以下のようにスレッドを分散させてやることによって1セル内のスレッド数を4スレッドに下げることができ、結果として高速化に成功しました。
以上のアルゴリズムを実装した後、処理時間の結果をもとのCPU実装と比較しました。
計測環境は以下の通りです。
Key | Value |
---|---|
CPU | Core i9-10900(2.80GHz) 10Core/20T |
GPU | RTX 3060 Ti |
入力画像 | SceauxCastleに含まれる11枚の画像 |
特徴点数 | 10,000点に固定 |
計測方法 | 各画像について100回実行の平均処理時間を計測し、最後に11枚の平均をとる |
実装\ディスクリプタ | HashSIFT256 | HashSIFT512 |
---|---|---|
リファレンスCPU Single Thread | 604.9 | 740.1 |
リファレンスCPU Multi Thread | 174.6 | 315.6 |
cuda-efficient-features | 0.89 | 0.99 |
CPUのマルチスレッド実装と比較しても200~300倍程度高速にすることができました。
hashSIFTのアルゴリズムは並列化と非常に相性が良かったようで、特徴記述の処理時間をほぼ気にする必要がないところまで高速化することができたのではないかと思います。
3、4か月ほど実装に取り組ませてもらっていましたが、様々な高速化技法に触れることができた貴重な期間でした。
最後に、メンターとして日々ご指導いただいている高木章洋さんに感謝申し上げます。
[1] I. Suárez, J. M. Buenaposada and L. Baumela, “Revisiting Binary Local Image Description for Resource Limited Devices,” in IEEE Robotics and Automation Letters, vol. 6, no. 4, pp. 8317-8324, Oct. 2021
[2] 藤吉弘亘、“Gradientベースの特徴量抽出-SIFTとHOG-”、情報処理学会研究報告、CVIM160、pp. 211-224、2007
コンピュータビジョンセミナーvol.2 開催のお知らせ - ニュース一覧 - 株式会社フィックスターズ in Realizing Self-Driving Cars with General-Purpose Processors 日本語版
[…] バージョンアップに伴い、オンラインセミナーを開催します。 本セミナーでは、...
【Docker】NVIDIA SDK Managerでエラー無く環境構築する【Jetson】 | マサキノート in NVIDIA SDK Manager on Dockerで快適なJetsonライフ
[…] 参考:https://proc-cpuinfo.fixstars.com/2019/06/nvidia-sdk-manager-on-docker/ […]...
Windowsカーネルドライバを自作してWinDbgで解析してみる① - かえるのほんだな in Windowsデバイスドライバの基本動作を確認する (1)
[…] 参考:Windowsデバイスドライバの基本動作を確認する (1) - Fixstars Tech Blog /proc/cpuinfo ...
2021年版G検定チートシート | エビワークス in ニューラルネットの共通フォーマット対決! NNEF vs ONNX
[…] ONNX(オニキス):Open Neural Network Exchange formatフレームワーク間のモデル変換ツー...
YOSHIFUJI Naoki in CUDAデバイスメモリもスマートポインタで管理したい
ありがとうございます。別に型にこだわる必要がないので、ユニバーサル参照を受けるよ...