ラズパイ5とカメラを使用してYOLOv8による物体の推論を行う

## はじめに
Ultralytics製の物体検出モデルであるYOLOv8を用いて、Raspberry Pi 5へ接続したカメラに何の物体が映ったか推論し、標準出力へ表示させてみます。

環境構築について長々と説明しています。
プログラムだけほしい方は[#カメラの映像を推論し続ける](#カメラの映像を推論し続ける)にジャンプしてください。

## ハードウェアを準備
まずは必要な機械たちを用意します。
Raspberry Piは4系でも動作はしますが、5だとそれなりの速さで推論できます。
microSDカードは16GB-32GB程度が適切です。
カメラは何でもいいですが、USBで接続できるものを用意してください。

今回使用したものはこんな感じです:
- Raspberry Pi 5 (メモリ8GBモデル)
  - ACアダプター (スイッチサイエンス; SSCI-093453)
- 家に転がっていたmicroSDカード(KLEVV NEO; 64GB)
  - OTGカードリーダー(詳細不明)
- 家に放置されていたウェブカメラ(詳細不明)

:::note warn
Raspberry Piへの接続やコードの開発に使用するPCは、Linux/MacなどUnixコマンドが使用できる環境であると仮定します。Windowsをお使いの場合は、ターミナル操作にWSLを使用してください。
:::


## イメージを作成
Raspberry Pi Imagerを用いて、SDカードにイメージを書き込みます。
ユーザー名やパスワード、Wi-Fi、SSHなども同時に設定します。

<details><summary>イメージ作成手順</summary>

デバイス、OS、ストレージを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/ba3c30b1-6131-1c75-c3b8-cabc9cc9ce20.png)

OS設定の変更も同時に行います。
`設定を編集する`をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/440c9728-b185-d14c-7170-1c44eebee546.png)

ホスト名は自分が区別しやすい適当なものに設定します。
ユーザー名とパスワードをデフォルトのまま放っておくと危ない上、Pi側にも怒られますので、適当なものに変えておきます。
有線接続できるネットワークがなければWi-Fiも設定しておきましょう。
タイムゾーンは正しく設定しておかないとログの日時が大変なことになったりします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/27f6fcdc-4539-0126-42ec-7ce5176d71ab.png)

いちいちディスプレイやキーボードを接続するのが面倒なので、SSH接続は有効化しておきます。
今回はより簡単に接続できるパスワード認証で作業を進めますが、安全性を高めたい場合は`公開鍵認証のみを許可する`を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/6981441b-1890-df67-5f68-c28afb21ccda.png)

設定が完了したら、`保存`をクリックし、元のウィンドウに戻ったら`はい`で設定を適用させます。

最終確認です。書き込みするデバイスが間違いないことを確認し、`はい`をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/90cc4269-b593-5536-07ee-f6ce732816cb.png "CSSを完全に理解したPi Imagerの図")

あとは待つだけでイメージのダウンロードや書き込み、検証を済ませてくれます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/52765029-bd7e-9c9c-3816-1b0579fb5457.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/8e4f80e8-dd10-3f63-aa76-c43e978d47a9.png)

書き込みが完了したら、microSDカードをRaspberry Piにセットしておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/adfcc6e7-3071-9f50-52bb-f047955fd879.png)

</details>

## SSH接続を確立
お手元の端末からRaspberry Piに接続します。
ディスプレイとキーボードがあれば、直接ターミナルから`ip a`等を実行してIPアドレスを確認できますが、今回は面倒なので手元からnmapを叩きます。

:::note warn
外のデバイスにスキャンする行為は一般的に**攻撃**とみなされます。
スキャンは自分のネットワーク上で自分のデバイスにのみ行ってください。
:::

```console:s23201@local
$ sudo nmap -sn 192.168.68.0/24 | grep -B2 -- "Raspberry Pi"
Nmap scan report for 192.168.68.138
Host is up (0.038s latency).
MAC Address: 2C:CF:67:3A:60:FE (Raspberry Pi (Trading))
```
今回セットアップしているRaspberry Piには`192.168.68.138`が割り振られているとわかりました。

