Jetson Nano セットアップとおためし

メモ。

TPLink Wireless Driver

開発ボード(これ)にはWi-Fiモジュールが乗ってないので、USBドングルで無線LANにつなぐ(セットアップは有線で)。

TPLink Archer T2U Nano(これ)を使う。

tp-link Archer T2U Nano AC600をLinuxで使う方法 | Shizuka's Style (Duo)

Jetson Nano で TP-LINK Archer T2U Nano を使う - Qiita

https://devtalk.nvidia.com/default/topic/1051503/jetson-nano/make-usb-wifi-dongle-rtl8812au-works-on-nano/

NVIDIA Developer Forumsのつい最近(11/27)のポストで、新しいcommitで動かなくなっちゃったからCommit IDd277c36がいいよ、っていってる人がいる。でもT2U NanoのIDが登録されたのは2つ後の4235b0eなので、これを使ってみたけど、これで動いた(OSはr32.2.3)。この1つ後(現在masterの最新commit)のfa68771は接続が確立しなかった(パスワードの入力を何度でも求められる)。警告回避のためにリファクタリングしたらバグったっぽい(コミットコメントプルリク)。

sudo apt install dkms

git clone https://github.com/abperiasamy/rtl8812AU_8821AU_linux.git
git checkout 4235b0ec7d7220a6364586d8e25b1e8cb99c36f1

Edit Makefile
CONFIG_PLATFORM_I386_PC = n
CONFIG_PLATFORM_ARM_JET_NANO = y

sudo make -f Makefile.dkms install
reboot

ibus-mozc

sudo apt install ibus-mozc
killall ibus-daemon
ibus-daemon -x &

Python

PyTorch使うならシステムのPython 3.6.9を使うのがよさそうだったので、そのまま

sudo apt install python3-pip

jetson-stats

Jetson NanoのGPUモニタリング - Qiita

いい感じに状態を確認できるやつ。

# pip3 install jetson-stats
# jtop
NVIDIA Jetson NANO/TX1 - Jetpack UNKNOWN [L4T 32.2.3]
CPU1 [|||||    Schedutil -  21%] 614MHz
CPU2 [|||      Schedutil -  15%] 614MHz
CPU3 [|||||    Schedutil -  20%] 614MHz
CPU4 [||||     Schedutil -  18%] 614MHz

Mem [|||||||||||||||||||||||||||||||                   2.1G/4.0GB] (lfb 95x4MB)
Imm [                                                    0kB/252kB] (lfb 252kB)
Swp [                                                 0.0GB/2.0GB] (cached 0MB)
EMC [||                                                              4%] 1.6GHz

