ししかわの二足ロボ研修

M5Stack UnitV2Frameworkをビルドする:ししかわの二足ロボ研修 Part.6

ししかわの二足ロボ研修

ししかわです。

社員研修の一環で二足歩行ロボットを作って、競技会「Humanoid Autonomous Challenge(HAC)」に参加します。

記事一覧

M5Stackの画像認識モジュールM5Stack UnitV2」をM5Stackのユニットとしてではなく単独で使い、ロボットを制御できるようにしよう!というマニアックな計画を進めています。

環境構築のゴールはUnitV2でカメラとの通信、ロボットとの通信ができることです。つまりカメラ画像からボールの位置やフィールドの形を認識したり、シリアル通信を使ってロボットに動作の指令を出したりできるようにします。

前回はクロスコンパイル環境を構築して、UnitV2で動作するプログラムを作れました。今回はOpenCVなど外部ライブラリの機能を使えるようにしていきます。

UnitV2のビルトイン機能のソースは公開されている

UnitV2は購入してすぐに様々なカメラ機能が使えてとても便利です。たとえば次のような機能があります。

  • カメラ画像のストリーミング
  • 顔認識、個人の識別
  • QRコード、バーコード認識

実はこれらの機能のソースコードは、GitHubリポジトリでUnitV2Frameworkという名前で公開されています。

GitHub - m5stack/UnitV2Framework
Contribute to m5stack/UnitV2Framework development by creating an account on GitHub.

自分で一からビルド環境を整えるのも良いですが、せっかくなので使えるものは使って時間を節約しましょう!ここからはUnitV2Frameworkのソースを出発点として機能を足していくことにします。

UnitV2Frameworkのソースを見る

CMake

前回使ったArm向けのコンパイラツールチェインがここでも使われています。ただしCMakeというビルドツールを介してビルドする点が異なります。

CMake - Wikipedia

リポジトリ直下にCMakeスクリプトが定義されています。スクリプトの読み方の説明は省略します(公式ドキュメントを参照してください)。ここでやっていることはそんなに難しくなくて、使用するコンパイラを指定したり、ビルド済みの外部ライブラリとリンクしたり、ビルド対象を指定したりしているだけです。

外部ライブラリ

UnitV2Frameworkは次の外部ライブラリに依存しています。

  • OpenCV…汎用画像処理ライブラリ
  • NCNN…Tencentが開発するDeep Learningを用いた画像認識ライブラリ
  • ZBar…バーコード/QRコード認識ライブラリ

なお、今後作りたいロボット制御アプリケーションは、認識処理にUnitV2Framework、ロボットの駆動にMAX−E1のライブラリを使います。全体の依存関係を図示すると次のようになります。

 

内部のソースコード

mainディレクトリには各ビルトイン機能に対応するサンプルコードが定義されています。各サンプルコードそれぞれにmain関数が定義されており、独立したバイナリになります。例えばcamera_stream.cppはUnitV2上でcamera_streamという名前のバイナリとして保存されています。

ビルトイン機能をブラウザから使う際は、Pythonのサーバプログラムserver.pyがこれらの機能の中から一つを選択して起動します。

mainディレクトリの中身

srcディレクトリに内部で使うクラス群が定義されています。ソースコードの中身の説明は、次回以降必要に応じて書きますので今は眺めるだけで大丈夫です。

srcディレクトリの中身

UnitV2FrameworkをDockerでビルドする

UnitV2Frameworkを、依存ライブラリを含めてビルドしてみます。ブログの読者が再現しやすくするために、必要なパッケージやライブラリはDockerイメージ内に準備していきます。私は普段VSCodeを使うので、VSCode devcontainerの機能を使います。

Developing inside a Container using Visual Studio Code Remote Development
Developing inside a Container using Visual Studio Code Remote Development

一式記述済みのDockerfileは以下です。このリポジトリをVSCodeで開き「Reopen in Container」を実行することで、コンテナが立ち上がります。

UnitV2Framework/Dockerfile at main · meganetaaan/UnitV2Framework
Contribute to meganetaaan/UnitV2Framework development by creating an account on GitHub.

ここではDockerを使った環境構築と依存ライブラリのビルド方法を説明します。とりあえず動かせればよい!という人は読み飛ばして構いません。

