アプリとサービスのすすめ

アプリやIT系のサービスを中心に書いていきます。たまに副業やビジネス関係の情報なども気ままにつづります

Unetでsigmoidとsoftmaxを使う方法とcompileまでまとめ

アプリケーション作成時にDPU対応、非対応の最終層sigmoidとsoftmaxをUnetで使う方法の備忘録。


目次
1.Unetで最終層(活性化関数=activation)がsigmoidのとき
2.Unetで最終層(活性化関数=activation)がsoftmaxのとき
3.追記:SeparableConv2Dでもsoftmaxを使える

f:id:trafalbad:20201010010917j:plain



1.Unetで最終層(活性化関数=activation)がsigmoidのとき


unetの最終層


c10 = Conv2D(filters=nClasses, kernel_size=1, data_format=IMAGE_ORDERING, activation="sigmoid")(c10)

model = Model(inputs=img_input, outputs=c10)

最終層がsigmoidの時のunetのcompile時のメッセージ


$ ./compile.sh
>>>>

**************************************************
* VITIS_AI Compilation - Xilinx Inc.
**************************************************
[VAI_C][Warning] layer [conv2d_23_Sigmoid] (type: Sigmoid) is not supported in DPU, deploy it in CPU instead.

Kernel topology "unet2_kernel_graph.jpg" for network "unet2"
kernel list info for network "unet2"
                               Kernel ID : Name
                                       0 : unet2_0
                                       1 : unet2_1

                             Kernel Name : unet2_0
--------------------------------------------------------------------------------
                             Kernel Type : DPUKernel
                               Code Size : 1.16MB
                              Param Size : 29.63MB
                           Workload MACs : 83685.54MOPS
                         IO Memory Space : 17.04MB
                              Mean Value : 0, 0, 0, 
                      Total Tensor Count : 40
                Boundary Input Tensor(s)   (H*W*C)
                            input_1:0(0) : 224*224*3

               Boundary Output Tensor(s)   (H*W*C)
              conv2d_23_convolution:0(0) : 224*224*11

                        Total Node Count : 35
                           Input Node(s)   (H*W*C)
                 conv2d_1_convolution(0) : 224*224*3

                          Output Node(s)   (H*W*C)
                conv2d_23_convolution(0) : 224*224*11

                             Kernel Name : unet2_1
--------------------------------------------------------------------------------
                             Kernel Type : CPUKernel
                Boundary Input Tensor(s)   (H*W*C)
                  conv2d_23_Sigmoid:0(0) : 224*224*11

               Boundary Output Tensor(s)   (H*W*C)
                  conv2d_23_Sigmoid:0(0) : 224*224*11

                           Input Node(s)   (H*W*C)
                       conv2d_23_Sigmoid : 224*224*11

                          Output Node(s)   (H*W*C)
                       conv2d_23_Sigmoid : 224*224*11

Ultra96v2上で動かすときの注意点


sigmoidはDPUで対応してないため、DPUKernelとCPUKernelに分割される。


なのでアプリケーション作成時に使う
「src/fpc_main.cc」のコードで#difineで、呼び出すDPUkernelは

#define KERNEL_CONV       "unet2_0"

// #define KERNEL_CONV   "unet2" から変更する


に置き換えないとultra96v2上で動かない。




2.Unetで最終層(活性化関数=activation)がsoftmaxのとき

Conv2dでsoftmaxを使ったとき


c10 = Conv2D(filters=nClasses, kernel_size=1, data_format=IMAGE_ORDERING, activation="softmax")(c10)

model = Model(inputs=img_input, outputs=c10)

普通にcompileすると次のエラーが出る。

$ ./compile.sh
>>>>

**************************************************
* VITIS_AI Compilation - Xilinx Inc.
**************************************************
〜
[VAI_C][Warning] Operator [ name: conv2d_transpose_4/strided_slice_2/stack_2, op: Const] will be deleted by dnnc because it's not parent or child of any other operator.
[VAI_C][Error] 'Const' op should be fused with current op [Max] by DECENT.

tensorflowのラッパーのtf.keras.layers.Conv2DTranspose
を使っても同じエラーが出てコンパイルできない。



Unetの活性化関数にsoftmaxを使う方法



FCN8のコードを真似て、Conv2DTranspose()Conv2D()の代わりに使ったらうまくいった


最終層の改造

FINE_N_CLASSES = 5
def create_fintune_model(model, nClasses=FINE_N_CLASSES):
    IMAGE_ORDERING =  "channels_last"
    inputs_ = model.inputs
    dense = model.get_layer(index=-2).output
    o1 = Conv2DTranspose(nClasses , kernel_size=(1,1) ,  strides=(1,1) , use_bias=False, data_format=IMAGE_ORDERING )(dense)
    o = (Activation("softmax"))(o1)
    models = Model(inputs=inputs_, outputs=o)
    return models


FCN8とUnetのin/output node の結果

# Unet
 TF input node name:
[<tf.Tensor 'input_1:0' shape=(?, 304, 480, 3) dtype=float32>]

 TF output node name:
[<tf.Tensor 'activation_19/truediv:0' shape=(?, ?, ?, 5) dtype=float32>]

# fcn8
 TF input node name:
[<tf.Tensor 'input_1:0' shape=(?, 256, 256, 3) dtype=float32>]

 TF output node name:
[<tf.Tensor 'activation_1/truediv:0' shape=(?, ?, ?, 5) dtype=float32>]


freezeとquantizeファイルのNODE名

# freeze_tf_graphs.shのNODE名
OUTPUT_NODE='activation_19/truediv'

# quantize.shのNODE名
INPUT_NODE="input_1"
Q_OUTPUT_NODE="conv2d_transpose_1/conv2d_transpose" # output node of quantized CNN


Unetのmodel.summary()の出力のlast

