AIワンダーランド

AIエンジニアの個人開発ブログ

【YOLOv8】WindowsでWEBカメラからリアルタイム物体検出してメッセージボックス表示

更新日:2023年6月3日

 

概要

 

【プロジェクトにゃーん4】

家の近くを散歩するネコちゃんをリアルタイムで検出して

アラートを飛ばしたい!!

 

さて、前回は【YOLOv8】でWEBカメラからリアルタイム物体検出できるとこまでやりましたので、今回は、出力結果からアラートを飛ばしてみたいと思います。

ai-wonderland.com

 

環境

環境:Winows11、メモリ32GB、GPU GeForce RTX3060 12GB

Python : 3.10.6、CUDA : 11.8、torch : 2.0.1+cu118

webcamera:Logicool(ロジクール) C270n

よくあるWEBカメラです。

2,500円くらいと安いけど使いやすくておすすめです。

後継版C310n ↓↓

 

※GPUをこれから購入を考えている方は

このRTX3060の12GB版の一択になるかと思います。(2023年6月時点)

理由は、コスパが最強なので!!

 

画像生成Stable Diffusionを使う場合、最低メモリ12GBほしいところなのですが、けっこうお高い買い物になるため躊躇されるかもしれません。

しかし!!こちらは5万円を切る圧倒的安さ!

ちなみに私は上位のRTX3070superだったのですが、メモリが8GBと画像生成には不足していたのでダウングレードしてこちらに乗り換えました。

交換作業は簡単なのですが、自分のPCに取り付け可能かは確認してくださいね。

重要なのは電源容量と、接続PIN数、空間です。

 

手順

【YOLOv8】の設定は前回の記事↑を見てね。

 

1.リアルタイム検出の出力結果保存

さて、どのように出力できるか調べてみます。

 

predictのオプション詳細がdocsの中、下記にありました。

https://github.com/ultralytics/ultralytics/blob/main/docs/modes/predict.md

 

パラメータを直訳するとこんな感じです。

 

  • save_txt=True 出力結果を保存
  • save_conf=True 確信度も必要になりそうなのでこちらもTrueに
  • vid_stride=1800

あと物体検出の回数が多すぎるのでそこを制御しないとですね。

フレームレート(fps)は1秒間の動画が何枚の画像で構成されているかを示す単位なので、こちらで制御できそうかな。

WEBカメラはよくある「30fps」を使っているので1秒で30枚ですね。

 

ただ。。どのように使うかは書かれていなさそうなので

とりあえず引数に追加して試してみますかね。

デフォルトは1っぽいので

とりあえず、1800フレームで1分なので1800にしてみました。

 

from ultralytics import YOLO
model = YOLO("yolov8x.pt")
model.to("cuda")

# WEBカメラからリアルタイム検出
results = model(0 , show=True, save_txt=True, save_conf=True, vid_stride=1800)
for i in enumerate(results):
    print(i)

 

「runs/detect/predict/labels」に一応出力されました!

 

中身はこんな感じ

 

一番左の番号が物体番号っぽい

続いて座標、一番右が確信度ですね。

 

vid_stride=1800にしたことで

画面上の検出は1分に1回になってたようだが

出力txtが1000ファイル以上出ちゃってる

結局検出してるじゃん。

 

vid_stride=1800はshowの設定か、別の制御がいるのね。。

 

2.物体検出頻度の制御

苦戦しました。。

  • subprocessで呼び出す方法 ➡失敗
  • importで「webcam.py」を呼び出しintervalで定期起動する方法 ➡失敗
  • ultralytics\yolo\engineの「results.py」を修正 ➡成功

   10秒に1回検出するようにしました。

 

まずwebcam.pyを作ります。

画面で確認する必要はないのでshowは削除。

 

webcam.py

from ultralytics import YOLO
#model = YOLO("yolov8x.pt")
model = YOLO("yolov8m.pt")
model.to("cuda")

# WEBカメラからリアルタイム検出
results = model(0, show=True, save_txt=True, save_conf=True)
for i in enumerate(results):
    print(i)

 