後述のコマンドはDockerfileの記述から抜粋しますが、適宜読み替えて貰えれば非DockerのLinux環境でも動くはずです。

コンパイラツールチェインのインストール

前回紹介したとおりArmアーキテクチャ向けのコンパイラツールチェインをインストールします。apt-getコマンドを使います。Dockerイメージのサイズを小さく保つためのベストプラクティスとして--no-install-recommendsオプションをつけて余分なパッケージのインストールを抑制したり、インストールと同時にキャッシュの削除も行ったりしています。

RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install --no-install-recommends \
gcc-arm-linux-gnueabihf \
g++-arm-linux-gnueabihf \
&& apt-get clean && rm -rf /var/lib/opt/lists/*

依存ライブラリの取得

依存ライブラリをまとめて取得します。

WORKDIR /external
RUN git clone --depth 1 https://github.com/opencv/opencv.git \
&& git clone --depth 1 https://github.com/opencv/opencv_contrib.git \
&& git clone --depth 1 https://github.com/Tencent/ncnn.git \
&& wget https://jaist.dl.sourceforge.net/project/zbar/zbar/0.10/zbar-0.10.tar.bz2 \
&& tar -jxvf zbar-0.10.tar.bz2 \
&& rm zbar-0.10.tar.bz2

それぞれ記事執筆時点での最新版を持ってきています。git cloneコマンドに--depth 1オプションを付けることで最新のコミットのみ取得します。これもDockerイメージのサイズを抑えるのに役立ちます。

依存ライブラリのビルド

依存ライブラリを順番にビルドしていきます。ビルドコマンドはUnitV2FrameworkのREADMEの指示に従います。

まずOpenCVのビルドです。ビルドフラグCMAKE_TOOLCHAINでArmツールチェインを指定しています。またOpenCVは外部の追加機能が別リポジトリ(opencv_contrib)に分かれているため、ビルドオプションでリポジトリのパスを指定する必要があります。

# Build OpenCV with extra modules
WORKDIR /external/opencv/platforms/linux/
RUN mkdir build \
&& cd build \
&& cmake -DCMAKE_TOOLCHAIN_FILE=../arm-gnueabi.toolchain.cmake -DOPENCV_EXTRA_MODULES_PATH=../../../../opencv_contrib/modules/ -DBUILD_LIST=tracking,imgcodecs,videoio,highgui,features2d,ml,xfeatures2d -DCMAKE_BUILD_TYPE=Release ../../.. \
&& make \
&& make install

続いてNCNNのビルドです。OpenCVと同じくツールチェインを指定する以外は特に注意することはありません。

WORKDIR /external/ncnn
RUN mkdir build \
&& cd build \
&& cmake -DCMAKE_TOOLCHAIN_FILE=../toolchains/arm-linux-gnueabihf.toolchain.cmake -DCMAKE_BUILD_TYPE=Release -DNCNN_VULKAN=OFF -DNCNN_BUILD_EXAMPLES=ON .. \
&& make \
&& make install

最後にzbarですが、元リポジトリのREADMEのコマンドそのままだとビルドに失敗します。環境変数NMを指定する、CFLAGSを空にするなどのおまじないが必要でした。

WORKDIR /external/zbar-0.10
RUN env NM=nm CFLAGS="" ./configure --prefix=$(pwd)/build --host=arm-none-linux-gnueabihf --build=x86_64-linux --enable-shared --without-gtk --without-python --without-qt --without-imagemagick --disable-video CC=arm-linux-gnueabihf-gcc CXX=arm-linux-gnueabihf-g++ \
&& make \
&& make install

フレームワークのビルド

ここまでで依存ライブラリの準備が完了しました。Dockerイメージをビルドして、コンテナを立ち上げてみましょう。

リポジトリをVSCodeで開き、コマンドパレット(Ctrl+Shift+P)から「Reopen in Container」と打ち込んで実行します。このときDockerイメージのビルド、コンテナ起動、ワークスペースのマウント等が自動で実行され、VSCodeがコンテナ内で再度開きます。

UnitV2FrameworkのCMakeで指定される通り、依存ライブラリの一部のファイルをlibディレクトリにコピーする必要があります。

$ cp /external/ncnn/build/install/lib/libncnn.a /workspaces/UnitV2Framework/lib/
$ cp /external/zbar-0.10/build/lib/libzbar.so /workspaces/UnitV2Framework/lib/

あとはプロジェクトのルートディレクトリでcmakeを実行します。先程ビルドしたOpenCVのパスと、ビルドするサンプルの名前をフラグで指定します。

cmake -DOPENCV_DIR=/external/opencv/platforms/linux/build -DTARGET=camera_stream .
make

ビルドが成功するとbinディレクトリにexampleという名前のバイナリができているはずです。

ビルドしたUnitV2Frameworkを動かしてみる

エラーなくビルドできたら、次は動作確認です。カメラ映像のストリーミング(camera_stream)のサンプルを実際にUnitV2上で動かしてみましょう。ただし、元のサンプルと全く同じ動作だと、入れ替えたプログラムなのかどうかが分かりづらいため一箇所ソースを書き換えます。ここではカメラの画像をグレースケールに変換します。

UnitV2Framework/main/camera_stream.cpp

#include <opencv2/core/utility.hpp>
#include <opencv2/tracking.hpp>
#include <opencv2/highgui.hpp>
// グレースケール変換のため追加でインクルード
#include <opencv2/imgproc/types_c.h>
#include "framework.h"

using namespace cv;
#define IMAGE_DIV 2

int main( int argc, char** argv )
{
    startFramework("Camera Stream", 640, 480);
    int low_quality_mode = false;

    while(1)
    {
        Mat mat_src;
        getMat(mat_src);
        /* 中略 */

        // カメラの画像をグレースケールに変換する 
        Mat gray_img;
        cvtColor(mat_src, gray_img, CV_BGR2GRAY);

        // クライアントに画像を送信する
        sendMat(gray_img);
    }
    
    return 0;
}

