メインコンテンツへスキップ
見出し画像

Raspberry Piでやってみた3(画像処理):YOLOv5を用いたリアルタイム物体検出

    1.概要

     Rasberry Pi×YOLOv5を用いてリアルタイムで物体検出をしてみます。前回の記事では静止画、動画、USBカメラでの利用は確認できました。今回は仮想環境下でカメラモジュールv3を用いてYOLOv5を動かしてみます。

     結論としては「Rasberry Pi4では処理能力が足りないため、普通のPCかJetsonを使用した方が良い」ため、あくまで勉強用となります。

    1-1.YOLOとは

     You only look once (YOLO) とはリアルタイムで高精度に物体を検出するAIモデルです。精度はやや低いけど高速に動作します。

    1-2.Rasberry Piの環境構築

     YOLOv5使用前にRasberry Piの環境構築を実施しておきます。仕様は下記の通りであり、手順の詳細は別記事をご確認ください。

    【仕様】

    • 本体:Rasberry Pi 4

      • CPU:ARM v8

    • Rasberry Pi OS:Debian Bullseys 64bit(Release:2023/5/3)

    • カメラモジュール:Raspberry Pi カメラモジュール V3

    • ラズパイシステムのPython Version:3.9.2

    • その他:Conda環境のためMiniforge追加

    画像

    【Rasberry Piの環境構築】

    1. Raspberry Pi OS(64-bit) :BullseyeをRasberry Pi Imagerで書き込み

    2. ラズパイ初期起動時の設定、インターフェースの有効化

    3. システム更新”sudo apt-get update && sudo apt-get upgrade”を実施

    4. 任意:リモートアクセスの準備

      • ファイルのやり取りができるためVNC Viewerを使用

    5. 任意:VS Code、Miniforgeをインストール

    6. 任意:ソフトウェア(vim, git)を追加

    7. 任意:Linuxコマンドパッケージ(Zip, aptitude)を追加

    8. カメラモジュールの環境構築

      1. 今回はRasberry Pi4×カメラモジュールV3の環境で作成

      2. 動作確認は事前に”libcamera-hello”で確認すること

    [Terminal※Webカメラ動作確認用]
    libcamera-hello

    2.参考:Linuxコマンド

     参考用に使用するLinuxコマンドを参考用として記載します。

    2-1.Linuxコマンド

    1. apt:「Advanced Package Tool」の略で、DebianベースのLinuxディストリビューション(例: Ubuntu, Raspberry Pi OS)でソフトウェアパッケージを管理するためのツールです。aptを使用することで、ソフトウェアのインストール、アップデート、削除などの操作を行うことができます。また、apt-cacheコマンドを使用して、利用可能なパッケージの情報を検索することもできます。

    2. apt-get:DebianベースのLinuxディストリビューション(例: Ubuntu)で使用されるパッケージ管理ツールです。これを使用してソフトウェアのインストール、アップデート、削除などを行います。

      • install:ソフトウェアをインストール

    3. bash:シェルスクリプトを実行するコマンド

      • -b:バッチモードでインストール+確認プロンプトを表示しない。つまりインストールが自動的に進行します。

      • -u:既存のインストールを更新。つまり既にMinicondaがあるなら、そのインストールを最新のバージョンに更新する。

      • -p:インストール先のディレクトリを指定

    4. cd:"change directory"の略。ターミナル上の作業場所を移動する

      • ~:ホームディレクトリを示す。”cd ~”で立ち上げ時のdirに移動

    5. cp:ファイルやディレクトリをコピーするコマンド

    6. df:ディスクの使用量を表示(-hオプションで体裁を綺麗にする)

    7. free:メモリ使用量を表示(-mオプションでMB単位で出力)

    8. ln:ファイルのリンクを作成するためのコマンド

    9. lsb_release:Linux Standard Base (LSB) に関する情報を表示するコマンドであり、-aオプションですべての情報を表示

    10. ls:作業ディレクトリ内のファイルを表示

      1. -lt:作成時間も含めて表示

    11. mkdir:”make directory”の略。新しいフォルダを作成

      • -p:指定したディレクトリが無い場合は作成する。また必要な親ディレクトリも合わせて作成

    12. nano:nanoエディタで指定したファイルを開く

    13. patch:ソースコードにパッチを適用するためのコマンド

      • -p1:パッチファイルのディレクトリ構造をどの程度削除するかを指定->1レベルのディレクトリ構造が削除されます。

      • -i:入力として使用するパッチファイルを指定

    14. rm:"remove"の略で、ファイルやディレクトリを削除するコマンド

      • -r(--recursive):ディレクトリとその内容を再帰的に削除する。ディレクトリを削除する場合はこのオプションが必要

      • -f(--force):エラーメッセージ、警告や確認を表示せずに強制的にファイルやディレクトリを削除

    15. shutdown:コンピューターの電源をオフ/再起動するためのコマンド

      • -rオプション:再起動

    16. sudo:「SuperUser DO」の略で特定のコマンドをシステム管理者(rootユーザー)として実行するためのコマンドです。sudoを使用することで、一時的に高い権限を持つrootユーザーとしてコマンドを実行できます。

    17. tar:ファイルやディレクトリをアーカイブ化、またはアーカイブを展開するためのコマンド(zipみたいに圧縮・展開する)

      • -x:展開

      • -z:gzipで圧縮されたアーカイブを処理

      • -v:詳細モードで動作->処理されているファイルのリストが表示

      • -f:アーカイブファイル名を指定

      • -C:展開先のディレクトリを指定->ホームディレクトリに展開

    18. update:aptのサブコマンドの一つでソフトウェアのリポジトリから最新のパッケージ情報を取得して、ローカルのパッケージデータベースを更新

    19. unzip:ダウンロードしたzipファイルを展開

    20. wget:インターネットからファイルをダウンロード

      • -O:DLしたファイルの保存先と名前を指定

    2-2.Rasberry Pi用コマンド

    1. raspi-config:Raspberry Piのシステム設定ツールを開く

    2. rpi-update:Raspberry Piのファームウェアをアップデート

    2-3.コマンド使用例

     実際のコマンド使用例は下記の通りです。

    1. ls -lt:作成時間も含めて作業ディレクトリ内のファイルを表示

    2. sudo apt update:インストール可能なソフトウェアのパッケージのリストを最新の状態に更新

    3. sudo apt upgrade:インストールされているソフトウェアを最新のバージョンに更新

    4. sudo shutdown -h now:今すぐシャットダウン(電源OFF)

    5. shutdown -r now:今すぐ再起動を実施する

      • ”sudo reboot now”でも再起動可能

    6. python3 -c "import cv2; print(cv2.file)":PythonでOpenCVをimportしてファイルの場所を表示

    7. sudo find / -name "opencv.hpp" 2>/dev/null:システム全体を検索して、OpenCVのヘッダーファイルやライブラリの場所を特定

    8. sudo find / -name "libopencv*.so*" 2>/dev/null:頭に"libopencv"で途中に".so"を持つファイルの場所を検索

    画像

    3.環境構築

     1章でカメラの準備は完了しているため、YOLOv5が使用できる環境の準備だけで問題ございません。

    3-1.YOLOv5の準備

    【YOLOv5の環境構築】
     詳細は下記記事参照のこと

    1. 仮想環境の作成:Miniforgeを用いてcondaで作成

    2. レポジトリをクローン:git clone https://github.com/ultralytics/yolov5

    3. ライブラリのインストール:pip install -r requirements.txt

    4. Pytorchを指定Ver.で再インストール

    4.設計方針

     結論としては下記でいきたいと思います。なおUSBカメラを使用できるならもっと簡単なやり方でできると思います。

    1. (システム環境ではなく)仮想環境で実行

      • システム環境で環境構築すると他のモデルに影響がでるため

      • 環境構築は前章の環境構築で実施済み

    2. libcameraを利用した画像キャプチャ

      • 仮想環境下でPiCamera2の環境構築が困難

    3. Pytorch Hubを利用したモデルの取得

      • 通常モデルだとRasberry Pi4では処理できないくらい重い

     YOLOは様々な種類がありますが、PytorchHubで取り扱いやすく環境構築も実績があるYOLOv5で実装していきます。

    画像
    https://docs.ultralytics.com/models/

    5.実装編

     設計方法も考慮しながら実装していきます。

    5-1.PytorchHubからモデル選択

     Pytorch1.0から導入されたPytorchHubではGitHub上で公開されたモデルをPytorch上で使える仕組みです(詳細は下記記事参照)。

     公式ページに移動しての検索マークに「YOLO」と入力して、YOLOv5モデルを選択するとYOLOV5のページに移動できます。

    画像
    https://pytorch.org/hub/

     モデルのスピードとパラメータ数を見ると"YOLOv5n"(Nano)が最軽量版、"YOLOv5s"(Small)が次に軽量であることが確認できます。

    画像
    https://pytorch.org/hub/ultralytics_yolov5/

     モデルの選択法は①Pathの後ろにモデル名を指定、②ローカルに重み(ptファイル)を保存しておき、'custom'とpathを指定する方法があります。

    [参考API]
    model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)
    model = torch.hub.load('ultralytics/yolov5', 'custom', path='./yolov5s.pt', force_reload=True)

    5-2.libcameraからフレーム取得

     今回のRasberry Pi環境(Debian Bullseys 64bit、カメラモジュールv3)だと、仮想環境からPiCamera2を使用するのが難しいため、libcameraを利用してフレーム取得します。
     詳細に関しては下記記事をご確認ください。

    5-3.スクリプト1:YOLOv5s

     ”YOLOv5s”を使用したモデルは下記の通りです。

    [tyolov.py]
    import cv2
    import numpy as np
    import subprocess
    import torch
    
    # YOLOv5モデルのロード(事前にダウンロードしたモデルを指定)
    model = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True)
    
    # 解像度の設定
    width, height = 640, 480
    
    # libcamera-vidコマンドの設定
    command = [
        'libcamera-vid',
        '--timeout', '0',  # 無限に動画を取得
        '--nopreview',     # プレビューを無効
        '--width', str(width),  # 幅の設定
        '--height', str(height),  # 高さの設定
        '--framerate', '50',  # フレームレートの設定
        '--codec', 'mjpeg',  # MJPEGコーデックを使用
        '-o', '-'  # 標準出力にフレームを出力
    ]
    
    # libcamera-vidプロセスを開始
    process = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=10**8)
    
    # OpenCVウィンドウの設定
    cv2.namedWindow("YOLOv5 Detection", cv2.WINDOW_AUTOSIZE)
    
    frame_buffer = bytearray()
    
    try:
        while True:
            # 標準出力からデータを読み込む
            while True:
                data = process.stdout.read(1024)
                if not data:
                    break
                frame_buffer += data
    
                # JPEGのフレームが終わる0xFF 0xD9(EOIマーカー)を探す
                a = frame_buffer.find(b'\xff\xd8')  # SOIマーカー
                b = frame_buffer.find(b'\xff\xd9')  # EOIマーカー
    
                if a != -1 and b != -1 and b > a:
                    jpg = frame_buffer[a:b+2]  # JPEGフレームの取り出し
                    frame_buffer = frame_buffer[b+2:]  # 次のフレームのためにバッファをクリア
                    frame = cv2.imdecode(np.frombuffer(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
                    break
                # フレームが完全でない場合は、さらに読み込む
                if b == -1:
                    continue
            
            # YOLOv5の推論を実行
            results = model(frame)
            
            # 推論結果を取得
            detections = results.xyxy[0].numpy()
            
            # 検出結果を描画
            for *xyxy, conf, cls in detections:
                label = f'{model.names[int(cls)]} {conf:.2f}'
                cv2.rectangle(frame, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (255,0,0), 2)
                cv2.putText(frame, label, (int(xyxy[0]), int(xyxy[1])-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 2)
            
            # フレームを表示
            cv2.imshow("YOLOv5 Detection", frame)
    
            # 1ミリ秒待って、ESCかEnterが押されたら終了
            if cv2.waitKey(1) & 0xFF in [27, ord('q')]:
                break
    
    finally:
        # プロセスとウィンドウのクリーンアップ
        process.terminate()
        cv2.destroyAllWindows()
    
    [Terminal]
    python3 tyolov.py

     最初カメラは横倒しにしておき、画面が映ったらすぐに縦向きに変更しました。起動までの時間は動画編集しておりますが、起動後は実際の速度としております。

    画像

     あまりに遅すぎて最初はエラーと勘違いするくらいです。検証としてコード内の”results = model(frame)”以下を削除すると通常通りのWebカメラとして動きます。
     つまりYOLOv5での推論が重すぎて処理できていないと判断できます。

    5-4.スクリプト2:YOLOv5n

     ”YOLOv5n”を使用したモデルは下記の通りです。

    [tyolov.py]
    import cv2
    import numpy as np
    import subprocess
    import torch
    
    # YOLOv5モデルのロード(事前にダウンロードしたモデルを指定)
    model = torch.hub.load('ultralytics/yolov5', 'yolov5n', pretrained=True)
    
    # 解像度の設定
    width, height = 640, 480
    
    # libcamera-vidコマンドの設定
    command = [
        'libcamera-vid',
        '--timeout', '0',  # 無限に動画を取得
        '--nopreview',     # プレビューを無効
        '--width', str(width),  # 幅の設定
        '--height', str(height),  # 高さの設定
        '--framerate', '50',  # フレームレートの設定
        '--codec', 'mjpeg',  # MJPEGコーデックを使用
        '-o', '-'  # 標準出力にフレームを出力
    ]
    
    # libcamera-vidプロセスを開始
    process = subprocess.Popen(command, stdout=subprocess.PIPE, bufsize=10**8)
    
    # OpenCVウィンドウの設定
    cv2.namedWindow("YOLOv5 Detection", cv2.WINDOW_AUTOSIZE)
    
    frame_buffer = bytearray()
    
    try:
        while True:
            # 標準出力からデータを読み込む
            while True:
                data = process.stdout.read(1024)
                if not data:
                    break
                frame_buffer += data
    
                # JPEGのフレームが終わる0xFF 0xD9(EOIマーカー)を探す
                a = frame_buffer.find(b'\xff\xd8')  # SOIマーカー
                b = frame_buffer.find(b'\xff\xd9')  # EOIマーカー
    
                if a != -1 and b != -1 and b > a:
                    jpg = frame_buffer[a:b+2]  # JPEGフレームの取り出し
                    frame_buffer = frame_buffer[b+2:]  # 次のフレームのためにバッファをクリア
                    frame = cv2.imdecode(np.frombuffer(jpg, dtype=np.uint8), cv2.IMREAD_COLOR)
                    break
                # フレームが完全でない場合は、さらに読み込む
                if b == -1:
                    continue
            
            # YOLOv5の推論を実行
            results = model(frame)
            
            # 推論結果を取得
            detections = results.xyxy[0].numpy()
            
            # 検出結果を描画
            for *xyxy, conf, cls in detections:
                label = f'{model.names[int(cls)]} {conf:.2f}'
                cv2.rectangle(frame, (int(xyxy[0]), int(xyxy[1])), (int(xyxy[2]), int(xyxy[3])), (255,0,0), 2)
                cv2.putText(frame, label, (int(xyxy[0]), int(xyxy[1])-10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255,255,255), 2)
            
            # フレームを表示
            cv2.imshow("YOLOv5 Detection", frame)
    
            # 1ミリ秒待って、ESCかEnterが押されたら終了
            if cv2.waitKey(1) & 0xFF in [27, ord('q')]:
                break
    
    finally:
        # プロセスとウィンドウのクリーンアップ
        process.terminate()
        cv2.destroyAllWindows()
    
    [Terminal]
    python3 tyolov.py

     最初カメラは横倒しにしておき、画面が映ったらすぐに縦向きに変更しました。起動までの時間は動画編集しておりますが、起動後は実際の速度としております。

    画像

     使えるレベルにはなりましたがかなり遅いです。
     ヌルヌルとした感じで使いたい場合、やはりシンプルなのは性能の良いPCやJetsonのようなGPU付きシングルボードを使用する方が初学者は楽だと思います。



    参考資料

    あとがき

     とりあえずRasberry Piでの画像関係は終わり!
     MediaPipeと動画の画像処理を終わらせてから次いこう

     
     

    KIYO

     
     
    普段は製造業で企画/開発/設計しております。記事はプログラミング・機械学習、IoT関係の記事をメインで作成し、なるべく1つの記事で知りたいことを網羅していきます。内容は学術的より実装・アウトプット(ほしくなるもの)を重視して作成しています。 面白そうな仕事があればやりたいです!

    あなたへのおすすめ