では接続しましょう。
```:s23201@local
$ ssh s23201@192.168.68.138
The authenticity of host '192.168.68.138 (192.168.68.138)' can't be established.
ED25519 key fingerprint is SHA256:****.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.68.138' (ED25519) to the list of known hosts.
s23201@192.168.68.138's password: 
Linux yolopi 6.6.51+rpt-rpi-2712 #1 SMP PREEMPT Debian 1:6.6.51-1+rpt3 (2024-10-08) aarch64

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Last login: Tue Nov 19 22:44:00 2024
s23201@yolopi:~ $ 
```

## 設定変更やパッケージ更新・追加
YOLOを触る前に、設定の変更やパッケージの更新・追加を行っておきます。

### raspi-config
対話型CLI上で簡単に設定を変更できます。

```:s23201@yolopi
$ sudo raspi-config
┌───────────────┤ Raspberry Pi Software Configuration Tool (raspi-config) ├───────────────┐
│                                                                                         │
│            1 System Options       Configure system settings                             │
│            2 Display Options      Configure display settings                            │
│            3 Interface Options    Configure connections to peripherals                  │
│            4 Performance Options  Configure performance settings                        │
│            5 Localisation Options Configure language and regional settings              │
│            6 Advanced Options     Configure advanced settings                           │
│            8 Update               Update this tool to the latest version                │
│            9 About raspi-config   Information about this configuration tool             │
│                                                                                         │
│                                                                                         │
│                                                                                         │
│                                                                                         │
│                                                                                         │
│                        <Select>                        <Finish>                         │
│                                                                                         │
└─────────────────────────────────────────────────────────────────────────────────────────┘
```

`1 System Options`を矢印キーで選択し、`Enter`で開きます。

GUIブートを無効化したいので、`S5 Boot / Auto Login`を選択し、`B1 Console`を選びます。Piへ物理的にアクセスできるのが自分のみだとわかっている場合は、`B2 Console Autologin`でもOKです。

スプラッシュスクリーンもいらないので切りましょう。`S6 Splash Screen`を選択し、`No`を選びます。

必要な変更を行い終わったら、`Finish`を選び、選択肢に従ってリブートします。
しばらく待った後、再接続しておいてください。

### パッケージの更新
今のうちに各パッケージを更新しておきます。
```console:s23201@yolopi
$ sudo apt update && sudo apt upgrade
Hit:1 http://deb.debian.org/debian bookworm InRelease
Get:2 http://deb.debian.org/debian-security bookworm-security InRelease [48.0 kB]
...
update-initramfs: Generating /boot/initrd.img-6.6.62+rpt-rpi-2712
'/boot/initrd.img-6.6.62+rpt-rpi-2712' -> '/boot/firmware/initramfs_2712'
```

### パッケージのインストール
piにはデフォルトでviとnanoが入っていますが、vimはありません。
このタイミングでインストールしておきます。
```console:s23201@yolopi
$ sudo apt install vim
...
The following additional packages will be installed:
  vim-runtime
Suggested packages:
  ctags vim-doc vim-scripts
The following NEW packages will be installed:
  vim vim-runtime
0 upgraded, 2 newly installed, 0 to remove and 1 not upgraded.
Need to get 8,449 kB of archives.
After this operation, 41.2 MB of additional disk space will be used.
Do you want to continue? [Y/n] 
...
```

```console:s23201@yolopi
$ sudo update-alternatives --config editor
There are 4 choices for the alternative editor (providing /usr/bin/editor).

  Selection    Path                Priority   Status
------------------------------------------------------------
* 0            /bin/nano            40        auto mode
  1            /bin/ed             -100       manual mode
  2            /bin/nano            40        manual mode
  3            /usr/bin/vim.basic   30        manual mode
  4            /usr/bin/vim.tiny    15        manual mode

Press <enter> to keep the current choice[*], or type selection number: 3
update-alternatives: using /usr/bin/vim.basic to provide /usr/bin/editor (editor) in manual mode
```

Pythonのパッケージ管理では一般的にpipを使用しますが、ここではより高速なuvをインストールします。
```console:s23201@yolopi
$ curl -LsSf https://astral.sh/uv/install.sh | sh
downloading uv 0.5.7 aarch64-unknown-linux-gnu
no checksums to verify
installing to /home/s23201/.local/bin
  uv
  uvx
everything's installed!

To add $HOME/.local/bin to your PATH, either restart your shell or run:

    source $HOME/.local/bin/env (sh, bash, zsh)
    source $HOME/.local/bin/env.fish (fish)
$ source $HOME/.local/bin/env
```

