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

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

Jetson本格仕様セットアップTips part1【ハードウェア】

Jetson NanoのJetPack SDK Card imageをinstallした後の、本格的に使うためのセットアップのTipsのメモ

目次
1. JetPack SDK Card imageをdownload (JetPack=4.6.1)
2. Jetson Nano上での必要パッケージのupdate
3. スワップ領域の確保
4. パワーモードの切り替え(カスタムパワーモードの設定)
5. プロセッサー(CPU/GPU)の使用率を調べる



1. JetPack SDK Card imageをdownload (JetPack=4.6.1)

JetPack
JetPack Version =4.6.1

4GBは
・sd-blob-b01.img
2GBは
・sd-blob.img


2. Jetson Nano上での必要パッケージのupdate

$ sudo apt-gwt update 
# $ apt-get upgrade 
# 依存関係のないパッケージをunistall
#$ apt-get autoremove
$ sudo apt-get install curl git unzip tree vim python3-pip
$ pip3 install --upgrade pip

# python3 version
$ python3 -V
>>> 3.6.9

# opencv version check 
$ dpkg -l | grep libopencv
>>> 4.1.1.2

3. スワップ領域の確保

1. スワップが有効化どうか確認する

# スワップが有効化どうか確認
$ free -m
###               total        used        free      shared  buff/cache   available
### Mem:           3955        1748         900          61        1306        2092
### Swap:          1977           0        1977

# SWAP領域を構築するZRAMを調べる
$ swapon -s
### NAME       TYPE        SIZE USED PRIO
### /dev/zram0 partition 494.5M   0B    5
### /dev/zram1 partition 494.5M   0B    5
### /dev/zram2 partition 494.5M   0B    5
### /dev/zram3 partition 494.5M   0B    5

4つのCPUあるので4つのSWAP領域が存在するのがわかる。

2. SWAP領域を追加する

4GBのSWAP領域を設定する。count=4で設定する

# SWAP領域の追加
$ sudo dd if=/dev/zero of=/var/swapfile bs=1G count=4
### 4+0 records in
### 4+0 records out
### 4294967296 bytes (4.3 GB, 4.0GiB) copied, 128.347s, 33.5 MB/s

3. 「/var/swapfile」をSWAP領域として使う

# SWAP領域の初期化と権限追加
$ sudo mkswap /var/swapfile
>>>>
### mkswap: /var/swapfile: insecure permissions 0644, 0600 suggested.
### Setting up swapspace version 1, size = 3 GiB (3221221376 bytes)
### no label, UUID=b97b85f8-5576-4fbd-8886-7cfadeece0b7

$ sudo chmod 600 /var/swapfile

4. SWAP領域をマウントするように「/etc/fstab」のラストの1行を追加
以下を追加
/var/swapfile none swap swap 0 0

$ sudo vi /etc/fstab
>>>>

# /etc/fstab: static file system information.
#
# These are the filesystems that are always mounted on boot, you can
# override any of these by copying the appropriate line from this file into
# /etc/fstab and tweaking it as you see fit.  See fstab(5).
#
# <file system> <mount point>             <type>          <options>                               <dump> <pass>
/dev/root            /                     ext4           defaults                                     0 1
/var/swapfile        none                  swap           swap                                         0 0  


5. SWAP領域を使用可能にする

$ sudo swapon /var/swapfile
# 再起動
$ sudo reboot


6. SWAP領域が確保されているか確認

SWAP領域が設定されてるか確認したら「/var/swapfile」がSWAP領域として使用されているのがわかる。

優先度は-1。ZRAMを使い切らない限りSWAP領域は使用されない。

$ free -m
              total        used        free      shared  buff/cache   available
Mem:           3956        1112        2160          23         683        2683
Swap:          6074           0        6074
$ swapon -s
Filename				Type		Size	Used	Priority
/var/swapfile                          	file    	4194300	0	-1
/dev/zram0                             	partition	506380	0	5
/dev/zram1                             	partition	506380	0	5
/dev/zram2                             	partition	506380	0	5
/dev/zram3                             	partition	506380	0	5


4. パワーモードの切り替え(カスタムパワーモードの設定)

MAXN:最大パフォーマンス
5W:低消費電力

# MAXNに切り替え
$ sudo nvpmodel -m 0
# 5Wに切り替え
$ sudo nvpmodel -m 1

カスタムパワーモードの設定

# LOWの設定
$ sudo vi /etc/nvpmodel.conf

