カテゴリー: ai-camera

  • Megadetector Raspi5+AI HAT+26TOPSで動かす

    以前AI HAT+2(HAILO10H)にmodel「 MDV6-yolov9-c」を入れて動かせたが、今回は、それより安価なAI HAT+26TOPS(HAILO8)で試してみた。
    MDV6-yolov10-cという軽量モデルもあったので今回はこちらで試す事にした。
    結果上手く動いたのでAIイノシシ追っ払い装置に使うことにした。

    このシステムを構築する為には、2つの壁がある、一つ目は、ダウンロードで提供されているmodel
    この場合、Raspberry Pi5+ HAT+2で利用可能(収まる)な最大性能なモデル「MDV6-yolov10-c」をHAT+
    用に変換する過程(hefファイルの書き出し)
    そして2つ目は、そのhefファイルを使ってイノシシ検出のスクリプトを作成するという事になる。

    作業は本ブログの「Jupyter Notebookが動くDockerを作る (Win11用)その1」の環境で行います。
    と言ってもhefファイル書き出し作業は、DockerもJupyter Notebookは全く使用しないで、(WSL2)
    Ubuntu-24.04のターミナルで行います、WSL2 Ubuntuが動く環境ならOKです。。
    (注:python version 3.11指定だが3.12.3でも動いた)

    HEFファイルへの書き出し手順

    1.必要なもの準備

     (1) Megadetector 「MDV6-yolov10-c」モデルの入手

      model 「MDV6-yolov10-c」はここからダウンロード出来ます。(登録が必要 無料)
      https://zenodo.org/records/20348979

      解凍してみると「model.onnx」が添付されていました。
      これを使用します。

     (2) HAILOのサイトから「hailo_dataflow_compiler」というソフトを入手します。

      ここからダウンロードする。(登録が必要 無料)
      https://hailo.ai/developer-zone/software-downloads/?product=ai_accelerators&device=hailo_8_8l

      これをダウンロードする。
      hailo_dataflow_compiler-3.34.0-py3-none-linux_x86_64.whl

     (3) 量子化キャリブレーション用の画像が必要です。

      webから「AI学習用赤外線夜間動物画像 無料」と検索すると見つかります。
      ここでは「Roboflow」さんから拝借しました。(無料 ユーザー登録必要)
        https://universe.roboflow.com/kawaharalabo/far-infrared-rays-
        animals/dataset/11/download/yolov9

      5000枚程度入ったフォルダを作成ここではフォルダ名「PICTURE」。(imageのみラベルは不要)
      640×480ピクセルの画像になります、画像名は何でも構いません、PICTUREフォルダ内に収めて
      下さい。

      以上(1),(2),(3)はworkspace内に配置しておきます。
      workspaceの場所は、この例では、
      Windows上の作業フォルダー:C:\Users\name\MyDocker1\HAILO8
      WSLからのパス:/mnt/c/Users/name/MyDocker1/HAILO8
      (nameの部分はあなたのユーザー名に変えて下さい)

    2.DFC(hailo dataflow compiler)用環境を作成

    WSLからのパス:/mnt/c/Users/name/MyDocker1/HAILO8で実行する。(nameはあなたの名前に変える)
    (あくまでここでの例なのでパスは任意の場所にして下さい)

    Hailo変換環境を作るところです、WSL Ubuntuで、以下を上から順に実行してください。
    WSLではWindows側のNVIDIAドライバーを使うため、Linux用ドライバーは入れず、CUDA Toolkitだけ
    をインストールします。

    sudo apt update
    sudo apt install -y python3.12-venv python3.12-dev python3-tk graphviz libgraphviz-dev build-essential git wget

    続いてCUDA Toolkit 12.8を入れます。

    cd /tmp
    wget https://developer.download.nvidia.com/compute/cuda/repos/wsl-ubuntu/x86_64/cuda-keyring_1.1-1_all.deb
    sudo dpkg -i cuda-keyring_1.1-1_all.deb
    sudo apt-get update
    sudo apt-get install -y cuda-toolkit-12-8

    nvidia-cuda-toolkit、cuda、cuda-12-8、cuda-driversはインストールしないでください。

    次に、DFC 3.3.4用の仮想環境「hailo834」を新しく作り、手元のwheelを入れます。

    sudo apt update &&
    sudo apt install -y \
      python3.12-venv python3.12-dev python3-tk \
      graphviz libgraphviz-dev build-essential git wget \
      cmake ninja-build &&
    python3.12 -m venv ~/hailo834-env &&
    source ~/hailo834-env/bin/activate &&
    python -m pip install --upgrade pip setuptools wheel &&
    python -m pip install \
      /mnt/c/Users/name/MyDocker1/HAILO8/hailo_dataflow_compiler-3.34.0-py3-none-linux_x86_64.whl

    次は、同じ(hailo834-env)のターミナルでHailo Model Zoo v2.19.0を導入
    DFC 3.34.0 と組み合わせる Model Zoo v2.19.0 を取得します。

    cd ~
    git clone --depth 1 --branch v2.19.0 \
      https://github.com/hailo-ai/hailo_model_zoo.git \
      hailo_model_zoo_h8
    cd ~/hailo_model_zoo_h8

    source ~/hailo834-env/bin/activate
    python -m pip install --no-build-isolation -e .
    hailomz --version
    hailomz info yolov10n

    Hailo Model Zoo v2.19.0とyolov10nのモデル情報が表示されれば完了です。ここでは HMZ のベース
    yolov10n` のネットワーク構造を利用し、MDV6 の 3 クラス用として解析・最適化します。v2.19.0 は DF
    C 3.34.0 と対応する版です。

    3. Hailo用HARを作る

    onnxモデルの確認

    ONNX="/mnt/c/Users/name/MyDocker1/HAILO8/model.onnx"
    CALIB="$HOME/mdv6_yolov10c_h8_calib_1024"
    OUT="$HOME/mdv6_yolov10c_h8"
    mkdir -p "$OUT"
    cd "$OUT"
    test -f "$ONNX" && echo "ONNX: $ONNX"
    test -d "$CALIB" && find "$CALIB" -maxdepth 1 -type f | wc -l

    ONNX を解析して HAR を作る

    source ~/hailo834-env/bin/activate
    export PATH=/usr/local/cuda-12.8/bin:$PATH
    export XLA_FLAGS=--xla_gpu_cuda_data_dir=/usr/local/cuda-12.8
    cd "$OUT"
    
    hailomz parse yolov10n \
      --ckpt "$ONNX" \
      --hw-arch hailo8

    正常終了すると、作業フォルダに yolov10n.har ができます。今回のログではモデル出力の NMS 終端6
    ノードが検出され、HAR の保存まで完了しました。

    3.校正画像を1,024枚コピー

      5,000枚から、再現性のある方法で1,024枚を新しい Linux 側フォルダにコピーします。
      WSの /mnt/c 上で直接キャリブレーションするより、ホームディレクトリ側にコピーして使い
      ます。

    cd /mnt/c/Users/name/MyDocker1/HAILO8
    mkdir -p ~/mdv6_calib_1024
    
    python - <<'PY'
    from pathlib import Path
    import random
    import shutil
    
    src = next(
        (Path(name) for name in ("OICTURE", "PICTURE") if Path(name).is_dir()),
        None,
    )
    if src is None:
        raise SystemExit("OICTURE または PICTURE フォルダーが見つかりません")
    
    files = sorted(p for p in src.iterdir() if p.is_file() and p.suffix.lower() == ".jpg")
    if len(files) < 1024:
        raise SystemExit(f"JPGが1024枚未満です: {len(files)}枚")
    
    random.seed(42)
    dst = Path.home() / "mdv6_calib_1024"
    for p in random.sample(files, 1024):
        shutil.copy2(p, dst / p.name)
    
    print(f"校正画像: {len(list(dst.glob('*.jpg')))}枚")
    print(f"フォルダー: {dst}")
    PY

      最後に 1024 と表示されることを確認します.

    4. 最適化を実行 

    hailomz optimize yolov10n \
    cd ~/mdv6_yolov10c_h8
    
    hailomz optimize yolov10n \
      --har ~/mdv6_yolov10c_h8/yolov10n.har \
      --calib-path /home/name/mdv6_calib_1024 \
      --classes 3 \
      --hw-arch hailo8

    classes 3は MegaDetector の animal / person / vehicle の3クラスに合わせる指定です。入力正規化やYOLOv10の出力処理は Model Zoo の設定を利用します。ログの最後に最適化済み HAR の保存先が表
    示されます。
    今回のログではyolov10n.harとして保存されました。

    5.Hailo-8向けHEFへコンパイル

    hailomz compile yolov10n \
      --hw-arch hailo8 \
      --har "yolov10n.har" 

    HEFは/mnt/c/Users/name/MyDocker1/HAILO8にあります。
    yolov10n.hef
    こちらをPiへコピーすれば、使用できます。





    Raspberry Pi5 + AI HAT2+のセットアップ

    Megadetector model 「yolov10n.hef」ファイルをゲットしたのでいよいよRaspberry Pi5の
    セットアップ作業に入ります。

    Raspberry Pi5とAI HAT+ (26TOPS)を接続組み立てて下さい。(13TOPS Typeでは動きません)
    準備が出来たらソフトのインストール作業に入ります

    インストール手順

    1,1,Raspberry Pi Imagerを使ってRaspberry Pi OS(64bit)
      A port of Debian Trixie with the Raspberry Pi Desktopをインストール

    2.下記のコマンドを実行して下さい。

    sudo apt update
    sudo apt full-upgrade -y
    sudo apt install -y dkms hailo-all
    sudo reboot

    再起動を省略しないでください。起動後に確認します。

    hailortcli fw-control identify
    hailortcli scan

    3.Python 実行環境とライブラリーのインストール

      hailo-allが導入したhailo_platform` を venv から使えるように、システムサイトパッケージを見え
      る venv を作ります。(この例では仮想環境HATを作る)

    sudo apt install -y python3-venv python3-dev python3-opencv
    python3 -m venv --system-site-packages ~/HAT
    source ~/HAT/bin/activate
    python -m pip install --upgrade pip
    python -m pip install "numpy==1.26.4" "opencv-python==4.11.0.86" paho-mqtt

      opencv-python を入れるときは NumPy 1.26.4 と同時に指定します。Pi の venv 内で NumPy 2.x に
      戻さないでください。既にこの venv が存在する場合は、作り直さず有効化してバージョンを確認
      します。

    source ~/HAT/bin/activate
    python - <<'PY'
    import sys, numpy, cv2
    import paho.mqtt.client as mqtt
    from hailo_platform import HEF, VDevice
    print('Python:', sys.executable)
    print('NumPy:', numpy.__version__)
    print('OpenCV:', cv2.__version__)
    print('HailoRT Python: OK')
    print('Paho MQTT: OK')
    PY

      この作業で hailo_platform が見つからない場合、venv が --system-site-packages 付きで作られ
      ているか、hailo-all の導入と再起動が完了しているかを確認します。

    HEF を Pi に転送して単体検査

    Raspberry Pi5に書き出したhefファイルをコピーして配置します。
    /home/pi/HAT/yolov10n.hef

    HEF の内容を確認します。

    hailortcli run /home/pi/HAT/yolov10n.hef --batch-size 1

    約103 FPSを表示しました。これで HEF と HailoRT の基本動作を、カメラ/MQTTから切り離して確認で
    きます。

    スクリプトは以下の内容になります。
    Raspberry Pi5に付属している「Thonny」から呼び出して使用します。
    Thonnyの設定では、Thonny画面の右下角にある三のところをクリックして「Cofigure Interpreter」
    をクリック
    次に表示される画面でPython Executableのところのパスを「/home/pi/HAT/bin/python3.13」にしてOK
    をクリックして下さい。

    import cv2
    import json
    import os
    import time
    import numpy as np
    import paho.mqtt.client as mqtt
    
    from contextlib import ExitStack
    from hailo_platform import (
        HEF,
        VDevice,
        HailoStreamInterface,
        ConfigureParams,
        InferVStreams,
        InputVStreamParams,
        OutputVStreamParams,
        FormatType,
    )
    
    
    # ============================================================
    # 設定
    # ============================================================
    HEF_PATH = "/home/pi/HAT/yolov10n.hef"
    
    CAMERAS = [
        {
            "id": "tapo310a_1",
            # USER:PASSWORDを、現在使用している認証情報に置き換えてください
            "rtsp_url": "rtsp://your_ID:Your_Passwd@192.168.1.101:554/stream1",
        },
    ]
    
    MQTT_BROKER = "192.168.1.200"#Your MQTT Server Address
    MQTT_PORT = 1883
    MQTT_TOPIC = "megadt"
    
    SAVE_DIR = "/home/pi/HAT/detected_images"
    SAVE_INTERVAL_SEC = 5
    CLASS_COOLDOWN_SEC = 10
    DEBUG_INTERVAL_SEC = 5
    
    INPUT_SIZE = 640
    
    CLASS_NAMES = {
        0: "animal",
        1: "person",
        2: "vehicle",
    }
    
    # HEFのScore threshold
    CLASS_THRESHOLDS = {
        0: 0.80,
        1: 0.80,
        2: 0.80,
    }
    
    os.makedirs(SAVE_DIR, exist_ok=True)
    
    
    # ============================================================
    # 前処理
    # ============================================================
    def letterbox_rgb(frame_bgr, size=INPUT_SIZE):
        """縦横比を維持して640x640へリサイズし、RGBに変換する。"""
        height, width = frame_bgr.shape[:2]
        scale = min(size / width, size / height)
    
        resized_w = int(round(width * scale))
        resized_h = int(round(height * scale))
    
        resized = cv2.resize(
            frame_bgr,
            (resized_w, resized_h),
            interpolation=cv2.INTER_LINEAR,
        )
    
        pad_w = size - resized_w
        pad_h = size - resized_h
        left = pad_w // 2
        right = pad_w - left
        top = pad_h // 2
        bottom = pad_h - top
    
        padded = cv2.copyMakeBorder(
            resized,
            top,
            bottom,
            left,
            right,
            cv2.BORDER_CONSTANT,
            value=(114, 114, 114),
        )
    
        rgb = cv2.cvtColor(padded, cv2.COLOR_BGR2RGB)
        return np.ascontiguousarray(rgb), scale, left, top
    
    
    # ============================================================
    # NMS出力をクラス別の配列へ整形
    # InferVStreamsの戻り値は、空のときと検出ありのときで
    # listの構造が異なる場合があるため、両方を処理する。
    # ============================================================
    def nms_output_by_class(nms_output):
        """HailoRTのNMS出力をクラス別の (N, 5) 配列3つにそろえる。"""
    
        # バッチなどの1要素ラッパーを外す
        value = nms_output
        while isinstance(value, (list, tuple)) and len(value) == 1:
            value = value[0]
    
        def as_rows(item, class_id):
            # クラスごとの出力にも1要素ラッパーがある場合に備える
            while isinstance(item, (list, tuple)) and len(item) == 1:
                item = item[0]
    
            try:
                arr = np.asarray(item, dtype=np.float32)
            except (TypeError, ValueError) as exc:
                raise RuntimeError(
                    f"NMSのクラス{class_id}出力を配列化できません。"
                    f"type={type(item)}, value={item!r}"
                ) from exc
    
            if arr.size == 0:
                return np.empty((0, 5), dtype=np.float32)
    
            if arr.ndim >= 2 and arr.shape[-1] == 5:
                return arr.reshape(-1, 5)
    
            if arr.ndim == 1 and arr.size % 5 == 0:
                return arr.reshape(-1, 5)
    
            raise RuntimeError(
                f"NMSのクラス{class_id}出力形状が想定外です: "
                f"shape={arr.shape}, dtype={arr.dtype}"
            )
    
        # クラスごとの可変長配列を持つリスト/タプル
        if isinstance(value, (list, tuple)) and len(value) == 3:
            return [as_rows(value[i], i) for i in range(3)]
    
        # (3, N, 5) 形式。N=0の未検出出力もここで扱う
        try:
            arr = np.asarray(value, dtype=np.float32)
        except (TypeError, ValueError) as exc:
            details = []
            if isinstance(value, (list, tuple)):
                for i, item in enumerate(value):
                    details.append(
                        f"[{i}] type={type(item).__name__}, "
                        f"shape={getattr(item, 'shape', None)}"
                    )
            raise RuntimeError(
                "NMS出力を解釈できません。"
                f"外側の型={type(value).__name__}; " + "; ".join(details)
            ) from exc
    
        if arr.ndim == 4 and arr.shape[0] == 1:
            arr = arr[0]
    
        if arr.ndim == 3 and arr.shape[0] == 3 and arr.shape[-1] == 5:
            return [arr[i].reshape(-1, 5) for i in range(3)]
    
        raise RuntimeError(
            f"NMS出力形状が想定外です: shape={arr.shape}, dtype={arr.dtype}"
        )
    
    
    # ============================================================
    # 後処理
    # 検出1件は y_min, x_min, y_max, x_max, score
    # ============================================================
    def decode_hailo_nms(
        nms_output,
        scale,
        pad_left,
        pad_top,
        orig_w,
        orig_h,
    ):
        class_arrays = nms_output_by_class(nms_output)
        detections = []
    
        for class_id, boxes in enumerate(class_arrays):
            for row in boxes:
                if not np.all(np.isfinite(row)):
                    continue
    
                y1, x1, y2, x2, score = map(float, row)
    
                if score < CLASS_THRESHOLDS[class_id]:
                    continue
    
                # 640x640の正規化座標を元画像座標へ戻す
                x1 = (x1 * INPUT_SIZE - pad_left) / scale
                x2 = (x2 * INPUT_SIZE - pad_left) / scale
                y1 = (y1 * INPUT_SIZE - pad_top) / scale
                y2 = (y2 * INPUT_SIZE - pad_top) / scale
    
                x1 = float(np.clip(x1, 0, orig_w - 1))
                x2 = float(np.clip(x2, 0, orig_w - 1))
                y1 = float(np.clip(y1, 0, orig_h - 1))
                y2 = float(np.clip(y2, 0, orig_h - 1))
    
                if x2 <= x1 or y2 <= y1:
                    continue
    
                detections.append({
                    "class": CLASS_NAMES[class_id],
                    "confidence": round(score, 3),
                    "box": [
                        int(round(x1)),
                        int(round(y1)),
                        int(round(x2)),
                        int(round(y2)),
                    ],
                })
    
        return detections
    
    
    def print_nms_summary(nms_output):
        class_arrays = nms_output_by_class(nms_output)
        summaries = []
    
        for class_id, class_name in CLASS_NAMES.items():
            boxes = class_arrays[class_id]
    
            if boxes.size > 0:
                scores = boxes[:, 4]
                scores = scores[np.isfinite(scores) & (scores > 0)]
            else:
                scores = np.empty((0,), dtype=np.float32)
    
            count = len(scores)
            max_score = float(np.max(scores)) if count else 0.0
    
            summaries.append(
                f"{class_name}: count={count}, max_score={max_score:.3f}"
            )
    
        print("[NMS] " + " | ".join(summaries))
    
    
    # ============================================================
    # HEF・MQTT・カメラの初期化
    # ============================================================
    print("Hailo-8用HEFを読み込み中...")
    
    hef = HEF(HEF_PATH)
    input_info = hef.get_input_vstream_infos()[0]
    output_infos = hef.get_output_vstream_infos()
    
    if len(output_infos) != 1:
        raise RuntimeError(
            f"HEF出力数が想定外です。期待=1、実際={len(output_infos)}"
        )
    
    output_info = output_infos[0]
    OUTPUT_NMS_NAME = output_info.name
    
    print(f"入力: {input_info.name}, shape={input_info.shape}")
    print(f"出力: {output_info.name}, shape={output_info.shape}")
    
    mqtt_client = mqtt.Client(client_id="mdv6-pi5")
    mqtt_connected = False
    
    try:
        mqtt_client.connect(MQTT_BROKER, MQTT_PORT, 60)
        mqtt_client.loop_start()
        mqtt_connected = True
        print(f"MQTT接続に成功しました: {MQTT_BROKER}:{MQTT_PORT}")
    except Exception as exc:
        print(f"MQTT接続に失敗しました。検出処理は続行します: {exc}")
    
    print("カメラストリームを開いています...")
    
    for camera in CAMERAS:
        camera["cap"] = cv2.VideoCapture(
            camera["rtsp_url"],
            cv2.CAP_FFMPEG,
        )
        camera["cap"].set(cv2.CAP_PROP_BUFFERSIZE, 1)
        camera["last_saved_time"] = 0.0
        camera["last_detected_by_class"] = {}
        camera["last_debug_time"] = 0.0
    
        if camera["cap"].isOpened():
            print(f"{camera['id']}: カメラ接続完了")
        else:
            print(f"{camera['id']}: カメラ接続失敗。RTSP URLを確認してください。")
    
    
    # ============================================================
    # 推論ループ
    # ============================================================
    mqtt_started = False
    
    print("MegaDetector監視を開始しました。終了するには Ctrl+C を押してください。")
    
    try:
        with VDevice() as target:
            configure_params = ConfigureParams.create_from_hef(
                hef=hef,
                interface=HailoStreamInterface.PCIe,
            )
    
            network_group = target.configure(hef, configure_params)[0]
            network_group_params = network_group.create_params()
    
            input_vstreams_params = InputVStreamParams.make(
                network_group,
                format_type=FormatType.UINT8,
            )
            output_vstreams_params = OutputVStreamParams.make(
                network_group,
                format_type=FormatType.FLOAT32,
            )
    
            # 推論パイプラインとネットワークを開いたまま連続推論する
            with ExitStack() as stack:
                infer_pipeline = stack.enter_context(
                    InferVStreams(
                        network_group,
                        input_vstreams_params,
                        output_vstreams_params,
                    )
                )
                stack.enter_context(
                    network_group.activate(network_group_params)
                )
    
                while True:
                    active_camera_count = 0
    
                    for camera in CAMERAS:
                        cap = camera["cap"]
    
                        if not cap.isOpened():
                            continue
    
                        active_camera_count += 1
                        ret, frame = cap.read()
    
                        if not ret:
                            print(f"{camera['id']}: フレームを取得できません")
                            time.sleep(0.1)
                            continue
    
                        frame_h, frame_w = frame.shape[:2]
                        input_rgb, scale, pad_left, pad_top = letterbox_rgb(frame)
    
                        input_buffer = np.expand_dims(input_rgb, axis=0)
                        input_buffer = input_buffer.astype(
                            np.uint8,
                            copy=False,
                        )
    
                        outputs = infer_pipeline.infer({
                            input_info.name: input_buffer
                        })
                        nms_output = outputs[OUTPUT_NMS_NAME]
    
                        if (
                            time.monotonic() - camera["last_debug_time"]
                            >= DEBUG_INTERVAL_SEC
                        ):
                            camera["last_debug_time"] = time.monotonic()
                            print_nms_summary(nms_output)
    
                        detections = decode_hailo_nms(
                            nms_output,
                            scale,
                            pad_left,
                            pad_top,
                            frame_w,
                            frame_h,
                        )
    
                        now = time.time()
                        last_by_class = camera["last_detected_by_class"]
    
                        active_detections = [
                            detection
                            for detection in detections
                            if now - last_by_class.get(
                                detection["class"], 0.0
                            ) >= CLASS_COOLDOWN_SEC
                        ]
    
                        if not active_detections:
                            continue
    
                        active_classes = sorted({
                            detection["class"]
                            for detection in active_detections
                        })
    
                        for class_name in active_classes:
                            last_by_class[class_name] = now
                            print(f"{camera['id']}: 検出 -> {class_name}")
    
                        if mqtt_connected:
                            payload = {
                                "timestamp": now,
                                "camera_id": camera["id"],
                                "detected": True,
                                "count": len(active_detections),
                                "objects": active_detections,
                            }
                            mqtt_client.publish(
                                MQTT_TOPIC,
                                json.dumps(payload),
                            )
    
                        if now - camera["last_saved_time"] >= SAVE_INTERVAL_SEC:
                            timestamp = time.strftime("%Y%m%d_%H%M%S")
                            save_path = os.path.join(
                                SAVE_DIR,
                                f"{camera['id']}_detected_{timestamp}.jpg",
                            )
                            cv2.imwrite(save_path, frame)
                            camera["last_saved_time"] = now
                            print(f"画像保存: {save_path}")
    
                    if active_camera_count == 0:
                        print("有効なカメラがありません。RTSP URLを確認してください。")
                        time.sleep(5)
                    else:
                        time.sleep(0.01)
    
    except KeyboardInterrupt:
        print("\n停止します。")
    
    finally:
        for camera in CAMERAS:
            cap = camera.get("cap")
            if cap is not None:
                cap.release()
    
        if mqtt_connected:
            mqtt_client.loop_stop()
            mqtt_client.disconnect()
    
        print("カメラとMQTTを終了しました。")
        

    検出間隔が短いと対象が留まっている場合、連続して検出が発布されますので具合が悪いです。
    上記スクリプトの以下の部分で調整して下さい。(現在は5s)
    ・同じ種類を無視する時間(秒)
     CLASS_COOLDOWN_SEC = 5

    ・種類ごとに誤検出の出やすさを見て調整します。
     HEF内蔵NMSのスコア閾値より高い値で、アプリ側でも絞り込みます

     CLASS_THRESHOLDS = {
     0: 0.50,
     1: 0.50,
     2: 0.50,
     }

    ・検出画像静止画で保存されます。(枠無し)
     SAVE_INTERVAL_SEC = 5

    さてセンサーは出来ましたので今後は、「わんわん吠えまくる装置」
    を作って行きます。