GPU [||||                                                            7%] 153MHz
Dsk [########################                                    10.1GB/29.2GB]
          [info]          [Sensor]   [Temp]         [Power/mW]   [Cur]   [Avr]
UpT: 0 days 0:30:1        AO         39.50C         POM_5V_CPU   288     672
FAN [         0%] Ta=  0% CPU        32.00C         POM_5V_GPU   82      85
Jetson Clocks: [inactive] GPU        28.00C         POM_5V_IN    1895    2511
NV Power[0]: MAXN         PLL        30.00C
APE: 25MHz                PMIC      100.00C
HW engine:                thermal    29.75C
 ENC: NOT RUNNING
 DEC: NOT RUNNING

いちおうデフォルトでもtegrastatsで状態をwatchできる。

パフォーマンス最大化(発熱注意)

5V4AのDC電源を用意して、J48にジャンパーピンを挿す(電源不足で落ちるかも)。

コマンドjetson_clocksはオプションなしで呼び出すとCPU、GPU、EMC(メモリ?)のクロックを最大化する。熱暴走するかもなので熱対策してない場合はやらない方がいいかも。

ヘルプ
# jetson_clocks -h

デフォルトの設定
# jetson_clocks --show
SOC family:tegra210  Machine:NVIDIA Jetson Nano Developer Kit
Online CPUs: 0-3
CPU Cluster Switching: Disabled
cpu0: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1428000 CurrentFreq=1036800 IdleStates: WFI=1 c7=1 
cpu1: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1428000 CurrentFreq=921600 IdleStates: WFI=1 c7=1 
cpu2: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1428000 CurrentFreq=1132800 IdleStates: WFI=1 c7=1 
cpu3: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1428000 CurrentFreq=1428000 IdleStates: WFI=1 c7=1 
GPU MinFreq=76800000 MaxFreq=921600000 CurrentFreq=230400000
EMC MinFreq=204000000 MaxFreq=1600000000 CurrentFreq=1600000000 FreqOverride=0
Fan: speed=0
NV Power Mode: MAXN

# jetson_clocks --store ~/default_clocks.data

# jetson_clocks

# jetson_clocks --show
SOC family:tegra210  Machine:NVIDIA Jetson Nano Developer Kit
Online CPUs: 0-3
CPU Cluster Switching: Disabled
cpu0: Online=1 Governor=schedutil MinFreq=1428000 MaxFreq=1428000 CurrentFreq=1428000 IdleStates: WFI=0 c7=0 
cpu1: Online=1 Governor=schedutil MinFreq=1428000 MaxFreq=1428000 CurrentFreq=1428000 IdleStates: WFI=0 c7=0 
cpu2: Online=1 Governor=schedutil MinFreq=1428000 MaxFreq=1428000 CurrentFreq=1428000 IdleStates: WFI=0 c7=0 
cpu3: Online=1 Governor=schedutil MinFreq=1428000 MaxFreq=1428000 CurrentFreq=1428000 IdleStates: WFI=0 c7=0 
GPU MinFreq=921600000 MaxFreq=921600000 CurrentFreq=921600000
EMC MinFreq=204000000 MaxFreq=1600000000 CurrentFreq=1600000000 FreqOverride=1
Fan: speed=255
NV Power Mode: MAXN

# jetson_clocks --restore ~/default_clocks.data
# jetson_clocks --show
SOC family:tegra210  Machine:NVIDIA Jetson Nano Developer Kit
Online CPUs: 0-3
CPU Cluster Switching: Disabled
cpu0: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1428000 CurrentFreq=1428000 IdleStates: WFI=1 c7=1 
cpu1: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1428000 CurrentFreq=921600 IdleStates: WFI=1 c7=1 
cpu2: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1428000 CurrentFreq=1132800 IdleStates: WFI=1 c7=1 
cpu3: Online=1 Governor=schedutil MinFreq=102000 MaxFreq=1428000 CurrentFreq=1326000 IdleStates: WFI=1 c7=1 
GPU MinFreq=76800000 MaxFreq=921600000 CurrentFreq=230400000
EMC MinFreq=204000000 MaxFreq=1600000000 CurrentFreq=1600000000 FreqOverride=0
Fan: speed=0
NV Power Mode: MAXN

PyTorch

https://devtalk.nvidia.com/default/topic/1049071/jetson-nano/pytorch-for-jetson-nano-version-1-3-0-now-available/

pip3 install numpy # takes long..
pip3 install torch-1.3.0-cp36-cp36m-linux_aarch64.whl

TorchVision

setup.pyからinstallの途中でPillowが自動でビルドされるけど、ライブラリがないと失敗するので事前インストール。

必須はlibjpegzlib。あと使いそうなlibfreetypeも(zliblibfreetypeはデフォルトで入ってる)。

Installation — Pillow (PIL Fork) 6.2.1 documentation

sudo apt install libjpeg-turbo8-dev zlib1g-dev
sudo apt install libfreetype6-dev

pip3 install Pillow
git clone https://github.com/pytorch/vision.git
cd vision
python3 setup.py install --user # takes long..

動作テスト

(Clock:デフォルト)

pip3 install ipython
echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc
source ~/.bashrc
In [1]: import torch                                                            

In [2]: torch.cuda.is_available()
Out[2]: True

In [3]: torch.cuda.device_count()
Out[3]: 1

In [4]: device = torch.device('cuda:0')

In [5]: x = torch.randn(1, 3, 40, 40)

In [6]: x = x.to(device)

In [7]: x.shape
Out[7]: torch.Size([1, 3, 40, 40])

In [8]: x
Out[8]: 
tensor([[[[-0.0362, -0.6796,  0.6484,  ..., -0.5588,  0.3949, -0.8214],
          [-0.5975,  0.6352,  0.9712,  ..., -0.4434,  0.4439, -0.2363],
          [ 0.2326,  0.1052,  0.5346,  ..., -0.7047, -0.0173, -1.1312],
          ...,
          [-0.0730, -0.7666,  0.7147,  ...,  0.8821, -0.0262, -0.6976],
          [ 0.9593,  1.0495,  0.6041,  ...,  0.2833,  0.5237, -0.5829],
          [-0.4293, -1.9287,  0.6741,  ..., -1.0791, -0.5570, -0.3463]],

         [[-0.5106, -0.3363,  0.2770,  ..., -1.6835,  2.2409,  0.5745],
          [-0.9233,  0.7389,  0.6966,  ..., -0.5657, -0.4024, -0.5671],
          [-1.7160, -1.3546,  0.4675,  ...,  1.8385, -0.9948,  1.0485],
          ...,
          [ 0.8693,  0.2434,  0.7501,  ..., -0.9752,  2.3783, -0.4887],
          [-0.2279, -1.1861, -1.2003,  ..., -0.7934,  0.3169, -2.3324],
          [-1.1039, -1.6662, -0.0719,  ...,  0.6115,  2.2238, -0.8375]],

         [[-1.2368,  0.3786, -0.7985,  ...,  0.0504,  0.3354, -0.3505],
          [ 0.3309,  0.8257,  0.4800,  ..., -1.6101, -0.4429,  0.3643],
          [ 0.5604,  1.6997, -0.5299,  ..., -0.4896,  1.0926,  0.0423],
          ...,
          [ 1.8049, -1.0500, -1.3723,  ..., -0.4516,  0.5884,  1.3404],
          [ 0.3622,  0.6343, -0.0500,  ..., -0.0991,  0.9009,  0.7298],
          [ 2.3776, -0.1111,  0.2054,  ..., -1.4465, -2.2340, -1.0085]]]],
       device='cuda:0')

動いたけど、toがめっちゃ遅かった気がする..

toが遅い。なんぞこれ

In [1]: import time
In [2]: import torch

In [3]: x = torch.randn(1, 3, 40, 40)

In [4]: ts = time.time(); x = x.cuda(); elapsed = time.time() - ts;

In [5]: elapsed
Out[5]: 8.121735572814941

8秒...

In [6]: z = torch.randn(1, 3, 40, 40)

In [7]: ts = time.time(); z = z.cuda(); elapsed = time.time() - ts;

In [8]: elapsed
Out[8]: 0.0011272430419921875

In [9]: y = torch.randn(1, 3, 512, 512)

In [10]: ts = time.time(); y = y.cuda(); elapsed = time.time() - ts;

In [11]: elapsed
Out[11]: 0.02282571792602539

初回だけ遅い。初期化でも走ってるんだろうか..

ipythonプロセスのMemを見てみると、初回のtoGPUの前後で600MiBくらいMem使用量が増えたが、2回目以降は0.1MiBくらいの増加だった。初回になにかしらの初期化処理が入ってるっぽい..

メモリ

デスクトップ環境あり、ブラウザ付きでMem 2.2/4.0GB、Swap 1.3/2.0GB。

import torch

x = torch.randn(1, 3, 512, 512)
x = x.cuda()

z = torch.randn(1, 3, 512, 512)
z = z.cuda()

ipythonプロセスをみると、起動直後33.6MiB、import torchで69.0MiB、x = torch.randn(1, 3, 512, 512)で72.4MiB、x = x.cuda()で726MiB、z = torch.randn(1, 3, 512, 512)で729MiB、z = z.cuda()で729Mib変化なし。

速度

(Clock:デフォルト)

In [1]: import time

In [2]: import torch

In [3]: import torchvision.models as M

In [4]: x = torch.randn(1, 3, 224, 224)

In [5]: x = x.cuda()

In [6]: model = M.resnet50(pretrained=False)

In [7]: model = model.cuda()

In [8]: model.eval();

In [9]: with torch.no_grad():
   ...:     ts = time.time()
   ...:     y = model(x)
   ...:     elapsed = time.time() - ts
   ...:                                                                         

In [10]: elapsed                                                                
Out[10]: 20.638437747955322

In [11]: y.device                                                               
Out[11]: device(type='cuda', index=0)

あれ? とても遅い。

In [12]: z = torch.randn(1, 3, 224, 224)                                        

In [13]: z = z.cuda()                                                           

In [14]: with torch.no_grad(): 
    ...:     ts = time.time() 
    ...:     w = model(z) 
    ...:     elapsed = time.time() - ts 
    ...:                                                                        

In [15]: elapsed                                                                
Out[15]: 0.5333945751190186

うーん?

In [16]: s = torch.randn(1, 3, 224, 224)

In [17]: s = s.cuda()

In [18]: with torch.no_grad():
    ...:     ts = time.time()
    ...:     t = model(s)
    ...:     elapsed = time.time() - ts
    ...:     

In [19]: elapsed
Out[19]: 0.04768824577331543

In [20]: u = torch.randn(1, 3, 224, 224).cuda()

In [21]: with torch.no_grad():
    ...:     ts = time.time()
    ...:     v = model(u)
    ...:     elapsed = time.time() - ts
    ...:     

In [22]: elapsed
Out[22]: 0.045920610427856445

2回くらい実行しないといいパフォーマンスがでないみたいですね..

仕切り直しでもう一回。

In [1]: import time

In [2]: import torch

In [3]: import torchvision.models as M

In [4]: model = M.resnet50(pretrained=False)

In [5]: model = model.cuda()

In [6]: model.eval();

In [7]: x = torch.randn(1, 3, 224, 224)

In [8]: x = x.cuda()

In [9]: with torch.no_grad():
   ...:     ts = time.time()
   ...:     y = model(x)
   ...:     elapsed = time.time() - ts
   ...:     

In [10]: elapsed                                                                
Out[10]: 7.917057514190674

スワップ作成

AWS Amazon Linux スワップファイル作成によりSwap領域のサイズを増やす - Qiita

このあとの節でメモリ不足に陥ったのでswapfileを追加する(Default Mem: 4GB、Swap: 2GB)。2GB追加することにする。Swapに乗るとパフォーマンス低下するはずなので注意。

次の節で変換をJetson Nanoでゴリ押さなければ必要ないかもしれない。

# check storage capacity
df -h

dd if=/dev/zero of=/swapfile bs=1M count=2048
chmod 600 /swapfile

mkswap /swapfile
swapon /swapfile

# Append to /etc/fstab
/swapfile            swap                     swap           defaults                                     0 0

# check swap size
jtop

高速化(TensorRT)

Jetson Nanoでリアルタイムに物体検出をする方法(TensorFlow Object Detection API/NVIDIA TensorRT) - Qiita

高速化のためにはNVIDIAのTensorRTなるプログラムでモデルを最適化する必要があるらしい。1回目2回目のオーバーヘッドがなくなることを期待。

NVIDIA TensorRT | NVIDIA Developer

You can import trained models from every deep learning framework into TensorRT.

GitHub - NVIDIA-AI-IOT/torch2trt: An easy to use PyTorch to TensorRT converter

PyTorch用の変換スクリプトはこれっぽい。Pythonコード上でパラメータ読み込み済みのモデルインスタンスを渡して変換、戻ってくるオブジェクト(torch2trt.TRTModule)はPyTorchのnn.Moduleを継承してて、ふつうのModule(Model)と同じようにtorch.loadMODULE.load_state_dicttorch.saveMODULE.state_dict()を使ってパラメータ読み書きできるみたい。

変換まではふつうのGPUマシンでやらないと重い(いちおう変換はできる)。

git clone https://github.com/NVIDIA-AI-IOT/torch2trt.git
cd torch2trt
python3 setup.py install --user
import torch
import torchvision.models as M
from torch2trt import torch2trt
from torch2trt import TRTModule

# On GPU Machine
torch_model = M.resnet50(pretrained=False).cuda()
dummy = torch.zeros(1, 3, 224, 224).cuda()

trt_model = torch2trt(torch_model, [ dummy ])
torch.save(trt_model.state_dict(), 'trt_model.pth')

# On Jetson Nano
trt_model = TRTModule()
trt_model.load_state_dict(torch.load('trt_model.pth'))
In [5]: ts = time.time(); model.load_state_dict(torch.load('trt_model.pth')); el
   ...: apsed = time.time() - ts

In [6]: elapsed
Out[6]: 38.654945611953735

In [7]: ts = time.time(); model.load_state_dict(torch.load('trt_model.pth')); el
   ...: apsed = time.time() - ts

In [8]: elapsed
Out[8]: 5.191670894622803

In [9]: ts = time.time(); model.load_state_dict(torch.load('trt_model.pth')); el
    ...: apsed = time.time() - ts

In [10]: elapsed
Out[10]: 7.234739542007446
In [18]: ts = time.time(); x = torch.randn(1, 3, 224, 224).cuda(); elapsed = tim
    ...: e.time() - ts                                                          

In [19]: elapsed                                                                
Out[19]: 0.3353462219238281

In [20]: ts = time.time(); x = torch.randn(1, 3, 224, 224).cuda(); elapsed = tim
    ...: e.time() - ts                                                          

In [21]: elapsed                                                                
Out[21]: 0.016706228256225586

In [22]: ts = time.time(); x = torch.randn(1, 3, 224, 224).cuda(); elapsed = tim
    ...: e.time() - ts                                                          

In [23]: elapsed                                                                
Out[23]: 0.015973806381225586
In [24]: ts = time.time(); y = model(x); elapsed = time.time() - ts             

In [25]: elapsed                                                                
Out[25]: 0.659074068069458

In [26]: ts = time.time(); y = model(x); elapsed = time.time() - ts             

In [27]: elapsed                                                                
Out[27]: 0.0033349990844726562

In [28]: ts = time.time(); y = model(x); elapsed = time.time() - ts             

In [29]: elapsed                                                                
Out[29]: 0.0031561851501464844

inferenceはだいぶ速い。ただモデルロードがとても遅い...。それに、初回実行の遅延は軽減してるとはいえ残ってる。

SDカード

これまでの速度にも関わっていそうなので、SDカードについて調べてみる。

UHS(Ultra-High Speed)とは - IT用語辞典 e-Words

安物を選んだので、UHS-I Class1 32GBのSamsung製Micro SDカード(これ)を使っている。UHS Class3の場合最低アクセス速度30MB/sだが、UHS Class1では最低アクセス速度10MB/sらしい。SDカードを変えれば(読み書きのかかるところでは)見込みで3倍以上速くなる可能性はあるのかなぁ?

Raspberry Pi 3B+(Raspbian)でNextcloud(Docker)を動かす(MySQL)

※ Dockerは入ってるものとします。あとスワップ領域を用意しておいたほうがいいかな

docker run --name ncdb --restart unless-stopped -v NEXTCLOUD_DIR/db:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=MY_ROOT_PASSWORD -e MYSQL_DATABASE=nextcloud -e MYSQL_USER=nextcloud -e MYSQL_PASSWORD=MY_PASSWORD hypriot/rpi-mysql --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci

docker run --name ncapp --restart unless-stopped --link ncdb -p MY_PORT:80 -v NEXTCLOUD_DIR/www:/var/www/html -e MYSQL_DATABASE=nextcloud -e MYSQL_USER=nextcloud -e MYSQL_PASSWORD=MY_PASSWORD -e MYSQL_HOST=ncdb:3306 -e NEXTCLOUD_TRUSTED_DOMAINS="MY_DOMAIN" nextcloud
  • NEXTCLOUD_DIR/www/config/config.php
    • overwriteprotocol: 'https'
    • overwritewebroot: ''
    • overwrite.cli.url: 'https://MY_DOMAIN'
    • この設定をしないとhttps環境ではクライアントからログイン(Grant access)できない(webrootはいらないかな)

RealSenseのカラー画像が暗い

使用:Intel RealSense SR300

pyrealsense2からget_color_frameで取得したカラー画像が暗かった。

RealSense SDKをインストールしてRealSense Viewerで確認してみると、RGB Cameraの項のEnable Auto Exposureの値がOFFになっていた。これをONにしたらプレビュー画像が明るくなった(露光の自動調整機能かな)。

f:id:kanomiya:20191123084133p:plain

次はコード(Python)からこのオプションを有効にする。(Pythonの)サンプルにはオプションをいじってるいい感じのものが見つからなかったので、以下を参考にしつつipythonで探した。

RealSense+Pythonの1コマンド環境構築 - Qiita

enable/disable auto exposure · Issue #3141 · IntelRealSense/librealsense · GitHub

バイスとカラーセンサ(カメラ)を見つける部分(前半)は適当だけど、コード全体はこんな感じ。

import pyrealsense2 as rs
import numpy as np
import cv2

ctx = rs.context()
device_list = ctx.query_devices()
num_devices = device_list.size()
print(num_devices)
assert num_devices > 0

device = device_list[0]
sensors = device.query_sensors()

color_idx = -1
for i in range(len(sensors)):
    if not sensors[i].is_depth_sensor():
        color_idx = i
        break
assert color_idx != -1

color_sensor = sensors[color_idx]
color_sensor.set_option(rs.option.enable_auto_exposure, 1) # on

pipeline = rs.pipeline(ctx=ctx)
config = rs.config()
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)