パッケージの更新やインストールが終わったら、もう一度リブートしておきます。

### rootパスワードを設定
いざという時に使うので忘れず設定しておきましょう。(1敗)
```console:s23201@yolopi
$ sudo passwd
New password: 
Retype new password: 
passwd: password updated successfully
```

### sudo時にパスワードを要求
なんとRaspberry Piのデフォルトではsudo使用時にパスワードが要求されません。
事故を避けるため、`visudo`を使って`/etc/sudoers.d/010_pi-nopasswd`の内容をコメントアウトします。

```console:s23201@yolopi
$ sudo visudo /etc/sudoers.d/010_pi-nopasswd
```
```diff_shell:/etc/sudoers.d/010_pi-nopasswd
- s23201 ALL=(ALL) NOPASSWD: ALL
+ #s23201 ALL=(ALL) NOPASSWD: ALL
```

## YOLOの環境を用意する
ここからが本題です。先程インストールした`uv`でプロジェクトを作成し、パッケージ`ultralytics[export]`をインストールします。この際、自動的にvenv環境が作成されるので、システムが汚れることはありません。
```console:s23201@yolopi
$ uv init ~/yolov8 && cd $_
Initialized project `yolov8` at `/home/s23201/yolov8`
$ uv add 'ultralytics[export]'
Using CPython 3.11.2 interpreter at: /usr/bin/python3.11
Creating virtual environment at: .venv
Resolved 113 packages in 25.73s
   Built coremltools==8.1
Prepared 79 packages in 2m 42s
Installed 79 packages in 1.24s
 + absl-py==2.1.0
 + astunparse==1.6.3
...
 + wheel==0.45.1
 + wrapt==1.17.0
```

:::note warn
pipを使用するのは避けてください。

どうしてもuvを使用できない場合は、Dockerイメージ[`ultralytics/ultralytics:latest-arm64`](https://docs.ultralytics.com/ja/guides/raspberry-pi/#start-with-docker)から`pip freeze | tee requirements.txt`でパッケージ一覧を出力し、Piで`pip install -r requirements.txt`を実行してください。

`pip install 'ultralytics[export]'`は数十時間かかる上、途中でエラー落ちするリスクがあります。
:::


## 開発環境を用意する
このままSSH越しでvimを使ってコードを書くという選択肢もありますが、これではあまりにしんどいので開発環境を用意しましょう。

<details><summary>ローカルにもPython環境を建てる場合</summary>

[yoloの環境を用意する](#yoloの環境を用意する)と同じ作業をローカルで繰り返してください。
このディレクトリを、好きなエディタで開けば完成です。

注意点として、開発環境で作成したファイルは手動で同期する必要があります。
scp等で転送するか、コードをgit管理下に置いて、開発環境からpushし、Pi側からpullすると良いでしょう。

以下はPi側にbareリポジトリを作成し、push/pullを行えるような環境を構築する例です。

```console:s23201@yolopi | bareリポジトリの作成
$ git init --bare -b main ~/yolov8.git
Initialized empty Git repository in /home/s23201/yolov8.git
```

```console:s23201@yolopi:~/yolov8 | pi側でコミット
$ git branch -m 'main'
$ rm hello.py
$ cat << EOF >> .gitignore

bus.jpg
yolo*.pt
runs/
EOF
$ git config --global user.name 's23201'
$ git config --global user.email 's23201@yolopi.local'
$ git add . && git commit -m 'Initialized repository'
[main (root-commit) e8dc213] Initialized repository
 5 files changed, 2274 insertions(+)
 create mode 100644 .gitignore
 create mode 100644 .python-version
 create mode 100644 README.md
 create mode 100644 pyproject.toml
 create mode 100644 uv.lock
```
```console:s23201@yolopi:~/yolov8 | bareリポジトリにpush
$ git remote add origin ~/yolov8.git
$ git push --set-upstream origin main
Enumerating objects: 7, done.
Counting objects: 100% (7/7), done.
Delta compression using up to 4 threads
Compressing objects: 100% (5/5), done.
Writing objects: 100% (7/7), 79.29 KiB | 5.66 MiB/s, done.
Total 7 (delta 0), reused 0 (delta 0), pack-reused 0
To /home/s23201/yolov8.git
 * [new branch]      main -> main
branch 'main' set up to track 'origin/main'.
```

```console:s23201@local:~ | ローカル側にクローンし開発環境を構築
$ curl -LsSf https://astral.sh/uv/install.sh | sh
downloading uv 0.5.7 x86_64-unknown-linux-gnu
no checksums to verify
installing to /home/s23201/.local/bin
  uv
  uvx
everything's installed!

To add $HOME/.local/bin to your PATH, either restart your shell or run:

    source $HOME/.local/bin/env (sh, bash, zsh)
    source $HOME/.local/bin/env.fish (fish)
$ source $HOME/.local/bin/env
$ git clone s23201@192.168.68.138:/home/s23201/yolov8.git
Cloning into 'yolov8'...
s23201@192.168.68.138's password: 
remote: Enumerating objects: 7, done.
remote: Counting objects: 100% (7/7), done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 7 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (7/7), 79.29 KiB | 3.60 MiB/s, done.
$ cd yolov8
$ uv sync --frozen
Using CPython 3.11.9 interpreter at: /usr/bin/python3.11
Creating virtual environment at: .venv
Prepared 91 packages in 48m 07s
Installed 91 packages in 264ms
 + absl-py==2.1.0
 + astunparse==1.6.3
...
 + wheel==0.45.1
 + wrapt==1.17.0
```

</details>

<details><summary>VSCodeでPiへ接続する場合</summary>

Visual Studio Codeを用いてPiへ接続し、Pi上で直接開発を行います。

`F1`でコマンドパレットを開き、「ssh」と入力したら、`Remote-SSH: ホストに接続する...`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/a011c1ee-19b0-a033-547c-80dd5857fb44.png)

`新規SSHホストを追加する...`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/9f41bc70-1242-2135-e035-660ac47fdfdf.png)

