Pythonで作成した物体検出アプリをC++、C#でWindowsネイティブアプリに作り直した話

## どのようなアプリか
ゲーム FF14のプレイ動画からユーザー名を黒塗りする動画編集アプリです。
ユーザー名の検出に物体検出を利用しています。
Pythonで実装、PyinstallerでEXE化していましたが、C++、C#で作り直し、この度Microsoftストアに公開できました。わーい！

https://apps.microsoft.com/detail/9P5W7QTSH297?hl=ja-jp&gl=JP&ocid=pdpshare

https://blog.calocenrieti.com/blog/wol_names_blacked_out_win/
 
ここでは、その経緯や直面した問題、得られたものなどを振り返りたいと思います。
 
## 最終的にそれぞれで利用した主なライブラリ
### Python
pytorch,torch-tensorrt,ultralytics,opencv-python,ffmpeg-python,ffmpeg.exe,flet,pyinstaller

Python Flet版のGUI
![2024112401 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3679605/a7dc1085-85b1-4229-bc7f-cdb5e3a8e496.jpeg)

### VC++、C#
onnxruntime(C++,nuget),onnxruntime-directml(C++,nuget),cuda-toolkit(C++),tensorrt(C++),opencv(C++),ffmpeg.exe,WinUI3

C# WinUI3版のGUI
![2025021502 (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3679605/44bcecaf-f844-461d-b5e7-d920fb72951b.jpeg)

 
## 作り直した動機
- Pythonで実行している時よりPyinstallerのEXEでは処理速度が劣化した
- UltralyticsのDiscordで質問した際、処理速度を求めるならPyinstallerよりネイティブで作る方が良いと提案を受けた
- VC++で作成すればPythonよりも処理速度が改善するのではないか？
- PyinstallerのEXEは署名のない野良アプリなので、実行時に不審なアプリとして警告が表示されてしまう
- FletのGUIに現状バグがあり、GUI上で数回操作するとGUIの応答がなくなる。
 
## 作り直し検討時の計画
- C++、C#共に未経験なので、可能な限り公開されているコードを利用させてもらいたい。
- Windows Copilotなど無料の生成AIを利用して、コードを生成してもらえないか。
- 処理速度を優先したいため、できる限りC++を利用する。GUIだけC#とするか？
- マテリアルデザインのFletを利用していたので、同じくマテリアルデザインなWinUI3を採用したい
- 物体検出はONNXRUNTIMEを利用する。GEFORCE向けにはONNXRUNTIME-CUDA、RADEON向けにはONNXRUNTIME-DirectMLで対応してはどうか
- FFMPEGのライブラリは難しそうなので、従来同様、FFMPEG.EXEを利用したい
 
## 直面した困難たち
### C++ ONNXでの物体検出で、1つ検出が漏れる問題
C++ ONNXでの物体検出は各所に公開されていましたが、きれいに動作させられるものがなかなか見つけられませんでした。
Qiitaで@soramimi_jp様が公開されているコードがシンプルでちゃんと動作するコードでした。
 
https://qiita.com/soramimi_jp/items/abffc689afcdbfc80107
 
全く分かっていないC++で不安しかありませんでしたが、ちゃんと物体検出できたことで、安堵感や今後の開発に光明が見えた思いでした。
何度も動かしてその動作に満足していたのですが、ある時、検出対象である物体が１つ検出されていないことに気づきました。
ONNXに変換したことで検出精度が劣化し、検出が漏れたのか？とも思いましたが、漏れた1件はPythonでの検出スコアはかなり高く、ONNXによる劣化の影響とは考えにくい状況でした。
よく見ると、Qiitaの記事においても、1つ検出が漏れているようにも思えました。
コード側の問題である可能性が高そうなので、わからないながら調査を行いました。
（恥ずかしながらsoramimi様に問い合わせをしたりもしました）
- 入力画像が入力テンソルの正方形にリサイズされ、絵が変形しているので検出されていないのではないか？
⇒ 変形しないように前処理してもやはり１つ検出されず
- 本当は検出しているが、検出の絵を描く際に１つだけループから抜けているのではないか？
- 検出値が0から1の間ではなくなっていて、結果が変になっているのか？
⇒ ループも網羅されており、値も問題なし
 
ぼんやりとOpenCVのアドカレ記事を眺めていた時、OpenCVがRGB読み込みに対応するという記事を目にしました。
 
https://qiita.com/hon_no_mushi/items/44afccf15ef083863952
 
OpenCVは基本BGRだけど、物体検出ではRGBで、変換忘れると変になるんだよねー
わたしも実際、変換せずに物体検出させていて検出精度が上がらない！ってぼやいてたこと、以前あったなー
・・・試しにRGBに変換しないでBGRで入力テンソルに入れてみる？
⇒ 全部検出されました＼(^o^)／
よくよくコードを見てみると、BをRに、RをBに入力するようになっていて、BGR2RGBの変換を不要としていたのだと思います。
物体検出はRGB入力、という思い込みもあって、全く気づきませんでした。
 
この1件検出漏れる問題で、少なくとも2週間は悩んでいたと思いますが、でもこの調査によってONNXやYOLOの仕組みを知る機会になり、結果的に良かったように思っています。
 
### ONNX-CUDAが遅い問題
soramimi様のコードには、ONNX-CPUだけでなくONNX-CUDAも利用できるようになっていました。
ONNX-CUDAの利用を検討していたので、早くも物体検出フェーズをクリアかな？と喜んでいました。
実際、ONNX-CUDAはちゃんと動作し、正しく物体検出できていました。
CPU推論より圧倒的に早いCUDA推論。これは勝ち確か？
試しに短い動画をFFMPEGで読み込んで、フレーム単位にONNX-CUDAで検出、動画に書き出しを動かしてみます。
・・・遅い。
現状のPyinstallerのEXEよりも圧倒的に遅かった。
よく考えるとPythonでは、torch-tensorrtにより、JIT的にtensorrtに変換され物体検出されています。
tensorrtとCUDA、と聞けば、確かにtensorrtの方が早そう・・・
現在のEXEよりも処理速度をあげるためにはC++においてもtensorrtを利用しないといけないことがわかりました。
 
tensorrtは下記のコードを利用させて頂きました。
記事の中央あたりに引用してあるyoloの説明図はわかりやすく図解されていると思います。わたしも1件検出できない問題でyoloの仕組みを勉強したこともあって改めて納得です。

https://cyberyang.com/inference/34.html
 
なお、ONNX-CUDAは遅くて不採用になりましたが、ONNX-DirectMLはONNX-CUDAよりも高速でした。
 
### VC++からffmpeg.exeを_popenで呼び出すとコンソール画面が出て、しかも処理が遅い問題
C++はやはり難しいと感じたので、WinUI3のGUIはC#で作成していました。
物体検出はC++でDLLを作成し、GUIのC#からそのDLLの関数を呼び出します。
XAMLを含めてC#も最初はさっぱりわかりませんでしたが、触り始めてみるとまるで実家のような安心感！
C++は全然わからずずっとアウェイゲームのような緊張感でしたが、C#はかなりお手軽で今後も使ってみたいなと感じるものがありました。
 
WinUI3のGUIも大まかには形になったので、GUIからC++で作成したDLLを呼び出して処理できるか、確認してみます。
ポチ！
WinUI3のGUI画面以外に、黒いコンソール画面が２つ表示されました。ダサい。せっかくのWinUI3なのに。
どうもC++から_popenを利用するとコンソール画面がでるそうで。（C#では出ない）
また_popenでのプロセス間通信はWindowsは特に遅いらしく、tensorrtを利用しても従来版と処理速度があまり変わらないのも問題でした。
⇒じゃあFFmpegのライブラリをINCLUDEして使いますか！
Qiitaの記事をなぞる形で作成してみたものの、シーケンシャルな動作だとスレッド化している_popenに勝てない。
⇒じゃあスレッド化しますか！
これがなかなかに難しく、2週間くらいは試行錯誤した気がしますが結局実現できませんでした。
動画の入力と動画の書き出しをそれぞれ別スレッドのFFmpegを実行したいのですが、同期をとるmutex、各フレームの時間、などなどをC++で対応しようとすると、どうしても実現できませんでした。（たぶんメモリがぐちゃぐちゃになってて、時間が変だったりすること多数）
C++のコードは、ほとんど生成AIにおんぶにだっこだったため、あまりデバッグをすることもできず完全にお手上げ、とりあえずは諦めました。

### Microsoftストアで何度もリジェクトされた件
MSIXパッケージにして、パートナーセンターにアップロード、認定されるのを待っていたのですが、何度もリジェクトされまして。
「ノートパソコンで動かしたら、ちゃんと動かなかったら、ダメ」だと。
型番も書いてあったので検索してみると、ちょっと古めのノートパソコンでグラフィックはCPUの統合グラフィックで。
少しでも処理を早くするために、IntelのGPUならQSVを利用するようにしているのですが、恐らくそれが使えない程度に古くてエラーになったんだろうと想像しています。
あー、じゃあ最低スペックにGEFORCEとRADEONを指定しておきますね！これでお願いします！
「ノートパソコンで動かしたら、ちゃんと動かなかったら、ダメ！」
え？いや、最低スペックでテストして欲しいんですけど？
「ノートパソコンで動かしたら、ちゃんと動かなかったら、ダメ！！」

時差があるのか基本1日1回しか対応してくれない感じで、しかもテンプレ回答しか書いてなくて正直意図がわからず困ったのですが、Microsoftストアのポリシーに合致していない、ということを言いたいようです。
これよく読んでおいてねって言ってほしかったなー（言ってたのかもしれないけど）

https://learn.microsoft.com/ja-jp/windows/apps/publish/store-policies

私が指摘されたのは、
> 10.1.2
>
>製品は完全に機能する必要があり、対象となるシステムとデバイスに適切な機能を提供する必要があります。

ですが、

> 10.4.1
>
>製品は、製品で指定されているソフトウェア、ハードウェア、画面解像度の要件との互換性など、ダウンロード先のデバイスとプラットフォームをサポートしている必要があります。 製品は、互換性のないデバイスにダウンロードされた場合、起動時にそれを検出し、要件の詳細を説明するメッセージを顧客に表示する必要があります。

この当たりも含めての指摘だったのかなー。
確かにごもっとも。
また、

>10.1.3
>
>検索用語は 7 つを超えることはできません。これらは製品に関連している必要があり、同じ提供元でない限り、他の製品のタイトルを使用することはできません。

これについても指摘されました。
FF14専用アプリなので、FF14と入れたいところですが、それはお前のソフトじゃないだろ？的なことを何度も指摘されました。
なお、アプリ説明文の方は別にいいっぽい。
 
## 作り直しで得られた成果
### 得られた成果（アプリ）
- アプリ起動の速さ
従来はPython環境の構築か起動に30秒はかかっていましたが、Windows対応で数秒で画面が表示されます。
- DirectML対応により利用可能なPCの増加
ゲーミングパソコンだけでなく、新しめのノートパソコンでも動作するのではないかと考えています。
- OpenCVの処理が速い
物体検出をせずにOpenCVで塗りだけする機能があるのですが、これがかなり早くなりました。
- Microsoftストア登録でアプリに署名がつき、警告がでない
1800円払いましたが、何があるかわからない野良アプリより安心感はありそう。
Microsoftストアは差分アップデートにも対応しているらしいので、アップデート毎に全てダウンロードだった従来よりも改善しそうです。
- アフィリエイト広告をつけれた
webview2をつけて、楽天アフィリエイトリンクをつけました。Python Fletでもつけたかったのですが、ブラウザ部品の利用が簡単ではなく諦めていました。成功報酬なのであまり期待はできませんが、広告をつけたかったので良かったです。
### 得たかったが得られなかったもの（アプリ）
- 処理速度の向上
特にGeforceでの処理速度向上を期待していたのですが、ほとんど従来と変わりませんでした。逆に最初に一度だけONNX-tensorrtの変換が必要で、これに6分ほどかかるため、改悪されたとも言えます。
Pythonでのtorch-tensorrtでの実行速度がかなり優秀だった、ということも改めて認識しました。
FFMPEGのライブラリを静的、動的にリンクして、スレッド化すれば、従来よりも速くなる可能性があると思い、私なりに試行錯誤しましたが、実現できるほどのスキルや知識がありませんでした。
- WinUI3なのにコンソールが表示される。
- ファイルサイズの縮小
Pyinstallerは紐づくライブラリを全て持っていくイメージだったので、Windows版ではかなり小さくできるのではないか？と予想していましたが、考えていたほどの違いはありませんでした。（どちらも圧縮時2.5GBオーバー）
処理速度やファイルサイズなどの点では、PyinstallerのEXEのままでも良かったとも言えると思います。
### 得られた成果（私が）
- Microsoftストアに公開できるようなWindowsアプリを作れた、という達成感。嬉しさ。
昔から様々なフリーソフトやオンラインソフトというものを利用させて頂いてきましたが、ニッチなソフトではありますがMicrosoftストアに公開できたというのはとても嬉しく、自信にもつながりそうです。
C++、C#を扱うのも全く初めてでしたが、いろいろ妥協もありましたが一応形にできたというのも大きいです。
C#はとっつきやすかったので、今後も何か作れたらいいなと思いました。
- 生成AIによるコード生成の現状を認識できた。
Windows Copilot、Gemini 2.0や、LMstudioでローカルdeepseekR1などでコード生成をしてきました。プロンプトで希望のロジックや希望の動作を伝えると、コードを作成してくれて、全く無知の私はとても重宝しました。
しかし生成AIが作成できるのは、それまでに公開されているコードを提案してくれる程度に過ぎず、世の中に公開されていないコードの生成はかなり難しいという印象を受けました。
特に感じたのは、ffmpeg_apiのスレッド化対応の時です。
私の方で検索した限りでは、C++でのffmpegのスレッド利用は見つけられませんでした。単純なスレッド処理であればなんとか実現できたのですが、ffmpeg_apiと物体検出の全てを満足するスレッド処理はうまく作れませんでした。
生成AIは学習した（≒公開された）データしか持ち合わせておらず、まだ広く公開されていないコードは、正しく生成できない印象です。逆に統廃合されたような今は存在しないヘッダを提示してきたりと、過去の情報にはとても強い印象を持ちました。
将来的にコーディング作業は生成AIにとってかわる、というのは確かにそうかなと思いますが、ドキュメントなどを理解してコーディングできるのはたぶんまだ先で、またC#よりはC++のような、公開されたコードが少ない言語はなかなか人に取って代わるのは難しいのではないかと感じました。
逆に部品レベルの規模の小さいサンプルの生成はすごく得意ですね。その言語を良く知っている人ほど生成AIによるコーディングもうまく利用できるのだろうと感じました。

## 後日談：2025/03/08追記
アプリのダウンロード数は雀の涙ほどですが、こちらの記事は思わぬ反響をいただいて、とても恐縮しております。
アプリリリースしてしばらく経ちましたが、てこ入れとして、処理速度の改善をしたいと考えていました。
物体検出の入力テンソルは正方形を利用していましたが、ゲーム画面の縦横比を維持したまま正方形に入れると、必ずといってもいいほど使わない余白部分が出てきます。
その部分を減らすことで、つまり正方形ではなく16:9の長方形の入力テンソルにすれば利用効率があがり今の精度を落とさずに処理速度改善ができるのではないか？
そんなことを妄想しながら、学習を行い、修正・テストをしようとしていた時でした。

### 異変１：DirectMLがGPUを使わず遅い
onnxruntime周りはコードを変えてないはずなのに、DirectMLがすんごい遅い。
え？なんで？？GPU使ってないっぽい？？
改善の修正してる時に変なところ触って消しちゃったのかな？？
試しにMicrosoftストアでリリースしてるアプリをダウンロードして、確認してみよ

### 異変２：動画が処理できない
ストアからインストールしたアプリで、動画の処理がエラーになって正常に処理できない。
動画の処理ができるアプリとして出しているのに、動画が処理できない。
C++からffmpeg.exeを実行する時に相対パスで指定していたため、ストアからインストールした際に影響があるためかなとも考えましたが、フルパス指定にしてもなんだかどうにもちゃんと動きません。
これってもしかして
>  注意
> Windows プログラムで使用すると、_popen 関数は無効なファイル ポインターを返し、その結果、プログラムは無期限に応答を停止します。

https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/popen-wpopen?view=msvc-170

開発の時には_popenでもイケるやん！って思ってそのままにしてましたが、MSIXにした後、自分のパソコンにお試しインストールはしていませんでした。
恐らく、C++から呼んでいたffmpeg.exeはMicrosoftストアにリリースされた当時から、動いていなかったと思われます。
Microsoftストアでリジェクトされ続けていたのは、これのことだったのか・・・（それならそうと言ってほしい、けどそんなのテストする人には見えないし）

### ffmpeg.exeの_popenへの対策
copilotに泣きついて、createprocessのコードを作成してもらいました。
いくつかうまく動かない点もあり、やはり試行錯誤しましたが、なんとか形になりました。助かりました。

### DirectMLがGPUを使わずに遅い対策
こちらはやはりコードは変更しておらず、うまく動作しない原因がわかりませんでした。
それならばと、世の中の動くコードを拝借して動作させようとすると、別のエラーが出始めてビルドできない始末。
一体どうなってるんだ？と悩みましたが。
・・・もしかすると、そもそもMicrosoftストアへリリースした際には正しく動作してなくて、遅かったのでは？
となると、うちの環境だけ特殊で早かったとか？？
そういえばNugetでonnxruntimeやDirectMLを導入してるから、開発前に使うのかも？とビルドしていたonnxruntimeを削除したけど、その影響？
ビルドしたonnxruntimeのdllなら早くて、パスか何かが通っていて速くなっていたとか？
⇒ onnxruntimeを再度ビルドして、作成されたonnxruntime.dll、onnxruntime_providers_shared.dllを試しにアプリのファイルに上書きしてみたところ、DirectMLの処理が速くなりました。
えー・・・Nugetさんよぉ・・・

正しく動作しないアプリを堂々とリリースしている恥ずかしさや、どうして動作しなくなったのかもわからずかなりパニくったので、ここに記して供養しておきます。