Raspberry Pi AI Cameraの出力をBIツールで可視化してみよう

# はじめに

ソニーセミコンダクタソリューションズ株式会社の萩尾と申します。

近年、エッジAI技術の進歩により、小型デバイス上でのリアルタイムなデータ処理が可能となり、様々な分野での応用が広がっています。その中でも、Raspberry Pi AI Cameraは、ソニーのIMX500センサを搭載し、画像認識などAIモデルの推論を行うことができるエッジデバイスとして注目されています。

データの収集・解析・可視化の重要性が増しており、ビジネスや研究の現場では、データを適切に管理し、迅速に意思決定を行うためのツールとしてBI (Business Intelligence) ツールの活用が求められています。特にGrafanaのようなBIツールは、リアルタイムデータの可視化や分析が可能であり、エッジデバイスからのデータ監視、運用状況の把握など幅広い用途に活用されています。

本記事では、Raspberry Pi AI Cameraを用いて、物体検出によるタグとスコアを取得し、それらのデータをMy SQLデータベースに格納、さらにBIツールであるGrafanaを活用し可視化する簡単なサンプルアプリケーションを紹介します。ぜひ最後までご覧ください。

![この記事で作成するダッシュボードのサンプル](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/0784f47b-54a4-4db6-b900-5402c69014d9.png)

# 1. セットアップ

まずはRaspberry PiとRaspberry Pi AI Cameraのセットアップを行いましょう。