model.summary()
>>>>
〜〜〜〜〜
batch_normalization_18 (BatchNo (None, 304, 480, 64) 256         conv2d_26[0][0]                  
__________________________________________________________________________________________________
activation_18 (Activation)      (None, 304, 480, 64) 0           batch_normalization_18[0][0]     
__________________________________________________________________________________________________
conv2d_transpose_1 (Conv2DTrans (None, 304, 480, 5)  320         activation_18[0][0]              
__________________________________________________________________________________________________
activation_19 (Activation)      (None, 304, 480, 5)  0           conv2d_transpose_1[0][0]         
==================================================================================================
Total params: 32,449,152
Trainable params: 32,437,376
Non-trainable params: 11,776

Compile時の出力結果。DPUのhwhファイル(dcfファイル)はsoftmax対応にした。
全部DPU Kernelにおさまってる。

$./compile.sh
〜〜〜
**************************************************
* VITIS_AI Compilation - Xilinx Inc.
**************************************************
custom.json
[VAI_C][Warning] Operator [ name: conv2d_transpose_1/strided_slice_1/stack, op: Const] will be deleted by dnnc because it's not parent or child of any other operator.
[VAI_C][Warning] Operator [ name: conv2d_transpose_1/strided_slice_1/stack_1, op: Const] will be deleted by dnnc because it's not parent or child of any other operator.

〜〜〜〜〜〜〜

Kernel topology "unet_kernel_graph.jpg" for network "unet"
kernel list info for network "unet"
                               Kernel ID : Name
                                       0 : unet

                             Kernel Name : unet
--------------------------------------------------------------------------------
                             Kernel Type : DPUKernel
                               Code Size : 2.89MB
                              Param Size : 30.92MB
                           Workload MACs : 248040.66MOPS
                         IO Memory Space : 45.01MB
                              Mean Value : 0, 0, 0, 
                      Total Tensor Count : 36
                Boundary Input Tensor(s)   (H*W*C)
                            input_1:0(0) : 304*480*3

               Boundary Output Tensor(s)   (H*W*C)
conv2d_transpose_1_conv2d_transpose:0(0) : 304*480*5

                        Total Node Count : 35
                           Input Node(s)   (H*W*C)
                 conv2d_1_convolution(0) : 304*480*3

                          Output Node(s)   (H*W*C)
  conv2d_transpose_1_conv2d_transpose(0) : 304*480*5


$makeでアプリケーションもうまく作成できた。


3.追記:SeparableConv2Dでもsoftmaxを使える

SeparableConv2Dでもsoftmaxを使えた。ただ精度はkernel_size=(1,1), strides=(1,1)でshapeを変えずに素通りさせてるから、やってることはあんま変わらないかも。

Convtranspose2Dの方がNN構造によっては若干良かった。

FINE_N_CLASSES = 5
def create_fintune_model(model, nClasses=FINE_N_CLASSES):
    IMAGE_ORDERING = "channels_last"
    inputs_ = model.inputs
    dense = model.get_layer(index=-2).output
    o1 = SeparableConv2D(nClasses, kernel_size=(1,1),  strides=(1,1), use_bias=False, data_format=IMAGE_ORDERING)(dense)
    o = (Activation("softmax"))(o1)
    models = Model(inputs=inputs_, outputs=o)
    return models
INPUT_NODE="input_1"
OUTPUT_NODE='activation_19/truediv'
Q_OUTPUT_NODE="separable_conv2d_1/separable_conv2d" # output node of quantized CNN
$./compile.sh
〜〜〜
 Code Size : 2.93MB
                              Param Size : 30.92MB
                           Workload MACs : 248059.33MOPS
                         IO Memory Space : 45.01MB
                              Mean Value : 0, 0, 0, 
                      Total Tensor Count : 36
                Boundary Input Tensor(s)   (H*W*C)
                            input_1:0(0) : 304*480*3

               Boundary Output Tensor(s)   (H*W*C)
separable_conv2d_1_separable_conv2d:0(0) : 304*480*5

                        Total Node Count : 35
                           Input Node(s)   (H*W*C)
                 conv2d_1_convolution(0) : 304*480*3

                          Output Node(s)   (H*W*C)
  separable_conv2d_1_separable_conv2d(0) : 304*480*5

参考サイト



run_fcn8.sh

Const' op should be fused with current op Conv2DBackpropInput by DECENT

Keras convolutionレイヤー Documentation

自作DPUをultra96v2で動かすまでの備忘録【Vitis-AI, hardware】

FPGA上でディープラーニングを動かすとき背後でDPUというものが動いていて、DPUないとFPGA上でディープラーニングが動かせない。

今回はDPU IPコアから自作DPUを作って、ultra96v2上で動かすまでの備忘録をまとめた。




目次
1.DPUのデザインblockをvivadoで作成
2.Petalinuxでbuildしてimageを作成するまで
3.image.ubとBOOT.binのコピーで動作させる




1.DPUのデザインblockをvivadoで作成

作業環境


Mac OS

VirtualBox

ubuntu 14.02.03LTS

・Vivado、Vitis 2020.1






メモリの増設



VirtualBoxで作業してるので、DPUは死ぬほどメモリを食うので


プロセッサーでCPUを6枚

・メインメモリを11800くらいに変更

して計算リソースを増やした。

f:id:trafalbad:20200808225619p:plain

f:id:trafalbad:20200808225623p:plain


計算リソースが足りないとBitStream時に「強制終了エラー」が出る。





DPUのversion


xilinxgithubで公開してるDPU IP(DPU-TRDフォルダ)はブランチとマスターでDPUのversionが違うので注意。

・branch v1.0 =>target version 1.4.0(3.1)

・branch v1.1 => target version 1.4.1(3.2)

・master => target version 1.4.1 (3.3)


masterのDPU IPはv1.0とv1.1とかなり違うし、vivado2019.2だと使えない気がした。(2020.1なら使える)。




DPU IPからXSAファイルの作成



このサイトを参考に作成して、DPUはこの変わった変わったDPUドライバを使った。



まず DPU IPを IP catalogにadd。
f:id:trafalbad:20200829123421p:plain


設計するときは、パラメータに加えて、

・Clock wizardでdspはdpuの2倍の周波数がないとダメ
・各Processor system resetと各DPUピンはシンクロしないとタイミングエラーになる

などなど、制約が多いのでXilinxのDPUの英語の最新文献を読み込まないとできなかった。


f:id:trafalbad:20200808230806p:plain



無事BitStreamが完了したので、XSAファイルを生成。

最後に右のダッシュボードからパラメータsummaryを載っけてみる。



f:id:trafalbad:20200808225704p:plain

f:id:trafalbad:20200808225700p:plain


XSAファイルからUltra96で使われているハードウェア情報ファイル(hwh)の作成


ultra96v2のハードウェア情報ファイルhwhを作成。コンパイルのときdcfに変換して使うので作っとく。

xsaファイルはzipなのでunzip

$ unzip ultra96v2dpu.xsa

>>>>
design_1.hwh            psu_init.html   sysdef.xml        xsa.json
design_1_axi_smc_0.hwh  psu_init.tcl    ultra96v2.jpg     xsa.xml
psu_init.c              psu_init_gpl.c  ultra96v2dpu.bit
psu_init.h              psu_init_gpl.h  ultra96v2dpu.xsa

2.Petalinuxでbuildするまで



1.必要ファイルのdownloadと環境設定


Ultra96v2用のbspファイル
Ultra96-v2のサイトからdownload。これはPetalinuxでUltra96v2用のproject作成に必要。

# wget -P <path> <URL>
$ wget -P ultra96_oob_2019.2.bsp.zip http://downloads.element14.com/downloads/zedboard/ultra96-v2/ultra96v2_oob_2019_2.bsp.zip?ICID=ultra96v2-datasheet-widget
$ unzip ultra96_oob_2019.2.bsp.zip

一応ultra96v2のplartform用ファイルもdownload

$ wget -P ULTRA96V2_1029_2.tar.xz http://avnet.me/ultra96v2-vitis-2019.2
$ tar -xf ULTRA96V2_1029_2.tar.xz
$ export SDX_PLATFORM=/home/user/ULTRA96V2/ULTRA96V2.xpfm


・DNNDKとかのファイルに必要

$ git clone https://github.com/xelalin/Ultra96v2-DPU


2.前準備



petalinux2019.2のarduino-toolchain_1.0.bbの書き換え。


petalinux2019.2のarduino-toolchain_1.0.bbを10、 29、30行目のhacksterの記事を参考に書き換え。

$ sudo vi /opt/petalinux/components/yocto/source/aarch64/layers/meta-petalinux/recipes-utils/arduino-toolchain/arduino-toolchain_1.0.bb
>>>>>
# 10行目
http://http.us.debian.org/debian/pool/main/e/elfutils/libelf1_0.176-1.1_arm64.deb;subdir=avrgcc;unpack=false;name=libelf1 \
# 29, 30行目

SRC_URI[libelf1.md5sum] = "a9244703eec4735e54108001cee4c408" 
SRC_URI[libelf1.sha256sum] = "7c550a5eb057ec5c38f37e79eba476785e3a84097f7b740866db39012b99470f"



3.projectの作成



#!/bin/sh
$ petalinux-create -t project -s ultra96v2_oob_2019_2.bsp --name dpuprj
$ cd dpuprj
# DNNDK用のファイルをコピー
cp -rp ../Ultra96v2-DPU/files/recipes-apps/dnndk/ project-spec/meta-user/recipes-apps/
cp -rp ../Ultra96v2-DPU/files/recipes-modules/ project-spec/meta-user/
cp -rp ../Ultra96v2-DPU/files/recipes-apps/autostart/ project-spec/meta-user/recipes-apps/
cp -rp ../Ultra96v2-DPU/files/recipes-core/base-files/ project-spec/meta-user/recipes-core/
# project-spec/meta-user/conf/user-rootfsconfigに書き足す
echo 'CONFIG_dpu' >> project-spec/meta-user/conf/user-rootfsconfig
echo 'CONFIG_autostart' >> project-spec/meta-user/conf/user-rootfsconfig
echo 'CONFIG_dnndk' >> project-spec/meta-user/conf/user-rootfsconfig


・Projectの初期化
「XSA」ファルダにxsaファイルを入れておく。

$ petalinux-config --get-hw-description=../XSA

=> 「Image Packaging Configuration」>「RootFilesystem formats」に「ext4」を追加=> save&exit






4.rootfsの設定



$ cd dpuprj
$ petalinux-config -c rootfs


=> 以下の項目にチェックを入れてファイルシステムに含める

<Filesystem Packages>
libs=>libmali-xlnx
<Petalinux Package Groups>
-> matchbox
-> opencv
-> python-modules
-> v4lutils
-> x11
<apps>
-> autostart
<modules>
-> dpu
<user packages>
-> dnndk

チェックを入れたらsave&exit







5.カーネルの設定


$ petalinux-config -c kernel

=> 「Device Drivers」>「Generic Driver Options」>「Size in MegaBytes(DMA)で256を1024に変更



・デバイスツリーにDPUを追記
DPUのドライバーから回路を扱えるように回路側の情報を登録

$ sudo vi project-spec/meta-user/recipes-bsp/device-tree/files/system-user.dtsi
# 追記
&amba {
        dpu{
               compatible = "xilinx,dpu";
               base-addr = <0x8F000000>;
               dpucore {
                        compatible = "xilinx,dpucore";
                        interrupt-parent = <&gic>;
                        interrupts = <0x0 89 0x4>;
                        core-num = <0x1>;
               };
        };
};
	




6.ビルド(1時間くらいかかる)


$ petalinux-build


>>>>
[INFO] building project
[INFO] sourcing bitbake
[INFO] generating user layers
[INFO] generating workspace directory
INFO: bitbake petalinux-user-image
Loading cache: 100% |############################################| Time: 0:00:00
Loaded 3988 entries from dependency cache.
Parsing recipes: 100% |##########################################| Time: 0:00:04
Parsing of 2903 .bb files complete (2900 cached, 3 parsed). 3990 targets, 148 skipped, 0 masked, 0 errors.
NOTE: Resolving any missing task queue dependencies
Initialising tasks: 100% |#######################################| Time: 0:00:16
Checking sstate mirror object availability: 100% |###############| Time: 0:01:04
Sstate summary: Wanted 3628 Found 2635 Missed 1986 Current 36 (72% match, 72% complete)
NOTE: Executing SetScene Tasks
NOTE: Executing RunQueue Tasks
NOTE: linux-xlnx: compiling from external source tree /home/user/ascprj/components/plnx_workspace/sources/linux-xlnx
WARNING: dnndk-1.0-r0 do_package_qa: QA Issue: dnndk: found library in wrong location: /usr/local/lib/libn2cube.so [libdir]
NOTE: Tasks Summary: Attempted 11029 tasks of which 7646 didn't need to be rerun and all succeeded.

Summary: There was 1 WARNING message shown.
INFO: Copying Images from deploy to images
INFO: Creating /home/user/ascprj/images/linux directory
NOTE: Failed to copy built images to tftp dir: /tftpboot 
[INFO] successfully built project



4.image.ubとBOOT.binを作成してイメージを書き込む

1.BOOT.BINとimages.ubの作成



$ cd images/linux
$ petalinux-package --boot --force --u-boot

>>>
INFO: File in BOOT BIN: "/home/user/dpuprj/images/linux/zynqmp_fsbl.elf"
INFO: File in BOOT BIN: "/home/user/dpuprj/images/linux/pmufw.elf"
INFO: File in BOOT BIN: "/home/user/dpuprj/images/linux/bl31.elf"
INFO: File in BOOT BIN: "/home/user/dpuprj/images/linux/u-boot.elf"
INFO: Generating zynqmp binary package BOOT.BIN...


****** Xilinx Bootgen v2019.2
  **** Build date : Oct 23 2019-22:59:42
    ** Copyright 1986-2019 Xilinx, Inc. All Rights Reserved.

INFO: Binary is ready.
WARNING: Unable to access the TFTPBOOT folder /tftpboot!!!
WARNING: Skip file copy to TFTPBOOT folder!!!

BOOT.BIN、image.ubができた。




2.SDKの作成



$ cd images/linux
$ petalinux-build --sdk

>>>>
NOTE: Tasks Summary: Attempted 8179 tasks of which 8140 didn't need to be rerun and all succeeded.
[INFO] Copying SDK Installer…

[INFO] successfully built project
# 保存するディレクトリを作成
$ mkdir SDCARD

# 保存先を聞いてくるので指定
$ ./sdk.sh

>>>
PetaLinux SDK installer version 2019.2
======================================
Enter target directory for SDK (default: /opt/petalinux/2019.2): /home/hagis/dpuprj/SDCARD
You are about to install the SDK to "/home/user/dpuprj/SDCARD". Proceed[Y/n]? Y
Extracting SDK…………………..
〜
Setting it up...done
SDK has been successfully set up and is ready to be used.
Each time you wish to use the SDK in a new shell session, you need to source the environment setup script e.g.
 $ . /home/hagis/dpuprj/SDCARD/environment-setup-aarch64-xilinx-linux



3.イメージの書き込み



このサイトからinstallしたイメージをEtcherでSD CARDにコピーすれば、rootとbootは勝手に作られてる。
だからfdiskコマンドでパーティションを作ってやる必要ないのでそのままコピー。

コピーするsd_cardの中身は下のファイル構成

$ tree sd_card
>>>
sd_card
├── boot
│   ├── BOOT.BIN
│   ├── boot.scr
│   ├── image.ub
│   └── system.dtb
└── root
    └── rootfs.tar.gz


ubuntuを入れたPCでコピーを実行

# boot(FAT32)にコピー
cd sd_card/boot
cp image.ub BOOT.BIN system.dtb boot.scr /media/user/boot/

# root(EXT4)にコピー
cd sd_card/root
sudo tar -C /media/user/root -xzvf rootfs.tar.gz

gpartedをインストールすればGUIで、パーティションを分割できる。

$ sudo apt-get install gparted


4.gtktermでultra96v2にログイン


VirtualBoxにUSBを認識させた。teratermubuntu版「gtkterm」を使った

Ultra96v2ボードは1をoffに2をOnにしてSDカードのモード(JTAGではなく)にしておく。

# gtktermのインストール
$ sudo apt-get install gtkterm

# USBが認識されてるか確認
$ ls -l /dev/ttyUSB*
>>>
crw-rw---- 1 root dialout 188, 0  828 17:11 /dev/ttyUSB0
crw-rw---- 1 root dialout 188, 1  828 17:11 /dev/ttyUSB1

# ultra96v2ボードにアクセス(terminalで実行)
$ gtkterm -p /dev/ttyUSB1 -s 115200


参考サイト



Vitis-AIのgithub

Zynq DPU v3.2 Product Guide PG338 (v3.2) July 7, 2020

DNNDK on Vitis AI on Ultra96v2(qiita)

Vitis-AI 1.1 Flow for Avnet VITIS Platforms - Part1(hackster)

ultara96v2用DPU用ファイル

Ultra96-v2でCNN推論エンジン(DPU)を動かすまで

Vitis-AIを使ってultra96v2上で学習済みモデルを動かすまで【hardware, FPGA, AI】

今回はultra96v2上で学習済みモデルを動かしてみる。

この作業がすんなりできれば、論理回路の高位合成とか組み込み部分を除けば、学習済みモデルを作れればドローン制御、自動運転の制御とかいろんなことの礎になる。

本家サイト「Ultra96V2向けVitis AI(2019.2)の組み立て方」通りに進めたけど情報が古かったのでかなり苦労した。


f:id:trafalbad:20200429092854p:plain


目次
1.動作環境「vitis AI」の構築
2.Deep Learning Processor Unit (DPU) IP の作成
3.学習済みデータと画像の用意
4.pbファイルの量子化(quantization)
5.量子化したファイルからFPGA用アプリケーションの作成
6.Ultra96v2ボードでの動作確認




1.動作環境「vitis AI」の構築

dockerのインストール



dockerが必要なのでdockerをインストール。

$ sudo apt-get update && sudo apt-get upgrade
$ sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
>>>
OK

$ sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
# Uninstall Old Versions of Docker & Install Docker
$ sudo apt-get remove docker docker-engine docker.io && sudo apt install docker.io
# Start and Automate Docker
$ sudo systemctl start docker && sudo systemctl enable docker
# コンテナ確認
$ sudo docker ps
>>>
CONTAINER ID        IMAGE               COMMAND   CREATED             STATUS              PORTS     NAMES


sudo なしで実行できるようにする。

# dockerグループ作成 & 現行ユーザをdockerグループに所属させる
$ sudo groupadd docker && sudo gpasswd -a $USER docker
# dockerデーモンを再起動する (CentOS7の場合)
$ sudo systemctl restart docker
# exitして再ログインすると反映される。
$ exit
# コンテナ確認
$ docker ps
>>>
CONTAINER ID        IMAGE               COMMAND   CREATED             STATUS              PORTS     NAMES

参考:Ubuntu 18.04にDockerをインストールする(+docker-composeも)


vitis-Aiのインストール


「tesnsorflow_q_val」コマンドがvirtualbocxだとCPUの関係で使えないので、EC2インスタンスで作業した。

次にvitis AIのインストール。
XilinxのVitis-AIがupgradeされてた。ブランチのv1.1をgit clone。

$ git clone -b v1.1 https://github.com/Xilinx/Vitis-AI
# Dockerの設定
$ cd Vitis-AI/docker
$ ./docker_build_cpu.sh
$ cd Vitis-AI
$ ./docker_run.sh xilinx/vitis-ai-cpu:latest


今回はCPU環境で試してみて、動いた!
f:id:trafalbad:20200429092932p:plain

# 抜ける
$ exit
# GPUの時はこちら
$ sudo ./docker_build_gpu.sh
$ sudo ./docker_run.sh xilinx/vitis-ai-gpu:latest




2.Deep Learning Processor Unit (DPU) IP の作成

DPU IPは「DPU for Convolutional Neural Network v1.1」によるとFPGAでCNNとかを動かすリソース(レジスタ設定、データ コントローラー、たたみ込み演算の各モジュールとか)が組み込まれてる。

DPU IPはvivadoで作成しなきゃならないけど、ここは本家サイトからresnet50用のDPUをdownloadした。


DPU(AI)IP作成パート
f:id:trafalbad:20200429092954p:plain

download後にultra96v2ボードにetcherでコピー。


コピー後のボード内ファイル一覧

$ ls ultra96_oob
BOOT.BIN        image.ub        system.dtb
README.txt        init.sh            ultra96v2_oob.hwh
dpu.xclbin        platform_desc.tx

これはSDCARDのboot領域(/media/user/${SDCARD}/boot)にコピーされる



3.学習済みネットワークと画像の用意

学習済みネットワークと画像の用意のパート
f:id:trafalbad:20200429093029p:plain



1.学習済みネットワークを用意



まず、作業環境のDocker起動(これ以降はほとんどdocker上で動かす)

$ cd Vitis-AI
$ sudo ./docker_run.sh xilinx/vitis-ai-cpu:latest
# 作業ディレクトリ作成
$ mkdir workspace
$ cd workspace


“vitis-ai-tensorflow”(または”vitis-ai-caffe”)をcondaで実行。

$ conda activate vitis-ai-caffe
# tensorflowの場合
$ conda activate vitis-ai-tensorflow


今回はtensorflowのunetを自分で学習して、kerasの重みファイル(hdf5, ckptファイル)からultra96v2で動くアプリケーションを作る。



2.評価用画像の用意





評価用として学習用に使った画像をprepare_dataset.pyで100枚用意した。

量子化に使う「graph_input_fn.py」を少し書き換えた。

graph_input_fn.py

calib_batch_size = 10

def calib_input(iter):
  images = []
  line = open(calib_image_list).readlines()
  #print(line)
  for index in range(0, calib_batch_size):
      curline = line[iter*calib_batch_size + index]
      #print("iter= ", iter, "index= ", index, "sum= ", int(iter*calib_batch_size + index), "curline= ", curline)
      calib_image_name = curline.strip()

      image_path = os.path.join(calib_image_dir, calib_image_name)
      image2 = NormalizeImageArr(image_path)

      #image2 = image2.reshape((image2.shape[0], image2.shape[1], 3))
      images.append(image2)

  return {"input_1": images}


def main():
  calib_input()

if __name__ == "__main__":
    main()


4.pbファイルの量子化(quantization)


kerasの訓練済の重みファイルを変換して、freezeしたpbファイル(frozen_graph.pb)を量子化する。

quantize.shで量子化

#!/bin/sh
FREEZE_DIR=freeze_tfpb
FROZEN_GRAPH_FILENAME=frozen_graph.pb
QUANT_DIR=quantized_model
INPUT_NODE="input_1"
Q_OUTPUT_NODE="conv2d_23/Sigmoid" # output node of quantized CNN

vai_q_tensorflow quantize \
	 --input_frozen_graph  ${FREEZE_DIR}/${FROZEN_GRAPH_FILENAME} \
	 --input_nodes         ${INPUT_NODE} \
	 --input_shapes        ?,224,224,3 \
	 --output_nodes        ${Q_OUTPUT_NODE} \
	 --output_dir          ${QUANT_DIR}/ \
	 --method              1 \
	 --input_fn            graph_input_fn.calib_input \
	 --calib_iter          10 \
	 --gpu 0




4.量子化データから、FPGA用アプリケーションの作成

アプリケーション作成のパートf:id:trafalbad:20200429093057p:plain


1.dcfファイルの作成



次に、etcherでイメージをSDカードにコピーしたときできた、”ultra96v2_oob.hwh”のhwhファイル(ハードウェア情報ファイル)を使って、dcfファイルを作る。

$ dlet -f ultra96v2_oob.hwh
>>>
[DLet]Generate DPU DCF file dpu-11-18-2019-18-45.dcf successfully.
# rename
$ mv dpu-11-18-2019-18-45.dcf resnet50.dcf

dletコマンドは“vitis-ai-caffe”(“vitis-ai-tensorflow”)内でのみ使える。
dcfファイルは後で使う。




2.compileを実行



compile.sh

#! /bin/sh
CNN=unet
COMPILE_DIR=output_compile
QUANT_DIR=quantized_model
TARGET=custom
ARCH=${TARGET}.json
vai_c_tensorflow \
 	 --frozen_pb ${QUANT_DIR}/deploy_model.pb \
 	 --arch ${ARCH} \
 	 --output_dir ${COMPILE_DIR}/${CNN}2 \
	 --options    "{'mode':'normal'}" \
	 --net_name ${CNN}2

dcfを入れたcustom.jsonの中身。

$cat custom.json
>>>
{"target": "dpuv2", "dcf": "dpu-11-18-2019-18-45.dcf", "cpu_arch": "arm64"}


compileを実行。

$ ./compile.sh
>>>>

**************************************************
* VITIS_AI Compilation - Xilinx Inc.
**************************************************
[VAI_C][Warning] layer [conv2d_23_Sigmoid] (type: Sigmoid) is not supported in DPU, deploy it in CPU instead.

Kernel topology "unet2_kernel_graph.jpg" for network "unet2"
kernel list info for network "unet2"
                               Kernel ID : Name
                                       0 : unet2_0
                                       1 : unet2_1

                             Kernel Name : unet2_0
--------------------------------------------------------------------------------
                             Kernel Type : DPUKernel
                               Code Size : 1.16MB
                              Param Size : 29.63MB
                           Workload MACs : 83685.54MOPS
                         IO Memory Space : 17.04MB
                              Mean Value : 0, 0, 0, 
                      Total Tensor Count : 40
                Boundary Input Tensor(s)   (H*W*C)
                            input_1:0(0) : 224*224*3

               Boundary Output Tensor(s)   (H*W*C)
              conv2d_23_convolution:0(0) : 224*224*11

                        Total Node Count : 35
                           Input Node(s)   (H*W*C)
                 conv2d_1_convolution(0) : 224*224*3

                          Output Node(s)   (H*W*C)
                conv2d_23_convolution(0) : 224*224*11

                             Kernel Name : unet2_1
--------------------------------------------------------------------------------
                             Kernel Type : CPUKernel
                Boundary Input Tensor(s)   (H*W*C)
                  conv2d_23_Sigmoid:0(0) : 224*224*11

               Boundary Output Tensor(s)   (H*W*C)
                  conv2d_23_Sigmoid:0(0) : 224*224*11

                           Input Node(s)   (H*W*C)
                       conv2d_23_Sigmoid : 224*224*11

                          Output Node(s)   (H*W*C)
                       conv2d_23_Sigmoid : 224*224*11

終わったあとのファルダ構造。

$ tree 
>>>
compile
├── compile.sh
├── custom.json
├── dpu-11-18-2019-18-45.dcf
├── output_compile
│   └── unet2
│       └── unet2_kernel_graph.gv
|        └── dpu_unet2_0.elf
├── quantized_model
│   ├── deploy_model.pb
│   └── quantize_eval_model.pb
└── ultra96v2_oob.hwh


3.アプリケーションの作成(sigmoidを使う


まずdocker。Xilinx提供のVitis AI runtimeを実行。

$ ./docker_run.sh xilinx/vitis-ai:runtime-1.0.0-cpu

今回unetにsigmoidを使った。

sigmoidはDPUで対応してないため、DPUKernelとCPUKernelが分割されるので、dpu_unet2_0.elf(unet2_0)が作成される。


なのでアプリケーション作成時に使う
「src/fpc_main.cc」のコードで

#define KERNEL_CONV   "unet2"

から

#define KERNEL_CONV       "unet2_0"

に置き換えないとultra96v2上で動かない。








5.量子化したファイルからFPGA用アプリケーションの作成


1.開発環境構築・ライブラリをSDカードへコピー



Vitis AIをUltra96上で動かすには、xilinxのライブラリーが必要なので、runtime パッケージをSDカード(rootのsdcardフォルダ)にコピー

# パッケージのinstall(開発環境構築)
$ sudo cp -r /opt/vitis_ai/xilinx_vai_board_package Vitis-AI/workspace/
$ cd Vitis-AI/workspace/xilinx_vai_board_package/
$ sudo ./install.sh

# パッケージをSDカードにコピー
$ sudo cp -r /opt/vitis_ai/xilinx_vai_board_package /media/user/${SDCARD}/root/home/root/


2.FPGA用アプリケーションの作成




さっき作った「dpu_unet2_0.elf」をのmodelファルダの中に移動。
「$make」コマンドでアプリケーション作成実行。

コードはXilinxsegmentationチュートリアルのコードを少し改造して使った。

# アプリケーションの作成実行
$ sudo make 


アプリケーション完成後のディレクトリ構造

$ tree 
>>>
├── common
│   ├── dputils.cpp
│   ├── dputils.h
│   └── dputils.py
└── unet2
    ├── Makefile
    ├── build
    │   ├── dputils.o
    │   └── fps_main.o
    ├── model
    │   ├── dpu_unet2_0.elf
    │   └── libdpumodelunet2.so
    ├── src
    │   └── fps_main.cc
    └── unet2

src/fps_main.ccの一部を変更。

// constants for segmentation network
#define KERNEL_CONV       "unet2_0"
#define CONV_INPUT_NODE   "conv2d_1_convolution"
#define CONV_OUTPUT_NODE  "conv2d_23_convolution"


アプリケーションとして「unet2」と「libdpumodelunet2.so」ができるので、それをSDカード(rootのsdcardフォルダ)にコピー。

$ cp resnet50 /media/user/${SDCARD}/root/home/root/



6.Ultra96v2ボードでの動作確認

いつもみたいにultra96v2の実機を起動後に、gtktermで接続してログイン

$ gtkterm -p /dev/ttyUSB1 -s 115200
# ライブラリのinstall
$ cd xilinx_vai_board_package
$ ./install.sh
>>>
Begin to install Xilinx DNNDK ...
Requirement already satisfied: Edge-Vitis-AI==1.0 from file:///sdcard/pkgs/python/Edge_Vitis_AI-1.0-py2.py3-none-any.whl in /usr/lib/python3.5/site-packages (1.0)
Complete installation successfully.

判別用画像をroot/home/rootの「worksapace/dataset1/」の中に入れておく。

$ cd unet2/
$ sudo chmod 755 *
$ ./unet2

f:id:trafalbad:20200902200235p:plain


中身は適当なので出力結果は気にしないけど、とりあえず動いた。





ここでこの部分の作業が終わって、AIを動かす過程が一通り終了。
f:id:trafalbad:20200429093241p:plain




ここまででようやくultra96v2上で学習済みモデルを動かせた。

f:id:trafalbad:20200429094131j:plain


参考サイト



Ultra96V2向けVitis AI(2019.2)の組み立て方

Ubuntu 18.04にDockerをインストールする(+docker-composeも)

Vitis AI User Guide

ザイリンクス社「Vitis AI開発環境」を評価キット ZCU102 で動かしてみた

GitHub - Xilinx/Vitis-AI at v1.1

Vitis-AI 1.1 Flow for Avnet VITIS Platforms - Part 1

UbuntuでSDカードのEXT4とFAT32のパーテションの作り方

ubuntuでSDカードのパーティション作成のメモ

目次
1.ubuntumacにインストールする
2. ubuntuを起動、SDカードの確認
3.パーティションの作成
4.GUIでパーテイションの内訳を確認してみる
追記.Ffrom CUI


1.ubuntumacにインストールする

1.USBカードを差し込む。

$ diskutil list
$ diskutil unMountDisk /dev/disk2 (USB= /dev/disk2)
$ sudo dd if=ubuntu-18.4.2.iso of=/dev/disk2 bs=1m

終わったら、「無視」をクリック。


2.macをoption押しながら起動してUSBの選択肢を選択する。


3.あとは[ubuntu〜 install]をクリックしてwifi設定も含めてubuntuをinstall
ほとんどvirtualBoxと同じ方法でinstall。




2. ubuntuを起動、SDカードの確認

macの「diskutil list」と同じコマンドを打って、SDカードの内訳確認

$ dmesg | tail
>>>>
...
[ 6854.215650] sd 7:0:0:0: [sdc] Mode Sense: 0b 00 00 08
[ 6854.215653] sd 7:0:0:0: [sdc] Assuming drive cache: write through
[ 6854.215659]  sdc: sdc1


この例では/dev/sdcとして認識されてる。





3.パーティションの作成

fdiskコマンドの確認
・p:確認
・d:パーティション削除
・n:パーティション新規作成
・a:起動フラグの有効化

$ sudo fdisk /dev/sdc

fdisk (util-linux 2.31.1) へようこそ。
ここで設定した内容は、書き込みコマンドを実行するまでメモリのみに保持されます。
書き込みコマンドを使用する際は、注意して実行してください。


・pコマンドで確認

コマンド (m でヘルプ): p
ディスク /dev/sdc: 14.9 GiB, 16022241280 バイト, 31293440 セクタ
単位: セクタ (1 * 512 = 512 バイト)
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
ディスクラベルのタイプ: dos
ディスク識別子: 0xf20f0c70

デバイス   起動 開始位置 最後から   セクタ サイズ Id タイプ
/dev/sdc1           2048  2936831  2934784   1.4G  c W95 FAT32 (LBA)
/dev/sdc2        2936832 14680063 11743232   5.6G 83 Linux


・dコマンドでパーティション削除

コマンド (m でヘルプ): d
パーティション番号 (1,2, 既定値 2): 2

パーティション 2 を削除しました。

コマンド (m でヘルプ): d
パーティション 1 を選択
パーティション 1 を削除しました。


・pで確認

コマンド (m でヘルプ): p
ディスク /dev/sdc: 14.9 GiB, 16022241280 バイト, 31293440 セクタ
単位: セクタ (1 * 512 = 512 バイト)
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
ディスクラベルのタイプ: dos
ディスク識別子: 0xf20f0c70


・nで新規作成(n => p => 1 => 2048)

コマンド (m でヘルプ): n
パーティションタイプ
   p   基本パーティション (0 プライマリ, 0 拡張, 4 空き)
   e   拡張領域 (論理パーティションが入ります)
選択 (既定値 p): p
パーティション番号 (1-4, 既定値 1): 1
最初のセクタ (2048-31293439, 既定値 2048): 2048
最終セクタ, +セクタ番号 または +サイズ{K,M,G,T,P} (2048-31293439, 既定値 31293439): 2936831

新しいパーティション 1 をタイプ Linux、サイズ 1.4 GiB で作成しました。
パーティション #1 には vfat 署名が書き込まれています。

署名を削除しますか? [Y]es/[N]o: Yes

署名は write (書き込み)コマンドを実行すると消えてしまいます。


・aで起動フラグを有効にする

コマンド (m でヘルプ): a
パーティション 1 を選択
パーティション 1 の起動フラグを有効にしました。


・pで確認

コマンド (m でヘルプ): p
ディスク /dev/sdc: 14.9 GiB, 16022241280 バイト, 31293440 セクタ
単位: セクタ (1 * 512 = 512 バイト)
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
ディスクラベルのタイプ: dos
ディスク識別子: 0xf20f0c70

デバイス   起動 開始位置 最後から  セクタ サイズ Id タイプ
/dev/sdc1  *        2048  2936831 2934784   1.4G 83 Linux

パーティション 1 にあるファイルシステム/RAIDの署名が完全に消去されます。


・nで2個めのパーティション作成(n => p => 2 => 2936832 => 31293439)

コマンド (m でヘルプ): n
パーティションタイプ
   p   基本パーティション (1 プライマリ, 0 拡張, 3 空き)
   e   拡張領域 (論理パーティションが入ります)
選択 (既定値 p): p
パーティション番号 (2-4, 既定値 2): 2
最初のセクタ (2936832-31293439, 既定値 2936832): 2936832
最終セクタ, +セクタ番号 または +サイズ{K,M,G,T,P} (2936832-31293439, 既定値 31293439): 31293439

新しいパーティション 2 をタイプ Linux、サイズ 13.5 GiB で作成しました。
パーティション #2 には ext4 署名が書き込まれています。

署名を削除しますか? [Y]es/[N]o: Yes

署名は write (書き込み)コマンドを実行すると消えてしまいます。


・pで最終確認 & 終了

コマンド (m でヘルプ): p
ディスク /dev/sdc: 14.9 GiB, 16022241280 バイト, 31293440 セクタ
単位: セクタ (1 * 512 = 512 バイト)
セクタサイズ (論理 / 物理): 512 バイト / 512 バイト
I/O サイズ (最小 / 推奨): 512 バイト / 512 バイト
ディスクラベルのタイプ: dos
ディスク識別子: 0xf20f0c70

デバイス   起動 開始位置 最後から   セクタ サイズ Id タイプ
/dev/sdc1  *        2048  2936831  2934784   1.4G 83 Linux
/dev/sdc2        2936832 31293439 28356608  13.5G 83 Linux

パーティション 1 にあるファイルシステム/RAIDの署名が完全に消去されます。
パーティション 2 にあるファイルシステム/RAIDの署名が完全に消去されます。

コマンド (m でヘルプ): ^C
終了してよろしいですか? y




4.GUIパーティションの内訳を確認してみる

deskopから、「desk」を選択。

SDカードを差し込むと「boot」と「root」の両方があるのが見える。
f:id:trafalbad:20200703212618j:plain

FAT32パーティション(boot)f:id:trafalbad:20200703212646j:plain


EXT4(linux用)パーティション(root)f:id:trafalbad:20200703212652j:plain



FAT32パーティション(boot)の中身f:id:trafalbad:20200703212700j:plain



EXT4パーティション(root)の中身f:id:trafalbad:20200703212708j:plain


sdカードをFAT32EXT4に分割できた。

ちなみにmacでは無理で、ubuntuをOSで入れないとできない



追記.From CUI

1.HDDを仮想環境に追加



設定=>ストレージ=>「コントロール:SOTA」の右のプラスボタンをクリックで作成。
・VDI(Virtual Disk Image)

・可変サイズ

・とりあえず50GB
で作成。

f:id:trafalbad:20200929020441p:plain

50GBのHDDが追加されたか確認。

$ dmesg | grep sdb
>>>
[    3.462702] sd 3:0:0:0: [sdb] 104857600 512-byte logical blocks: (53.7 GB/50.0 GiB)



2.パーティションの作成



fdiskじゃなくてpartedコマンドで作る

$ sudo parted /dev/sdb 

>>>>>>>
GNU Parted 3.2
/dev/sdb を使用
GNU Parted へようこそ! コマンド一覧を見るには 'help' と入力してください。
###入力コマンド
(parted) mklabel msdos                                                    
(parted) mkpart primary fat32 0% 8%                                       
(parted) mkpart primary ext4 8% 100%
(parted) q  
###                                                              
通知: 必要であれば /etc/fstab を更新するのを忘れないようにしてください。

これで、/dev/sdb1と/dev/sdb2ができた。マウント用ファイル「/media/sd_card/root」と「/media/sd_card/boot」





3.boot領域、root領域にファイルのコピー



# FAT32領域の作成
$ sudo mkfs.vfat -F 32 /dev/sdb1
>>>
mkfs.fat 4.1 (2017-01-24)

# EXT4領域の作成
$ sudo mkfs.ext4 /dev/sdb2
>>>
mke2fs 1.44.1 (24-Mar-2018)
Creating filesystem with 12058624 4k blocks and 3014656 inodes
〜                       
Creating journal (65536 blocks): done
Writing superblocks and filesystem accounting information: done

# ext4(root)領域のマウント
$ sudo mount /dev/sdb2 /media/sd_card/root
#  roots.tar.gzをコピー。
$ sudo tar -C /media/sd_card/root -xzvf rootfs.tar.gz
# コンパイルしたアプリケーションとかコピー
$ sudo cp -r sdk vitis-ai_v1.1_dnndk /media/sd_card/root/home/root/
# マウント解除
$ sudo umount /media/sd_card/root
# FAT32(boot)領域にコピー
$ sudo mount /dev/sdb1 /media/sd_card/boot
$ sudo cp BOOT.BIN image.ub system.dtb dpu.xclbin init.sh ultra96v2_oob.hwh /media/sd_card/boot/
$ sudo umount /media/sd_card/boot


4. /dev/sdbの内訳



$ fdisk -l

>>>
ディスク /dev/sda: 400 GiB, 429530284032 バイト, 838926336 セクタ
デバイス   起動 開始位置  最後から    セクタ サイズ Id タイプ
/dev/sda1  *        2048 838924287 838922240   400G 83 Linux
〜
ディスク /dev/sdb: 50 GiB, 53687091200 バイト, 104857600 セクタ
〜
デバイス   起動 開始位置  最後から   セクタ サイズ Id タイプ
/dev/sdb1           2048   8388607  8386560     4G  c W95 FAT32 (LBA)
/dev/sdb2        8388608 104857599 96468992    46G 83 Linux


あとはVirtualBoxSDKカードを読み込ませる。




参考サイト



How to format SD card for SD boot

fdiskでフォーマットする

VirtualBox の Ubuntu にHDDを追加する方法

Ultra96-v2でCNN推論エンジン(DPU)を動かすまで

PytorchのTransformerでテキストからの音声生成(TextToSpeech)をやってみた【機械学習】

今回はTransformerを改造して、文章から音声を生成してみた。

俗に言う、エンコーダ・デコーダ形式のモデル。
プロセスが長くなりそうなので、要点だけ備忘録もかねてまとめようと思う。


目次
1.Transformer-TextToSpeechとは?
2.テキスト前処理
3.TransformerとPostNetの学習
4.テキストから音声を作成してみる



1.transformer-TextToSpeechとは?

今回作ったtransformerでの音声生成は、Google が発表したTactron2を改造した。

tactron2のEncoderとDecoderをTransformerに置き換えて、waveGlowをpostnetに置き換えたモデル。

Tactron2はそもそもGoogleのこれまでの音声生成プロジェクトで作られた、WaveNetと初代Tacotronのネットワークを組み合わせたもので、詳しくはサイトを見て欲しい。

tacotron2での音声生成処理の流れ
f:id:trafalbad:20200630215005p:plain




今回作ったTransformerの音声生成処理の流れ
f:id:trafalbad:20200630215350j:plain


つまり、

・tacotron2 => Transformer
・waveGlow => PostNet
に置き換えた。

単純なAttentionモデルのseq2seqを使った場合より、4倍くらい速く、精度もbetter。

特にtransformerのattentionの部分が正確な音声の作成にかなり有効っぽい。




2.テキスト前処理

今回データセットは「The LJ Speech Dataset」を使った。

英語での音声を想定したデータセット


前処理では、日本語でもローマ字(英語のスペル)に変換する。
今回は英語対応なので、すべての文字を英語のスペルに変換。


試しにデータセットの一部分の、LJ050-0278のデータをのぞいてみる。

textデータ(csv)

LJ050-0278|the recommendations we have here suggested would greatly advance the security of the office without any impairment of our fundamental liberties.|the recommendations we have here suggested would greatly advance the security of the office without any impairment of our fundamental liberties.


音声データ

LJ050-0278.mag.npy
LJ050-0278.pt.npy
LJ050-0278.wav


まず訓練前にこのテキストデータを前処理した。







3.TransformerとPostNetの学習

まず、Transformerを学習させてから、最後にPostNetを学習して、音声合成する。

Transformerはスペクトログラム(人間の音高知覚に調整した特徴量:メル周波数)を出力。

PostNetは音声の波形データを出力する。

Pytorchなのでmodelをprint()してみた

Transformer

model = Transformer_model()
print(model)

>>>>>
Model(
(encoder): Encoder(
(pos_emb): Embedding(1024, 256)
(pos_dropout): Dropout(p=0.1, inplace=False)
(encoder_prenet): EncoderPrenet(
  (embed): Embedding(149, 512, padding_idx=0)
  (conv1): Conv(
    (conv): Conv1d(512, 256, kernel_size=(5,), stride=(1,), padding=(2,))

 ~略~

  (1): Attention(
    (key): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=False)
    )
    (value): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=False)
    )
    (query): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=False)

 ~略~

(decoder): MelDecoder(
(pos_emb): Embedding(1024, 256)
(pos_dropout): Dropout(p=0.1, inplace=False)
(decoder_prenet): Prenet(
  (layer): Sequential(
    (fc1): Linear(
      (linear_layer): Linear(in_features=80, out_features=512, bias=True)
    )

 ~略~

  (pre_batchnorm): BatchNorm1d(256, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True)
  (dropout1): Dropout(p=0.1, inplace=False)
  (dropout_list): ModuleList(
    (0): Dropout(p=0.1, inplace=False)
    (1): Dropout(p=0.1, inplace=False)
    (2): Dropout(p=0.1, inplace=False)
  )
)
)
)

PostNet

model = PostNet_model() 
print(model)
>>>>>>

ModelPostNet(
(pre_projection): Conv(
(conv): Conv1d(80, 256, kernel_size=(1,), stride=(1,))
)
(cbhg): CBHG(
(convbank_list): ModuleList(
  (0): Conv1d(256, 256, kernel_size=(1,), stride=(1,))
  (1): Conv1d(256, 256, kernel_size=(2,), stride=(1,), padding=(1,))
  (2): Conv1d(256, 256, kernel_size=(3,), stride=(1,), padding=(1,))
  (3): Conv1d(256, 256, kernel_size=(4,), stride=(1,), padding=(2,))
  (4): Conv1d(256, 256, kernel_size=(5,), stride=(1,), padding=(2,))
  (5): Conv1d(256, 256, kernel_size=(6,), stride=(1,), padding=(3,))
  (6): Conv1d(256, 256, kernel_size=(7,), stride=(1,), padding=(3,))
  (7): Conv1d(256, 256, kernel_size=(8,), stride=(1,), padding=(4,))

  ~略~

  (linears): ModuleList(
    (0): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=True)
    )
    (1): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=True)
    )
    (2): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=True)
    )
    (3): Linear(
      (linear_layer): Linear(in_features=256, out_features=256, bias=True)
    )
  )
)
(gru): GRU(256, 128, num_layers=2, batch_first=True, bidirectional=True)
)
(post_projection): Conv(
(conv): Conv1d(256, 1025, kernel_size=(1,), stride=(1,))
)
)

