[6日目] 学校の食堂をIT化させる話　混雑度カメラを実際に作る

こんにちは、なりかくんと申します。
この記事はなりかくん Advent Calender 2023の6日目の記事です。

この話は、1日目から始めた学校の食堂をIT化させる話の続きとなります。前回までで、混雑度カメラの構成などを考えました。
今回から実際に作っていった内容について説明していきます。

実際には事前に動かしているのですが、その時のスクリーンショットや履歴等が無いので覚えていることをただひたすらに書いてきます。

# Raspberry Piのセットアップ
まず最初にRaspberry Piが届いたら、Raspberry Pi ImagerでOSデータを書き込んでいきます。
最初は、UbuntuのCUI版をインストールしたのですがWiFiの設定がうまくいきませんでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549951/fb4aab1b-fcb5-bd14-5a09-d5848f32b56c.png)


うまくいかなかった原因は、学校のWiFiにあります。私の学校のWiFiは「WPA2 Enterprise」という暗号化（認証）方式がされていて、ドメインと利用者ごとのIDとパスワードで認証を行います。
ですが、私はあまりLinuxの知識が無いため、WiFiの設定をNetworkManagerのコマンドから行うのが難しい問題でした。（特に証明書関係が知識不足）

そのため、CUI版でのセットアップを諦めました。また、普段からVPSなどではUbuntuを使っていたのでUbuntuを無意識に選んでいたのですが、「Raspberry PiならRaspberry Pi OSを使えばいいじゃないか。」という結論に至りました。

そして、Raspberry Pi OS Desktop版をインストールするわけです。
（GUI上では難なくWiFiの設定等完了しました。）

# 外部からいつでもアクセスできるようにしておく
混雑度カメラは、IoT製品であり一度設置すれば何も問題が無い限りその場所から動かしたりすることはありません。
ですが、途中でプログラムが動作しなくなってしまったり更新を行いたい場合に「Raspberry Piを動かしてディスプレイを繋いで...」という作業はしたくありません。

そこで、外部からいつでもアクセスできるようにしておきます。ですが、ポート開放等をするのはセキュリティ的にも問題があります。（まず学内ネットワークで出来ない）

そこで、Cloudflare Zero Trustを使うわけです。

:::note info
**Zero Trustとは？**
Zero Trustセキュリティとは、ネットワーク境界（ソフトウェア定義の境界）の内側にいるか外側にいるかに関係なく、内部リソースにアクセスしようとしているすべての人物とデバイスのIDの厳格な検証を必要とするセキュリティモデルのことです。

※引用元 : https://www.cloudflare.com/ja-jp/learning/security/glossary/what-is-zero-trust/
:::

その中でも今回は、Cloudflare Accessという無料で使えるVPNのようなサーバーにアクセスすることが出来るようになる製品を使います。

このCloudflare Accessを利用すると、Cloudflareが用意してくれている認証方法で認証しないと、まずサーバーにアクセスすることが出来ず、Cloudflareとサーバー間ではポート開放をせずにセキュリティに保たれた通信を行い、安全に通信することが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/549951/8bea8070-7119-0f37-4ea6-48b21fd78460.png)

詳しくは、Googleなどで「Cloudflare Access」と調べた方が分かりやすいと思います。
（解説投げやり）

# Pythonのライブラリのインストール
今回、混雑度を測るプログラムはPythonで書きます。Raspbery Pi OSには標準でPython 3.11がインストールされている（2023年10月ぐらい現在）ので、それをそのまま使います。
ライブラリは、YOLOv8とOpenCVが使えるようにそれぞれインストールします。

確かコマンドはこんな感じだった気がします。
```shell
pip install opencv-python
pip install ultralytics
```

そして、普通に物体認識プログラムを動かそうとしてもRaspberry Piが原因なのかエラーが出ます。
確か、「segmentation fault」みたいなエラーでした。

原因はなんかパッケージの依存関係みたいです。いろいろ調べてみるとultralyticsのドキュメントにRaspberry Piのセットアップガイドがありました。
YOLOv8でRaspberry Pi 4では不要と書いてあったコマンドですが、torchとtorchvisionのバーションを下げてみるとしっかり動くようになりました。

https://docs.ultralytics.com/guides/raspberry-pi/#quick-start-with-yolov5

いやー、謎ですよね。まあ、動くようになったので一段落です。

汚いコードですが、一応このようなコードになっています。（長くなるので不要そうなコードは削除してます。）
これで人間（person）だけを数えるプログラムが完成です。
```python
import cv2
from time import sleep
import requests
from ultralytics import YOLO

cap = cv2.VideoCapture(0)

cap.set(cv2.CAP_PROP_FRAME_WIDTH, 960)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 540)

model = YOLO("yolov8n.pt")

counter = 0

while(cap.isOpened()):
    counter = 0
    ret, frame = cap.read()
    results = model(frame)
    names = results[0].names
    classes = results[0].boxes.cls
    boxes = results[0].boxes
    annotatedFrame = results[0].plot()
    for box, cls in zip(boxes, classes):
        name = names[int(cls)]
        x1, y1, x2, y2 = [int(i) for i in box.xyxy[0]]
        if name != "person":
            continue
        cv2.rectangle(frame,(int(x1),int(y1)),(int(x2),int(y2)),(255,0,0),2)
        counter = counter + 1
    cv2.putText(frame, str(counter), (0, 50), cv2.FONT_HERSHEY_PLAIN, 4, (0, 0, 0), 5, cv2.LINE_AA)
    cv2.imwrite("image.png", frame)
    binary = open("image.png", 'rb').read()
    files = { "image": ("image.png", binary, "image/png") }
    # リクエスト処理
    sleep(1)

cap.release()
cv2.destroyAllWindows()
```

# 最後に
まあ、今回の記事は結構文字多めでしたがいかがでしたか。
最後までお読みいただきありがとうございました。