M1 Macでncnnを使ってYOLOXを動かしたメモ

目次

M1 Macでncnnを使ってカメラからの入力をYOLOXに入れて遊んでみたら意外と詰まるところがあったのでメモ。基本的にハマったところしか書いていません。

きっかけ

インターネットを眺めていたらncnn(https://github.com/Tencent/ncnn)という面白そうなものを見つけたので、とりあえず面白そうなYOLOXを動かしてみた。
せっかくなのでLinuxではなくmacOSで動かしてみてmacOSの開発環境にも触ってみることにした。

環境

  • MacBook Pro 14インチ (2021) & M1 Pro
  • macOS 12.4
  • Xcode 13.4.1

(どうでもいいですけどmacOSのバージョンとXcodeのバージョンってずれるんですね)

本編

OpenCVをインストール

サンプルコードがopencvを呼んでいたので素直に使うことにした。OpenCVを使わずにncnn&YOLOXができるかどうかは不明。
今回はOpenCVをHomebrewでインストールする。Homebrewが導入されていない場合は事前に導入しておく。

brew install opencv

Xcodeでプロジェクト作成

C++でコードを書くため、XcodeのテンプレートはmacOSCommand Line Toolを選択し、LanguageでC++を選ぶ。

OpenCVを使うためにビルド設定を変更していく。
まず、Xcodeのファイル一覧画面で一番上のプロジェクト名の行を選択し、Build Settingsタブを開く。
Search Pathsセクションの中のHeader Search PathsLibrary Search Pathsを設定するため、以下のようにpkg-configコマンドを実行してopencv4のパスを確認する。

$ pkg-config --cflags opencv4
-I/opt/homebrew/opt/opencv/include/opencv4

$ pkg-config --libs opencv4
-L/opt/homebrew/opt/opencv/lib -lopencv_gapi -lopencv_stitching -lopencv_alphamat -lopencv_aruco -lopencv_barcode -lopencv_bgsegm -lopencv_bioinspired -lopencv_ccalib -lopencv_dnn_objdetect -lopencv_dnn_superres -lopencv_dpm -lopencv_face -lopencv_freetype -lopencv_fuzzy -lopencv_hfs -lopencv_img_hash -lopencv_intensity_transform -lopencv_line_descriptor -lopencv_mcc -lopencv_quality -lopencv_rapid -lopencv_reg -lopencv_rgbd -lopencv_saliency -lopencv_sfm -lopencv_stereo -lopencv_structured_light -lopencv_phase_unwrapping -lopencv_superres -lopencv_optflow -lopencv_surface_matching -lopencv_tracking -lopencv_highgui -lopencv_datasets -lopencv_text -lopencv_plot -lopencv_videostab -lopencv_videoio -lopencv_viz -lopencv_wechat_qrcode -lopencv_xfeatures2d -lopencv_shape -lopencv_ml -lopencv_ximgproc -lopencv_video -lopencv_xobjdetect -lopencv_objdetect -lopencv_calib3d -lopencv_imgcodecs -lopencv_features2d -lopencv_dnn -lopencv_flann -lopencv_xphoto -lopencv_photo -lopencv_imgproc -lopencv_core

今回は上記の結果になったため、
Header Search Paths = /opt/homebrew/opt/opencv/include/opencv4
Library Search Paths = /opt/homebrew/opt/opencv/lib

次に、リンク設定を変更するためLinkingセクションのOther Linker Flagspkg-configの結果の-lから始まる部分全てを入れる。

これでこのプロジェクトでOpenCVが使えるようになる。

ncnnをダウンロード

ncnnの自前ビルドはせず、ncnnのGitHubのReleaseからビルド済みのバイナリをダウンロードして使う。
今回使ったファイルは ncnn-20220420-macos.zip
GPUも使うためにはVulkanとついている方になるが、今回はとりあえず動かすことが目的なので、CPUのみのバイナリを使うことにした。

ncnnをプロジェクトに追加

(結局正しい方法はよくわからなかったので間違っているかもしれない)

ncnnのReleaseのzipをダウンロードして展開すると、ncnn.frameworkopenmp.frameworkの2つのフォルダが出てくる。
このframeworkフォルダはどうやらXcodeの機能でヘッダとバイナリをまとめたものらしい。

Xcodeのファイル一覧画面で一番上のプロジェクト名の行を選択し、GeneralタブのFrameworks and Librariesセクションで+を押し、Add Other -> Add Filesから上記の2つのframeworkフォルダを選択すると追加される。
デフォルトではEmbed & Signになっているが、ビルド時にエラーになったので適当に動かすだけならEmbed without Signingにする。

これで本来ならframeworkフォルダに入っているヘッダファイルも使えるようになるようだが、自分の環境では参照エラーが出てビルドが通らなかった。そのため、Header Search Pathsncnn.framework/Versions/A/Headers/ncnnを追加してエラーを回避した。(多分間違った対処法)

コーディング

ncnnリポジトリのexamplesからYOLOXのものを持ってくる。
https://github.com/Tencent/ncnn/blob/master/examples/yolox.cpp

カメラからの映像を入力にしたいので、mainを少しだけ書き換えこんな感じにする。

cv::Mat frame;
std::vector<Object> objects;

// カメラを開く
cv::VideoCapture cap(0);

// 映像を取得してYOLOXに渡してobjectsを四角で囲った画像を表示する
cap.read(frame)
detect_yolox(frame, objects);
draw_objects(frame, objects);

モデルを配置する

絶対にビルド時に自動的にコピーさせる方法があるが、調べるのが面倒くさかったため、ビルドフォルダに手動でモデルをコピーしてお茶を濁した。

ビルドしたファイルが置かれるフォルダは以下の通り

~/Library/Developer/Xcode/DerivedData/プロジェクト名-ランダムな文字列?/Build/Products/Debug

ここにyolox.paramyolox.binを置くと動く。

モデルはncnn用に変換されたものをどこかから持ってくるか、YOLOXのリポジトリからONNXファイルを持ってきて、ncnnのtoolsに入っているonnx2ncnnを使って変換する。

https://github.com/Megvii-BaseDetection/YOLOX/tree/main/demo/ONNXRuntime
https://yolox.readthedocs.io/en/latest/demo/ncnn_cpp_readme.html

自分で学習させたモデルをONNXに変換してncnnで使うこともできるらしい。

ちなみにonnx2ncnnなどのツールはLinuxのprebuildには入っているが、macOS版には入っていないような気がする。ちょっと悲しい。