再びビルドします。

cmake -DOPENCV_DIR=/external/opencv/platforms/linux/build -DTARGET=camera_stream .
make

ビルドしたバイナリexampleをUnitV2に転送して、元のバイナリと交換してみます。

このとき元のバイナリを上書きしないように注意しましょう!先にUnitV2の/home/m5stack/payloadディレクトリ毎バックアップを取るのが良いかもしれません。

# ホストPC上で実行
# 元のcamera_streamをcamera_stream.bakという名前に変更
$ ssh m5stack@unitv2.local 'mv /home/m5stack/payload/bin/camera_stream{,.bak}'
# ビルドしたバイナリと差し替え
$ scp ./bin/example m5stack@unitv2.local:/home/m5stack/payload/bin/camera_stream

なお、転送するのはサンプルコードのバイナリであるexampleだけで十分です。本来バイナリが動作するためには依存ライブラリも必要なのですが、すでにUnitV2側にインストール済みであるためそちらを代用します。

ビルトイン機能の自動起動を無効化しているので、手動でサーバを立ち上げます。

# UnitV2上で実行
unitv2% cd /home/m5stack/payload
unitv2% sudo env LD_LIBRARY_PATH=/home/m5stack/payload/opencv4/lib:$LD_LIBRARY_PATH python server.py

Password:
/usr/lib/python3.8/subprocess.py:838: RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used
/usr/lib/python3.8/subprocess.py:844: RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used
server_core.py:3: DeprecationWarning: The import 'werkzeug.secure_filename' is deprecated and will be removed in Werkzeug 1.0. Use 'from werkzeug.utils import secure_filename' instead.
from werkzeug import secure_filename
rm: can't remove './uploads/temp/*': No such file or directory
2021-08-10 04:36:35,060 - [line:795] - INFO: Server PID = 1158, Core PID = 1159
/usr/lib/python3.8/subprocess.py:838: RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used
/usr/lib/python3.8/subprocess.py:844: RuntimeWarning: line buffering (buffering=1) isn't supported in binary mode, the default buffer size will be used
2021-08-10 04:36:35,072 - [line:833] - INFO: Wait for process to start

最後にホストPCのブラウザからunitv2.localにアクセスします。白黒のカメラ画像が表示されれば成功です!


以上です。開発環境を頑張ってDocker化してみたのでぜひ試してみてください。既存のコードのバックアップを取るのをお忘れなく!

次回はフレームワークに修正を加えていき、いよいよUnitV2からロボットを動かします!

タイトルとURLをコピーしました