コードだと分かりにくいのでモデルの全体像。
f:id:trafalbad:20200630215037p:plain



学習はfine-tuneで行った。
学習率のlearning rateは0.001がベストプラクティス。
あと学習が進むにつれてlearning rateも下がる調整もかなり重要っぽい

def adjust_learning_rate(optimizer, step_num, warmup_step=4000):
    lr = hp.lr * warmup_step**0.5 * min(step_num * warmup_step**-1.5, step_num**-0.5)
    for param_group in optimizer.param_groups:
        param_group['lr'] = lr


loss曲線はtctron2からの引用だけど、同等もさくはそれ以上に、かなりいい具合に減少。
f:id:trafalbad:20200630215049p:plain




でかいデータセットでやるとメモリエラーも起こるし、時間もとんでもなくかかるからfine-tuneがおすすめ。

最近はもうfine-tuneの方がかなり効率いいので、end-to-endの学習は余程のことがないとしないんじゃないかな?





4.テキストから音声を作成してみる

映画ホームアローンのハリーのセリフを作成してみる。

f:id:trafalbad:20200630215151j:plain



text1 = "I never made it to sixth grade"
text2 = "it dose not look like you are gonna"

周波数(fs)は低くするとドスのきいた声になり、高いと早口言葉みたいになる。