# LOWに切り替え
$ sudo nvpmodel -m 1
$ sudo nvpmodel -q
>>>>
# NVPM WARN: fan mode is not set!
# NV Power Mode: LOW
# 2
# MAXN is the NONE power model to release all constraints
< POWER_MODEL ID=0 NAME=MAXN >
CPU_ONLINE CORE_0 1
CPU_ONLINE CORE_1 1
CPU_ONLINE CORE_2 1
CPU_ONLINE CORE_3 1
CPU_A57 MIN_FREQ  0
CPU_A57 MAX_FREQ -1
GPU_POWER_CONTROL_ENABLE GPU_PWR_CNTL_EN on
GPU MIN_FREQ  0
GPU MAX_FREQ -1
GPU_POWER_CONTROL_DISABLE GPU_PWR_CNTL_DIS auto
EMC MAX_FREQ 0

< POWER_MODEL ID=1 NAME=5W >
CPU_ONLINE CORE_0 1
CPU_ONLINE CORE_1 1
CPU_ONLINE CORE_2 0
CPU_ONLINE CORE_3 0
CPU_A57 MIN_FREQ  0
CPU_A57 MAX_FREQ 918000
GPU_POWER_CONTROL_ENABLE GPU_PWR_CNTL_EN on
GPU MIN_FREQ 0
GPU MAX_FREQ 640000000
GPU_POWER_CONTROL_DISABLE GPU_PWR_CNTL_DIS auto
EMC MAX_FREQ 1600000000

< POWER_MODEL ID=2 NAME=LOW >
CPU_ONLINE CORE_0 1 # online CPU core is 1
CPU_ONLINE CORE_1 0 # off line CPU core is 0
CPU_ONLINE CORE_2 0
CPU_ONLINE CORE_3 0
CPU_A57 MIN_FREQ  0
CPU_A57 MAX_FREQ 102000 # CPU movement Hz range
GPU_POWER_CONTROL_ENABLE GPU_PWR_CNTL_EN on
GPU MIN_FREQ 0
GPU MAX_FREQ 640000000
GPU_POWER_CONTROL_DISABLE GPU_PWR_CNTL_DIS auto
EMC MAX_FREQ 1600000000 # Memory Movement Hz MAX
# mandatory section to configure the default mode
< PM_CONFIG DEFAULT=0 >


5. プロセッサー(CPU/GPU)の使用率を調べる

$ sudo -H pip3 install -U jetson-stats
$ sudo reboot
# 使用率を見る
$ jtop

MAXN(パワーモード)

5W(低電力モード)


参考

JetsonNanoの電源として使えるモバイルバッテリーについて

MacからJetson NanoにRDP接続する(Headless化)

Macからjetsonにssh接続できる環境は前の記事でできた。
trafalbad.hatenadiary.jp



けどいかんせん、Jetson Nano用のキーボード、マウス、ディスプレイをいちいち接続するのが面倒で邪魔だから、なんとかしたいということで、

コマンドライン操作だけできればいい
・他のデバイスにマイクロデバイス(PC)として付属させたい

という前提で、今度はRDP(Remote Desktop Protocol)で、Macからリモート接続してHeadless化してみた
VNC接続よりはるかに簡単なのでこっちの方がいい。


目次
1. jetson nanoにサーバーをinstall
2. Macにクライアントをinstallして、RDP接続でアクセス

1. jetson nanoにサーバーをinstall

まずJetson側でサーバー用のxrdpをinstallする。


Jetson側

# hostnameの変更 (parallels)
$ hostnamectl set-hostname <新しいホスト名> 
# ホスト名の確認
$ hostname
# Ubuntuのipアドレスを確認
$ ifconfig
>>>
## enp0s5: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
## inet 10.xxx.xx.8  netmask 255.255.255.0  

# RDPサーバをインストール
$ sudo apt update

# xrdp単体だと落ちてしまうのでデスクトップ環境をxfceに変更
$ sudo apt install -y xfce4
$ echo xfce4-session > ~/.xsession

$ sudo apt install -y xrdp

/etc/xrdp/startwm.sh の最後の2行をコメントアウトして以下を追加
test -x /etc/X11/Xsession && exec /etc/X11/Xsession
exec /bin/sh /etc/X11/Xsession

以下を追加
startxfce4

startxfce4
#test -x /etc/X11/Xsession && exec /etc/X11/Xsession
#exec /bin/sh /etc/X11/Xsession
# 再起動する
$ sudo reboot

****必要かわからないけど落ちないようにした操作

$ vi /etc/systemd/journald.conf
#Storage=aotoのオプション設定を「#」を削除して以下に変更
Storage=persistent
# 反映
$ systemctl restart systemd-journald.service


2. Macにクライアントをinstallして、RDP接続でアクセス

Mac

Microsoft Remote Desktop for Mac」をdownload。

「Add PC」から以下の項目を入れてく。
・PC名/ip address:10.xxx.xx.8($ ifconfig)
・ユーザー名:$ hostnameで調べたやつ
・パスワード:Jetson側のパスワード