## 1.1. Raspberry Pi OSの書き込み
Raspberry Pi OS Imager ([ダウンロード先](https://www.raspberrypi.com/software/)) を使うことで、SDカードへのOSイメージの書き込みができます。Windows、MacOS、Ubuntu対応です。
手順は[こちら](https://www.raspberrypi.com/software/)を参照ください。

## 1.2. AI Cameraの接続

Raspberry Pi公式ドキュメントに沿って進めていきます。[こちらの記事](https://qiita.com/daiki0321/items/4223611adb453d6c7aa2)も参考になります。  
Picamera2のサンプルアプリケーションが動作すればAI Cameraのセットアップは完了です。

https://www.raspberrypi.com/documentation/accessories/ai-camera.html

## 1.3. Docker

本記事ではDockerを使用してMy SQLとGrafanaを立ち上げます。  
公式ドキュメントに沿ってDockerをRaspberry Piにインストールしましょう。

https://docs.docker.com/engine/install/debian/

以下のスクリプトを実行するとDockerがインストールできます。

<details><summary>Docker インストール用スクリプト</summary>

```bash
for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do sudo apt-get remove $pkg; done

sudo apt update
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

echo \
    "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
    $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
    sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update

sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

sudo groupadd docker
sudo usermod -aG docker $USER
newgrp docker

# Dockerの動作確認
docker run hello-world
```
</details>

# 2. My SQLのセットアップ

それでは、My SQLをセットアップしましょう。今回はdocker composeを使用してコンテナを立ち上げます。

1. プロジェクトフォルダを作成

    ```bash
    mkdir ~/sample_BI_tool
    cd ~/sample_BI_tool
    ```

2. docker-compose.ymlの作成

    docker-compose.ymlではアプリケーションの立ち上げとユーザー名やパスワードの設定をしています。  
    また、My SQLだけでなくGrafanaも同時に立ち上げるように設定しました。

    <details><summary>docker-compose.yml</summary>

    ```yaml:docker-compose.yml
    services:
      mysql:
        image: mysql:8.0
        container_name: mysql
        restart: always
        environment:
          MYSQL_ROOT_PASSWORD: rootpassword
          MYSQL_DATABASE: detection_data
          MYSQL_USER: user
          MYSQL_PASSWORD: userpassword
        ports:
          - "3306:3306"
        volumes:
          - ./mysql_data:/var/lib/mysql
        security_opt:
          - no-new-privileges=false

      grafana:
        image: grafana/grafana:11.5.2
        container_name: grafana
        restart: always
        ports:
          - "3000:3000"
        environment:
          - GF_SECURITY_ADMIN_USER=user
          - GF_SECURITY_ADMIN_PASSWORD=userpassword
        volumes:
          - ./grafana_data:/var/lib/grafana
    ```
    </details>

3. My SQLとGrafanaの起動

    事前に必要なフォルダを作成します。

    ```bash
    mkdir mysql_data
    mkdir grafana_data

    chmod 777 mysql_data
    chmod 777 grafana_data
    ```

    次に、Docker Composeを使用してコンテナを起動しましょう

    ```bash
    docker compose up -d
    ```

    `docker ps -a`でMy SQLとGrafanaのコンテナが起動しているか確認しましょう。

    ![コンテナが起動している例](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/e5e09d24-f88b-49ad-9ef3-5be04d247324.png)

4. テーブルの作成

    立ち上げたMy SQLコンテナに接続し、物体検知の推論結果を格納するデータベースとテーブルを作成します。

    接続に必要なユーザー名とパスワードはdocker-compose.yml内で設定しました。  
    rootユーザーの場合のパスワードは`rootpassword`です。

    ```bash
    docker exec -it mysql mysql -u root -p
    ```
    
    ログイン後、推論結果を格納するテーブルを作成します。

    ```sql
    /* 操作するデータベースの選択 */
    USE detection_data;

    /* テーブルの作成 */
    CREATE TABLE detections (
      id INT AUTO_INCREMENT PRIMARY KEY,
      label VARCHAR(255),
      score FLOAT,
      timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
    );

    /* ログアウト */
    exit
    ```

    以上で推論結果を格納するデータベースの構築が完了しました。

# 3. AI Cameraを使った物体検知

GitHub上で公開されている[AI Camera向けModel Zoo](https://github.com/SonySemiconductorSolutions/aitrios-rpi-model-zoo)からスクリプトとAIモデルをお借りして実装していきます。

作成するフォルダの構成は以下の通りです。

```bash
# 新たに用意するファイルは、app.py, coco_labels_80.txt, network.rpk
# 上記以外は前のセクションで作成済み

~/sample_BI_tool
├── app.py
├── assets
│   └── coco_labels_80.txt
├── network.rpk
├── docker-compose.yml 
├── grafana_data
└── mysql_data
```

1. [AI Camera向けModel Zoo](https://github.com/SonySemiconductorSolutions/aitrios-rpi-model-zoo)をclone

    ```bash
    git clone https://github.com/SonySemiconductorSolutions/aitrios-rpi-model-zoo.git
    ```

2. 仮想環境のセットアップ

    My SQLをPythonスクリプトで操作するためにパッケージをインストールする必要があります。

    ```bash
    python -m venv .venv --system-site-packages
    source .venv/bin/activate

    # 仮想環境内
    pip install mysql-connector-python==9.2.0
    ```

    以降は仮想環境内で実行していきます。

3. 物体検知およびデータベースへ格納するスクリプトの実装

    ソースコードはModel Zooから[物体検知のスクリプト](https://github.com/SonySemiconductorSolutions/aitrios-rpi-model-zoo/tree/main/models/object-detection/nanodet_plus_416x416)をコピーして使用します。

    コピーしてきたスクリプトにMySQLの接続情報と推論結果をデータベースに格納するinsert_detection関数を追加します。  
    My SQL操作の詳細は省きますが、推論結果から取り出したタグ情報とスコア情報をデータベースに格納しています。

    最終的な`app.py`は以下になります。

    <details><summary>app.py</summary>

    ```python:app.py
    import time
    import cv2

    from picamera2.devices.imx500 import IMX500
    from picamera2.devices.imx500 import (NetworkIntrinsics, postprocess_nanodet_detection)
    from picamera2 import MappedArray, Picamera2

    import mysql.connector


    # 【追加】My SQLデータベースの接続情報
    DB_CONFIG = {
        "host": "localhost",
        "user": "user",
        "password": "userpassword",
        "database": "detection_data",
    }
    THRESHOLD = 0.55
    IOU = 0.65
    MAX_DETECTION = 10

    last_detections = []
    with open("./aitrios-rpi-model-zoo/assets/coco_labels_80.txt", "r") as f:
        labels = f.read().split("\n")


    class Detection:
        def __init__(self, coords, category, conf, metadata):
            """Create a Detection object, recording the bounding box, category and confidence."""
            self.category = category
            self.conf = conf
            self.box = imx500.convert_inference_coords(coords, metadata, picam2)


    def parse_and_draw_detections(request):
        """Analyse the detected objects in the output tensor and draw them on the main output image."""
        detections = parse_detections(request.get_metadata())

        # 【追加】物体が検知された場合、データベースに格納する関数を呼び出す
        if len(detections) > 0:
            for detection in detections:
                label = labels[int(detection.category)]
                score = detection.conf
                insert_detection(label, score)

        draw_detections(request, detections)


    def insert_detection(label, score):
        """【追加】検知された物体のlabelとscoreの情報をデータベースに格納する関数"""
        try:
            conn = mysql.connector.connect(**DB_CONFIG)
            cursor = conn.cursor()
            cursor.execute(
                "INSERT INTO detections (label, score) VALUES (%s, %s)", (label, score)
            )
            conn.commit()
            print(f"Inserted: {label} with score: {score}")
        except mysql.connector.Error as err:
            print(f"Error: {err}")
        finally:
            cursor.close()
            conn.close()


    def parse_detections(metadata: dict):
        """Parse the output tensor into a number of detected objects, scaled to the ISP out."""
        global last_detections

        np_outputs = imx500.get_outputs(metadata, add_batch=True)
        input_w, input_h = imx500.get_input_size()
        if np_outputs is None:
            return last_detections
        boxes, scores, classes = \
            postprocess_nanodet_detection(outputs=np_outputs[0], conf=THRESHOLD, iou_thres=IOU, max_out_dets=MAX_DETECTION)[0]
        from picamera2.devices.imx500.postprocess import scale_boxes
        boxes = scale_boxes(boxes, 1, 1, input_h, input_w, False, False)

        last_detections = [
            Detection(box, category, score, metadata)
            for box, score, category in zip(boxes, scores, classes)
            if score > THRESHOLD
        ]
        return last_detections


    def draw_detections(request, detections, stream="main"):
        """Draw the detections for this request onto the ISP output."""
        with MappedArray(request, stream) as m:
            for detection in detections:
                x, y, w, h = detection.box
                label = f"{labels[int(detection.category)]} ({detection.conf:.2f})"
                cv2.putText(m.array, label, (x + 5, y + 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 255), 1)
                cv2.rectangle(m.array, (x, y), (x + w, y + h), (0, 0, 255, 0))
            b_x, b_y, b_w, b_h = imx500.get_roi_scaled(request)
            cv2.putText(m.array, "ROI", (b_x + 5, b_y + 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1)
            cv2.rectangle(m.array, (b_x, b_y), (b_x + b_w, b_y + b_h), (255, 0, 0, 0))

    if __name__ == "__main__":
        # This must be called before instantiation of Picamera2
        imx500 = IMX500("./aitrios-rpi-model-zoo/models/object-detection/nanodet_plus_416x416/network.rpk")

        picam2 = Picamera2()
        config = picam2.create_preview_configuration(controls={"FrameRate": 1}, buffer_count=28)
        
        imx500.show_network_fw_progress_bar()
        picam2.start(config, show_preview=True)
        imx500.set_auto_aspect_ratio()
        picam2.pre_callback = parse_and_draw_detections
        while True:
            time.sleep(0.5)
    ```
    </details>

4. `app.py`の実行

    Model Zooレポジトリのcloneと`app.py`の準備ができたら、以下のスクリプトで実行してみましょう。

    ```bash
    python app.py
    ```

    COCOデータセット[^1]の画像を画面越しに撮影して、`Inserted: person with score: 0.622459352016449`のような出力が確認できれば正しくデータベースに推論結果を格納できています。

    ![app.pyの実行ログ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/61a8618f-38ed-4a53-bc01-0d0bd48ea2b9.png)

# 4. Grafanaで可視化してみよう

いよいよGrafanaを使って推論結果を可視化してみましょう。

## 4.1. Grafanaのセットアップ

1. Grafanaへログイン

    既にGrafanaコンテナは立ち上げてあるので、Webブラウザで`localhost:3000`にアクセスするとログインページが表示されます。   
    docker-compose.yml内で設定したユーザー名`user`とパスワード`userpassword`でログインします。

2. データソースの追加

    Grafanaが参照するデータベースをMy SQLに設定します。  
    ページ左のメニューバーから Data Sourcesページに移動し、My SQLを追加します。

    ![add_mysql_as_data_source.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/561c7b85-e619-445f-838b-b713212db211.gif)

    MySQLを選択すると設定ページが表示されます。  
    `docker-compose.yml`内で設定したデータベースの接続情報を記入していきます。  

    入力する情報は以下の通りです。

    | 設定項目      |         設定値 |
    | :------------ | -------------: |
    | Host URL      |     mysql:3306 |
    | Database name | detection_data |
    | Username      |           user |
    | Password      |   userpassword |


    ![接続情報の入力](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/7ad46bf8-b665-4bd6-b103-ed61722e817a.png)
    
    入力したらページ下部の`Save & test`をクリックします。

    ![データベースの接続確認](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/465fb4e6-d7c7-45ff-b771-5800dc596af2.png)

3. ダッシュボードの追加

    データを表示するダッシュボードを作っていきます。

    サイドバーの`Dashboards`を選択し、"Create dashboard > Create dashboard > Add visualization"の順でクリックします。  
    データソースを選択する画面でMy SQLを選択し、パネル編集ページを開きます。

    ![open_panel_editer.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/92d1c51a-5702-469d-b49d-b3e5f5ac4e7e.gif)

## 4.2. パネルを追加してみよう

まずは、検知されたクラスの数を集計し上位5クラスのクラス名と検知数を表示するパネルを作成してみます。

右のサイドバーから "Bar chart"を選択し、My SQLのクエリを入力する画面を開きます。

![open_mysql_code_space.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/522dcf68-a521-4848-88a4-282165a825e8.gif)

開いたコードスペースに以下のクエリを入力すると、上部にグラフが確認できます！

```sql
SELECT label, count(*) AS total_count FROM detection_data.detections GROUP BY label ORDER BY total_count DESC LIMIT 5;
```

![bar_chart.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/538f96aa-ffac-4fa3-bbf5-844f3e75941d.png)

グラフが確認できれば、画面上部の "Back to dashboard" をクリックし、"Save dashboard" をクリックして保存します。

## 4.3. TreeMapを使ったパネルを追加してみよう

BIツールの強みと言えば視覚的に情報を確認できる部分です。  
先ほどのセクションで作成したバーチャートとは別の方法で可視化してみましょう。

1. プラグインページからTreeMapプラグインをインストール

    ![install_Treemap.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/3fe7163d-249a-4356-a690-5f5263938e39.gif)

2. パネルの追加

    ダッシュボードを開き、新しいパネルを追加します。右のサイドバーからTreemapを選択し、My SQLのコードスペースを開きます。

    ![open_edit_panel_treemap.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/18173a91-ccf6-4450-8aeb-a11a7649da5a.gif)

3. クエリを入力し、パネルを完成させる

    コードスペースに以下のクエリを入力し "Run query" をクリックすると、上部にグラフが確認できます。Treemapでは最大上位20クラスを可視化しています。

    ```sql
    SELECT label, count(*) AS total_count FROM detection_data.detections GROUP BY label ORDER BY total_count DESC LIMIT 20;
    ```

    さらに、右のサイドバーでパネルを操作して、以下のように設定していきます。設定が完了したらダッシュボードに戻り、保存します。

    | 設定項目                   |           設定値 |
    | :------------------------- | ---------------: |
    | Treemap > Tiling algorithm |           Binary |
    | Dimensions > Label by      |            label |
    | Dimensions > Size by       |      total_count |
    | Dimensions > Color by      |      total_count |
    | Standard options           | Green-Yellow-Red |

## 4.4. 物体検知を実行しながら可視化してみよう

実行前にGrafanaの自動読み込みを`5s`に設定します。

![auto_refresh](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/597a4b53-1982-4b07-8c5c-2f44b1a7506b.png)

完成したGrafanaのダッシュボードを使って物体検知の推論結果による変化を確認しましょう。

```bash
python app.py
```

pythonスクリプトを実行し、物体検知を実行してみると検知結果に応じてパネルが変化していることが確認できます。

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3992485/b6f5fbe3-640f-4461-a422-647ab5aea0c8.gif)

# おわりに

本記事では、Raspberry Pi AI Cameraを活用し、物体検出のデータをMy SQLに保存し、Grafanaで可視化する方法を紹介しました。エッジデバイスで得られた情報をBIツールで可視化することで、データの活用範囲が広がることが分かりました。  
今回のシステムは基本的なサンプルですが、データの蓄積や分析を深めることで、より高度な可視化やアラート機能の追加、他のBIツールとの連携など、さらに発展させることができます。また、クラウドと連携することで、より広範なデータ活用の可能性も広がります。  
まだまだ拡張性があるので、ぜひ試してみてください。

# 困った時は

もし、記事の途中でうまくいかなかった場合は、気軽にこの記事にコメントいただくか、以下のサポートのページもご覧ください。  
コメントのお返事にはお時間を頂く可能性もありますがご了承ください。

<!-----------------RasPiに関する記事が含まれる場合は下記を追加してください。------------------->
Raspberry Piに関連する疑問がある場合は、下記フォーラムを確認、活用ください。
- [Raspberry Pi Forums](https://forums.raspberrypi.com/)


[^1]: Microsoft COCO: Common Objects in Context[2014] https://arxiv.org/abs/1405.0312