【追記】

「YOLOv8x」で動かしていましたが、けっこうGPU使用量が大きいので

軽い「YOLOv8m」の方が無難です。猫検出くらいならmでも十分だと思います。

 

ultralytics\yolo\engineの「results.py」

import timeとtime.sleep(10)を追記します。

    def save_txt(self, txt_file, save_conf=False):
        """
        Save predictions into txt file.

        Args:
            txt_file (str): txt file path.
            save_conf (bool): save confidence score or not.
        """
        boxes = self.boxes
        masks = self.masks
        probs = self.probs
        kpts = self.keypoints
        texts = []
        if probs is not None:
            # Classify
            n5 = min(len(self.names), 5)
            top5i = probs.argsort(0, descending=True)[:n5].tolist()  # top 5 indices
            [texts.append(f'{probs[j]:.2f} {self.names[j]}') for j in top5i]
        elif boxes:
            # Detect/segment/pose
            for j, d in enumerate(boxes):
                c, conf, id = int(d.cls), float(d.conf), None if d.id is None else int(d.id.item())
                line = (c, *d.xywhn.view(-1))
                if masks:
                    seg = masks[j].xyn[0].copy().reshape(-1)  # reversed mask.xyn, (n,2) to (n*2)
                    line = (c, *seg)
                if kpts is not None:
                    kpt = (kpts[j][:, :2] / d.orig_shape[[1, 0]]).reshape(-1).tolist()
                    line += (*kpt, )
                line += (conf, ) * save_conf + (() if id is None else (id, ))
                texts.append(('%g ' * len(line)).rstrip() % line)

        if texts:
            time.sleep(10)
            with open(txt_file, 'a') as f:
                f.writelines(text + '\n' for text in texts)

 

これで10秒ごとにtxtが出力されるようになりました。

 

3.クラスIDの確認

まずはネコちゃんが何番なのか調べます。

 

物体検出モデル、特にYOLO系列のモデルでは、各物体カテゴリ(人間、車、犬など)は一意のID(通常は整数)で識別されます。

このIDはデータセットに依存しているので、学習データで使っているCOCOデータセットのクラスIDのようですね。

COCO Dataset Class Labels

ClassID English Japanese
0 person
1 bicycle 自転車
2 car 自動車
3 motorcycle モーターサイクル
4 airplane 飛行機
5 bus バス
6 train 電車
7 truck トラック
8 boat ボート
9 traffic light 信号機
10 fire hydrant 消火栓
11 stop sign ストップサイン
12 parking meter 駐車料金メーター
13 bench ベンチ
14 bird
15 cat
16 dog
17 horse
18 sheep
19 cow
20 elephant
21 bear
22 zebra シマウマ
23 giraffe キリン
24 backpack バックパック
25 umbrella
26 handbag ハンドバッグ
27 tie ネクタイ
28 suitcase スーツケース
29 frisbee フリスビー
30 skis スキー
31 snowboard スノーボード
32 sports ball スポーツボール
33 kite
34 baseball bat 野球バット
35 baseball glove 野球グローブ
36 skateboard スケートボード
37 surfboard サーフボード
38 tennis racket テニスラケット
39 bottle ボトル
40 wine glass ワイングラス
41 cup カップ
42 fork フォーク
43 knife ナイフ
44 spoon スプーン
45 bowl ボウル
46 banana バナナ
47 apple リンゴ
48 sandwich サンドイッチ
49 orange オレンジ
50 broccoli ブロッコリー
51 carrot ニンジン
52 hot dog ホットドッグ
53 pizza ピザ
54 donut ドーナツ
55 cake ケーキ
56 chair 椅子
57 couch ソファ
58 potted plant 鉢植え
59 bed ベッド
60 dining table ダイニングテーブル
61 toilet トイレ
62 tv テレビ
63 laptop ノートパソコン
64 mouse マウス
65 remote リモコン
66 keyboard キーボード
67 cell phone 携帯電話
68 microwave 電子レンジ
69 oven オーブン
70 toaster トースター
71 sink シンク
72 refrigerator 冷蔵庫
73 book
74 clock 時計
75 vase 花瓶
76 scissors ハサミ
77 teddy bear テディベア
78 hair drier ヘアドライヤー
79 toothbrush 歯ブラシ

 