max_lenはtextの長さに比例するのでそれぞれちょうどいい具合に調整した。

def calculate_melsp(x, n_fft=1024, hop_length=128, n_mels=128):
    stft = np.abs(librosa.stft(x, n_fft=n_fft, hop_length=hop_length))**2
    log_stft = librosa.power_to_db(stft)
    melsp = librosa.feature.melspectrogram(S=log_stft, n_mels=n_mels)
    return melsp

# display wave in plots
def show_wave(x):
    plt.plot(x)
    plt.show()
    
    
# display wave in heatmap
def show_melsp(melsp, fs):
    librosa.display.specshow(melsp, sr=fs)
    plt.colorbar()
    plt.show()

text1 = "I never made it to sixth grade, kid."

max_len = 500
fs = 25000

text1 = "I never made it to sixth grade, kid."
wav = create_audio_wave(text1, max_len)

print(wav.shape)  # (137225,)
show_wave(wav)

melsp = calculate_melsp(wav, n_fft=fs, hop_length=max_len, n_mels=max_len)
print(melsp.shape) # (500, 275)

show_melsp(melsp, fs)

# 実際にjupyter上で音声が聞ける
ipd.Audio(wav, rate=fs)

この投稿をInstagramで見る

開発用 "I never made it to sixth grade"

