【#UnitV2入門】camera_streamを改造して、リアルタイムエッジ抽出をやってみた♪

今回は、UnitV2で遊んでみた。
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/UnitV2?src=hash&amp;ref_src=twsrc%5Etfw">#UnitV2</a> で、camera_streamを改造して、リアルタイムエッジ抽出をやってみました🎵<a href="https://twitter.com/hashtag/M5Stack?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack</a> <a href="https://t.co/4CpUxiL3rF">pic.twitter.com/4CpUxiL3rF</a></p>&mdash; ウワン (@MuAuan) <a href="https://twitter.com/MuAuan/status/1455526370310819843?ref_src=twsrc%5Etfw">November 2, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
##しかし、その道のりが長かったので、まとめます

以下の解説で、「VSCodeを使うので、VSCode devcontainerの機能を使います」
ということで、VSCode+Docker containerを利用というのは、Ｗｉｎｄｏｗｓ１０だと、ＷＳＬ２＋VSCodeということで、それなりにハードルは、高い。
さらに、ししかわさんのサイトの通りやっても、OpenCVのversionが現在4.4.0から4.5.4に上がってウワンには動かせなかった。
ということで、ＷＳＬ２インストールからのお話をまとめることとする。
【参考】
[M5Stack UnitV2Frameworkをビルドする：ししかわの二足ロボ研修 Part.6](https://rt-net.jp/humanoid/archives/3962)
[SSH Connect](https://docs.m5stack.com/en/quick_start/unitv2/config)
[UnitV2](https://docs.m5stack.com/en/unit/unitv2?id=description)
[WSL2の環境構築手順](https://qiita.com/poramal/items/3562472d52fe60f61c56)

###WSL2環境構築
新しいマシンのインストールは、以下のリンクのとおりで実施できる。
Power shellを管理者権限で開いて以下を実行して、helpが出たら以下の古い場合の手順へ

```
wsl --install
```
[WSL のインストール](https://docs.microsoft.com/ja-jp/windows/wsl/install)

####古い場合の手順
[以前のバージョンの WSL の手動インストール手順](https://docs.microsoft.com/ja-jp/windows/wsl/install-manual)
[WSL2 + Docker Desktop + VSCode（Remote - Containers） でGoの開発環境構築](https://qiita.com/poramal/items/11912b5533ec8e7dbaac)

####管理者として PowerShell を開き、以下を実行します。
```
dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart
```
#### [設定] メニューで、最新の Windows バージョンに更新
Windows Updateを実施し、最新版に更新する
####仮想マシンの機能を有効にする
管理者として PowerShell を開き、以下を実行します。

```
dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart
```
#### Linux カーネル更新プログラム パッケージをダウンロードする
[x64 マシン用 WSL2 Linux カーネル更新プログラム パッケージ](https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi)
をダウンロードしてダブルクリックしてインストールする。
####WSL 2 を既定のバージョンとして設定する
PowerShell を管理者権限で開いて次のコマンドを実行する
これを実行すると、次にインストールするubuntu 20.04のベースWSLがWSL2になる。

```
wsl --set-default-version 2
```
####Microsoft Store を開き、ubuntu20.04を選択
StoreでWSLやubuntuで検索すると候補が出てくる。ちなみにubuntuもインストールできるが、バージョンが時々になるとのこと。
やり直したいときのuninstallは「アプリと機能」からアンインストールして、やり直しします。
[Windows Subsystem for LinuxからUbuntuをアンインストール](https://qiita.com/akabei/items/1e2931cdfd8c0b206990)
####インストール状況の確認
PowerShellを管理者権限で開いて以下を実行すると２が確認できる

```
PS C:\WINDOWS\system32> wsl -l -v
  NAME            STATE           VERSION
* Ubuntu-20.04    Running         2
```
また、ubuntuもWSLのコマンドラインから以下で確認できる。

```
$  lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04.3 LTS
Release:        20.04
Codename:       focal
```
###vscodeのインストール
####Dockerインストール
以下をダウンロードしてインストールする。
[Docker Desktop for Windows](https://hub.docker.com/editions/community/docker-ce-desktop-windows/)
Power Shellを管理者権限で開いて以下実行し、確認する。

```
PS C:\WINDOWS\system32> docker version
error during connect: In the default daemon configuration on Windows, the docker client must be run with elevated privileges to connect.: Get "http://%2F%2F.%2Fpipe%2Fdocker_engine/v1.24/version": open //./pipe/docker_engine: The system cannot find the file specified.
Client:
 Cloud integration: 1.0.17
 Version:           20.10.8
 API version:       1.41
 Go version:        go1.16.6
 Git commit:        3967b7d
 Built:             Fri Jul 30 19:58:50 2021
 OS/Arch:           windows/amd64
 Context:           default
 Experimental:      true
```
WSL2でも同様に、ただしこちらはDockerをactivateしていないと注意がでたので、

```
$ docker version
The command 'docker' could not be found in this WSL 2 distro.
We recommend to activate the WSL integration in Docker Desktop settings.
For details about using Docker Desktop with WSL 2, visit:
https://docs.docker.com/go/wsl2/
```
Desctopに出ているポップからactivate(Docker start;下図参照)を実行した。
その後、以下のようにUbuntu上でもpermission deniedが出ているが上と同様な情報が得られる。
![docker_start.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/ca8ff358-4b1d-fc61-3aec-f5e1267e8724.jpeg)


```
$ docker version
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Get "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/version": dial unix /var/run/docker.sock: connect: permission denied
Client: Docker Engine - Community
 Cloud integration: 1.0.17
 Version:           20.10.8
 API version:       1.41
 Go version:        go1.16.6
 Git commit:        3967b7d
 Built:             Fri Jul 30 19:54:22 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true
```
そして、

```
$ sudo docker version
[sudo] password for ubuntu:
Client: Docker Engine - Community
 Cloud integration: 1.0.17
 Version:           20.10.8
 API version:       1.41
 Go version:        go1.16.6
 Git commit:        3967b7d
 Built:             Fri Jul 30 19:54:22 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.8
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.16.6
  Git commit:       75249d8
  Built:            Fri Jul 30 19:52:31 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.9
  GitCommit:        e25210fe30a0a703442421b0f60afac609f950a3
 runc:
  Version:          1.0.1
  GitCommit:        v1.0.1-0-g4144b63
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0
```
###VScodeのインストール
[Thanks for downloading VS Code for Windows!](https://code.visualstudio.com/docs/?dv=win)
から、ダウンロードしてインストール。
これで、以下のコマンドで立ち上げられます。

```
$ code .
```
###VSCoceの拡張機能 Remote-Containersをインストール
###Docker Official Images
[Docker Official Images](https://github.com/orgs/docker-library/repositories)
###venv利用して仮想環境構築＋requirements.txtでインストール
####WSL2で仮想環境構築
まず、WSL2上で以下の作業を実施。python_ex2という仮想環境を構築する。

```
$ python3 -m venv python_ex2
$ cd python_ex2
ubuntu@DESKTOP-4CJ3TFH:~/python_ex2$ source bin/activate
(python_ex2) ubuntu@DESKTOP-4CJ3TFH:~/python_ex2$ code .
```
最後の`code .`でVSCodeを立ち上げると、下の図の最下段のバーのようにこの仮想環境の属性が見える。
インストールは、requirements.txtで行えるものと追加でpytorch関係をpipでインストールした。
結果として、`pip freeze`して出力したものをrequirements.txtに張り付けた。
![vscode_env_requirements_py.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/e2580169-2a7a-0227-f473-2ec7214b2131.jpeg)

```
ubuntu@DESKTOP-4CJ3TFH:~/python_ex2$ pip install -r requirements.txt
Collecting cycler==0.10.0
  Using cached cycler-0.10.0-py2.py3-none-any.whl (6.5 kB)
Collecting kiwisolver==1.3.2
  Using cached kiwisolver-1.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl (1.2 MB)
Collecting matplotlib==3.4.3
  Using cached matplotlib-3.4.3-cp38-cp38-manylinux1_x86_64.whl (10.3 MB)
Collecting numpy==1.21.3
  Using cached numpy-1.21.3-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl (15.7 MB)
Collecting Pillow==8.4.0
  Using cached Pillow-8.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (3.1 MB)
Collecting pyparsing==3.0.3
  Using cached pyparsing-3.0.3-py3-none-any.whl (96 kB)
Collecting python-dateutil==2.8.2
  Using cached python_dateutil-2.8.2-py2.py3-none-any.whl (247 kB)
Collecting six==1.16.0
  Using cached six-1.16.0-py2.py3-none-any.whl (11 kB)
Collecting typing-extensions==3.10.0.2
  Using cached typing_extensions-3.10.0.2-py3-none-any.whl (26 kB)
Installing collected packages: six, cycler, kiwisolver, python-dateutil, numpy, pyparsing, Pillow, matplotlib, typing-extensions
Successfully installed Pillow-8.4.0 cycler-0.10.0 kiwisolver-1.3.2 matplotlib-3.4.3 numpy-1.21.3 pyparsing-3.0.3 python-dateutil-2.8.2 six-1.16.0 typing-extensions-3.10.0.2
ubuntu@DESKTOP-4CJ3TFH:~/python_ex2$ pip3 install torch torchvision torchaudioCollecting torch
  Using cached torch-1.10.0-cp38-cp38-manylinux1_x86_64.whl (881.9 MB)
Collecting torchvision
  Using cached torchvision-0.11.1-cp38-cp38-manylinux1_x86_64.whl (23.3 MB)
Collecting torchaudio
  Downloading torchaudio-0.10.0-cp38-cp38-manylinux1_x86_64.whl (2.9 MB)
     |████████████████████████████████| 2.9 MB 2.1 MB/s 
Requirement already satisfied: typing-extensions in ./python_ex2/lib/python3.8/site-packages (from torch) (3.10.0.2)
Requirement already satisfied: pillow!=8.3.0,>=5.3.0 in ./python_ex2/lib/python3.8/site-packages (from torchvision) (8.4.0)
Requirement already satisfied: numpy in ./python_ex2/lib/python3.8/site-packages (from torchvision) (1.21.3)
Installing collected packages: torch, torchvision, torchaudio
Successfully installed torch-1.10.0 torchaudio-0.10.0 torchvision-0.11.1
```
これでpython環境が構築できました。
いよいよUnitV2の環境を構築します。

###UnitV2を使う
ここからは、以下の参考の記事の通りにすすめます。
【参考】
[【中級者向け】M5Stack UnitV2のOpenCVバージョンを上げる手順](https://raspberrypi.mongonta.com/how-to-increase-opencv-version-of-unitv2/)
####gitする
WSL2上で、以下のようにgitする。

```
$ git clone https://github.com/mongonta0716/UnitV2Framework-docker.git
$ cd UnitV2Framework-docker
$ code .
```
VSCodeが開いたら、reopen in containerして、環境構築

一か所、opencv2の所在が参考とは異なっていたので、以下に変更
`"/external/opencv/install/arm/include/opencv4/opencv2",`

```c_cpp_properties.json
{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/external/opencv/install/arm/include/opencv4/opencv2", 
                "${workspaceFolder}/include",
                "${workspaceFolder}/include/ncnn"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c11",
            "cppStandard": "c++14",
            "intelliSenseMode": "linux-clang-x64"
        }
    ],
    "version": 4
}
```
以下で、cmakeすると、

```
cmake -B build/arm -DOPENCV_DIR=/external/opencv/build/arm/ -DCMAKE_TOOLCHAIN_FILE=./platforms/arm.toolchain.cmake -DTARGET=camera_stream .
cmake --build build/arm
```
以下のように、bin配下にcamera_streamが作られている。
![VSCODE_Docker_Opencv_454.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/233744/816f1ff2-e747-b4c2-92b1-b36e1b2a31ad.jpeg)
ちなみに、camera_streamのコードは、以下の通り

```camera_stream.cpp
#include <opencv2/core/utility.hpp>
#include <opencv2/tracking.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc/types_c.h>
#include <iostream>
#include <cstring>
#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);

        if(low_quality_mode == true)
        {
            resize(mat_src, mat_src, Size(), 1.0f / IMAGE_DIV, 1.0f / IMAGE_DIV, INTER_AREA);
        }

        std::string payload;
        if(readPIPE(payload))
        {
            try
            {
                DynamicJsonDocument doc(1024);
                deserializeJson(doc, payload);
                if((doc["config"] == "web_update_data") || (doc["config"] == getFunctionName()))
                {
                    if(doc["operation"] == "enable")
                    {
                        low_quality_mode = true;
                        sendPIPEMessage("Low Quality Mode Enabled.");
                    }
                    else
                    {
                        low_quality_mode = false;
                        sendPIPEMessage("Low Quality Mode Disabled.");
                    }
                }
            }
            catch(...)
            {
                fprintf(stderr, "[ERROR] Can not parse json.");
            }
        }

        Mat gray_img;
        cvtColor(mat_src, gray_img, CV_BGR2GRAY);
        sendMat(gray_img);
    }
    
    return 0;
}
```
try～catchの部分を削除すると、以下のようにシンプルになる。
以下はこのコードですすめ、入れ替えができているかを見ることとする。

```camera_stream.cpp
#include <opencv2/core/utility.hpp>
#include <opencv2/tracking.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc/types_c.h>
#include <iostream>
#include <cstring>
#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);
        sendMat(mat_src);
    }
    
    return 0;
}
```
まず、上と同じようにVSCodeのターミナルで、以下を実行してbuildする。

```
$ cmake -B build/arm -DOPENCV_DIR=/external/opencv/build/arm/ -DCMAKE_TOOLCHAIN_FILE=./platforms/arm.toolchain.cmake -DTARGET=camera_stream .
$ cmake --build build/arm
```
opencv4.5.4のlibを転送するために、VSCodeにインストールされた資材を参考の通り圧縮し、UnitV2に転送する。

```
cd /external/opencv/install/arm
tar -zcvf /workspaces/UnitV2Framework-docker/opencv.tar.gz bin include lib share
```
以下参考のとおり、UnitV2側でsudoを利用できるようにする
[M5Stack UnitV2を弄るための備忘録](https://zenn.dev/blkcatman/articles/89a2d54a77f020)

次にUnitV2に入って、

```
$ ssh m5stack@10.254.239.1
```
以下で書き込み可にする。

```
unitv2% sudo chmod 666 /dev/null
```
PC側に戻って、scpで先ほどの圧縮ファイルとcamera_streamを転送

```
unitv2% ctrl-D
```

```
$ cd ~/UnitV2Framework-docker
$ scp ./opencv.tar.gz m5stack@10.254.239.1:/home/m5stack
$ scp bin/camera_stream m5stack@10.254.239.1:/home/m5stack
```
そして、転送したファイルをunitv2上で解凍する。

```
unitv2% sudo cp opencv.tar.gz /
        cd /
        sudo zcat opencv.tar.gz | sudo tar -xvf -
```
次にcamera_streamの入れ替えをする。
実行ファイルは、/home/m5stack/payload/binに置かれてる。
※念のため元のファイルはバックアップを取りましょう。

```
unitv2% cd /home/m5stack
unitv2% sudo mv payload/bin/camera_stream camera_stream.bak
unitv2% sudo mv camera_stream payload/bin
```
これで、UnitV2を以下のように再起動すると、web（http://10.254.239.1/）に新しい実行ファイルが起動しているのがわかります。
※立ち上がりにタイムラグ（１min程度）かかります

```
unitv2% sudo reboot
```
以下の部分のコメントアウト部分が白黒画像、元のものがカラーであり、これを入れ替えてbuildし、一連の置き換えをすると変更されたことがわかります。

```.cpp
        //Mat gray_img;
        //cvtColor(mat_src, gray_img, CV_BGR2GRAY);
        //sendMat(gray_img);
        sendMat(mat_src);
```
###ちょっと書き換えて
以下の参考から「カメラからの映像をリアルタイムにエッジ抽出」のコードを参考にcamera_streamを書き換えてみました。
【参考】
[OpenCV 4.3 (C++) で Hello World と顔検出](https://qiita.com/niwasawa/items/87feb5f7923bbe2a7e26)

```Real_time_edge_extract.cpp
#include "opencv2/imgproc.hpp"
#include <opencv2/core/utility.hpp>
#include <opencv2/tracking.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc/types_c.h>
#include <iostream>
#include <cstring>
#include "framework.h"


using namespace cv;

int main(int, char**) {

    //VideoCapture cap(0);
    //if(!cap.isOpened()) return -1;
    startFramework("Camera Stream", 640, 480);
    int low_quality_mode = false;

    while(1)
    {
        Mat mat_src;
        getMat(mat_src);
        Mat edges;
        //namedWindow("edges", WINDOW_AUTOSIZE);
    
        //cap >> mat_src;

        // 色をグレースケールに
        cvtColor(mat_src, edges, COLOR_BGR2GRAY);

        // ガウスフィルターで画像をぼかす
        GaussianBlur(edges, edges, Size(7,7), 1.5, 1.5);

        // キャニー法によるエッジ抽出
        Canny(edges, edges, 0, 30, 3);

        // ウィンドウを表示
        //imshow("edges", edges);
        //if(waitKey(30) >= 0) break;
        sendMat(edges);
    }

  return 0;
}
```

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr"><a href="https://twitter.com/hashtag/UnitV2?src=hash&amp;ref_src=twsrc%5Etfw">#UnitV2</a> で、camera_streamを改造して、リアルタイムエッジ抽出をやってみました🎵<a href="https://twitter.com/hashtag/M5Stack?src=hash&amp;ref_src=twsrc%5Etfw">#M5Stack</a> <a href="https://t.co/4CpUxiL3rF">pic.twitter.com/4CpUxiL3rF</a></p>&mdash; ウワン (@MuAuan) <a href="https://twitter.com/MuAuan/status/1455526370310819843?ref_src=twsrc%5Etfw">November 2, 2021</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>