普段接続に使用しているコマンドを入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/8eb4a1e7-bc03-41b0-7ba2-0d6bf45be50e.png)

構成を保存したら、通知から`接続`をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/19b0d8d0-e03c-d2fe-ae00-5b59218a9b59.png)

新しいウィンドウが開きます。ここでパスワードが要求されるので入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/77fe2d93-55b5-9866-e166-80d99bc80312.png)

自動的にVS Code Serverがインストールされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/c4074d9f-98ad-8c31-a5ff-38b59886f90c.png)

`フォルダーを開く`から、ultralyticsパッケージをインストールしたディレクトリを選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/b22638d8-e342-cc4d-a103-7466105f6d02.png)

拡張機能をPi側にインストールします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/927dc5ff-7ee9-0de5-3b7a-cafea4effac0.png)

完了です。ここで書いたコードは、すぐにPiへ反映されます。

</details>

## YOLOv8の動作確認を行う
まずは推論が行えることを確認します。
今回はYOLOv8系モデルのうち、軽量な`yolov8n.pt`を使用します。
```python:~/yolov8/yolov8-test.py
from ultralytics import YOLO
from ultralytics.engine.results import Results

model = YOLO('yolov8n.pt')
results: list[Results] = model(
    'https://ultralytics.com/images/bus.jpg',
    save=True
)
```
```console:s23201@yolopi:~/yolov8
$ uv run yolov8-test.py
Creating new Ultralytics Settings v0.0.6 file ✅ 
View Ultralytics Settings with 'yolo settings' or at '/home/s23201/.config/Ultralytics/settings.json'
Update Settings with 'yolo settings key=value', i.e. 'yolo settings runs_dir=path/to/dir'. For help see https://docs.ultralytics.com/quickstart/#ultralytics-settings.
Downloading https://github.com/ultralytics/assets/releases/download/v8.3.0/yolov8n.pt to 'yolov8n.pt'...
100%|██████████████████████████████████████████████████| 6.25M/6.25M [00:06<00:00, 946kB/s]

Downloading https://ultralytics.com/images/bus.jpg to 'bus.jpg'...
100%|███████████████████████████████████████████████████| 134k/134k [00:00<00:00, 3.45MB/s]
image 1/1 /home/s23201/yolov8/bus.jpg: 640x480 4 persons, 1 bus, 1 stop sign, 602.9ms
Speed: 7.9ms preprocess, 602.9ms inference, 2.3ms postprocess per image at shape (1, 3, 640, 480)
Results saved to /home/s23201/yolov8/runs/detect/predict
```
結果をPi上で開けない場合は、代わりにscpで転送して閲覧します。
```console:s23201@local:~/Downloads
$ scp s23201@192.168.68.138:/home/s23201/yolov8/runs/detect/predict/bus.jpg ./
s23201@192.168.68.138's password: 
bus.jpg                                                  100%  357KB   3.0MB/s   00:00 
```