問題なければ接続できた。

xrdpがうまく起動してることも確認できた。

# xrdpがうまく起動してることが確認
$ systemctl status xrdp

あっさりとheadless化できた。VNC接続なんかしなくてもこれでいいと思う。
ただubuntu21.14以降は環境が変わってるのでこのやり方ではダメらしい。

参考

Jetson NanoでMacから手軽にリモート接続する方法
XRDP client crashing on loading screen for Jetson Nano
【全オプション解説】journaldログが削除されない設定とおススメ設定を解説

MacからJetson NanoにVNC接続する(headless化)

Macからjetsonにssh接続できる環境は前の記事でできた。
trafalbad.hatenadiary.jp



けどいかんせん、Jetson Nano用のキーボード、マウス、ディスプレイをいちいち接続するのが面倒で邪魔だから、なんとかしたいということで、

コマンドライン操作だけできればいい
・他のデバイスにマイクロデバイス(PC)として付属させたい

という前提で、リモートデスクトップがいらないVNC接続をJetson NanoとMac間でやってみた備忘録。

あとロボットとか、外部のデータを送るマイクロデバイスとして使うときにこの操作は大事



目次
1. MacからJetson NanoにVNC接続
2. Jetson NanoでVNC Serverをデーモンとして自動起動させる


1. MacからJetson NanoにVNC接続

Jetson Nano側

初めは設定のためにキーボード、マウス、デイスプレイが必要。

重要:まずJetson側でGUIでログイン時にパスワードを要求しないように設定。

# Ubuntuのipアドレスを確認
$ ifconfig
>>>
## enp0s5: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
## inet 10.xxx.xx.8  netmask 255.255.255.0  
$ sudo apt update 
$ sudo apt install tigervnc-standalone-server tigervnc-scraping-server
# パスワード設定
$ vncpasswd
>>> 

# Password:

# Verify:

# TigerVNCを起動
# x0vncserver -display :1 -passwordfile ~/.vnc/passwd
$ x0vncserver -display :0 -passwordfile ~/.vnc/passwd

Mac

[command]+[スペース]でSpotlight検索で「画面共有」で開く
10.xxx.xx.8
パスワード:[jetsonのパスワード]

VNC接続を確認。



2. Jetson NanoでもVNC Serverをデーモンとして自動起動させる

VNCサーバを毎回手動で起動するのは面倒なので、システム起動時に自動的にVNCサーバが起動するようにする。

# systemdデーモンのサービス設定ファイルの作成
$ sudo vi /etc/systemd/system/x0vncserver.service


/etc/systemd/system/x0vncserver.service

・ユーザー名==> hagi
pwd ==> /home/hagi

[Unit]
Description=Remote desktop service (VNC)
After=syslog.target
After=network.target remote-fs.target nss-lookup.target
After=x11-common.service 
 
[Service]
Type=forking
User=hagi
Group=hagi
WorkingDirectory=/home/hagi
ExecStart=/bin/sh -c 'sleep 10 && /usr/bin/x0vncserver -display :0  -rfbport 5900 -passwordfile /home/hagi/.vnc/passwd &'
 
[Install]
WantedBy=multi-user.target
# 設定 & 起動確認
$ systemctl daemon-reload
$ sudo systemctl start x0vncserver.service
$ sudo systemctl status x0vncserver.service
>>>
# 正しく動いてると以下のように表示される
#● x0vncserver.service - Remote desktop service (VNC)
#   Loaded: loaded (/etc/systemd/system/x0vncserver.service; en
#   Active: active (running) since Mon 2023-02-06 19:39:33 JST;
#  Process: 4717 ExecStart=/bin/sh -c sleep 10 && /usr/bin/x0vn
# Main PID: 4791 (x0vncserver)
#    Tasks: 1 (limit: 4181)
#   CGroup: /system.slice/x0vncserver.service
#           └─4791 /usr/bin/x0vncserver -display :0 -rfbport 59

# 2月 06 19:39:33 jetson systemd[1]: Starting Remote desktop se
# 2月 06 19:39:33 jetson systemd[1]: Started Remote desktop ser
#lines 1-11
# サービスを有効化して再起動
$ sudo systemctl enable x0vncserver.service
>>>>
# Created symlink /etc/systemd/system/multi-user.target.wants/x0vncserver.service → /etc/systemd/system/x0vncserver.service.

$ sudo reboot yes
# 再起動後、ちゃんとサービスが起動しているか確認
$ systemctl list-units | grep vnc
>>>
# loaded active     running   Remote desktop service (VNC)  

# 停止
$ systemctl stop x0vncserver.service