pipeline.start(config)

print('Initialized')

try:
    while True:
        frames = pipeline.wait_for_frames()
        color_frame = frames.get_color_frame()
        if not color_frame:
            print('no frame')
            continue

        color_image = np.asanyarray(color_frame.get_data(), dtype=np.uint8)
        print(color_image.max(), color_image.min())
        print(color_image.shape)

        cv2.namedWindow('RealSense', cv2.WINDOW_AUTOSIZE)
        cv2.imshow('RealSense', color_image)
        cv2.waitKey(1)

finally:
    pipeline.stop()

センサに対してオプションを設定する部分。

color_sensor = sensors[color_idx]
color_sensor.set_option(rs.option.enable_auto_exposure, 1) # on

darknet.py OpenCV

darknet.pyにコードが古くなってるっぽいexamples/detector-scipy-opencv.pyOpenCV部を取り込み、Python 3用に

import cv2

import sys, os
sys.path.append(os.path.join(os.getcwd(),'python/'))

import darknet as dn

net = dn.load_net("cfg/yolov3.cfg", "backup/hoge.weights", 0)
meta = dn.load_meta('cfg/yolov3.cfg')

np_image = cv2.imread('data/dog.jpg')
r = dn.detect_cv(net, meta, np_image)

print(r)
from ctypes import *
import math
import random