<details><summary>bus.jpgの例</summary>

![bus.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/0617f4e6-8571-8c58-8a1b-4ca672c8c135.jpeg)

</details>

または、waypipeを使用して、Piで描画したウィンドウをローカルに転送することもできます。

<details><summary>waypipeを使用する例</summary>

```console:s23201@yolopi
$ sudo apt install waypipe
[sudo] password for s23201: 
...
The following NEW packages will be installed:
  waypipe
0 upgraded, 1 newly installed, 0 to remove and 1 not upgraded.
Need to get 78.2 kB of archives.
After this operation, 285 kB of additional disk space will be used.
...
```

```console:s23201@local
$ waypipe ssh s23201@192.168.68.138 eom /home/s23201/yolov8/runs/detect/predict/bus.jpg
s23201@192.168.68.138's password: 

(eom:21645): dbind-WARNING **: 21:58:20.466: AT-SPI: Error retrieving accessibility bus address: org.freedesktop.DBus.Error.ServiceUnknown: The name org.a11y.Bus was not provided by any .service files

(eom:21645): EOM-WARNING **: 21:58:20.504: Error loading Peas typelib: Typelib file for namespace 'Peas', version '1.0' not found


(eom:21645): EOM-WARNING **: 21:58:20.504: Error loading PeasGtk typelib: Typelib file for namespace 'PeasGtk', version '1.0' not found
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3925450/05b3091c-8b9d-a7e2-dae3-d94f88e80710.png)

</details>

## カメラの映像を推論し続ける
カメラの映像を取得し推論させつづけることで、カメラに何が映っているかコンソールから確認できるようにします。

まずはカメラが接続されていることを確認します。
```console:s23201@yolopi
$ v4l2-ctl --list-devices
...
USB2.0 Camera: PC Camera (usb-xhci-hcd.0-2):
        /dev/video0
        /dev/video1
        /dev/media3
```
`/dev/video0`で利用可能だとわかりました。
このインデックス`0`をPython側でも使用します。

カメラは`cv2`の`VideoCapture()`にインデックスを渡すことで取得できます。
カメラの映像は`read()`から得られます。
```python:s23201@local:~/yolov8 | .venv/bin/python
>>> import cv2
>>> camera = cv2.VideoCapture(0)
>>> success, frame = camera.read()
>>> success
True
>>> cv2.imwrite('image.jpg', frame)
True
```
```console:s23201@local:~/yolov8
$ file image.jpg
image.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 640x480, components 3
```


では本体となるコードを準備します。

煩雑なコードは関数化して`util.py`に分割することにします。
```python:~/yolov8/util.py
import cv2

class NoCameraFoundException(Exception):
    pass

class CameraCaptureException(Exception):
    pass

def get_cam() -> cv2.VideoCapture:
    max_try = 20
    for i in range(max_try):
        cam = cv2.VideoCapture(i)
        if cam.isOpened():
            return cam
        cam.release()
    raise NoCameraFoundException(f'Cannot detect available camera; tried {max_try} cameras')

def shot(cam: cv2.VideoCapture) -> cv2.typing.MatLike:
    success, frame = cam.read()
    if not success:
        raise CameraCaptureException('Failed to read frame from camera')
    return frame