Tatsuya Hagiwara(@gosei_creater)がシェアした投稿 -





text2 = "it dose not look like you are gonna"

text2 = "it dose not look like you are gonna"
wav = create_audio_wave(text2, max_len)

ipd.Audio(wav, rate=fs)

この投稿をInstagramで見る

開発用2 "it dose not look like you are gonna"

Tatsuya Hagiwara(@gosei_creater)がシェアした投稿 -




AttentionはNLPだけじゃなく、いろんな精度向上に役立つっぽい。

ガチの音声生成をしたのははじめてだった。分類系より、生成系は面白い。



参考サイト



GitHub - soobinseo/Transformer-TTS: A Pytorch Implementation of "Neural Speech Synthesis with Transformer Network"

強化学習で「手押し井戸ポンプ」で水をくむ動作をArduino Unoに学習させる【AI・Hardware】

「手押し井戸ポンプ」は、昔から田舎で使われてる「井戸から手動で水をくむポンプ」のこと(Amazonでも売ってる)。

「手押し井戸ポンプ」の水をくむまでの大まかな動作は

押す(down)=>水をくむ(push pump)=>あげる(up)

の行動パターンを、シーソーのごとく繰り返すことで、井戸から水をくめる。

手押し井戸ポンプの動作原理f:id:trafalbad:20200620111544g:plain