import os

DARKNET_SO_PATH = os.environ.get('DARKNET_SO_PATH', os.path.realpath('./libdarknet.so'))
# assert os.path.exists(DARKNET_SO_PATH), DARKNET_SO_PATH


def sample(probs):
    s = sum(probs)
    probs = [a/s for a in probs]
    r = random.uniform(0, 1)
    for i in range(len(probs)):
        r = r - probs[i]
        if r <= 0:
            return i
    return len(probs)-1

def c_array(ctype, values):
    arr = (ctype*len(values))()
    arr[:] = values
    return arr

class BOX(Structure):
    _fields_ = [("x", c_float),
                ("y", c_float),
                ("w", c_float),
                ("h", c_float)]

class DETECTION(Structure):
    _fields_ = [("bbox", BOX),
                ("classes", c_int),
                ("prob", POINTER(c_float)),
                ("mask", POINTER(c_float)),
                ("objectness", c_float),
                ("sort_class", c_int)]


class IMAGE(Structure):
    _fields_ = [("w", c_int),
                ("h", c_int),
                ("c", c_int),
                ("data", POINTER(c_float))]

class METADATA(Structure):
    _fields_ = [("classes", c_int),
                ("names", POINTER(c_char_p))]



#lib = CDLL("/home/pjreddie/documents/darknet/libdarknet.so", RTLD_GLOBAL)
# lib = CDLL("libdarknet.so", RTLD_GLOBAL)
lib = CDLL(DARKNET_SO_PATH, RTLD_GLOBAL)
lib.network_width.argtypes = [c_void_p]
lib.network_width.restype = c_int
lib.network_height.argtypes = [c_void_p]
lib.network_height.restype = c_int

