1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【今更】Raspberry Pi 3 ModelBを使って監視カメラ

1
Last updated at Posted at 2025-11-25

はじめに

Raspberry Pi 3 ModelBが余っていました。サーバにするにもスペックが低いし、何か転用できるものがないか…と考えたときに、ふとLegacyCameraがあったので、監視カメラを作ろうかと思いました。

やりたいこと

ここで実際にやりたいことを整理します。

  • 最新の映像をブラウザ経由で見られるようにしたい。
  • どうせなら不審者が来たら、検出してDiscordにでも通知してほしい。

環境

pi@pi:/scripts/live_streaming/mediamtx $ cat /proc/device-tree/model
Raspberry Pi 3 Model B Rev 1.2pi
pi@pi:/scripts/live_streaming/mediamtx $ lsb_release -a
No LSB modules are available.
Distributor ID: Debian
Description:    Debian GNU/Linux 13 (trixie)
Release:        13
Codename:       trixie

※TrixieのFull版だと、メモリが枯渇するのかすぐフリーズします。そのため、CUI(Lite版)をインストールしています。

Legacy Camera:https://www.amazon.co.jp/dp/B086MK17K5?ref=ppx_yo2ov_dt_b_fed_asin_title
(OV5647センサの安いやつ)

目指すシステム構成

青がサーバ、デバイス。緑はフォーマット。
雑に描いたので、統一感なくても許してください。
画像1.png

前提

  • 使用ポート:80・8554・2222。sudo ufw allow等でポート開放を行う。
  • ブラウザでの閲覧はVPN経由で行う。
  • pip install や apt installで必要なものは省きます。

rpicamでの映像取得 ~ RTSPサーバへの転送

カメラから取り込んだ映像を配信します。
システム構成で言うと、CAMERA→tcp/h264の線が以下コマンドに当たります。

rpicam-vid -t 0 --inline --listen -o tcp://127.0.0.1:2222

※この辺りは https://karakuri-musha.com/inside-technology/02-raspi-camera-common-capture01/ を参考にしました。

rpicam-vidでのストリーミングは、1対1となってしまいます。
そのため、ストリーミング映像をリアルタイムで見られるようになることと、動体検知ができるようになるという2点を満たすことが出来ません。

なので、tcp/h264をRTSPサーバ経由でマルチストリーミングします。
RTSPサーバにはMediaMTX、RTSPサーバにRaspberry Piの映像を転送する役割をffmpegで行ってもらいます。

①MediaMTXのダウンロード・サーバ起動

wget https://github.com/bluenviron/mediamtx/releases/download/v1.15.4/mediamtx_v1.15.4_linux_armv7.tar.gz
tar xzvf mediamtx_v1.15.4_linux_armv7.tar.gz
chmod 777 mediamtx
./mediamtx

②rpicam-vidで取得したデータをRTSPサーバに配信(tcp/h264→MediaMTXの線)

ffmpeg -i tcp://0.0.0.0:2222 -c copy -f rtsp rtsp://0.0.0.0:8554/stream

VLC Media Playerで rtsp://<ラズパイのアドレス>:8554/stream でネットワークストリーミングを開くと、リアルタイムで映像が更新されます。

http形式でのリアルタイム配信

nginxでサーバを建てます。デフォルトは/var/www/htmlですが、必要に応じてルートディレクトリを変えてください。
※参考: https://www.khstasaba.com/?p=953
私は /scripts/live_streaming/html配下にindex.htmlを置いています。

index.htmlの中身は以下の通り。

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Raspberry Pi カメラ HLS</title>
</head>
<body>
  <h1>カメラ映像 HLS</h1>
  <video id="video" controls autoplay muted width="640" height="480"></video>

  <!-- Hls.js CDN -->
  <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
  <script>
    const video = document.getElementById('video');

    if (Hls.isSupported()) {
      const hls = new Hls();
      hls.loadSource('stream.m3u8'); // HLS のパス
      hls.attachMedia(video);
      hls.on(Hls.Events.MANIFEST_PARSED,function() {
        video.play();
      });
    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      video.src = 'hls/stream.m3u8';
      video.addEventListener('loadedmetadata', function() {
        video.play();
      });
    }
  </script>