今回はArduinoを使って、実際に近い状況を再現(シミュレーション)して、強化学習で水をくむ動作を学習させた。

深層学習の強化学習アルゴリズムはCartPoleでお馴染みの「DQN」を使う。特にkeras-rlとか強化学習ライブラリを使わずに、普通にDQNの学習コード書いた。

その要点をまとめて行こうと思う。

目次
1.Arduinoで「手押し井戸ポンプ」の状況の再現
2.Arduinoの構成
3.DQNで学習
4.学習結果
5.最後に




1.Arduinoで「手押し井戸ポンプ」の状況の再現


まず、Arduinoで実際の「手押し井戸ポンプ」の状況を実際に再現してシミュレーションした。

はじめにwebカメラでデフォルトの画像を読み込ませる。

それをニューラルネットワーク(NN)に読み込ませて、DQNの学習システムに沿って水をくむ行動パターンを学習させていく。

1.webカメラで画像を読み込み、NNに入れる

2.PC側でNNから行動を出力して、Arduinoに送る

3.Arduinoで行動を読み取り動作させる。ポンプで水をくめたら、PCに「1」の信号送る

4.Arduinoから「1」を受け取りPC側で報酬(Reward)として受け取る


f:id:trafalbad:20200620111631j:plain

#include <Servo.h>

Servo mServo;

int state[2];
int pin_number = 10;

void setup() {
  mServo.attach(9);
  mServo.write(10);
  delay(500);
  Serial.begin(9600);
  pinMode(pin_number, OUTPUT);
  state[0] = 0;
  state[1] = 1;
  digitalWrite(pin_number, LOW);
}

void loop() {
  if (Serial.available() > 0) {
    char c = Serial.read();
    if (c == 'p') {
      if (state[0] == 0) {
        Serial.print("0");
        mServo.write(90);
        delay(500);
        state[0] = 1;
      }
      else {
        Serial.print("0");
        mServo.write(10);
        delay(500);
        state[0] = 0;
        state[1] = 1;
        digitalWrite(10, LOW);
      }
    }
    else if (c == 'i') {
      if (state[0] == 1 && state[1] == 1) {
        Serial.print("1");
        delay(500);
        digitalWrite(10, HIGH);
        state[1] = 0;
      }
      else {
        Serial.print("0");
      }
    }
    else if (c == 'c') {
      state[0] = 0;
      state[1] = 1;
      digitalWrite(10, LOW);
      mServo.write(10);
      delay(500);
    }
  }
}



