Zybo+OpenCVでリアルタイム画像処理

以下の記事を試す
32. Linux on Zynq / Real-Time Video Processing
https://sites.google.com/site/powellsshowcase/home/32linuxonzynqreal-timevideoprocessing

1. GitHubレポジトリをクローン

git clone https://github.com/andrewandrepowell/zybo_petalinux_video_hls

2. ビットストリームを生成

Vivadoを起動し、zybo_petalinux_video_hls.xprを開く
Automatically Upgrade IPを指定して、IPコアをアップグレードする。
PSのGPIOを使う必要があるので、ZYNQでMIO GPIOの設定を行う。
ZYNQブロックをダブルクリックし、MIO Configurationを開く
GPIO MIO=MIO、EMIO GPIO(Width)=64に設定した。
その後Generate Bitstreamする。

3. クロスコンパイル環境(Debian)にOpenCVをインストールする

OpenCVのインストールはこの記事を参考:
Zynq上のDebianにpyenvでPython3とOpencvをインストール
http://d.hatena.ne.jp/seinzumtode/20171123/1511470206
Pythonのバージョンは3.4.7(pyenv), OpenCVのバージョンは3.0.0
(クロスコンパイルDebian環境の作り方は以下の記事を参照:)
ubuntuにzynqのクロス環境debianをdebootstrapで入れる
http://d.hatena.ne.jp/seinzumtode/20171225/1514177692

4. SDKの起動

まず古いハードウェアプロジェクトを削除する(VivadoからLaunch SDKして、block_design_0_wrapperプロジェクトをDeleteする)
その後VivadoでハードウェアをExportし、Launch SDKSDKを起動する。
今回試したいLinuxプロジェクトはvideo_processing_app_0である。
main.ccを開くと、OpenCV関連のヘッダファイルが見えていないので、以降でこれらを追加する

5. Linuxプロジェクトの設定

video_processing_app_0アプリケーションプロジェクトを右クリックして、C/C++ Build Settingsを開く

6. コンパイラの設定


7. リンカースクリプトの設定


リンクするライブラリ(-l)
(デフォルトではlibtiffとかlibjpegとか"lib"の接頭辞がついていたので、それらは外す)

dl
opencv_videoio
opencv_imgcodecs
opencv_highgui
opencv_imgproc
opencv_photo
opencv_video
opencv_features2d
opencv_objdetect
opencv_shape
opencv_core
opencv_ml
tiff
webp
IlmImf
png
jpeg
jasper
z
pthread
rt

リンカーフラグでsysrootとrpathを指定する。
20180321追記:rpathの指定はrpath=dirのように、=(イコール)が必要。-Wl,-rpath=のところで、-Wl, -rpathのように空白をいれない。

--sysroot="/home/shohei/zynqfs" -Wl,-rpath=/home/shohei/zynqfs/lib/arm-linux-gnueabihf -Wl,-rpath=/home/shohei/zynqfs/usr/lib/arm-linux-gnueabihf


これでプログラムはビルドできた。

8. Petalinuxの設定

いつも通りにビルドする。
カーネル設定で必要なのはUVCカメラ(V4L)、Userspace I/Oあたり。
生成したbitstreamを元に、BOOT.BINを用意する

9. デバイスツリーの設定