もうこの時点でVNC接続できたので、キーボード、マウス、HDMIケーブルは外してもOK。

ここまできたら、キーボード、マウス、HDMIケーブルは不要で、MacからVNC接続できる。


1. Jetson Nano電源接続
2. Macの[command]+[スペース]でSpotlight検索で「画面共有」で開く
3. ipアドレスを入力
4. パスワードを入れて接続完了。


MacからVNC接続で、もう重いにキーボード、マウス、HDMIケーブル不要でJetson nanoに簡単接続できた。

キーボード、マウス、HDMIケーブル不要でMacから接続


重要
・パスワードなしの自動ログインに設置しておく
・displayが「0」でエラーになるなら使用済みの可能性もあるので「1」とかにしてみる

これでJetbotととかのロボットにJetsonを取り付けて動かせる。

参考

Jetson Nanoにリモートデスクトップ(VNC)環境を用意する

Jetson nanoでPWM制御をするまでの手順【ハードウェア】

今回はJetson nanoでPWM(Pulse Width Modulation)制御をするまでの手順をまとめてく。

Jetson nanoでPWMのPinは「32, 33」の二つ
これを使ってサーボモータをマルチスレッドで二つ動かす。


目次
1. 配線図
2. Jetson nanoでGPIOのインストール
3. PWM設定-「Jetson-IO tool」の実行
4. PIN番号を合わせる
5. PWMでサーボモータを制御

1.配線図