Arduinoとリアルの「手押し井戸ポンプ」の行動の対応関係


「手押し井戸ポンプ」から水をくむのに必要な行動パターンは3つあって、順番に行動する(行動パターンを覚える)必要がある。



水をくむまで行動パターン

Action1(a1) : 押す(down)
Action2(a2) : 水をくむ(Push pump)
Action(a3) : あげる(up)



リアルの「手押し井戸ポンプ」の行動パターンf:id:trafalbad:20200620112256j:plain



Arduinoのシミュレーションの行動パターンf:id:trafalbad:20200620111714j:plain







2.Arduinoの構成

Arduino(Uno)で用意したのは付属パーツは

サーボモータ

・USBカメラ(Mac用)

・抵抗

・LED



Arduino回路図f:id:trafalbad:20200620111744j:plain




実際にUSBカメラで読み込むArduinoの映像

この投稿をInstagramで見る

#arduino for development

Tatsuya Hagiwara(@gosei_creater)がシェアした投稿 -








3.DQNで学習

アルゴリズムはDeep-QNetwork(DQN)。
DQNはモデルフリーなので報酬とか戦略の設定が大事だと思う。


このDQNのメカニズムを使ってNNに「手押し井戸ポンプ」から水を汲む行動パターンを覚えさせる。



DQNでの学習サイクル(Episode cycle)
f:id:trafalbad:20200620111911j:plain








Model



DQNではlossにHuber lossを使うのが一般的らしい。「Squeeze-and-Excitation Networks」を使って少し凝ったCNNを作った。

ef QFunction(inputs_):
    o = Conv2D(32, (3, 3), padding='same', kernel_initializer='random_uniform')(inputs_)
    o = channel_spatial_squeeze_excite(o)
    o = MaxPooling2D((2, 2))(o)
    o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o)
    o = channel_spatial_squeeze_excite(o)
    o = MaxPooling2D((2, 2))(o)
    o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o)
    o = channel_spatial_squeeze_excite(o)
    o = Flatten()(o)
    o = Dense(2, activation='linear')(o)
    model = Model(inputs=inputs_, outputs=o)
    model.compile(optimizer=Adam(lr=0.001), loss=huber_loss_mean, metrics=['acc'])
    return model

USBカメラから画像を読み込む部分。

cap = cv2.VideoCapture(0)

def capture(ndim=3):
    ret, frame = cap.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    xp = int(frame.shape[1]/2)
    yp = int(frame.shape[0]/2)
    d = 400
    resize = 128
    cv2.rectangle(gray, (xp-d, yp-d), (xp+d, yp+d), color=0, thickness=10)
    cv2.imshow('gray', gray)
    gray = cv2.resize(gray[yp-d:yp + d, xp-d:xp + d],(resize, resize))
    env = np.asarray(gray, dtype=np.float32)
    if ndim == 3:
        return env[np.newaxis, :, :] 
    else:
        return env[np.newaxis, np.newaxis, :, :] 

camera_image = capture(ndim=3)
cap.release()



Environment


1回のepisodeでStepを30回回して、水汲みに8回成功したらsuccessに+1する。それを全episode中に5回(success=5)繰り返せたら学習終了。

NUM_EPISODES = 50 # エピソード数
GAMMA = 1.0 

# 探索パラメータ
E_START = 1.0 # εの初期値
E_STOP = 0.01 # εの最終値
E_DECAY_RATE = 0.001 # εの減衰率

SUCCESS_REWARD = 8

# メモリパラメータ
MEMORY_SIZE = 10000 # 経験メモリのサイズ
MAX_STEPS = 30 # 最大ステップ数
BATCH_SIZE = MAX_STEPS

H = 128
W = 128
C = 1
inputs = Input([H, W, C])

class Environment:
    def __init__(self, inputs):
        # main-networkの作成
        self.main_qn = QFunction(inputs)

        # target-networkの作成
        self.target_qn = QFunction(inputs)
        # 経験メモリの作成
        self.memory = Memory(MEMORY_SIZE)

 # 行動価値関数
    def action_value_function(self, action_b, step_reward_b):
        target = 0
        if action_b == 1:
            target = GAMMA + step_reward_b/MAX_STEPS
        else:
            target = GAMMA + step_reward_b/MAX_STEPS
        return target

    def default(self):
       # Memoryの初期化
        self.memory = Memory(MEMORY_SIZE)

    def run(self):
        # エピソード数分のエピソードを繰り返す
        total_step = 0 # 総ステップ数
        success_count = 0 # 成功数
        for episode in range(1, NUM_EPISODES+1):
            step = 0 # ステップ数
            R = 0
            batch_size = 0
            ser.write(b"c") # Arduino側を初期状態に戻す

            # target-networkの更新
            self.target_qn.set_weights(self.main_qn.get_weights())
            
            # 1エピソードのループ
            for _ in range(1, MAX_STEPS+1):
                step += 1
                total_step += 1
                camera_state = capture(ndim=3)
                camera_state = camera_state.reshape(1, H, W, C)
                # εを減らす
                epsilon = E_STOP + (E_START - E_STOP)*np.exp(-E_DECAY_RATE*total_step)
          
                # ランダムな行動を選択
                if epsilon > np.random.rand():
                    action = int(np.random.randint(0, 2, 1))
                # 行動価値関数で行動を選択
                else:
                    action = np.argmax(self.main_qn.predict(camera_state))
                    pred = self.main_qn.predict(camera_state)
   
                # 行動に応じて状態と報酬を得る
                reward = action_step(action)
                R += reward
                self.memory.add((camera_state, action, reward, R)) 
                print('step', step, 'action', action, "R", R, 'reward', reward)
            if R >=SUCCESS_REWARD:
                success_count += 1
                
            # ニューラルネットワークの入力と出力の準備
            inputs = np.zeros((BATCH_SIZE, H, W, C)) # 入力(状態)
            targets = np.zeros((BATCH_SIZE, 2)) # 出力(行動ごとの価値)
            # バッチサイズ分の経験を取得
            minibatch = self.memory.sample(BATCH_SIZE)
            
            # ニューラルネットワークの入力と出力の生成
            for i, (state_b, action_b, reward_b, step_reward_b) in enumerate(minibatch):
                
                # 入力に状態を指定
                inputs[i] = state_b
                
                # 採った行動の価値を計算
                target = self.action_value_function(action_b, step_reward_b)
                # 出力に行動ごとの価値を指定
                targets[i] = self.main_qn.predict(state_b)
                targets[i][action_b] = target # 行動の価値

            # 行動価値関数の更新
            print('training....')
            self.main_qn.fit(inputs, targets, epochs=30, verbose=0)
            
            # エピソード完了時のログ表示
            print('エピソード: {}, ステップ数: {}, epsilon: {:.4f}'.format(episode, step, epsilon))
            self.default()
            # 5回成功で学習終了
            if success_count >= 5:
                break

WEbカメラから画像をNNに入れる => 行動価値関数を出力 => 行動に変換 => 報酬get


の簡単な流れを図にしてみた。
f:id:trafalbad:20200620111700j:plain




4.学習結果


epochの1~50までのパターン行動の成功回数の推移
f:id:trafalbad:20200620112004p:plain

順調にパターンを学習して、水をくむ回数が増えていってるのがわかる。


行動パターンを覚えさせるために、行動価値関数を次のように設計した。

# 行動価値関数
 def action_value_function(action_b, step_reward_b):
        target = 0
        if action_b == 1:
            target = GAMMA + step_reward_b/MAX_STEPS
        else:
            target = GAMMA + step_reward_b/MAX_STEPS
        return target

# 行動を返す関数
def action_step(actions):
    r = 0
    if actions==0:
        ser.write(b"p")
    else:
        ser.write(b"i")
    time.sleep(1.0)
    r = ser.read() 
    return int(r)

この設定だとepisodeが進むにつれて、それぞれの行動(0と1)の行動価値が最後は均衡してくる。

NNからoutputされる行動価値の推移f:id:trafalbad:20200620112018j:plain

もっと上手い設定方法はあるけど、とりあえずうまく学習できてたので自分としては上出来。



5.最後に

50回目までにStep1回で水汲み16回以上を達成できるようになって、無事クリア。DQN

・報酬の設定

・行動価値をどう決めるか 

・戦略

・経験の学習



とかが一番学習結果に影響した。逆にネットワークは別にそんな複雑なものである必要はなかった。

現に今回は白黒画像でやってもこれだけの成果が出たし。強化学習は戦略とか「どう学ばせるか」が肝だと思う。


ハードウェアで強化学習ができるのはいろいろと厨二病的な愉悦。

f:id:trafalbad:20200620112502j:plain

webカメラ+Arduino Uno+CNN画像分類(AI)でカギの開錠・施錠システムを作ってみた【hardware,機械学習】

今回はArduino機械学習(AI)を埋め込んで、webカメラから読み込んだストリーミング画像の画像分類結果からカギを解錠・施錠できるシステムを作ってみた。

機械学習(AI)をハードウェアで実際に取り込んで作ったのはVitis AI以来、今回がはじめて。

Arduino系の記事はたくさんあるけど、機械学習を使った例はあんまなかったので、今回はその作成過程をまとめてく。


目次
1.CNN+Arduinoの鍵の開錠・施錠システムの構造
2.データセットの作成・CNNで学習
3.CNN+Arduinoで金庫の鍵の開錠・施錠


1.CNN+Arduinoの鍵の開錠・施錠システムの構造

用意したもの