15番が猫ちゃんですね。

 

4.検出結果からメッセージボックスを表示させる

runs\detect ディレクトリとそのすべてのサブディレクトリを監視して

新しく作成された.txtファイルが存在して、そのファイルの中身が条件を満たす場合、アラート音を出して10秒間メッセージボックスを表示させる。

 

手順2で作った「webcam.py」を起動させた状態で

別のコマンドプロンプトで「」

 

 条件:クラスID=15、確信度>=0.7

 

「tkinter.messagebox」「watchdog」「winsound」を使います。

 

"import tkinter"ができない場合

Pythonインストール時にtkinterにチェックを入れていなかったことが考えられます。

Python exeから「Modify」を押して「td/tk and IDLE」にチェックをいれて実行すればインストールされます。

 

watchdogは「pip install watchdog」でインストール。

 

winsound

Python標準ライブラリでアラート音を鳴らすことができます。このライブラリはWindows専用なので、他のOSでは利用できません。

 

作成したコードがこちら

tkinterオリジナルのメッセージボックスだと自動で閉じるのができなさそうだったのでメッセージだけ表示するようにしています。

 

webcam.py

import os
import time
import winsound
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
from tkinter import Tk, Label, TOP

class MyHandler(FileSystemEventHandler):
    def on_created(self, event):
        filepath = event.src_path
        if filepath.endswith('.txt'):
            with open(filepath, 'r') as f:
                lines = f.readlines()
                for line in lines:
                    cols = line.strip().split()
                    if cols[0] == "15" and float(cols[5]) >= 0.5:
                        self.show_message('Condition matched in file: ' + filepath)

    def show_message(self, message):
        root = Tk()
        root.overrideredirect(1)  # ウィンドウのタイトルバーと枠を削除
        root.after(10000, root.destroy)  # 10秒後にウィンドウを自動的に閉じる
        Label(root, text=message, padx=20, pady=20).pack(side=TOP, fill='both', expand=True)
        winsound.MessageBeep()  # Windowsのアラート音を鳴らす
        root.mainloop()


if __name__ == "__main__":
    path = r'runs\detect'
    event_handler = MyHandler()
    observer = Observer()
    observer.schedule(event_handler, path, recursive=True)
    observer.start()
    try:
        while True:
            time.sleep(1)
    except KeyboardInterrupt:
        observer.stop()
    observer.join()

 

こんな感じのメッセージボックスがアラート音と共に表示されます!!

 

これを起動しておけばパソコン作業中でも外にネコちゃんが散歩してたら気付くことができますね~

ちらちら確認しなくても癒しタイムを逃しません!!

やりました(=^・・^=)

 

====================================

【おすすめ書籍】

2023年7月発売

鋭い洞察と深みのある言葉がささります。

読み物としてお勧め。なので電子版でもいいです。

 

2023年10月発売

AI、データ活用が必須の時代に。

豊富な企業事例で自社や自分を俯瞰できると思います。

 

【お勧めアイテム】

肩こりに悩むエンジニアの皆様へ!!!

 

私も慢性的な重度の肩こりプログラマですが

数年間いろいろな枕を買っては捨ててを繰り返してきまして

やーーーーっといい枕に出会いました。

朝起きて首が痛くない!すばらしい

横向けでも仰向けでもいい感じに寝ることができています。

 

ので肩こりが多いエンジニアの皆様にお勧めします。

高価ですが、整体2回分と考えたら安いものですね笑

====================================

 

以上、【YOLOv8】WindowsでWEBカメラからリアルタイム物体検出してメッセージボックス表示 でした。

 

次はメールかLINEに通知を飛ばしたいですね つづく

 

ではまた。