Jetson Nanoをネットワークカメラ(grpc)にしたときの備忘録

# はじめに
Jetson NanoにRaspberry Piカメラを接続し、ネットワークカメラのようなものを作りました。
Jetson Nano上で撮影、圧縮を行い、gRPCサーバで配信します。
また、systemdを用いてJetson Nano起動時に自動的に撮影機能・サーバ機能が起動するようにしました。
クライアント側からのリクエストで最新の撮影画像を取得できます。

# ソースコード
[Github](https://github.com/noueezy/cvgrpc)にアップしています。
今回はサービス間通信に初めてgRPCを使ってみました。サーバ・クライアントともにpythonで実装しました。
gRPCでの実装自体はいろんなサイトで説明されているとおりでしたので、ここでは特に説明はしません。

# 詰まったところ（備忘録）
実装時に悩んだところを備忘録的に書いておきます。

## 画像のエンコード・デコード
今回の実装では、Jetson Nano側で撮影したあと、OpenCVのimencode関数でJpg圧縮したものをgRPCで配信するようにしています。

```python
encode_param = [int(cv2.IMWRITE_JPEG_QUALITY), 50]
#imgはOpenCVの画像データ型
result, encimg = cv2.imencode('.jpg', img, encode_param)
b64e = base64.b64encode(encimg)
```

grpcのクライアント側でデータを受信したあと下記のコードでデコードしようとしましたがうまくいきませんでした。

```python
#response.dataはgrpcで受信した画像データ
jpg = base64.b64decode(response.data)
img = cv2.imdecode(jpg, cv2.IMREAD_COLOR)
```

正しく動作させるには一行、numpy.frombufferを挟む必要がありました。

```python
jpg = base64.b64decode(response.data)
jpg = np.frombuffer(jpg, dtype=np.uint8)
img = cv2.imdecode(jpg, cv2.IMREAD_COLOR)
```

[この記事](https://qiita.com/TsubasaSato/items/908d4f5c241091ecbf9b)を参考にしました。

## Systemd登録
Jetson Nanoの起動のたびに撮影・サーバスクリプトを実行するのは面倒なので、
電源投入後に自動でスクリプトが起動するようにSystemdにサービスを登録します。
下記のようなサービスを作成しました。

```systemd
[Unit]
Description = camera daemon

[Service]
ExecStart = /opt/camera.sh
Restart = always
Type = simple

[Install]
WantedBy = multi-user.target
```

ExecStartに記述したシェルスクリプトにはサーバスクリプト(jetson_nano_camera_server.py)の起動コマンドを記入しています。

```sh
#!/bin/bash

python3 /path/to/jetson_nano_camera_server.py
```

この状態でサービスを起動しステータスを確認すると、起動に失敗していました。

```terminal
Loaded: loaded (/etc/systemd/system/camera.service; enabled; vendor preset: enabled)
Active: failed (Result: exit-code) since Sun 2021-03-14 22:13:08 JST; 1min 55s ago
Process: 7090 ExecStart=/opt/camera.sh (code=exited, status=1/FAILURE)
Main PID: 7090 (code=exited, status=1/FAILURE)
```

/var/log/syslogでログを確認したところ、次のようなエラーが出ていました。

```
Mar 14 22:13:08 xxxxxxxx camera.sh[7090]: Traceback (most recent call last):
Mar 14 22:13:08 xxxxxxxx camera.sh[7090]:   File "/path/to/jetson_nano_camera_server.py", line 7, in <module>
Mar 14 22:13:08 xxxxxxxx camera.sh[7090]:     import grpc
Mar 14 22:13:08 xxxxxxxx camera.sh[7090]: ModuleNotFoundError: No module named 'grpc'
```

pythonスクリプトのgprcのインポートで失敗しています。
camera.shを直接起動するとサーバは正常に起動するのに、systemdだと何故かうまく起動しませんでした。
grpcioはインストールしているはずなので、pip3 show grpcioでパッケージの情報を確認してみました。

```terminal
Name: grpcio
Version: 1.36.1
Summary: HTTP/2-based RPC framework
Home-page: https://grpc.io
Author: The gRPC Authors
Author-email: grpc-io@googlegroups.com
License: Apache License 2.0
Location: /home/USER_NAME/.local/lib/python3.6/site-packages
Requires: six
Required-by: grpcio-tools
```

Locationがユーザディレクトリになっていました。もしかして？と思い、Systemdのサービスにユーザ名の項目を追加してみました。

```systemd
[Unit]
Description = camera daemon

[Service]
ExecStart = /opt/camera.sh
Restart = always
Type = simple
User = USER_NAME

[Install]
WantedBy = multi-user.target
```

この状態でサービスを起動、ステータスを確認すると、正常に起動していることを確認できました。

```terminal
   Loaded: loaded (/etc/systemd/system/camera.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2021-03-14 22:35:16 JST; 8s ago
 Main PID: 9221 (camera.sh)
    Tasks: 19 (limit: 4183)
   CGroup: /system.slice/camera.service
           ├─9221 /bin/bash /opt/camera.sh
           └─9233 python3 /path/to/jetson_nano_camera_server.py
```

再起動時に自動でサーバが起動することを確認できました。

# 終わりに
Jetson Nanoをネットワークカメラ化してみました。
gRPCによる通信、systemdでのサービスの設定などは興味がありつつも触れてこなかったところなので
わからないこともありましたがなんとか思った通りのものになりました。