Arduino Uno
・ジャックDIP化キット
サーボモータ
・ACアダプター(5V)
・USBカメラ
・スイッチ


Arduinoの回路図の概略f:id:trafalbad:20200601112800j:plain



作業手順

1.webカメラで学習用画像集めてデータセットを作成する

2.CNNで学習

3.key.txtに開錠番号を記載

4.Webカメラで取り込んだストリーミング画像を推論
=> 推論結果とkey.txtと同じ番号ならArduinoでカギの解錠

今回工夫したのは、
Webカメラからデータセットを作れるようにした

Webカメラのストリーミング画像を推論してkey.txtの番号と一致したら解錠する仕組み

の2点。


実物f:id:trafalbad:20200601113319j:plain



2.データセットの作成・CNNで学習

学習から推論までは下のフローで進めてく。

Webカメラからデータセットを作成=>訓練=>Webカメラのストリーミング画像を推論

解錠システムのデータセット作成から推論までの流れは図にするとこんな感じ
f:id:trafalbad:20200601112958j:plain





Webカメラから学習データ収集


今回はWebカメラから直接データセットを作れるようにした。

訓練画像にしたい部分をwebカメラバウンディングボックス内に写す。
そのとき、PCキーボードの番号(0~5)を押せばその番号のimgファルダに画像が保存されるので、大量の画像を短時間で作れる。

今回は訓練画像は6種類。何も写ってない画像を0として含めた。

訓練画像1~5f:id:trafalbad:20200601113108j:plain

Webカメラで写しながら、キーボードの数字を押せばimgファルダの該当番号のファルダに保存されてく仕組み。

import cv2

n0 = 0
n1 = 0
n2 = 0
n3 = 0
n4 = 0
n5 = 0
cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    xp = int(frame.shape[1]/2)
    yp = int(frame.shape[0]/2)
    d = 400 # bbox size
    resize = 256
    cv2.rectangle(gray, (xp-d, yp-d), (xp+d, yp+d), color=0, thickness=10)
    cv2.imshow('gray', gray)
    gray = cv2.resize(gray[yp-d:yp + d, xp-d:xp + d],(resize, resize))
    c =cv2.waitKey(10) 
    if c == 48:#0
        cv2.imwrite('img/0/{0}.png'.format(n0), gray)
        n0 = n0 + 1
    elif c == 49:#1
        cv2.imwrite('img/1/{0}.png'.format(n1), gray)
        n1 = n1 + 1
    elif c == 50:#2
        cv2.imwrite('img/2/{0}.png'.format(n2), gray)
        n2 = n2 + 1
    elif c == 51:#3
        cv2.imwrite('img/3/{0}.png'.format(n3), gray)
        n3 = n3 + 1
    elif c == 52:#4
        cv2.imwrite('img/4/{0}.png'.format(n4), gray)
        n4 = n4 + 1
    elif c == 53:#5
        cv2.imwrite('img/5/{0}.png'.format(n5), gray)
        n5 = n5 + 1
    elif c == 27:#Esc
        break
cap.release()
$ tree img
img
├── 0
│   └── 0.png
├── 1
│   └── 1.png
├── 2
│   └── 2.png
├── 3
│   └── 3.png
├── 4
│   └── 4.png
└── 5
  └── 5.png




CNNで学習



MNIST並みに単純な画像なので、普通のCNNで訓練した。
Google colab上でGPUで訓練してから、学習済みモデルをローカルに落として使うことで、簡単に再訓練ができる構造。

def create_model(inputs_):
  o = Conv2D(32, (3, 3), padding='same', kernel_initializer='random_uniform')(inputs_)
  o = channel_spatial_squeeze_excite(o)
  o = MaxPooling2D((2, 2))(o)
  o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o)
  o = channel_spatial_squeeze_excite(o)
  o = MaxPooling2D((2, 2))(o)
  o = Conv2D(64, (3, 3), padding='same', kernel_initializer='random_uniform')(o)
  o = channel_spatial_squeeze_excite(o)
  #x = Dense(64, activation='relu')(x)
  o = Flatten()(o)
  o = Dense(6, activation='softmax')(o)
  model = Model(inputs=inputs_, outputs=o)
  model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['acc'])
  return model


def make_train_dataset(img_dir):
    x = []
    y =[]
    label = 0
    for c in os.listdir(img_dir):
        print('class: {}, class id: {}'.format(c, label))
        d = os.path.join(img_dir, c) 
        try:       
            imgs = os.listdir(d)
        except:
            continue
        for i in [f for f in imgs if ('png' in f)]:
            x.append(cv2.imread(os.path.join(d, i), 0))
            y.append(label)            
        label += 1
    return np.array(x)/255, to_categorical(y)


X, Y = make_train_dataset('img')
H = X.shape[1]
W = X.shape[2]
X = np.reshape(X, (len(X), H, W, 1))
model = create_model(inputs)
model.summary()

try:
    model.fit(X, Y, batch_size=batch_size, epochs=num_ep, callbacks=callback,
                          validation_data=(valid_X, valid_y), shuffle=True)
finally:
    model.save('CNN_model.h5')




3.CNN+Arduinoで金庫の鍵の開錠・施錠

Webカメラで深層学習の画像分類できるかテスト



バウンディングボックスに写った画像を推論。

推論結果がkey.txtの番号と一致していたら開錠の指令をシリアル通信でArduinoに送る仕組み。
f:id:trafalbad:20200601112840j:plain




Webカメラで写したPC画面はこんな感んじ。
f:id:trafalbad:20200601113042p:plain

Security.py

import numpy as np
import os
import cv2
import keras
from keras.models import Model
from keras.layers import *
from train_modules.scse import channel_spatial_squeeze_excite
from CNN_model import create_model
import serial
import time

H = 256
W = 256
inputs = Input((H, W, 1))
model = create_model(inputs)
model.load_weights('CNN_model.h5')
key_file = 'key.txt'
cap = cv2.VideoCapture(0)

with serial.Serial('/dev/cu.usbmodem14301', timeout=0.1) as ser:

    while True:
        ret, frame = cap.read()            
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
        xp = int(frame.shape[1]/2)
        yp = int(frame.shape[0]/2)
        d = 400 # bbox size
        resize = 256
        cv2.rectangle(gray, (xp-d, yp-d), (xp+d, yp+d), color=0, thickness=10)
        cv2.imshow('gray', gray)
        if cv2.waitKey(10) == 27:
            break
        a = ser.read()
        print(a)
        if a == b'1':
            print('pass')
            gray = cv2.resize(gray[yp-d:yp + d, xp-d:xp + d],(resize, resize))
            img = np.asarray(gray,dtype=np.float32)  
            img = np.reshape(img, (1, 256, 256, 1))
            img = img/255
            y_pred = model.predict(img)
            c = int(np.argmax(y_pred, axis=1))
            print(c)
            with open('key.txt', 'r') as f:
                b = int(f.read())
            if b==0:
                if c!=0:
                    time.sleep(5.0) # wait 5 second
                    ser.write(b"o")
                    with open(key_file, 'w') as f:
                        f.write(str(c))
                    print('close')
            else:
                if b==c:
                    time.sleep(5.0) # wait 5 second
                    ser.write(b"c")
                    with open(key_file, 'w') as f:
                        f.write('0')
                    print('open')
cap.release()




Aruduinoで画像分類して、サーボモータで鍵を開錠・施錠


シリアル通信で開錠の指令がきたらサーボモータを180度回転させて解錠。

番号が間違ってたら施錠したまま。

Security.ino

#include <Servo.h>

Servo myservo; // Servoオブジェクト宣言

void setup() {
  Serial.begin(9600);
  myservo.attach(9); //9番ピンでサーボモータを動かす
  pinMode(2, INPUT_PULLUP); // Inputモードでプルアップ抵抗を有効
  pinMode(LED_BUILTIN, OUTPUT);
  myservo.write(120); // angle
  digitalWrite(LED_BUILTIN, HIGH);
}
 
void loop(){
  static int flag=0;
  if(digitalRead(2)==LOW){
    flag=1;
  }
  else{
    if(flag==1){
      flag=0;
      Serial.write('1');
      delay(500);
    }
  }
  if(Serial.available()>0){
    char a = Serial.read();
    if(a=='o'){
      myservo.write(120); // angle
      digitalWrite(LED_BUILTIN, HIGH);
    }
    else if(a=='c'){
      myservo.write(20);  // angle
      digitalWrite(LED_BUILTIN, LOW);
    }
  }
}

PythonコードとArduinoコードが用意できたら、ラストはPC側でPythonコードを実行。

Arduino回路のスイッチを押せば推論が行われるので、推論結果がkey.txtと合ってれば解錠。

# PC側で実行
$ python3 Security.py
>>>
Using TensorFlow backend.
StreamExecutor device (0): Host, Default Version
b''
b''
b’1' # 推論実行
pass
5
close # 不一致なので施錠

b''
b''
b’1' # 推論実行
pass
3
open # key.txtの番号と推論結果が一致したので解錠
b''


下の画像は解錠・施錠を交互に行った動画。

この投稿をInstagramで見る

保存用動画

Tatsuya Hagiwara(@gosei_creater)がシェアした投稿 -

金庫がないのでわかりにくいけど、USBカメラに画像を写せば「ピピっ」と漫画みたいに反応した後、キーの解錠・施錠ができる。

セキュリティのために金庫の鍵の解錠施錠とかいろんな用途に使えそう。

f:id:trafalbad:20200601113617j:plain



Vitis AIでチュートリアルをやった以来、はじめてハードウェアで機械学習(AI)を組み混んでモノを作ってみた。
Arduinoは速度は置いといて、仕組みさえわかれば、かなり簡単に機械学習システムを組み込めた。


参考サイト


Arduinoを用いてサーボモータを制御する