Jetson Nanoでmediapipeのpython向け環境構築をしてHandTrackingした(2021年3月)

こんにちは。

最近、研究の方でmediapipeというgoogleのMachine Learning Platformを使用するために環境構築をしていました。私は環境構築との相性が抜群に悪いので、数日間ハマってしまいました。
本記事ではその環境構築について一貫した手順をまとめ、最終的に下画像のようなHandTrackingを行います。

f:id:ume-boshi:20210316061154p:plain:w400
見えないところまで精度高く推測できる。

mediapipeのインストール方法

windows環境

windows環境であれば、mediapipeはものすごく簡単にセットアップできます(Python環境に限る)(jetson nanoやraspiを除くLinuxMac OSでも可能?)。Python周りの環境は事前に整っていると仮定しますが、次のコマンドをcommand promptで実行するだけで環境構築が完了します。

pip install mediapipe

サンプルコードは、Kazuhito00さんのサンプル集などが使えます。素晴らしいサンプルをありがとうございます。

楽勝ですね。


Jetson Nanoでの環境構築手順

さて、ここからは本題であるJetson Nanoにおける環境構築について紹介します。JetsonやRaspiのarchitectureは、AMDの中でもaarch64という特殊なものらしく、前述のようにpip install mediapipeと1行で環境構築することができません。
そのため、1からビルドをしてあげる必要があります。

基本的には公式サイトこのサイトに従って構築していきますが、色々とハマったので全部を通した手順を本記事にまとめます。

手順1:bazelのインストール

まず、bazelというbuildのためのツールを用意します。この際、バージョンは3.4.1以上である必要があり、aptによるインストールではバージョン指定が不可でした。

mkdir ~/bazel-3.4.1
cd ~/bazel-3.4.1
wget https://github.com/bazelbuild/bazel/releases/download/3.4.1/bazel-3.4.1-dist.zip
sudo apt-get install build-essential openjdk-8-jdk python zip unzip
unzip bazel-3.4.1-dist.zip
env EXTRA_BAZEL_ARGS="--host_javabase=@local_jdk//:jdk" bash ./compile.sh
sudo cp output/bazel /usr/local/bin/

手順2:mediapipeのcloneと各ライブラリのinstall

mediapipeの実行には、opencvffmpegも必要となります。ffmpegはlibopencv-video-devでinstallされます。
この手順の最後には、ビルドの設定ファイルの内容を変更します(aarch64用)。

git clone https://github.com/google/mediapipe.git
cd ~/mediapipe
sudo apt-get install libopencv-core-dev libopencv-highgui-dev \
                       libopencv-calib3d-dev libopencv-features2d-dev \
                       libopencv-imgproc-dev libopencv-video-dev
sed -i "s/x86_64-linux-gnu/aarch64-linux-gnu/g" third_party/opencv_linux.BUILD

手順3:OpenCVのセットアップ

シェルスクリプトを実行するだけです。このスクリプトの内容をざっと見てみた感じ、opencvopencv-contribのソースコードを基にbuildをしていました。

bash ./setup_opencv.sh

手順4:gccとg++のバージョンの変更

後々、bazelによるbuildをするときに、gccとg++のバージョンが8以上でなければ変なエラーが出ます。(こんなやつ:mediapipe/calculators/tensor/image_to_tensor_converter_opencv.cc:106:12: error: could not convert 'tensor' from 'mediapipe::Tensor' to 'absl::lts_2020_09_23::StatusOr<mediapipe::Tensor>')
ということで、gcc --versiong++ --versionでバージョン確認し、8未満だった場合はこのサイトに従って下記の様に更新していきます。

# インストールとコマンドの設定
sudo apt install gcc-8 g++-8 -y
sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-8 8
sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-8 8

# 設定できているか確認
sudo update-alternatives --config gcc
sudo update-alternatives --config g++

手順5:protobuf-compilerの設定

このサイトを参考に最新版のprotocをインストールします。
その際のwgetのURLには、このリポジトリから最新版のaarch64用のzipを引っ張ってきています。

sudo apt install -y protobuf-compiler
wget https://github.com/protocolbuffers/protobuf/releases/download/v3.15.6/protoc-3.15.6-linux-aarch_64.zip
unzip protoc-3.15.6-linux-aarch_64.zip -d protoc3