</body>
</html>

これでhtmlの準備は完了です。
html形式で配信を行うためには、HLS形式に変える必要があります。
RTSPサーバから配信された映像データをHLS形式に変えて /scripts/live_streaming/html/ 配下に配置するコマンドが以下です。

ffmpeg -i rtsp://127.0.0.1:8554/stream -c copy -f hls -hls_time 2 -hls_list_size 5   -hls_flags delete_segments /scripts/live_streaming/html/stream.m3u8

nginxを有効にすると、ちょっとラグはあるものの、カメラ映像が映るようになりました。
image.png

動体検知

動体検知して、Discordに通知するソースコードです。
動体検知を行ったら、検出した箇所を□で囲った画像を保存。その後、Discordに投稿するといったコードです。
検知しすぎても通知がうっとうしいので、検出後30分は動体検知しないようにしています。

WebhookURLの取得方法はこちらを参考ください。
https://zenn.dev/discorders/articles/discord-webhook-guide

import cv2
import time
import requests

# config
WEBHOOK_URL = '★WebhookURLを入力★'
WORKING_DIRECTORY = "/scripts/live_streaming/" ★pythonを実行するディレクトリを入力

def send_to_discord(filepath):
    with open(filepath, "rb") as f:
        files = {"file": f}
        data = {"content": "人を検知しました!"}
        requests.post(WEBHOOK_URL, data=data, files=files)

# RTSPストリームを開く
cap = cv2.VideoCapture("rtsp://192.168.50.50:8554/stream")
if not cap.isOpened():
    print("RTSPストリームを開けませんでした。")
    exit()

prev_gray = None

while True:
    ret, frame = cap.read()
    if not ret:
        print("フレーム取得失敗")
        break

    gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

    # 初回のみ prev_gray をセットして継続
    if prev_gray is None:
        prev_gray = gray
        continue

    # 差分
    diff = cv2.absdiff(prev_gray, gray)
    _, thresh = cv2.threshold(diff, 30, 255, cv2.THRESH_BINARY)

    # ノイズ除去:膨張して輪郭を強調
    kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (5, 5))
    dilated = cv2.dilate(thresh, kernel, iterations=2)

    # 輪郭を取得
    contours, _ = cv2.findContours(dilated, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)

    detected = False
    biggest_box = None

    # 動きのある領域を探索
    for cnt in contours:
        area = cv2.contourArea(cnt)
        if area < 5000:  # ノイズ除去:小さい動きは無視
            continue
        
        # 大きい動体と判定
        x, y, w, h = cv2.boundingRect(cnt)
        biggest_box = (x, y, w, h)
        detected = True

    if detected and biggest_box:
        x, y, w, h = biggest_box

        # 枠を描画(赤色)
        cv2.rectangle(frame, (x, y), (x + w, y + h), (0, 0, 255), 2)

        # 保存
        filename = f"detected_{int(time.time())}.jpg"
        cv2.imwrite(filename, frame)
        print(f"動体検知 → 保存しました: {filename}")
        work_directory = WORKING_DIRECTORY + filename
        send_to_discord(work_directory)

        # 必要であれば break も可能
        # break

    prev_gray = gray

    if detected:
        time.sleep(30 * 60)
        cap.release()
        cap = cv2.VideoCapture("rtsp://192.168.50.50:8554/stream")
        if not cap.isOpened():
           print("RTSPストリームを開けませんでした。")
           exit()

cap.release()

・・・まぁ動作不安定です。ここは要改善。とりあえず30分に一回は監視カメラの映像が送られてくるので、

  • なぜか動いていないはずの箇所も検知する
  • 30分に一回必ず動体検知する

image.png

終わりに

とりあえず監視カメラっぽい何かは出来たので、良しとしましょう。
進展合ったら、また記事書きます。

映像配信は大変ということがわかりました。htmlで配信できるフォーマットを気にしないといけないなど、フォーマット関連の縛りがあるなぁと思いました

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?