predict = lib.network_predict
predict.argtypes = [c_void_p, POINTER(c_float)]
predict.restype = POINTER(c_float)

set_gpu = lib.cuda_set_device
set_gpu.argtypes = [c_int]

make_image = lib.make_image
make_image.argtypes = [c_int, c_int, c_int]
make_image.restype = IMAGE

get_network_boxes = lib.get_network_boxes
get_network_boxes.argtypes = [c_void_p, c_int, c_int, c_float, c_float, POINTER(c_int), c_int, POINTER(c_int)]
get_network_boxes.restype = POINTER(DETECTION)

make_network_boxes = lib.make_network_boxes
make_network_boxes.argtypes = [c_void_p]
make_network_boxes.restype = POINTER(DETECTION)

free_detections = lib.free_detections
free_detections.argtypes = [POINTER(DETECTION), c_int]

free_ptrs = lib.free_ptrs
free_ptrs.argtypes = [POINTER(c_void_p), c_int]

network_predict = lib.network_predict
network_predict.argtypes = [c_void_p, POINTER(c_float)]

reset_rnn = lib.reset_rnn
reset_rnn.argtypes = [c_void_p]

_load_net = lib.load_network
_load_net.argtypes = [c_char_p, c_char_p, c_int]
_load_net.restype = c_void_p