sudo mv protoc3/bin/* /usr/local/bin/
sudo mv protoc3/include/* /usr/local/include/
sudo chown [user] /usr/local/bin/protoc
sudo chown -R [user] /usr/local/include/google

※ 最初にprotobuf-compilerをapt installしているが、古いバージョンなので実行しなくていいかも。

手順6:build用にコードを編集

aarch64のarchitectureに合わせて、実行不可能な内容をdisableにするなど、build時に使用されるコードを編集していきます。

・~/mediapipe/setup.pyについて、208行目あたりを編集

︙
sys.stderr.write('cannot find required file: %s\n' % source)
sys.exit(-1)

# protoc_command = [self._protoc, '-I.', '--python_out=.', source]
protoc_command = [self._protoc, '-I.', '-I/usr/local/include', '--python_out=.', source]
︙

OpenCVについて不必要なモジュールとリンカを取り除くらしい(sed~~/BUILD までが1つのコマンド)

cd ~/mediapipe
sed -i -e "/\"imgcodecs\"/d;/\"calib3d\"/d;/\"features2d\"/d;/\"highgui\"/d;/\"video\"/d;/\"videoio\"/d" third_party/BUILD

sed -i -e "/-ljpeg/d;/-lpng/d;/-ltiff/d;/-lImath/d;/-lIlmImf/d;/-lHalf/d;/-lIex/d;/-lIlmThread/d;/-lrt/d;/-ldc1394/d;/-lavcodec/d;/-lavformat/d;/-lavutil/d;/-lswscale/d;/-lavresample/d" third_party/BUILD

・~/mediapipe/requirements.txtからopencv-pythonなどを削除し、下記の状態にする(numpyのバージョン指定も無し)

absl-py
dataclasses
numpy
protobuf>=3.11.4
six
wheel

・~/mediapipe/mediapipe/python/solutions/__init__.pyの14行目あたりから drawing_utilsを削除(aarch64 Linuxではopencv-pythonが使用できないため)

︙
# import mediapipe.python.solutions.drawing_utils
import mediapipe.python.solutions.face_mesh
import mediapipe.python.solutions.hands
import mediapipe.python.solutions.holistic
︙

・~/mediapipe/third_party/BUILDの110行目あたりからcarotene_o4tを無効化(下の2行を追加している)

︙
"WITH_ITT": "OFF",
"WITH_JASPER": "OFF",
"WITH_WEBP": "OFF",
"ENABLE_NEON": "OFF",
"WITH_TENGINE": "OFF",
︙

手順6.5:bazelのキャッシュを削除

gccのバージョンを上げる前にこのコードを実行してしまい、本記事を参考にbuildをやり直している場合のみ、この手順を実行してください。

gccのバージョンを変えてから再度setupに挑戦する場合は、こういうエラーが発生します。そのため、一度bazelのキャッシュを削除する必要があると考えられます。その際は下記のようなPATHを探してrm -rfしてあげてください。

# 私の場合、下記の場所にあるキャッシュを削除 
~/.cache/bazel/_bazel_username/hfgdsahgklsdahgfkujasdhfalskd(長い名前のディレクトリ)


bazelのcacheを削除した後に、もう一度python3 setup.py gen_protos && python3 setup.py bdist_wheelをしたときに、401 authorization的なエラーが出力されました。
これは、githubの自動ログイン用ファイルが邪魔して生じる現象のようです。githubの設定で~/.netrcファイルを設定している場合、bdist_wheelのsetupが上手くできませんでした。この場合は、1次的に~/.netrcをbkpファイルにリネームして、rebootしてからsetup.pyを実行すると正常に動きました。

手順7:mediapipeのsetup.pyを実行

ここまで来たら、ついにmediapipeのbuildします。buildには結構時間がかかります。上手く終了することを願うばかりです。。。

cd ~/mediapipe/
python3 setup.py gen_protos && python3 setup.py bdist_wheel

※うまくいくと下記のファイルができているはず。
~/mediapipe/dist/mediapipe-0.8-cp36-cp36-linux_aarch64.whl

手順8:mediapipeを実行するためのinstallを施行

buildして生成されたwheelファイルを用いてpip installします。私の場合、installした内容は~/.local/lib/python3.6/site-packages/mediapipeに出力されました。

python3 -m pip install cython
python3 -m pip install numpy
python3 -m pip install pillow
python3 -m pip install ~/mediapipe/dist/mediapipe-0.8-cp36-cp36-linux_aarch64.whl

手順9:mediapipeのPATHを設定する

Python実行時にmediapipeを認識できるようにPYTHONPATHを追加設定します。 そのために、~/.bashrcの末尾に下記の行を追加します。

export PYTHONPATH=$PYTHONPATH:/home/username/.local/lib/python3.6/site-packages/mediapipe/python

コード編集後はsource ~/.bashrcで設定を更新しましょう。

手順10:試す!

ここまで来れたら、ちょっとしたバグが発生しても諦めないでください。ゴールは目の前です!

実行するサンプルコードには、Kazuhito00さんのを用いたいと思います。偉大。

cd ~/
git clone https://github.com/Kazuhito00/mediapipe-python-sample
cd mediapipe-python-sample

このサンプル集から"sample_hand.py”を用いて、webCameraから画像取得してHandTrackingします。その際webcameraからストリーミングがうまくいかない問題があったので、私はコードの280行目あたりを下記のように書き換える必要がありました。

cap = cv.VideoCapture(cap_device, cv.CAP_V4L2)  # 引数にcv.CAP_V4L2を追加



最後に実行して勝利をつかみ取りましょう。

python3 sample_hand.py

実行結果

これはwindows側で撮影した動画です。Jetson Nano側で撮影したのはまだ記録していないのです。。。そのうちすり替えます。

youtu.be


おわりに苦労話

本記事ではpython3 setup.py gen_protos && python3 setup.py bdist_wheelを用いてbuildをしましたが、他の方法も色々試していました。
例えばbazelによるソースコードからの自力buildを試してみたり、dockerによる環境構築を試したりしたのですが、どれも失敗に終わりました。

bazelの方に関しては、ビルド時にandroid-sdkのbuild-toolsが必要となるのですが、その導入のためにはandroidのemulatorが先に入っている必要があります。それらはcmdline-toolsのsdkmanagerによって導入するのですが、aarch64の特性なのかemulatorが導入可能な項目のリストに無いのです。つまり、bazelを動かす為に必要なbuild-toolsを得るために必要なemulatorが得られないという、連鎖的詰み状態に陥っていました。 aarch64の情報が少なくて呪う。

dockerの方に関しては、build途中で32GBの容量を超過しそうになってフリーズし、最終的にjetsonが起動しなくなりました。。。結果、研究用途で書いていたソースコードにアクセスできなくなり、ちゃんとバックアップを取っていなかった自分を呪う羽目に。。。
これを機にDockerを自分でも使いこなせるよう、勉強しようと決意したのでした。

参考文献

手順毎に参考にしたURLは張っていますが、ここにすべてまとめて置きます。

issue