ここで激しくハマった。FilterのVDMAにはReadとWriteの2チャンネルあるのだが、それぞれに対してトップモジュール(というのか?)的にデバイスを宣言しないといけない。(下でいう、dma0とdma1)
UVCカメラの設定をしておくのと(次を参考→Zybo+PetalinuxでUVCカメラを使う:http://d.hatena.ne.jp/seinzumtode/20171120/1511167136 )、各デバイスはuioとして見えるようにしておく(compatible = generic=uio)。
DTSの設定はここを参考に:(https://github.com/andrewandrepowell/zybo_petalinux_video_hls/tree/master/petalinux_proj/subsystems/linux/configs/device-tree
以下の設定をすることで、Linuxに入ったときに、uioが見えるようになる。

shohei@debian8-zynq:~$ ls -l /dev/uio*
crw------- 1 root root 245, 0  1月  1 00:00 /dev/uio0
crw------- 1 root root 245, 1  1月  1 00:00 /dev/uio1
crw------- 1 root root 245, 2  1月  1 00:00 /dev/uio2
crw------- 1 root root 245, 3  1月  1 00:00 /dev/uio3
crw------- 1 root root 245, 4  1月  1 00:00 /dev/uio4
crw------- 1 root root 245, 5  1月  1 00:00 /dev/uio5
crw------- 1 root root 245, 6  1月  1 00:00 /dev/uio6

shohei@debian8-zynq:~$ cat /sys/class/uio/uio*/maps/map0/name
/amba_pl/axi_dispctrl@43c00000
/amba_pl/gpio@41200000
/amba_pl/dma@43000000
/amba_pl/dma0@43010000
/amba_pl/dma1@43010000
/amba_pl/filt@43c10000
/amba_pl/gpio@41210000

system.dtsの抜粋
PLの設定

	amba_pl {
		#address-cells = <0x1>;
		#size-cells = <0x1>;
		compatible = "simple-bus";
		ranges;

		axi_dispctrl@43c00000 {
			compatible = "generic-uio";
			reg = <0x43c00000 0x10000>;
			xlnx,blue-width = <0x5>;
			xlnx,green-width = <0x6>;
			xlnx,red-width = <0x5>;
			xlnx,use-bufr-div5 = <0x0>;
		};

		gpio@41200000 {
			#gpio-cells = <0x2>;
			#interrupt-cells = <0x2>;
			compatible = "generic-uio";
			gpio-controller;
			interrupt-controller;
			interrupt-parent = <0x4>;
			interrupts = <0x0 0x1d 0x4>;
			reg = <0x41200000 0x10000>;
			xlnx,all-inputs = <0x1>;
			xlnx,all-inputs-2 = <0x0>;
			xlnx,all-outputs = <0x0>;
			xlnx,all-outputs-2 = <0x1>;
			xlnx,dout-default = <0x0>;
			xlnx,dout-default-2 = <0x0>;
			xlnx,gpio-width = <0x4>;
			xlnx,gpio2-width = <0x4>;
			xlnx,interrupt-present = <0x1>;
			xlnx,is-dual = <0x1>;
			xlnx,tri-default = <0xffffffff>;
			xlnx,tri-default-2 = <0xffffffff>;
		};

		dma@43000000 {
			#dma-cells = <0x1>;
			clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_mm2s_aclk";
			clocks = <0x1 0xf 0x1 0xf 0x1 0xf>;
			compatible = "generic-uio";
			interrupt-parent = <0x4>;
			interrupts = <0x0 0x1e 0x4>;
			reg = <0x43000000 0x10000>;
			xlnx,addrwidth = <0x20>;
			xlnx,flush-fsync = <0x1>;
			xlnx,num-fstores = <0x4>;

			dma-channel@43000000 {
				compatible = "xlnx,axi-vdma-mm2s-channel";
				interrupts = <0x0 0x1e 0x4>;
				xlnx,datawidth = <0x20>;
				xlnx,device-id = <0x0>;
				xlnx,genlock-mode;
			};
		};

		dma0@43010000 {
			#dma-cells = <0x1>;
			clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk", "m_axi_s2mm_aclk";
			clocks = <0x1 0xf 0x1 0xf 0x1 0xf 0x1 0xf 0x1 0xf>;
			compatible = "generic-uio";
			interrupt-parent = <0x4>;
			interrupts = <0x0 0x20 0x4>;
			reg = <0x43010000 0x10000>;
			xlnx,addrwidth = <0x20>;
			xlnx,flush-fsync = <0x1>;
			xlnx,num-fstores = <0x4>;

			dma-channel@43010000 {
				compatible = "xlnx,axi-vdma-mm2s-channel";
				interrupts = <0x0 0x20 0x4>;
				xlnx,datawidth = <0x20>;
				xlnx,device-id = <0x1>;
			};
		};

		dma1@43010000 {
			#dma-cells = <0x1>;
			clock-names = "s_axi_lite_aclk", "m_axi_mm2s_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk", "m_axi_s2mm_aclk";
			clocks = <0x1 0xf 0x1 0xf 0x1 0xf 0x1 0xf 0x1 0xf>;
			compatible = "generic-uio";
			interrupt-parent = <0x4>;
			interrupts = <0x0 0x21 0x4>;
			reg = <0x43010000 0x10000>;
			xlnx,addrwidth = <0x20>;
			xlnx,flush-fsync = <0x1>;
			xlnx,num-fstores = <0x4>;

			dma-channel@43010030 {
				compatible = "xlnx,axi-vdma-s2mm-channel";
				interrupts = <0x0 0x21 0x4>;
				xlnx,datawidth = <0x20>;
				xlnx,device-id = <0x1>;
				xlnx,genlock-mode;
			};
		};

		filt@43c10000 {
			compatible = "generic-uio";
			interrupt-parent = <0x4>;
			interrupts = <0x0 0x1f 0x4>;
			reg = <0x43c10000 0x10000>;
			xlnx,s-axi-control-bus-addr-width = <0x4>;
			xlnx,s-axi-control-bus-data-width = <0x20>;
		};

		gpio@41210000 {
			#gpio-cells = <0x2>;
			compatible = "generic-uio";
			gpio-controller;
			reg = <0x41210000 0x10000>;
			xlnx,all-inputs = <0x0>;
			xlnx,all-inputs-2 = <0x0>;
			xlnx,all-outputs = <0x1>;
			xlnx,all-outputs-2 = <0x0>;
			xlnx,dout-default = <0x1>;
			xlnx,dout-default-2 = <0x0>;
			xlnx,gpio-width = <0x1>;
			xlnx,gpio2-width = <0x20>;
			xlnx,interrupt-present = <0x0>;
			xlnx,is-dual = <0x0>;
			xlnx,tri-default = <0xffffffff>;
			xlnx,tri-default-2 = <0xffffffff>;
		};
	};

bootargsの設定(uioを有効化するための設定)

	chosen {
		bootargs = "console=ttyPS0,115200 earlyprintk root=/dev/nfs nfsroot=192.168.100.205:/tftpboot/nfsroot,tcp ip=dhcp rw uio_pdrv_genirq.of_id=generic-uio";
		stdout-path = "serial0:115200n8";
	};

Webカメラの設定

usb@e0002000 {
            compatible = "xlnx,zynq-usb-2.20a", "chipidea,usb2";
            status = "okay";
            clocks = <0x1 0x1c>;
            dr_mode = "host";  // ←←←←ここを追加
            interrupt-parent = <0x4>;
            interrupts = <0x0 0x15 0x4>;
            reg = <0xe0002000 0x1000>;
            usb-phy = <&usb_phy0>;   // ←←←←ここを追加
        };


/{ 
   usb_phy0: phy0 {
	compatible = "ulpi-phy";
	#phy-cells = <0>;
	reg = <0xe0002000 0x1000>;
	view-port = <0x170>;
	drv-vbus;
	};
};

9. Linuxを起動する。

OpenCVのライブラリの場所を読み込むための引数としてLD_LIBRARY_PATHをつけて、さらにsudo権限で起動する。
起動スクリプトにした。
./run.sh

#!/bin/bash
sudo LD_LIBRARY_PATH=/home/shohei/.pyenv/versions/3.4.7/usr/local/lib/ ./video_process_app_0.elf

実行

shohei@debian8-zynq:~$ ./run.sh
[sudo] password for shohei:

Checking to see if the video stream opened and setting resolution...
Configuring display...
Configuring framebuffer with opencv Mats...
Configuring standalone GPIO driver...
Configuring filt core...
Running main application...

なんか下が切れた画像が表示されて止まる。

VDMAのWriteでページフレームを指定するSOF(Start of Frame)(AXIのtuser信号に乗っかってる?)が帰ってこないので無限にスタックしているようである。
(Vivado上でBlock diagramをみると、VDMA1に対してSOF on tuserを送出する必要があるのは、解像度変換を行っているHLS IPコアのようにみえる)

ソフトウェア上では、linuxmmap.cppのwait()の中でread(2)しているのだが、ここで無限読み込み待ち状態に入っている。
main.cc

                //ループ処理の最後
		/* Trigger VDMA with fsync. Wait until interrupt is triggered. */
		XGpio_DiscreteWrite( &fsync_obj, 1, 0 );
                //ここでVDMAのWrite(vdma_1_1=linuxmmapオブジェクト)に対してwait()する
		filt_vdma_mmap_1_obj.wait();
		filtvdma_clear_vdma_write_int( &filtvdma_obj );
		filt_vdma_mmap_1_obj.ready();
		XGpio_DiscreteWrite( &fsync_obj, 1, 1 );

linuxmmap.cpp

uint32_t linuxmmap::wait()
{
	if ( isuio )
	{
		uint32_t nints;
                //ここのread()で無限待機状態に入っている 
		if ( read( fd, &nints, sizeof( nints ) ) != sizeof( nints ) )
			throw std::runtime_error( "Interrupt failed!" );
		return nints;
	}
	return 0;
}

これ以上追えなくなってスタックしたので、PYNQのJupyter notebookからHDMI入力を入れて使う方法を進めてみることにする。