do_nms_obj = lib.do_nms_obj
do_nms_obj.argtypes = [POINTER(DETECTION), c_int, c_int, c_float]

do_nms_sort = lib.do_nms_sort
do_nms_sort.argtypes = [POINTER(DETECTION), c_int, c_int, c_float]

free_image = lib.free_image
free_image.argtypes = [IMAGE]

letterbox_image = lib.letterbox_image
letterbox_image.argtypes = [IMAGE, c_int, c_int]
letterbox_image.restype = IMAGE

_load_meta = lib.get_metadata
lib.get_metadata.argtypes = [c_char_p]
lib.get_metadata.restype = METADATA

load_image = lib.load_image_color
load_image.argtypes = [c_char_p, c_int, c_int]
load_image.restype = IMAGE

rgbgr_image = lib.rgbgr_image
rgbgr_image.argtypes = [IMAGE]

predict_image = lib.network_predict_image
predict_image.argtypes = [c_void_p, IMAGE]
predict_image.restype = POINTER(c_float)

def classify(net, meta, im):
    out = predict_image(net, im)
    res = []
    for i in range(meta.classes):
        res.append((meta.names[i], out[i]))
    res = sorted(res, key=lambda x: -x[1])
    return res

def load_net(cfg_path, weights_path, clear=0):
    return _load_net(cfg_path.encode('ascii'), weights_path.encode('ascii'), clear)
def load_meta(cfg_path):
    return _load_meta(cfg_path.encode('ascii'))

def _detect(net, meta, im, thresh=.5, hier_thresh=.5, nms=.45):
    num = c_int(0)
    pnum = pointer(num)
    predict_image(net, im)
    dets = get_network_boxes(net, im.w, im.h, thresh, hier_thresh, None, 0, pnum)
    num = pnum[0]
    if (nms): do_nms_obj(dets, num, meta.classes, nms);

    res = []
    for j in range(num):
        for i in range(meta.classes):
            if dets[j].prob[i] > 0:
                b = dets[j].bbox
                res.append((meta.names[i], dets[j].prob[i], (b.x, b.y, b.w, b.h)))
    res = sorted(res, key=lambda x: -x[1])

    free_detections(dets, num)
    return res

def detect(net, meta, image_path, thresh=.5, hier_thresh=.5, nms=.45):
    im = load_image(image_path, 0, 0)
    res = _detect(net, meta, im, thresh, hier_thresh, nms)
    free_image(im)

    return res

def array_to_image(arr):
    arr = arr.transpose(2,0,1)
    c = arr.shape[0]
    h = arr.shape[1]
    w = arr.shape[2]
    arr = (arr/255.0).flatten()
    data = c_array(c_float, arr)
    im = IMAGE(w,h,c,data)
    return im

def detect_cv(net, meta, np_image, thresh=.5, hier_thresh=.5, nms=.45):
    im = array_to_image(np_image)
    rgbgr_image(im)

    return _detect(net, meta, im, thresh, hier_thresh, nms)

Yolo v3でObject Detectionする(darknet)

https://github.com/pjreddie/darknet

git clone https://github.com/pjreddie/darknet.git

データセットの作成

mydata.data

classes = CLASS_NUM
train = mydata-train.txt
test = mydata-test.txt
names = mydata.names
backup = backup/mydata/

各ファイルパスはdarknetの実行ディレクトリからの相対パス

mydata.names