Jetson nanoの40 PINs(サーボモータの配線は3つあってそれぞれ、PWMとGNDと電圧V。


Jetson nanoとサーボモータ(SG-5010)の配線図。



2 Jetson nanoでGPIOのインストール

PWM用のGPIOをインストール。

$ git clone https://github.com/NVIDIA/jetson-gpio.git
$ cd jetson-gpio
$ sudo python3 setup.py install

# グループ作成とユーザーの追加
$ sudo groupadd -f -r gpio
$ sudo usermod -a -G gpio <ユーザー名>

# udevルールの追加と反映
$ sudo cp lib/python/Jetson/GPIO/99-gpio.rules /etc/udev/rules.d/
$ sudo udevadm control --reload-rules && sudo udevadm trigger
# 再起動して使えるようにする
$ sudo reboot

3 PWM設定-「Jetson-IO tool」の実行

Jetson nanoにあるPINを外部ピンへ拡張する。

$ sudo /opt/nvidia/jetson-io/jetson-io.py

「Jetson Expansion Header Tools」でpwm0, pwm2を選択
「save and reboot to reconfigure pins」で再起動して反映。

|                Select desired functions (for pins):                |
 |                                                                    |
 |                 [ ] aud_mclk      (7)                              |
 |                 [ ] i2s4          (12,35,38,40)                    |
 |                 [*] pwm0          (32)                             |
 |                 [*] pwm2          (33)                             |
 |                 [ ] spi1          (19,21,23,24,26)                 |
 |                 [ ] spi2          (13,16,18,22,37)                 |
 |                 [ ] uartb-cts/rts (11,36)                          |


****jetson-io.py実行時に一瞬、画面に何かが表示されて直ぐに終了してしまう場合

DTB ファイルが /boot ディレクトリ配下に存在しているので、/boot/dtb ディレクトリを作成して、その中にコピー。

cd /boot
sudo mkdir dtb
sudo cp *.dtb* dtb/

再度
$ sudo /opt/nvidia/jetson-io/jetson-io.py

PWMが設定できてるなら下の様にpwm-0, pwm-2がnullになる

$ sudo cat /sys/kernel/debug/pwm
>>>>>>

platform/70110000.pwm, 1 PWM device
 pwm-0   (pwm-regulator       ): requested enabled period: 2500 ns duty: 0 ns polarity: normal

platform/7000a000.pwm, 4 PWM devices
 pwm-0   ((null)              ): period: 0 ns duty: 0 ns polarity: normal
 pwm-1   (pwm-regulator       ): requested enabled period: 8000 ns duty: 1440 ns polarity: normal
 pwm-2   ((null)              ): period: 0 ns duty: 0 ns polarity: normal
 pwm-3   (pwm-fan             ): requested enabled period: 45334 ns duty: 0 ns polarity: normal

4. PIN番号を合わせる

Jetson Nanoでは普通に40PINを拡張しただけではサーボモータが動かない。

サーボモータが動くようにするにはさらに設定が必要で一番苦労したところ。

# assign Pin32 to PWM0
$ busybox devmem 0x700031fc 32 0x45
$ busybox devmem 0x6000d504 32 0x2
# assign Pin33 to PWM2
$ busybox devmem 0x70003248 32 0x46
$ busybox devmem 0x6000d100 32 0x00

$ cd /sys/devices/7000a000.pwm/pwm/pwmchip0
# Control Pin 32 of PWM0
echo 0 > export
echo 20000000 > pwm0/period
echo 2500000 > pwm0/duty_cycle
echo 1 > pwm0/enable

# Control Pin33 of PWM2
echo 2 > export
echo 20000000 > pwm2/period
echo 1500000 > pwm2/duty_cycle
echo 1 > pwm2/enable

設定したらサーボモータが動いた。



NVIDIAの元記事

参考記事


5 PWMでサーボモータを制御

1つのサーボモータを動かす

import RPi.GPIO as GPIO
import time

output_pins = {
    'JETSON_XAVIER': 18,
    'JETSON_NANO': 32,
    'JETSON_NX': 33,
    'CLARA_AGX_XAVIER': 18,
    'JETSON_TX2_NX': 32,
    'JETSON_ORIN': 18,
}
output_pin = output_pins.get(GPIO.model, None)
if output_pin is None:
    raise Exception('PWM not supported on this board')


def main():
    # Pin Setup:
    # Board pin-numbering scheme
    GPIO.setmode(GPIO.BOARD)
    # set pin as an output pin with optional initial state of HIGH
    GPIO.setup(output_pin, GPIO.OUT, initial=GPIO.HIGH)
    p = GPIO.PWM(output_pin, 50)
    val = 7.25
    incr = 0.25
    p.start(val)

    print("PWM running. Press CTRL+C to exit.")
    try:
        while True:
            time.sleep(1)
            if val >= 12:
                incr = -incr
            if val <= 2.5:
                incr = -incr
            val += incr
            p.ChangeDutyCycle(val)
    finally:
        p.stop()
        GPIO.cleanup()

if __name__ == '__main__':
    main()

マルチスレッドで2つのサーボモータを動かす

#!/usr/bin/env python

import Jetson.GPIO as GPIO
import time
import threading
import sys
from formura import Angle2Duty

OUTPUT_PIN1 = 32 
OUTPUT_PIN2 = 33
CYCLE = 50
t=2

def setup_device():
    GPIO.setmode(GPIO.BOARD)
    GPIO.setup(OUTPUT_PIN1, GPIO.OUT, initial=GPIO.HIGH)
    pw = GPIO.PWM(OUTPUT_PIN1, CYCLE)

    GPIO.setup(OUTPUT_PIN2, GPIO.OUT, initial=GPIO.HIGH)
    ph = GPIO.PWM(OUTPUT_PIN2, CYCLE)
    return pw, ph

def pw_loop(pw):
    while flag:
        dc1 = Angle2Duty(450)
        pw.start(dc1)
        print("width dc {}".format(dc1))
        time.sleep(t)
        dc2 = Angle2Duty(500)
        pw.start(dc2)
        print("width dc {}".format(dc2))
        time.sleep(t)
        dc3 = Angle2Duty(410)
        pw.start(dc3)
        print("width dc {}".format(dc3))
        time.sleep(t)
        dc4 = Angle2Duty(300)
        pw.start(dc4)
        print("width dc {}".format(dc4))
        time.sleep(t)

def ph_loop(ph):
    while flag:
        dc1 = Angle2Duty(120)
        ph.start(dc1)
        print("ph height {}".format(dc1))
        time.sleep(t)
        dc2 = Angle2Duty(180)
        ph.start(dc2)
        print("ph height {}".format(dc2))
        time.sleep(t)
        dc3 = Angle2Duty(240)
        ph.start(dc3)
        print("ph height {}".format(dc3))
        time.sleep(t)
        dc4 = Angle2Duty(170)
        ph.start(dc4)
        print("ph height {}".format(dc4))
        time.sleep(t)
        

if __name__ == '__main__':
    flag = True
    c=0
    pw, ph = setup_device()
    th1 = threading.Thread(target=pw_loop, args=(pw,))
    th1.start()
    th2 = threading.Thread(target=ph_loop, args=(ph,))
    th2.start()
    while True:
        c +=1
        if c==3000:
            flag =False
            th1.join()
            th2.join()
            pw.stop()
            ph.stop()
            GPIO.cleanup()
            sys.exit(1)


参考記事

JetPack 4.3 (r32.3.1) で追加された Jetson-IO tool を使用して Pinmux テーブルを設定してみた。
Jetson Nano の 2 つのハードウェア PWM を使用してみた
Jetson Nano の GPIO にサーボモータをつないで制御してみる

物体検出(yolov7)のバウンディングボックスから奥行きの距離(depth)を計算してみた

Jetsonに2つのカメラをつけて、それぞれに物体検出のyolov7で推論をかける。そこで得たbboxの位置座標から視差(disparity)を求めて、奥行きの距離(depth)を求めてみた。

その過程と結果の備忘録


1. データセットの用意
2. disparityからdipthを求める手順
3. パラメーターとDipthの計算式
4. yolov7でbboxを推論
5. 結果:bboxと計測値からdisparityとdepthの関係図


1. データセットの用意

JetsonでCSIカメラのimx219を2台使って、右(right) と左(left) 用カメラとして使った。

実際の使用した場面の画像

使ったCSIカメラ


2. disparityからdipthを求める手順

下のサイトを参考にした。

webカメラ2台で距離測定その2

disparityの公式図

参考サイトによると、上のdisparityの公式を近似式で求めることができ、その結果disparityとdepthは反比例する関係になる。

・disparityが大きくなればdepthは小さくなる
・disparityが小さくなればdepthは大きくなる




参考サイトだとopencvで物体の重心からdepthを求めてだけど、今回は物体検出のbboxの位置座標からdisparityを求めた。

opencvを使ったdisparityからdipthの計算手順

1. カメラから画像を取得
2. 色抽出&二値化
3. 平滑化処理
4. 対象の重心位置の推定
5. 左右の画像から得られた対象の重心位置の差を求める
6. 重心位置の差から距離を求める


物体検出を使ったdisparityからdipthの計算手順

1. カメラから画像を取得
2. 物体検出で推論してbboxを取得
3. 左右のbboxのx座標の差の絶対値を求める
4. 3からdipth(距離)を求める




3. パラメーターとDipthの計算式

カメラとかの採寸やサイズは下の通り。

各変数の値を求めた結果、dipthの計算式は下のようになった。

パラメーター

画像サイズ:(Width, Height) = (1280, 960)

焦点距離:0.315 cm. ( f= 0.315

カメラ間の距離:2.6 cm. ( T= 2.6

画像素子:2.8μm = 0.00028 cm ( imgelement= 0.00028

Depth計算式

dipth(cm)= \dfrac {Tf} {imgelement \times disparity} = \dfrac {2925} {disparity}(cm)




4. yolov7でbboxを推論

下がyolov7で検出した画像。クラスが間違ってるのは後処理をすっ飛ばしてるからだけど、今は関係ないので割愛。


物体検出(yolov7)のバウンディングボックスから奥行きの距離(depth)を計算してみた

今回はbboxのrightとleftのx座標を引き算して、その絶対値を求めて、disparityを求める。


5. 結果:bboxと計測値からdisparityとdepthの関係図

下が大まかな計測値を表にしてグラフ化したもの。
disparityとdipthは計算式から求めた。


import pandas as pd

# 表の項目 :index, right_x, right_y, right_w, right_h, left_x. left_y, left_w, left_h, disparity
columns = ['idx', 'Lx', 'Ly', 'Lw', 'Lh', 'Rx', 'Ry', 'Rw', 'Rh']
# listed は計測値のlist
df = pd.DataFrame(listed, columns=columns)


# 計算式
def cm_disparity(x, y):
    disp = abs(x-y)
    return disp

def dist_formula(disparity):
    T = 2.6 # cm
    f = 0.315 # cm
    img_element = 0.0001*2.8 # cm
    K = int(T*f/img_element) # 2925
    dist = K/disparity
    return dist 
 
# Disparityを計算
df["disparity"] = df.apply(lambda x : cm_disparity(x["Lx"], x["Rx"]), axis=1)

# dispを計算
df['distance(z)'] = df['disparity'].apply(dist_formula)


# disparity と dipthの関係図をplot

fig, ax = plt.subplots()
x = np.array(df['disparity'])
y = np.array(df['distance(z)'])
ax.plot(x, y, label="test")
ax.set_xlabel('disparity')
ax.set_ylabel('distance(z)')
plt.show()

計測値

disparityとdipthの関係図

disparityとdepthが上手く反比例してる。
物体検出のbboxの座標からでも十分正確なdepthの距離が求められることがわかった。


参考サイト

webカメラ2台で距離測定その2

Swiftでファイルをs3にuploadしてみる。


目次
1. cocoapodsをinstallして、Podfileからライブラリをinstall
2. AWSのUpload用のバケットをs3で作成
3. AppDelegate.swiftにS3のupload用のコードに貼り付け
4. SwiftからS3に画像をアップロード
5. s3で画像の確認


1. cocoapodsをinstallして、Podfileからライブラリをinstall

brewじゃなく、gemでcocoapodsをinstall。

1. install CocoaPods and setup

$ gem install cocoapods
$ pod setup

2. プロジェクト floder へ移動

3. podfile 作成
$ pod init

4. podfile 書き直す ('AWSCognito' 'AWSS3'を付け足す)
$ open Podfile

# pod 'AWSCognito' 'AWSS3' をPodfileに追記
```
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'ResNet50prj' do
  # Comment the next line if you don't want to use dynamic frameworks
  use_frameworks!

  # Pods for ResNet50prj
  pod 'AWSCognito' # ここ
  pod 'AWSS3' # ここ
  target 'ResNet50prjTests' do
    inherit! :search_paths
    # Pods for testing
  end

  target 'ResNet50prjUITests' do
    # Pods for testing
  end

end
```

5. ライブラリをinstall
$ pod install
```


2. AWSのUpload用のバケットをs3で作成

AWS > Cognito > IDプールの管理 > 新しい ID プールの作成

・IDプール名 :に任意の名前を入力
・認証されていないIDのアクセスを有効にする

でプールの作成

次に詳細で「ポリシードドキュメント」を編集

S3のPutObject(必要であればGetObjectも)の許可を追加。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",   ←ここを追加
        "s3:GetObject",   ←もしS3に保存した画像をアプリで取得したければ追加
        "mobileanalytics:PutEvents",
        "cognito-sync:*",
        "cognito-identity:*"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}

「許可」をクリック

Amazon Cognitoで作業開始」で赤枠のコードをコピー

3. AppDelegate.swiftにS3のupload用のコードに貼り付け

XocdeのAppDelegate.swiftを開き、下の作業。
・AWSCognitoをインポート
・コピーしたコードをのdidFinishLaunchingWithOptionsの中に貼り付け

import AWSCognito
...
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        FirebaseApp.configure()
  let credentialsProvider = ****
  let configuration = ****
  AWSServiceManager.default().defaultServiceConfiguration = configuration
  return true
}


4. SwiftからS3に画像をアップロード

s3で「"pacifista-px3"」名前のバケットを作成。


AWSS3.swift

import SwiftUI
import AWSS3

func uploadData(data: Data)-> String{
    var results_ = ""
    
    let transferUtility = AWSS3TransferUtility.default()
    // アップロードするバケット名/アップしたいディレクトリ
    let bucket = "pacifista-px3"
    // ファイル名
    let key = "users.png"
    let contentType = "application/png"
    // アップロード中の処理
    let expression = AWSS3TransferUtilityUploadExpression()
    expression.progressBlock = {(task, progress) in
       DispatchQueue.main.async {
         // アップロード中の処理をここに書く
       }
    }
        
    // アップロード後の処理
    let completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?
    completionHandler = { (task, error) -> Void in
       DispatchQueue.main.async {
         if let error = error {
             fatalError(error.localizedDescription) // 失敗
             results_ = "upload falid"
         } else {
            // アップロード後の処理をここに書く
             results_ = "upload succeed"
         }
       }
     }
        
     // アップロード
     transferUtility.uploadData(
       data,
       bucket: bucket,
       key: key,
       contentType: contentType,
       expression: expression,
       completionHandler: completionHandler
     ).continueWith { (task) -> Any? in
       if let error = task.error as NSError? {
           fatalError(error.localizedDescription)
           results_ = "upload falid"
       } else {
           // アップロードが始まった時の処理をここに書く
           results_ = "upload succeed"
       }
       
       return nil
     }
    return results_
   }


ContentView.swift

struct ContentView: View {
    // config
    @State var S3result = ""

    〜〜〜〜〜〜〜
    func S3Upload(uiimage:UIImage){
        if let pngImage = uiimage.pngData() {
            S3result = uploadData(data: pngImage)
        }
    }
    // main
    var body: some View {
        VStack {
            〜〜〜〜
            Text(S3result)
                .padding()
                .font(.title)
            Button("Upload to S3"){
                S3Upload(uiimage:captureImage!)
            }
            .frame(height: 50)
            .multilineTextAlignment(.center)
   〜〜〜〜〜〜
         } 
    }
}

uploadしてみる。




成功した。

5. s3で画像の確認

バケットにちゃんとあるのがわかる


Swiftでs3に画像をuploadできた。

参考

【Xcode/Swift】CocoaPodsの使い方を徹底解説
SwiftからS3に画像をアップロードする方法

Yolov7にSinkhorn lossを使って実験してみた

OTA-loss とかOCcostとか話題になってる昨今に絡んで、「Sinkhorn」とかいう手法が気になってた。

何でもlossかなんかのmatrixを最適化する手法でSinkhorn lossとかいうのもある。Yolov7を使ってる最中だったので、なんとかYolov7にSinkhornを使ってみたかった。

なので、物体検出の最新版Yolov7にSinkhorn使って検証してみた。その備忘録。

目次
1. Sinkhornとは
2. Sinkhornと Yolov7のOTA-lossの融合
3. 検証結果


1. Sinkhornとは

Sinkhornとは二つの変数で求めたcost matrixを最小化する手法で最適化輸送問題に使われる。

簡単にどう使うかというとpredictionとground truthで求めたlossをcost matrixとして使って、Sinkhornで最適化して、lossをさらに改善しようという話。


predictionとground truthを確立分布とみなして同じに近づけるので、Kullback-Leibler Divergence lossと仕組みは近いと思う。

Sinkhornとlossの仕組み


細かい説明はした記事を参照。

OTA(Optimal Transport Assignment for Object Detection)
最適化輸送問題




Sinkhorn loss

import torch
import torch.nn as nn

# Adapted from https://github.com/gpeyre/SinkhornAutoDiff
class SinkhornDistance(nn.Module):
    def __init__(self, model, eps, max_iter, reduction='none'):
        super(SinkhornDistance, self).__init__()
        self.device = next(model.parameters()).device
        self.eps = eps
        self.max_iter = max_iter
        self.reduction = reduction

    def forward(self, cost, pred, truth):
        '''
    	We can easily see that the optimal transport corresponds to assigning each point 
    	in the support of pred(x) to the point of truth(y)
    	'''
        x, y = pred, truth
        # The Sinkhorn algorithm takes as input three variables :
        C = cost  # Wasserstein cost function
        x_points = x.shape[-2]
        y_points = y.shape[-2]
        if x.dim() == 2:
            batch_size = 1
        else:
            batch_size = x.shape[0]

        # both marginals are fixed with equal weights
        mu = torch.empty(batch_size, x_points, dtype=torch.float,
                         requires_grad=False, device=self.device).fill_(1.0 / x_points).squeeze()
        nu = torch.empty(batch_size, y_points, dtype=torch.float,
                         requires_grad=False, device=self.device).fill_(1.0 / y_points).squeeze()

        u = torch.zeros_like(mu)
        v = torch.zeros_like(nu)
        # To check if algorithm terminates because of threshold
        # or max iterations reached
        actual_nits = 0
        # Stopping criterion
        thresh = 1e-1

        # Sinkhorn iterations
        for i in range(self.max_iter):
            u1 = u  # useful to check the update
            u = self.eps * (torch.log(mu+1e-8) - torch.logsumexp(self.M(C, u, v), dim=-1)) + u
            v = self.eps * (torch.log(nu+1e-8) - torch.logsumexp(self.M(C, u, v).transpose(-2, -1), dim=-1)) + v
            err = (u - u1).abs().sum(-1).mean()

            actual_nits += 1
            if err.item() < thresh:
                break

        U, V = u, v
        # Transport plan pi = diag(a)*K*diag(b)
        pi = torch.exp(self.M(C, U, V))
        # Sinkhorn distance
        cost = torch.sum(pi * C, dim=(-2, -1))

        if self.reduction == 'mean':
            cost = cost.mean()
        elif self.reduction == 'sum':
            cost = cost.sum()

        return cost #, pi, C

    def M(self, C, u, v):
        "Modified cost for logarithmic updates"
        "$M_{ij} = (-c_{ij} + u_i + v_j) / \epsilon$"
        return (-C + u.unsqueeze(-1) + v.unsqueeze(-2)) / self.eps

    @staticmethod
    def ave(u, u1, tau):
        "Barycenter subroutine, used by kinetic acceleration through extrapolation."
        return tau * u + (1 - tau) * u1

2. Sinkhornと Yolov7のOTA-lossの融合

Yolov7のOTA-lossに Sinkhornを使ってみた。object lossとiou lossは3次元でなかったり、相性が悪かったので、class lossだけに使うことにした。

Sinkhornを使ったyolov7のOTA-loss

class ComputeLossOTA:

          if self.use_cost:
               lcls_cost = self.BCEcls(ps[:, 5:], t)
               lcls += self.sinkhorn(cost=lcls_cost.unsqueeze(2), pred=ps[:, 5:].unsqueeze(2), truth=t.unsqueeze(2))
          else:
               lcls += self.BCEcls(ps[:, 5:], t)  # BCE


3. 検証結果

普通のYolov7

confusion matrix

loss curve

P_curve

F1 curve


Sinkhornを使ったYolov7

confusion matrix

loss curve

P_curve

F1 curve

うまい具合にいってるけど、全体で的に見るとSinkhornに入れる引数のshapeが3次元の必要性から、使うドメインは限られてくる。

今回はclass lossにしか使ってなかったから、object lossとiou lossに使えばもっと改善する可能性もある。

少なくともlossの改善には悪い影響は与えないんじゃないかと思う。

参照記事

yolov7
SinkHorn