SDSoCチュートリアル with ZYBO (Zynq) (4)OpenCVを使う(CPU用)

# この記事について
今回は以下をやります。

- OpenCVを使えるようなLinuxイメージを作る
- rootfsをSDカードに変更する
- JPEG画像をカラーからグレースケールに変換するアプリケーションを作る

本当は、OpenCV関数をハードウェア化してくれるxfOpenCVとかいうのをやりたかったのですが、上手くいかなかったです。今回はその途中経過ということで、まずはCPUコードでOpenCVを使う方法を本記事に記します。xfOpenCVを使うにはreVISIONとかにも手を出さないとダメっぽくて、まだまだハードルは高かったです。。。

本記事の内容は、SDSoCだけではなく、通常通りPetaLinuxでlinuxイメージを作って、Xilinx SDK(XSDK)でLinuxアプリケーションを開発するときにも使えます。

- [(1)standaloneプラットフォーム](https://qiita.com/take-iwiw/items/8948def8d0eb699d3811 )
- [(2)Linuxプラットフォーム](https://qiita.com/take-iwiw/items/482ecc7355b39be1795f )
- [(3)画像処理IPを例に一からプロジェクトを作る](https://qiita.com/take-iwiw/items/a37f5b020d2ba9e5d6da )
- __[(4)OpenCVを使う(CPU用)]() <--- 今回の内容__


## rootfsの場所について
以前別の記事でも記しましたが、PetaLinuxでLinuxイメージやブートローダを作成すると、デフォルトではrootfsはRAMに展開されます(initramfs)。これはこれで、電源をいつでも切れるとか、毎回cleanな環境で動かせるといったメリットはあるのですが、rootfsのサイズが増加してくるとその分RAMサイズを圧迫したり、起動のたびに必要なファイルをコピーする必要があるといったデメリットがあります。

今回、OpenCVライブラリを取り込むので、rootfsサイズはかなり増加します。素の状態だと、rootfsは16MByteくらいだったのが100MByteくらいになります(ext4イメージファイルサイズで500MByteくらい)。そのため、rootfsをSDカードに展開することにしました。


## 環境
- 開発用PC: Windows 10 64-bit 
	- SDx IDE 2018.2
- 開発用PC (Linux): Ubuntu 16.04 本家 (日本語版じゃない) (on VirtualBox 5.2.4)
	- PetaLinux 2018.2
- ターゲットボード: ZYBO (Z7-20) 

ハードウェアは[第1回目](https://qiita.com/take-iwiw/items/8948def8d0eb699d3811) に作成したhdfとdsaを使います。

- design_1.hdf
- design_1.dsa

# Linuxイメージを作る
design_1.hdfを、PetaLinxuインストール済みのUbuntuにコピーしておきます。
プロジェクト作成からコンフィギュレーション、ビルドまでの全コマンドを以下に記します。(各コンフィギュレーションの設定は後述します)
ついでに、作成したイメージをまとめるコマンドも、まずは全部記してしまいます。
僕の場合、`~/Desktop/win_share/`がwindowsとubuntuの共有ディレクトリなので、そこを介して成果物をやり取りします。

```sh:PetaLinuxビルド(onUbuntu)
# プロジェクトを作る
petalinux-create --type project --template zynq --name design_1_cv
cd design_1_cv/

# コンフィギュレーション
petalinux-config --get-hw-description=~/Desktop/win_share/
petalinux-config -c kernel
petalinux-config -c rootfs

# デバイスツリーの設定
code project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi &

# ビルド
petalinux-build 

# SDSoC用に生成物をまとめる(非SDSoCの場合不要)
mkdir -p output/boot
mkdir -p output/image
code output/boot.bif
cp images/linux/u-boot.elf  output/boot/.
cp images/linux/*fsbl.elf   output/boot/fsbl.elf
cp images/linux/image.ub    output/image/.
cp -rf output ~/Desktop/win_share/

# sysrootを生成して固める(Linuxアプリケーション開発用)
petalinux-build --sdk
petalinux-package --sysroot
zip -r sysroot.zip images/linux/sdk/sysroots/cortexa9hf-neon-xilinx-linux-gnueabi/
mv sysroot.zip ~/Desktop/win_share/

# Linux image領域を書き込む(パーティション生成後に実行)
sudo umount /dev/sdb2
sudo dd if=images/linux/rootfs.ext4 of=/dev/sdb2 bs=4096
sync
sudo resize2fs /dev/sdb2 
sync
```

## boot loaderの設定
`petalinux-config --get-hw-description=~/Desktop/win_share/` 実行時に現れるコンフィグ画面で、ROOTFSをSDカードにするよう設定します。
`Image Packaging Configuration → Root filesystem type`で、SD cardを指定します。その下の`Device node of SD device`には自動的に`/dev/mmcblk0p2`が設定されているはずです。

`project-spec/configs/config`ファイルの中身が以下のようになっていればOKです。

```bootコンフィグ(一部)
CONFIG_SUBSYSTEM_ROOTFS_SD=y
CONFIG_SUBSYSTEM_BOOTARGS_GENERATED="console=ttyPS0,115200 earlyprintk root=/dev/mmcblk0p2 rw rootwait"
```

![00.png](https://qiita-image-store.s3.amazonaws.com/0/214268/c7511fcc-8e3f-daa1-5c11-21e8dfcabeb8.png)


なお、以前、SDSoC用にはKernel Bootargs(`DTG Settings → Kernel Bootargs`)の編集も必要と書きました。xilinxのドキュメントにもそう書いてあったのですが、そちらは編集不要で`generate boot args automatically`を有効にしたままで大丈夫でした。(`console=ttyPS0,115200 earlyprintk root=/dev/mmcblk0p2 rw rootwait`)
もしかしたら、`rootfstype=ext4`といった一文を追加しておいた方がいいかもしれません。

![01.png](https://qiita-image-store.s3.amazonaws.com/0/214268/982dac41-b548-a80b-21ea-75de8246c622.png)


## kernelの設定
`petalinux-config -c kernel`実行時に現れるコンフィグ画面で、SDSoCに必要なXilinx APFと、OpenCVに必要そうなv4l2やUSB_VIDEO_CLASSを有効にします。
設定画面が出たら、スラッシュ(`/`)キーで検索モードに入って、下記の、例えばCONFIG_CMA_SIZE_MBYTES等を入力してEnterキーを押すと、簡単に設定項目に行けます。
注意点として、有効にするときには、`<M>`ではなく、`<*>`になるようにしてください。
`project-spec/meta-user/recipes-kernel/linux/linux-xlnx`以下に作られる`user_20xx-xx-xx-xx-xx.cfg`ファイルの中身が以下のようになっていればOKです。カーネルコンフィグに関しては、ここに出力されるcfgファイルを日付順にマージして使用されるようです。

```txt:カーネルコンフィグ
CONFIG_CMA_SIZE_MBYTES=256
CONFIG_STAGING=y
CONFIG_XILINX_APF=y
CONFIG_XILINX_DMA_APF=y
CONFIG_MEDIA_USB_SUPPORT=y
CONFIG_USB_VIDEO_CLASS=y
CONFIG_USB_VIDEO_CLASS_INPUT_EVDEV=y
CONFIG_USB_GSPCA=y
CONFIG_VIDEOBUF2_VMALLOC=y
```
![02.png](https://qiita-image-store.s3.amazonaws.com/0/214268/a3241fe3-9159-c89f-06e8-50708b2233f5.png)
![03.png](https://qiita-image-store.s3.amazonaws.com/0/214268/5c1956a2-f64c-72b4-c9c3-0173fcbaed12.png)
![04.png](https://qiita-image-store.s3.amazonaws.com/0/214268/890b43b3-e78c-a928-3475-15e6440ab873.png)


### ★重要★ SDSoC用ではない場合
SDSoC用にLinuxカーネルを作るときは、上述のXilinx APF(Xilinx APF, CONFIG_XILINX_APF, CONFIG_XILINX_DMA_APF)が必要になります。
しかし、これを付けてしまうと、BOOT.binはSDSoCで作らないと起動が出来なくなってしまいます。
SDSoCを使わずにPetaLinuxでBOOT.binを作る場合には、Xilinx APFは無効のままにしてください。なお、この場合は、hdfにはビットストリームファイルが含まれている必要があります。

## rootfsの設定
`petalinux-config -c rootfs`実行時に現れるコンフィグ画面で、必要なパッケージをインストールします。今回はOpenCVとそれに必要なライブラリ(jpeg等)、ついでにPython3をインストールします。また、SDSoCに必要なlibstdc++もインストールします。自分で地道に1つづつ設定してもいいのですが、kernelと同様に、スラッシュ(`/`)キーで設定項目を検索するのが楽だと思います。

設定結果は`project-spec/configs/rootfs_config`に書かれます。面倒なら、rootfs_configファイルに下記設定をコピペしてしまってもOKです。

なお、下記設定はいろいろと冗長なところがあると思います。適当に設定項目を眺めて必要そうなのを有効にしただけなので。また、僕が未だに`-dev`有無の違いとどういうときに必要になるか分かっていないので、とりあえず全部取り込んでみました。

```txt:rootfsコンフィグ
CONFIG_dbus-glib=y
CONFIG_dbus-glib-dev=y
CONFIG_mtd-utils=y
CONFIG_xz=y
CONFIG_liblzma=y
CONFIG_canutils=y
CONFIG_openssh-sftp-server=y
CONFIG_pciutils=y
CONFIG_run-postinsts=y
CONFIG_libjpeg-turbo=y
CONFIG_libturbojpeg=y
CONFIG_jpeg-tools=y
CONFIG_libjpeg-turbo-dev=y
CONFIG_libpng=y
CONFIG_libpng-dev=y
CONFIG_libtool=y
CONFIG_libltdl=y
CONFIG_libwebp=y
CONFIG_libwebp-bin=y
CONFIG_libwebp-dev=y
CONFIG_opencv=y
CONFIG_opencv-dev=y
CONFIG_opencv-apps=y
CONFIG_udev-extraconf=y
CONFIG_libstdcPLUSPLUS=y
CONFIG_glib-2.0=y
CONFIG_glib-2.0-dev=y
CONFIG_packagegroup-core-boot=y
CONFIG_packagegroup-core-ssh-dropbear=y
CONFIG_python3=y
CONFIG_python3-smtpd=y
CONFIG_python3-syslog=y
CONFIG_python3-subprocess=y
CONFIG_python3-pickle=y
CONFIG_python3-db=y
CONFIG_python3-fcntl=y
CONFIG_python3-html=y
CONFIG_python3-re=y
CONFIG_python3-core=y
CONFIG_python3-distutils=y
CONFIG_python3-man=y
CONFIG_python3-sqlite3-tests=y
CONFIG_python3-terminal=y
CONFIG_python3-pprint=y
CONFIG_python3-tkinter=y
CONFIG_python3-unixadmin=y
CONFIG_python3-mime=y
CONFIG_python3-logging=y
CONFIG_python3-signal=y
CONFIG_python3-resource=y
CONFIG_python3-email=y
CONFIG_python3-math=y
CONFIG_python3-json=y
CONFIG_python3-image=y
CONFIG_python3-stringold=y
CONFIG_python3-pydoc=y
CONFIG_python3-codecs=y
CONFIG_python3-debugger=y
CONFIG_python3-selectors=y
CONFIG_python3-xmlrpc=y
CONFIG_python3-io=y
CONFIG_python3-pkgutil=y
CONFIG_python3-idle=y
CONFIG_python3-lang=y
CONFIG_python3-readline=y
CONFIG_python3-reprlib=y
CONFIG_python3-difflib=y
CONFIG_python3-unittest=y
CONFIG_python3-netserver=y
CONFIG_python3-netclient=y
CONFIG_python3-gdbm=y
CONFIG_python3-profile=y
CONFIG_python3-sqlite3=y
CONFIG_python3-textutils=y
CONFIG_libpython3=y
CONFIG_python3-xml=y
CONFIG_python3-threading=y
CONFIG_python3-modules=y
CONFIG_python3-importlib=y
CONFIG_python3-dev=y
CONFIG_python3-curses=y
CONFIG_python3-multiprocessing=y
CONFIG_python3-crypt=y
CONFIG_python3-enum=y
CONFIG_python3-compression=y
CONFIG_python3-shell=y
CONFIG_python3-tests=y
CONFIG_python3-numbers=y
CONFIG_python3-audio=y
CONFIG_python3-pyvenv=y
CONFIG_python3-asyncio=y
CONFIG_python3-misc=y
CONFIG_python3-datetime=y
CONFIG_python3-compile=y
CONFIG_python3-mmap=y
CONFIG_python3-mailbox=y
CONFIG_python3-argparse=y
CONFIG_python3-ctypes=y
CONFIG_tcf-agent=y
CONFIG_tiff=y
CONFIG_tiff-dev=y
CONFIG_v4l-utils=y
CONFIG_libv4l=y
CONFIG_v4l-utils-dev=y
CONFIG_media-ctl=y
CONFIG_libv4l-dev=y
CONFIG_videoproto-dev=y
CONFIG_xf86-video-fbdev=y
CONFIG_bridge-utils=y
CONFIG_libdrm=y
CONFIG_libdrm-dev=y
CONFIG_libdrm-drivers=y
CONFIG_packagegroup-petalinux-opencv=y
CONFIG_packagegroup-petalinux-opencv-dev=y
CONFIG_ROOTFS_ROOT_PASSWD="root"
```

## デバイスツリーの設定
ビルドの前に、デバイスツリー(`project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi`)を以下のように設定します。

```txt:system-user.dtsi
/include/ "system-conf.dtsi"
/{
    xlnk {
        compatible = "xlnx,xlnk-1.0";
    };
};
```

## 生成物をまとめる
### SDSoC用の場合
上述のコマンドの通りです。bifファイルは以下のようになります。

```txt:output/boot.bif
/* linux */
the_ROM_image:
{
    [bootloader]<fsbl.elf>
    <bitstream>
    <u-boot.elf>
}
```

### 非SDSoC用の場合
SDSoCを使わない場合は、ここでBOOT.binまで作ってしまいます。

```sh:BOOT.bin生成コマンド
petalinux-package --boot --force --fsbl images/linux/zynq_fsbl.elf --fpga images/linux/system.bit --u-boot
```

## sysrootを生成する(Linuxアプリケーション開発用)
OpenCVライブラリなどを使うアプリケーションをビルドするためには、ヘッダファイルやライブラリが必要になります。それを用意します。PetaLinux2017.x以前だと、自動的に生成されたのですが、2018.x以降だと自分で作らないといけないようです。(https://forums.xilinx.com/t5/Embedded-Linux/Where-is-the-quot-linux-sysroot-path-quot/td-p/874525 )

コマンドは前述の(`petalinux-build --sdk && petalinux-package --sysroot`)になります。その結果、Linux System Root(sysroot)が`images/linux/sdk/sysroots/cortexa9hf-neon-xilinx-linux-gnueabi/`に生成されます。(2017.x以前だと`build/tmp/sysroots/plnx_arm`)

この生成されたsysrootをSDSoCやXSDKで参照します。SDSoCやXSDKもUbuntuに入れている人はこのまま使えばいいのですが、僕のようにWindowsで開発している人は、windows側にコピーする必要があります。シンボリックリンクを実体化してzip化します(`-r`オプション)。その後、windows側にコピーして解凍します。

windowsで解凍するときは、msys等で、`unzip sysroot.zip`で解凍します。途中、上書きしますか? と聞かれますが、yes(All)で大丈夫です。

# SDカードを準備する
SDカードを、以下の2つの領域に分けます。パーティションを分けるには、UbuntuのDisksを使いました。

- boot領域: FATフォーマット。100MBくらいでOK
- Linux image領域: ext4。残り全て。

![05.png](https://qiita-image-store.s3.amazonaws.com/0/214268/e35a68a7-bdd3-b0ef-68d5-8e51900c1ce4.png)


前述の`sudo dd if=images/linux/rootfs.ext4 of=/dev/sdb2 bs=4096`コマンドをUbuntuで実行して、Linuxイメージを書き込みます。起動時に問題があったら、rootfs.ext3でも試してみてください。または、bootargsに`rootfstype=ext4`を設定することで解決するかも。

SDSoCの場合には、boot領域に書き込むファイルはSDxで生成するので今は何もしないでOKです。
非SDSoCの場合には先ほど作成したBOOT.binとimage.ubをFATフォーマットのboot領域にコピーします。

# SDSoCカスタムプラットフォームを作る
[(2)Linuxプラットフォーム](https://qiita.com/take-iwiw/items/482ecc7355b39be1795f )と同じ手順です。
既存の「design_1」に追加する場合には、linux_opencvなどと名前を変えておきます。

# Linuxアプリケーションを作る
## sysrootの設定
SDx上で先ほど作成したカスタムプラットフォームをターゲットに新規アプリケーションを作成します。アプリケーション名は、test_opencvとします。
作成ウィザードの途中の、System configurationのAdditional SettingsのLinux RootFileSystemに、先ほどwindows側にコピーして解凍したsysrootのフォルダを指定します。

![06.png](https://qiita-image-store.s3.amazonaws.com/0/214268/e3f75836-095b-c075-65ea-c57d621881fc.png)


## ビルド設定
### ビルド変数の設定
生成されたアプリケーションを、Project Explorer上で右クリック → Properties → C/C++ Build → Environment -> Environment variables to setの所に、`SYSROOT`を追加して、値に先ほどと同じsysrootのフォルダを設定します。(アプリケーション生成時に既に設定しているのに、再度必要な理由は不明。とにかく、ここに追加しないとビルドが通らなかった。`cannot find crt1.o:`といったエラーが発生)

![07.png](https://qiita-image-store.s3.amazonaws.com/0/214268/3d705096-b1e8-9ab1-1b1b-4eb4f8ebcce0.png)


 C/C++ Build → Settings → SDS++ Compiler(SDSCC Compilerも同様) → Directoriesに、先ほど設定したsysrootのパスが設定されていることを確認します。なかったら、 `"${SYSROOT}/usr/include"`を追加します。(デフォルトだと環境変数を使わないフルパスになっているかもしれません。)

C/C++ Build → Settings → SDS++ Linker → Libraries → Library search path(-L)、先ほど設定したsysrootのパスが設定されていることを確認します。なかったら、 `"${SYSROOT}"`を追加します。(デフォルトだと環境変数を使わないフルパスになっているかもしれません。)
ちなみに、`cannot find /lib/libpthread.so.0`といったエラーが出た場合は、デフォルトでは`C:/path-to-sysroots/sysroots/cortexa9hf-neon-xilinx-linux-gnueabi/usr/lib`となっているのを、`C:/path-to-sysroots/sysroots/cortexa9hf-neon-xilinx-linux-gnueabi`としたら治りました。(https://forums.xilinx.com/t5/Embedded-Development-Tools/Linker-can-t-find-pthread-library/td-p/849896)

### OpenCVライブラリを追加する
自分でOpenCVライブラリをリンクするように設定していってもいいのですが、楽にする方法がありました。ただ、本当はこれはxfOpenCV用だと思うので、正しいかどうかは不明。

File -> New -> SDx Projectで、新しいプロジェクトを作ろうとします。Applicationを選び、適当なプロジェクト名を入れて進みます(実際には、このプロジェクトは作らないので名前は何でもいいです)。最後に、Templatesを選ぶ画面で、`SDx Libraries`をクリックします。

![09.png](https://qiita-image-store.s3.amazonaws.com/0/214268/8c4ce1fe-19f7-75b5-d926-e69915afcb87.png)


Xilinx xfOpenCV Libraryを選択します。無かったら、UpdateやRefreshをクリックして取得します。その後、右下のAdd to projectから、既に作成済みのプロジェクトを選びます。
これで、OpenCVライブラリに必要な設定がされるので、今作成中プロジェクトのウィザードは閉じてしまって大丈夫です。

![10.png](https://qiita-image-store.s3.amazonaws.com/0/214268/309a5f52-d747-c30f-65da-59bc1937f8b9.png)


再度、C/C++ Build → Settings → SDS++ Linker → Libraries 、を見ると下記のように必要そうなライブラリを使うように設定されています。ただし、video(`-lvideo`)だけはリンクエラーを消せなかったので、このリストから消しておきます。Linuxイメージ作成時のrootfsで必要なライブラリを取り込めてないのが原因だと思うのですが、解決できませんでした。

![11.png](https://qiita-image-store.s3.amazonaws.com/0/214268/78dc5d42-456a-c33c-2c8c-d22de9f674bd.png)


# アプリケーションを作る
いよいよ、OpenCVを使ったプログラムの実装を行います。
JPEGファイルを読み込んで、グレースケールに変換して、再びJPEGファイルに出力する、というプログラムを書きます。

```cpp:main.cpp
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <opencv2/opencv.hpp>

int main()
{
	cv::Mat imgSrc;
	cv::Mat imgDst;
	
	imgSrc = cv::imread("org.jpg");
	printf("rows = %d\n",imgSrc.rows);
	printf("cols = %d\n",imgSrc.cols);

	cvtColor(imgSrc, imgDst, CV_RGB2GRAY);

	cv::imwrite("cv_gray.jpg", imgDst);

	imgSrc.release();
	imgDst.release();
}
```

## 実行準備
ビルドが完了したら、SD Card Image (BOOT.binとimage.ub、test_opencv.elf) をSDカードのboot領域(FATフォーマット)にコピーします。rootfsが書き込まれているext4側領域はそのままでOKです。

## 実行
SDカードをさして、ZYBOを起動します。
起動後、以下のようなコマンドでJPEGファイルを転送します。(あらかじめSDカードにコピーしておいてもOK)

```sh:JPEGファイルコピー(msysターミナル)
scp /c/Users/tak/Desktop/*.jpg root@192.168.1.87:/home/root
```

その後、ZYBO側ターミナルで以下のように実行すると、変換されたJPEGが出力されます。

```sh:実行(ZYBOターミナル)
root@design_1_cv:~# ls
org.jpg
root@design_1_cv:~# /run/media/mmcblk0p1/test_opencv.elf
rows = 1280
cols = 1920
root@design_1_cv:~# ls
cv_gray.jpg  org.jpg  sds_trace_data.dat
```

その後、再度以下のようなコマンドでJPEGファイルをPC側にコピーすることで、白黒に変換された画像を確認することが出来ました。

```sh:JPEGファイル結果コピー(msysターミナル)
scp root@192.168.1.87:/home/root/*.jpg  /c/Users/tak/Desktop/.
```