category1
category2
category3
...
categoryCLASS_NUM

ラベル=[クラスの数値表現]に対応するクラス名(行番号=ラベル)を記述する。

mydata-train.txtmydata-test.txtにはデータセット(訓練データ、テストデータ)に含まれる画像のパスをそれぞれ1画像=1行で記述する。

データセットの画像と同じ階層に、IMAGE.jpgアノテーション情報としてIMAGE.txtを配置する必要がある(1画像=1アノテーションファイル)。

IMAGE.txt

label center_x center_y width height

カラムはスペース区切りで、BoundingBox1つ=1行で記述する。

画像中のオブジェクトの種類はlabel(0始まりの数値)で表現する。

画像中のオブジェクトのBoundingBoxはcenter_xcenter_ywidthheight(オブジェクト中心X座標、オブジェクト中心Y座標、オブジェクト幅、オブジェクト高さ)の4パラメータで表現する。center_xwidthは画像幅で除算、center_yheightは画像高さで除算し、実数で記述する。

設定ファイルの作成

cfgディレクトリ以下のファイルをベースに使う。

batchsubdivision, width, heightなどをメモリサイズなどに応じて変更する。デフォルトでは画像にランダムリサイズがかかるようになっているので、メモリ使用量が変動することに注意。

Yolo v3

cfg/yolov3.cfgをコピーする(mydata-yolov3.cfg)。

CLASS_NUM=クラス数

# L610, 696, 783
classes=CLASS_NUM

# L603, 689, 776
filters=(CLASS_NUM + 5) * 3

# L610, 696, 783
classes=1

# L603, 689, 776
filters=18

Tiny Yolo v3

cfg/yolov3-tiny.cfgをコピーする(mydata-yolov3-tiny.cfg)。

CLASS_NUM=クラス数

# L135, 177
classes=CLASS_NUM

# L127, 171
filters=(CLASS_NUM + 5) * 3

# L135, 177
classes=1

# L127, 171
filters=18

コマンド

darknetの学習済みモデルをダウンロードする。

wget https://pjreddie.com/media/files/darknet53.conv.74
# 学習
./darknet detector train mydata.data mydata-yolov3.cfg darknet53.conv.74

# 画像ファイルでテスト(対話型)
./darknet detector test mydata.data mydata-yolov3.cfg backup/mydata/mydata-yolov3_ITER.weights

# 画像ファイルでテスト
./darknet detector test mydata.data mydata-yolov3.cfg backup/mydata/mydata-yolov3_ITER.weights IMAGE_FILE

※ ファイルパス・カテゴリ名は(たぶん)ASCIIでないとSegmentation Error吐きます

Python

darknet/python/darknet.pylibdarknet.soをctypesでPythonから呼び出すことのできるスクリプトになっているが、Python 2ベースのようなのでPython 3で使うのに便利なインタフェースを作成した。darknet.py_darknet.pyにリネームした。PIL.Imageの場合にtempfileを使わない改修をしたほうがいいかもしれないが、今回は割愛。

(追記 19/10/27) ※ examples/以下にちゃんとしたサンプルがあるみたいです.