def simplify_summary(summaries: list) -> str:
    sorted_summaries = sorted(
        summaries,
        key=lambda x: x['confidence']
    )
    simplified_summaries = [
        f'{summary["name"]}[{round(summary["confidence"] * 100, 1)}%]'
        # f'{summary["name"]}@{summary["class"]}[{round(summary["confidence"] * 100, 1)}%]'
        for summary in sorted_summaries
    ]
    return ', '.join(simplified_summaries)
```

```python:~/yolov8/main.py
import logging
from logging import getLogger

from ultralytics import YOLO
from ultralytics.engine.results import Results

import util

logger = getLogger(__name__)
logging.basicConfig(level='INFO')

model = YOLO('yolov8n.pt')
camera = util.get_cam()

try:
    while True:
        frame = util.shot(camera)
        results: list[Results] = model(frame, verbose=False)
        summary = util.simplify_summary(results[0].summary())
        summary and logger.info(summary)
except KeyboardInterrupt:
    logger.warning('Exiting...')
finally:
    camera.release()
    logger.warning('Camera released')
```

カメラを自分に向けて、`person`と表示されれば成功です。
```console
$ uv run main.py
INFO:__main__:person[71.4%]
INFO:__main__:person[70.4%]
INFO:__main__:person[76.0%]
INFO:__main__:person[77.9%]
INFO:__main__:person[68.6%]
INFO:__main__:person[72.8%]
INFO:__main__:keyboard[26.7%]
INFO:__main__:remote[42.3%]
INFO:__main__:keyboard[40.7%]
INFO:__main__:keyboard[27.5%]
INFO:__main__:remote[50.4%]
INFO:__main__:remote[27.7%]
INFO:__main__:cat[25.0%], laptop[26.6%]
INFO:__main__:person[50.8%]
INFO:__main__:keyboard[41.6%]
INFO:__main__:mouse[36.5%], keyboard[56.9%]
INFO:__main__:cell phone[26.4%], mouse[27.2%], keyboard[52.5%], laptop[63.5%]
INFO:__main__:laptop[27.9%], toothbrush[36.7%], keyboard[57.3%]
INFO:__main__:cell phone[25.7%]
INFO:__main__:cell phone[28.4%], laptop[38.2%], keyboard[52.3%]
INFO:__main__:cell phone[36.1%], cell phone[39.5%], keyboard[48.5%], laptop[56.0%]
INFO:__main__:keyboard[40.8%], laptop[47.8%]
INFO:__main__:laptop[52.9%], keyboard[56.7%]
INFO:__main__:laptop[57.6%], keyboard[58.6%]
INFO:__main__:laptop[31.6%], keyboard[50.3%]
INFO:__main__:book[25.5%], toothbrush[67.8%]
INFO:__main__:toothbrush[56.1%]
INFO:__main__:toothbrush[51.3%]
INFO:__main__:toothbrush[28.9%]
INFO:__main__:tie[36.5%], person[81.9%]
INFO:__main__:person[86.3%]
INFO:__main__:person[82.2%]
...
^CWARNING:__main__:Exiting...
WARNING:__main__:Camera released
```

## システムを改善する
より実用的なシステムを組み上げる際、必要になるかもしれない事項をまとめておきます。

### ロギングを整理する
`dictConfig`やカスタムハンドラー、カスタムフォーマッターを用いて、ロギングを整えられます。
標準出力やファイルへの書き出しだけでなく、調整次第ではWebhookでのログ送信にも対応できます。

### 動画ファイルを書き出す
カメラから映像を取得し、推論しながら書き出すことで、動画ファイルという形で記録を残せます。

録画を推論と同スレッドで実行すると動画のフレームレートが著しく下がるため、`multiprocessing`等で並列処理を行うのが理想です。

cv2で書き出す際、mp4形式などを使用する場合は、`release()`を必ず行ってください。
終了処理が正しく行われないまま、例えばエラーなどでプログラムが終了すると、書き込み途中の動画ファイルは再生できなくなります。

### 設定を変更できるようにする
`python-dotenv`や`pyyaml`等で、設定ファイルを読み込めるようにすると、システムがより柔軟になります。
使用するモデルや各種しきい値、ログの書き出し先などは重要です。

### 安定性を高める
外部ストレージを接続し、Pi本体にOverlayFSを設定することで、SDカードへの書き込みを避けられます。これにより、長期的に運用する場合でもSDカードが故障しづらくなります。