_darknet.pydarknet.soを指定してる箇所(https://github.com/pjreddie/darknet/blob/master/python/darknet.py#L48)を環境変数化したりすると汎用性上がると思う。

darknet/python/Darknet.py

import os
import sys

import tempfile

sys.path.append(os.path.dirname(__file__))

from _darknet import *

# 標準出力・標準エラー出力の抑制
def silent(verbose=False):
    def _silent(func):
        def wrapper(*args, **kwargs):
            if not verbose:
                devnull = open(os.devnull, 'w')
                stdout = os.dup(1)
                stderr = os.dup(2)
                os.dup2(devnull.fileno(), 1)
                os.dup2(devnull.fileno(), 2)

            res = func(*args, **kwargs)

            if not verbose:
                os.dup2(stdout, 1)
                os.dup2(stderr, 2)
                devnull.close()

            return res

        return wrapper
    return _silent

class Darknet:
    def __init__(self, meta_file, cfg_file, weights_file, verbose=False):
        @silent(verbose=verbose)
        def _init():
            self.net = load_net(cfg_file.encode('ascii'), weights_file.encode('ascii'), 0)
            self.meta = load_meta(meta_file.encode('ascii'))

        _init()

    # [ name, conf, (x, y, w, h) ]
    # x, y is the center of the object

    def detect_pil(self, img_pil, format='jpg', verbose=False):
        with tempfile.NamedTemporaryFile(suffix='.%s' % os.path.basename(format)) as fp:
            filename = fp.name
            img_pil.save(fp.name)

            return self.detect_file(filename, verbose)

    def detect_file(self, img_file, verbose=False):
        @silent(verbose=verbose)
        def _detect():
            return detect(self.net, self.meta, img_file.encode('ascii'))

        results = _detect()

        ret = []
        for result in results:
            name, conf, box = result
            ret.append((
                name.decode('utf-8'),
                conf,
                box,
            ))

        return ret

if __name__ == "__main__":
    import argparse
    import time

    parser = argparse.ArgumentParser()
    parser.add_argument('meta', type=str) # *.data
    parser.add_argument('cfg', type=str) # *.cfg
    parser.add_argument('weights', type=str) # *.weights
    parser.add_argument('img', type=str)
    parser.add_argument('-v', '--verbose', action='store_true')

    args = parser.parse_args()

    darknet = Darknet(meta_file=args.meta, cfg_file=args.cfg, weights_file=args.weights, verbose=args.verbose)

    ts = time.time()
    # direct
    results = darknet.detect_file(args.img, verbose=args.verbose)

    # PIL example
    # from PIL import Image
    # img_pil = Image.open(args.img)
    # results = darknet.detect_pil(img_pil, verbose=args.verbose)

    elapsed = time.time() - ts

    print('FPS: %f (%f s)' % (1/elapsed, elapsed))

    print('%d boxes found' % len(results))
    for result in results:
        name, conf, box = result
        print(name, conf, box)

参考

付録

makeでエラーが出るとき

Path to libdevice library not specified環境変数PATHにcuda/binを追加(export PATH=/usr/local/cuda/bin:$PATH

train/test 分割

ImageFolder形式(クラス別ディレクトリ)、1ディレクトリ形式(クラス問わず同ディレクトリ)に対応。アノテーションデータは別途作成する。

SplitImageFolder.py

import os
import random
from tqdm import trange

if __name__ == '__main__':
    import argparse

    parser = argparse.ArgumentParser()
    parser.add_argument('indir', type=str)
    parser.add_argument('test_rate', type=float)
    parser.add_argument('--flat-input', action='store_true')
    parser.add_argument('--out-train', type=str, default='train.txt')
    parser.add_argument('--out-test', type=str, default='test.txt')
    args = parser.parse_args()

    current_dir = os.path.realpath(args.indir)

    files = []
    if args.flat_input:
        for imgfile in os.listdir(current_dir):
            imgpath = os.path.join(current_dir, imgfile)
            files.append(imgpath)
    else:
        for catfile in os.listdir(current_dir):
            catpath  = os.path.join(current_dir, catfile)
            for imgfile in os.listdir(catpath):
                imgpath = os.path.join(catpath, imgfile)
                files.append(imgpath)

    test_end = int( len(files) * args.test_rate )
    assert len(files) - test_end > 0, args.test_rate

    print('Data num: %d' % len(files))
    print('Train: %d, Test: %d' % (len(files)-test_end, test_end))

    indexes = list(range(len(files)))
    random.shuffle(indexes)

    with open(args.out_train, 'w') as file_train:
        with open(args.out_test, 'w') as file_test:
            for index in trange(len(files)):
                file_idx = indexes[index]

                imgpath = os.path.realpath(files[file_idx])
                imgfile = os.path.basename(imgpath)
                _, imgext = os.path.splitext(imgfile)
                if imgext == '.txt':
                    continue

                if index < test_end:
                    file_test.write(imgpath + "\n")
                else:
                    file_train.write(imgpath + "\n")

CUDA setup (make darknet)

darknetのmakeに失敗するので、CUDA/NVIDIA Driverの再セットアップ。

https://developer.nvidia.com/cuda-downloads

runfile (local)をダウンロード。

apt purge nvidia-*
apt purge cuda-*
reboot

あとはrunfileを実行してCUINVIDIA DriverとCUDA Toolkitをインストール(CUIモードでsystemctl stop lightdm←16.04の場合だった、18.04ならgdm?)。

export PATH=/usr/local/cuda/bin:$PATH
export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATH

.bashrc(Ubuntuの場合)に上の2行を追記する。

上記の環境でdarknetのmakeに成功した。

https://pjreddie.com/darknet/

参考

Python ログ出力抑制 デコレータ

出力を抑制するデコレータ。

Python 3.7.4、ctypesを使ったライブラリ呼び出しで動作を確認(darknet.py)。

# 標準出力・標準エラー出力の抑制
def silent(verbose=False):
    def _silent(func):
        def wrapper(*args, **kwargs):
            if not verbose:
                devnull = open(os.devnull, 'w')
                stdout = os.dup(1)
                stderr = os.dup(2)
                os.dup2(devnull.fileno(), 1)
                os.dup2(devnull.fileno(), 2)

            res = func(*args, **kwargs)

            if not verbose:
                os.dup2(stdout, 1)
                os.dup2(stderr, 2)
                devnull.close()

            return res

        return wrapper
    return _silent

使い方のイメージ(デコレータなせいで長くなってしまう、デコレータじゃなくて単純にコールバック風に関数を呼び出すラップ関数作ってもいいかも...)。

def func(verbose=False):
    @silent(verbose=verbose)
    def _func():
        cfunc()
    
    funcA()
    _func()
    funcB()

参考