【ラズベリーパイ】監視カメラの作り方|PythonでカメラモジュールV2を自在に操作

【PR】この記事には広告が含まれています。

【ラズベリーパイ】監視カメラの作り方|PythonでカメラモジュールV2を自在に操作
  • ラズベリーパイでカメラを使ってみたい
  • 外出先からもカメラの画像を確認したい
  • AIを活用した監視・通知システムを自作したい

ラズベリーパイは一般的なパソコンとは違い、プログラミングでのカメラ操作が必要です。その一方で、ただの撮影だけでなく、人感センサーやAIを組み合わせた本格的な監視システムを構築することもできます。

この記事では、カメラの接続方法からAIによる物体検出まで幅広く、初心者向けに解説しています。

使用する機器はカメラモジュールV2、プログラミング言語はPythonを使います。

カメラモジュールV3の使用方法は、以下の記事で解説しています。
≫ Raspberry Pi カメラモジュール V3をOpenCVで操作する方法

そぞら
そぞら

本記事のゴールはAIを活用した物体検出により、防犯カメラや監視システムとして活用できる仕組みを構築することです。

この記事はボリュームがあるため、一度にすべて試す必要はありません。ブックマークして、気になった内容から少しずつ挑戦してみてください。

ラズベリーパイ用カメラの基礎知識

ラズベリーパイのカメラコネクタに接続して使用する専用カメラは、「カメラモジュール」と呼ばれています。

カメラモジュールの種類

ラズベリーパイで使えるカメラモジュールは、さまざまなメーカーから発売されています。本記事ではRaspberry Pi財団が提供するカメラモジュールV2を使用します。800万画素のソニー製イメージセンサーを搭載しており、ラズベリーパイと接続するためのケーブルも付属しています。

Raspberry Pi Camera V2
カメラモジュールV2

チェックポイント

僕が持っているカメラモジュールV2の基板には「V2.1」と印字されています。これは製造時の改訂番号で、機能や性能に大きな違いはありません。通常はV2.1も含めて「カメラモジュールV2」と呼ばれています。

2023年1月に発売されたカメラモジュール V3は、V2と比べて画素数の向上やオートフォーカス対応などの改良が行われています。主な違いは以下のとおりです。


Camera Module 2

Camera Module 3
画素数800万画素
(3280 × 2464) 
1200万画素
(4608 x 2592)
センサーSony IMX219Sony IMX708
フォーカス手動調節式
(固定フォーカス)
モーター駆動
(オートフォーカス)
HDR対応なしあり
参考価格3,300円5,940円
詳細詳細を見る詳細を見る

画像認識を行う場合は高画素なカメラほど有利とは限りません。解像度が高いほど処理負荷が大きくなるため、顔認識や物体認識では解像度を下げて使用することも少なくありません。そのため、800万画素のカメラモジュールV2でも画像認識の用途には十分活用できます。

カメラモジュールV3は使用方法が異なります。V3の使用方法は以下の記事で解説しています。
≫ Raspberry Pi カメラモジュール V3をOpenCVで操作する方法

カメラモジュールはリボンケーブルで接続

カメラモジュールとラズベリーパイはリボンケーブルと呼ばれるテープのような形状のケーブルで接続します。

リボンケーブル外観

Raspberry Pi 4などの標準サイズモデルでは1.0 mmピッチのコネクタが使用されていますが、Raspberry Pi 5やZeroシリーズでは0.5 mmピッチの小型コネクタが採用されています。カメラと本体のコネクタサイズが異なる場合は変換ケーブルが必要です。

カメラモジュールの固定方法

リボンケーブルは形状を保とうとする力が強いため、曲げるとすぐに戻ってしまいます。このため、カメラモジュールを思い通りの場所に取り付けたり、角度を調整したりするのが難しいです。

そぞら
そぞら

僕はレゴブロックを使ってカメラスタンドを作りました。

自作のカメラスタンド
自作のカメラスタンド

簡易的な固定方法として、洗濯ばさみを利用する方法もあります。

出典:エンジニアの電気屋さん

3Dプリンターを使って、カメラスタンドを作るという方法もあります。以下のスタンドは、3Dモデル共有サイトThingiverseで公開されているデータ(Raspberry Pi Camera Holder)をプリントしたものです。

3Dプリンターで作成したスタンド

ラズベリーパイでカメラを使う準備をする

使用したOS

この記事で使用しているOSはRaspberry Pi OS(64-bit)Trixieです。OSの準備方法は以下の記事で解説しています。なお、後半で紹介しているMediaPipeを使用した物体検出は、Trixieでは動作しないためご注意ください。
≫【2026年最新版】OSインストールから初期設定まで|開始手順のすべて

まずはカメラの接続方法とカメラを有効にする手順を解説します。

カメラモジュールの接続方法

ラズベリーパイ本体へカメラのケーブルを接続する手順は下記のとおりです。

  1. 黒いロックを持ち上げる。
  2. リボンケーブルを挿入する
  3. ロックを下に押し込んで固定する

まず、カメラポートの黒いロック部分を上に持ち上げます。

カメラポートのロック解除方法
カメラ接続

リボンケーブルを差し込みます。Raspberry Pi 4以前のモデルでは、端子部分がHDMIコネクタ側にを向くように接続します。Raspberry Pi 5にはカメラコネクタが2つ搭載されています。どちらのコネクタも使用できますが、Raspberry Pi 5では、端子部分がLANコネクタ側を向くように接続してください。

リボンケーブルの差し込み方
Pi 4の場合のリボンケーブルの向き

リボンケーブルを挿入したら、黒いロック部分を押し下げて固定します。

リボンケーブルのロック方法
ケーブルのロック

ケーブルを軽く引っ張って外れないことを確認します。

リボンケーブルのロック確認方法
ケーブルのロック確認

これでカメラの取り付けは完了です。

ライブラリをインストールする

Pythonで画像を扱うためのライブラリであるOpenCVをラズパイにインストールします。ライブラリとはよく使う機能をまとめて保管している箱のようなものです。OpenCVをインストールすると、「カメラの画像を表示する」などの機能を「箱」から取り出して使えるようになります。

ターミナルを開いて、コマンドを一行ずつ実行していきます。

下記のコマンドをターミナルに入力してEnterキーを押してください。

インストールできるソフトウェアの情報を最新の状態に更新します。

そぞら
そぞら

sudo管理者権限で実行するときに使います。

sudo apt update

sudoは管理者モードで実行する命令であり、安全確認としてパスワード入力が求められます。ラズベリーパイにログインするときのパスワードを入力後にEnterキーを押してください。

OpenCVをインストールします。

sudo apt install -y python3-opencv

以上でカメラを使用するための準備は完了です。

カメラ映像(動画)を表示する

カメラの映像を表示するためのPythonプログラムを作ります。プログラミングには、初心者でも扱いやすい「Thonny」というソフトを使うのがおすすめです。

Thonnyの使い方は以下の記事で詳しく解説しています。
≫【ラズベリーパイ入門】Pythonプログラミングの始め方

まず、「Thonny」を開きます。

カメラ映像(動画)を表示するプログラム

以下のプログラムをThonnyへコピペします。

from picamera2 import Picamera2
import cv2

# Picamera2を初期化
picam2 = Picamera2()

# Camera Module 2の最大解像度(3280×2464)を使用
config = picam2.create_preview_configuration(
    main={"size": (3280, 2464)}
)

# 設定を反映
picam2.configure(config)

# カメラを起動
picam2.start()

while True:

    # カメラ画像を取得
    frame = picam2.capture_array()

    # OpenCVで扱えるBGR形式へ変換
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

    # 表示用に1024×768へ縮小
    display = cv2.resize(frame, (1024, 768))

    # カメラ映像を表示
    cv2.imshow("Camera", display)

    # Escキーで終了
    if cv2.waitKey(1) == 27:
        break

# OpenCVウィンドウを閉じる
cv2.destroyAllWindows()

# カメラ停止
picam2.stop()

このプログラムは、カメラの映像をリアルタイムで画面に表示するものです。

最初にカメラを使うための準備を行い、続いてカメラ画像を繰り返し取得します。取得した画像は画面表示しやすいサイズに変更して表示しています。

そぞら
そぞら

1コマ分の画像表示を高速で繰り返すことで、リアルタイム映像(動画)になります。

OpenCVは waitKey 関数でキー操作を待つことができます。 Escキーを押すとプログラムが終了し、カメラも停止します。

プログラムの実行

Thonnyの「Runボタン」を押して、プログラムを実行してみましょう。

数秒待つとカメラが起動します。下の写真のように映像が表示されれば成功です。

ピントの調整方法

カメラモジュール V2はピント(焦点)を調整できます。文字がぼやけるなどピントが合っていないと感じたら調整してみましょう。調整方法はカメラのレンズの周りの黒い部分を左右に回します。近くのモノがはっきり見えないときは左方向に回します。逆に遠くのモノにフォーカスしたいときは右方向に回します。20~30°くらい回すだけでもピントは変わります。

そぞら
そぞら

映像を確認しながら少しずつ調整しましょう。

回しすぎるとレンズが外れるので要注意です。

ラズベリーパイカメラモジュールのピント調整方法
ラズベリーパイカメラのピント調整方法

ピント調整を手で行うのは厳しいです。ツマミが小さすぎてうまく回りません。下の動画のような専用の調整工具がカメラに同封されている場合があります。お手元にない方は工具のみ単品で購入することもできます。スイッチサイエンスで工具を見る

出典:adafruit.com

調整用工具が無い方は、先の細いピンセットでも代用可能です。

ピンセットでカメラモジュールのピントを調整する方法
ピンセットでピント調整

スマホからカメラ映像を確認してみよう

Raspberry Pi Connectを使うと、Raspberry Piの画面をスマートフォンから確認できます。つまり、先ほど表示したカメラ映像も、そのままスマートフォンから見られるようになります。

そぞら
そぞら

外出先から映像を確認したい場合にも便利です。

Raspberry Pi Connectは、Raspberry Pi財団が公式で提供しているリモート接続機能です。ブラウザ経由でRaspberry Piへアクセスできるため、専用アプリをインストールしなくても使えます。

Raspberry Pi Connectを有効にする

Raspberry Pi Connectでカメラ映像を確認する手順は以下の通りです。

  1. Raspberry Pi Connectを有効にする
  2. Raspberry Pi IDを作成する
  3. ラズベリーパイをRaspberry Pi IDに登録する
  4. スマートフォンやPCからRaspberry Pi Connectへ接続できることを確認する
  5. 本記事のカメラ表示プログラムを実行する
  6. Raspberry Pi Connectでラズベリーパイのデスクトップ画面を開く

Raspberry Pi Connectのセットアップ方法や、スマートフォンから接続する手順は以下の記事で詳しく解説しています。
≫ 外からラズパイを操作!Raspberry Pi Connectの設定方法

Raspberry Pi Connectを使ったカメラ映像の確認

以下のプログラムを実行した状態で、スマートフォンからRaspberry Pi Connectへ接続すると、ラズベリーパイで表示しているカメラ映像をスマートフォンから確認できます。

from picamera2 import Picamera2
import cv2

# Picamera2を初期化
picam2 = Picamera2()

# Camera Module 2の最大解像度(3280×2464)を使用
config = picam2.create_preview_configuration(
    main={"size": (3280, 2464)}
)

# 設定を反映
picam2.configure(config)

# カメラを起動
picam2.start()

while True:

    # カメラ画像を取得
    frame = picam2.capture_array()

    # OpenCVで扱えるBGR形式へ変換
    frame = cv2.cvtColor(
        frame,
        cv2.COLOR_RGB2BGR
    )

    # 表示幅(スマホ向け)
    display_width = 355

    # 元画像サイズ取得
    h, w = frame.shape[:2]

    # 縦横比を維持して高さを計算
    display_height = int(
        h * display_width / w
    )

    # 表示用に縮小
    display = cv2.resize(
        frame,
        (display_width, display_height)
    )

    # カメラ映像を表示
    cv2.imshow(
        "Camera",
        display
    )

    # Escキーで終了
    if cv2.waitKey(1) == 27:
        break

# OpenCVウィンドウを閉じる
cv2.destroyAllWindows()

# カメラ停止
picam2.stop()

このコードは、最初に紹介したリアルタイムでカメラ映像を表示するプログラムを改良したものです。表示用の映像のみ横幅355ピクセルに縮小し、縦横比を維持したまま表示するように変更しています。

スマートフォンに表示したカメラ映像

縮小処理では画素数を減らしているだけで、画像の一部を切り抜いているわけではありません。そのため、写る範囲(画角)は変わらず、スマートフォンからRaspberry Pi Connectで接続した場合でも、映像全体を表示できます。

より高機能な監視カメラに挑戦

次はカメラを使った監視システムを作ってみましょう。人の接近などを検知して、自動で映像を保存したり通知を送ったりすることが可能です。まずは基本となる、映像を保存する方法から見ていきましょう。

Pythonプログラムで静止画を保存する

カメラ映像を静止画として保存するプログラムは以下のとおりです。

from picamera2 import Picamera2
import cv2
import time
from datetime import datetime

# Picamera2を初期化
picam2 = Picamera2()

# Camera Module 2の最大解像度を使用
config = picam2.create_preview_configuration(
    main={"size": (3280, 2464)}
)

# 設定を反映
picam2.configure(config)

# カメラを起動
picam2.start()

# カメラが安定するまで少し待機
time.sleep(2)

# カメラ画像を取得
frame = picam2.capture_array()

# OpenCVで扱えるBGR形式へ変換
frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

# 現在時刻をファイル名に使用
filename = datetime.now().strftime(
    "%Y年%m月%d日%H時%M分%S秒.jpg"
)

# 静止画として保存
cv2.imwrite(filename, frame)

# カメラ停止
picam2.stop()

print(f"{filename}を保存しました")

このプログラムを実行すると、カメラで撮影した画像が実行したフォルダに保存されます。ファイル名には撮影日時が使われるため、いつ撮影した写真なのかも一目でわかります。

保存した画像は、デスクトップ画面左上のフォルダアイコンをクリックして確認できます。

保存されたJPEGファイルをダブルクリックすると画像が表示されます。

ファイル名を時刻にして保存すると過去の画像も残る

Pythonでは現在時刻をかんたんに取得できます。秒単位の現在時刻をファイル名にして保存すれば、ファイル名が毎回変わるので上書きされることはありません。

Pythonプログラムで動画を保存する

カメラで撮影した映像を動画として保存するプログラムです。

from picamera2 import Picamera2
import cv2
from datetime import datetime

# Picamera2を初期化
picam2 = Picamera2()

# Camera Module 2の最大解像度を使用
config = picam2.create_preview_configuration(
    main={"size": (3280, 2464)}
)

# 設定を反映
picam2.configure(config)

# カメラを起動
picam2.start()

# 現在時刻をファイル名に使用
filename = datetime.now().strftime(
    "%Y年%m月%d日%H時%M分%S秒.mp4"
)

# mp4形式で動画保存
fourcc = cv2.VideoWriter_fourcc(*"mp4v")

video = cv2.VideoWriter(
    filename,
    fourcc,
    20.0,
    (640, 480)
)

while True:

    # カメラ画像を取得
    frame = picam2.capture_array()

    # OpenCVで扱えるBGR形式へ変換
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

    # 640×480へ縮小
    display = cv2.resize(frame, (640, 480))

    # 動画へ保存
    video.write(display)

    # カメラ映像を表示
    cv2.imshow("Camera", display)

    # Escキーで終了
    if cv2.waitKey(1) == 27:
        break

# 動画保存終了
video.release()

# OpenCVウィンドウを閉じる
cv2.destroyAllWindows()

# カメラ停止
picam2.stop()

print(f"{filename}を保存しました")

プログラムを実行してescキー(エスケープキー)を押すまでの間の動画が保存されます。

動画はプログラムと同じフォルダーに保存されます。

動画の保存場所
動画の保存先

ダブルクリックして動画を再生してみましょう。

 

人感センサーが反応したら撮影する

ラズベリーパイにはGPIOピンがあるため、センサーを接続して信号を読み取れます。ここでは焦電型赤外線センサー(人感センサー)とカメラを組み合わせ、人が近づくと自動で撮影するシステムを作ります。
≫ 秋月電子で焦電型赤外線センサーを見る

焦電型赤外線センサー
焦電型赤外線センサー

人感センサーの接続

ラズベリーパイと人感センサーを以下のように接続します。

人感センサーとラズベリーパイの接続方法(回路図)
線の色センサー側ラズベリーパイ側
5V
オレンジOUTGPIO 18
GND
人感センサーの接続
GPIOピン配置
人感センサーとラズベリーパイの接続方法(写真)
焦電型赤外線センサーの配線

基板側の3つのツマミで感度などの調整が可能です。

感度調整方法など、焦電型赤外線センサーの使い方は以下の記事で詳しく解説しています。
≫ 焦電型赤外線センサーの使い方

僕は3Dプリンターでカメラとセンサーのマウントを作成しました。

カメラモジュールと人感センサー用マウント作成例

上記の写真ではセンサーの動作を可視化するため、LEDを使っています。本記事のプログラムではLED点灯の部分は省略しています。

人感センサーが反応したら静止画を保存するプログラム

from picamera2 import Picamera2
import cv2
from gpiozero import DigitalInputDevice
from datetime import datetime
import time

# Picamera2を初期化
picam2 = Picamera2()

# Camera Module 2の最大解像度を使用
config = picam2.create_preview_configuration(
    main={"size": (3280, 2464)}
)

# 設定を反映
picam2.configure(config)

# カメラを起動
picam2.start()

GPIO_PIN = 18

# GPIO18を入力モードで使用
sensor = DigitalInputDevice(GPIO_PIN)

while True:

    # HIGHの場合
    if sensor.value == 1:
        print("反応あり!")
        
        # カメラ画像を取得
        frame = picam2.capture_array()

        # OpenCVで扱えるBGR形式へ変換
        frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

        # 現在時刻をファイル名に使用
        filename = datetime.now().strftime(
            "%Y年%m月%d日%H時%M分%S秒.jpg"
        )

        # 静止画として保存
        cv2.imwrite(filename, frame)
        
        print(f"{filename}を保存しました")
        
        time.sleep(10)

    # LOWの場合
    else:
        print("反応なし")
        time.sleep(1)

上記のプログラムを実行すると、人が近づいた時に静止画を撮影して保存します。

人感センサーが反応したときのカメラ画像をGmailで送信する

GmailのSMTPサーバーを利用すると、メールをプログラムから自動送信できます。ここでは人感センサーが反応したタイミングで撮影した画像を、Gmailで通知する方法を紹介します。

アプリパスワードの取得

まず、Googleのアプリパスワードを取得します。アプリパスワードとは、プログラムからGoogleアカウントにアクセスするために発行される16桁のパスワードです。

ブラウザで Googleのアプリパスワードの設定ページ を開きます。Google アカウントのログインが求められる場合、使用するメールアドレスとパスワードを入力してログインしてください。

アプリ名を入力します。「Raspberry Pi」など分かりやすい名前を入力することをおすすめします。「作成」をクリックするとアプリパスワードが生成されます。

表示された16桁のアプリパスワードをコピーして、Pythonプログラムで使用します。パスワードは再表示ができないため、必要に応じて安全な場所に控えておいてください。

Gmail送信テスト

以下のコードはPyhtonプログラムでGmailを送信するためのものです。

import smtplib

# 変数の定義
YOUR_GMAIL_ADDRESS = "xxxxx@gmail.com"  # Gmailアドレス
APP_PASSWORD = "xxxxxxxxxxxxxxxx"       # アプリパスワード
TO_ADDRESS = YOUR_GMAIL_ADDRESS    # 送信先アドレス
SMTP_SERVER = "smtp.gmail.com"          # SMTPサーバー
SMTP_PORT = 587                         # SMTPポート番号
EMAIL_SUBJECT = "Test Mail from Python" # メールの件名
EMAIL_BODY = "This is a test email sent using Python."  # メール本文

# メール送信処理
connection = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
connection.starttls()  # TLS暗号化を開始
connection.login(YOUR_GMAIL_ADDRESS, APP_PASSWORD)  # Gmailにログイン

# メールを送信
message = f"Subject: {EMAIL_SUBJECT}\n\n{EMAIL_BODY}"  # メール全体を構築
connection.sendmail(YOUR_GMAIL_ADDRESS, TO_ADDRESS, message)

# 接続を終了
connection.quit()

最初に、Gmailアドレスやアプリパスワード、送信先アドレス、SMTPサーバーやポート番号、メールの件名や本文などを変数に定義します。これらの変数には、自分のGmailアカウント情報などを設定する必要があります。

YOUR_GMAIL_ADDRESSに自分のGmailアドレス、APP_PASSWORDに発行したアプリパスワードを設定します。TO_ADDRESSには送信先メールアドレスを設定しますが、自分宛てに送信する場合はTO_ADDRESS = YOUR_GMAIL_ADDRESSのままで構いません。


次に、smtplibを使ってSMTPサーバーに接続し、TLS暗号化を開始して安全に通信できるようにします。その後、Gmailアカウントにログインし、件名と本文を含めたメールメッセージを作成します。
最後に、sendmailメソッドを使ってメールを送信し、接続を終了します。

画像ファイルを添付してメールを送信

メールの送信テストが完了したら、次に画像を添付する方法をチェックしてみましょう。

import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

# 変数の定義
YOUR_GMAIL_ADDRESS = "xxxxx@gmail.com"  # Gmailアドレス
APP_PASSWORD = "xxxxxxxxxxxxxxxx"       # アプリパスワード
TO_ADDRESS = YOUR_GMAIL_ADDRESS    # 送信先アドレス
SMTP_SERVER = "smtp.gmail.com"          # SMTPサーバー
SMTP_PORT = 587                         # SMTPポート番号
EMAIL_SUBJECT = "Test Mail with Attachment" # メールの件名
EMAIL_BODY = "This is a test email with an attached image file."  # メール本文
ATTACHMENT_PATH = "image.jpg"  # 添付ファイルのパス

# メールオブジェクトの作成
message = MIMEMultipart()
message["From"] = YOUR_GMAIL_ADDRESS
message["To"] = TO_ADDRESS
message["Subject"] = EMAIL_SUBJECT

# メール本文を追加
message.attach(MIMEText(EMAIL_BODY, "plain"))

# 添付ファイルを読み込み
with open(ATTACHMENT_PATH, "rb") as attachment:
    part = MIMEBase("application", "octet-stream")
    part.set_payload(attachment.read())
    encoders.encode_base64(part)
    part.add_header("Content-Disposition", f"attachment; filename={ATTACHMENT_PATH}")

# 添付ファイルをメールに追加
message.attach(part)

# メール送信処理
connection = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
connection.starttls()
connection.login(YOUR_GMAIL_ADDRESS, APP_PASSWORD)
connection.send_message(message)
connection.quit()

このプログラムでは、添付ファイルも扱えるMIMEMultipart形式でメールを作成し、画像ファイルを追加してから送信しています。ATTACHMENT_PATH変数には、添付する画像ファイルのパスを設定します。画像ファイルを変更したい場合は、このパスを書き換えることで対応可能です。

人感センサーが反応したときにGmailで通知する

人感センサーが反応したとき写真をGmail送信するコードは以下のようになります。

from gpiozero import DigitalInputDevice
from picamera2 import Picamera2
import cv2
import time
import datetime
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

# Gmail送信の関数
def send_gmail(Discovery_time):
    YOUR_GMAIL_ADDRESS = "xxxxx@gmail.com"  # Gmailアドレス
    APP_PASSWORD = "xxxxxxxxxxxxxxxx"       # アプリパスワード
    TO_ADDRESS = YOUR_GMAIL_ADDRESS    # 送信先アドレス
    SMTP_SERVER = "smtp.gmail.com"          # SMTPサーバー
    SMTP_PORT = 587                         # SMTPポート番号
    EMAIL_SUBJECT = "侵入者検知アラート"     # メールの件名
    EMAIL_BODY = f"侵入者を検知しました。\n検出時間: {Discovery_time}"  # メール本文

    try:
        # メール作成
        msg = MIMEMultipart()
        msg['From'] = YOUR_GMAIL_ADDRESS
        msg['To'] = TO_ADDRESS
        msg['Subject'] = EMAIL_SUBJECT

        # メール本文を追加
        msg.attach(MIMEText(EMAIL_BODY, 'plain'))

        # 画像ファイルを添付
        attachment = open("image.jpg", "rb")
        part = MIMEBase('application', 'octet-stream')
        part.set_payload(attachment.read())
        encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="image.jpg"')
        msg.attach(part)

        # メール送信
        connection = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
        connection.starttls()
        connection.login(YOUR_GMAIL_ADDRESS, APP_PASSWORD)
        connection.send_message(msg)
        connection.quit()
        print("メール送信成功")

    except Exception as e:
        print(f"メール送信失敗: {str(e)}")

# Picamera2を初期化
picam2 = Picamera2()

# Camera Module 2の最大解像度を使用
config = picam2.create_preview_configuration(
    main={"size": (3280, 2464)}
)

# 設定を反映
picam2.configure(config)

# カメラを起動
picam2.start()

# センサーを使う準備
GPIO_PIN = 18
sensor = DigitalInputDevice(GPIO_PIN)

while True:
    if sensor.value == 1:
        # センサー検出時の処理
        print("1")

        # 検出時間の取得
        dt_now = datetime.datetime.now()
        Discovery_time = dt_now.strftime('%Y年%m月%d日%H時%M分%S秒')
        print(Discovery_time)

        # カメラ画像を取得
        frame = picam2.capture_array()

        # OpenCVで扱えるBGR形式へ変換
        frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

        # カメラ画像を保存する
        cv2.imwrite("image.jpg", frame)

        # Gmail送信
        send_gmail(Discovery_time)

        # 10秒待機
        time.sleep(10)
    else:
        # センサー未検出時の処理
        print("0")
        time.sleep(1)

GPIO.cleanup()

ラズベリーパイのGPIO18ピンに接続されたセンサーが反応すると、カメラで写真を撮影し、その画像と検出日時を含めたメールを自動送信します。

OpenCVで顔を検出する

OpenCVには、顔を検出するための機能が用意されています。ここでは、カメラに映った顔を検出するプログラムを作成してみましょう。

まずは、顔を見つけるための学習データ(Haar Cascade)をインストールします。ターミナルを開き、以下のコマンドを実行してください。

sudo apt install opencv-data

リアルタイムで顔を検出する

カメラ映像からリアルタイムで人の顔を探し出して、四角で囲むプログラムは以下の通りです。

from picamera2 import Picamera2
import cv2

# Haar Cascadeのファイルパス
HAAR_FILE = (
    "/usr/share/opencv4/haarcascades/"
    "haarcascade_frontalface_default.xml"
)

# 顔認識用CascadeClassifierを作成
cascade = cv2.CascadeClassifier(HAAR_FILE)

# Picamera2を初期化
picam2 = Picamera2()

# Camera Module 2の最大解像度を使用
config = picam2.create_preview_configuration(
    main={"size": (3280, 2464)}
)

# 設定を反映
picam2.configure(config)

# カメラを起動
picam2.start()

while True:

    # カメラ画像を取得
    frame = picam2.capture_array()

    # OpenCVで扱えるBGR形式へ変換
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

    # 表示用に1024×768へ縮小
    display = cv2.resize(frame, (1024, 768))

    # グレースケール画像へ変換
    gray = cv2.cvtColor(display, cv2.COLOR_BGR2GRAY)

    # 顔認識
    faces = cascade.detectMultiScale(
        gray,
        scaleFactor=1.1,
        minNeighbors=5,
        minSize=(50, 50)
    )

    # 顔を四角で囲む
    for (x, y, w, h) in faces:

        cv2.rectangle(
            display,
            (x, y),
            (x + w, y + h),
            (0, 0, 255),
            3
        )

    # カメラ映像を表示
    cv2.imshow("Camera", display)

    # Escキーで終了
    if cv2.waitKey(1) == 27:
        break

# OpenCVウィンドウを閉じる
cv2.destroyAllWindows()

# カメラ停止
picam2.stop()

最初に、顔を見つけるための学習データ(Haar Cascade)を読み込みます。次に、カメラモジュールを起動して映像を取得します。取得した画像は、そのままだと処理が重いため、表示用に縮小してから白黒画像へ変換します。

cascade.detectMultiScale()が顔を探す処理です。顔が見つかると、顔の位置と大きさの情報が取得されます。最後に、見つかった顔を赤い四角で囲んで画面に表示します。

プログラムを実行すると以下のように表示されます。※顔部分は後から加工により消しています。

顔認識と電子工作を組み合わせる

顔を検出するだけでも面白いですが、電子工作と組み合わせると活用の幅が大きく広がります。ここでは、顔を認識したときにLEDを光らせてみます。

LEDとラズベリーパイは以下のように接続します。LEDに電流を流しすぎると壊れてしまうので、100Ωの抵抗を使用しています。

ラズベリーパイでLEDを点滅させる方法について以下の記事で詳しく解説しています。
≫【ラズベリーパイ電子工作の始め方】失敗したくない初心者のための完全ガイド

プログラムは以下の通りです。

from picamera2 import Picamera2
import cv2
from gpiozero import LED

# GPIO18に接続したLEDを操作するための設定
led = LED(18)

# Haar Cascadeのファイルパス
HAAR_FILE = (
    "/usr/share/opencv4/haarcascades/"
    "haarcascade_frontalface_default.xml"
)

# 顔認識用CascadeClassifierを作成
cascade = cv2.CascadeClassifier(HAAR_FILE)

# Picamera2を初期化
picam2 = Picamera2()

# Camera Module 2の最大解像度を使用
config = picam2.create_preview_configuration(
    main={"size": (3280, 2464)}
)

# 設定を反映
picam2.configure(config)

# カメラを起動
picam2.start()

while True:

    # カメラ画像を取得
    frame = picam2.capture_array()

    # OpenCVで扱えるBGR形式へ変換
    frame = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR)

    # 表示用に1024×768へ縮小
    display = cv2.resize(frame, (1024, 768))

    # グレースケール画像へ変換
    gray = cv2.cvtColor(display, cv2.COLOR_BGR2GRAY)

    # 顔認識
    faces = cascade.detectMultiScale(
        gray,
        scaleFactor=1.1,
        minNeighbors=5,
        minSize=(50, 50)
    )
    
    # 顔が1つ以上見つかったらLED点灯
    if len(faces) > 0:
        led.on()
    else:
        led.off()

    # 顔を四角で囲む
    for (x, y, w, h) in faces:

        cv2.rectangle(
            display,
            (x, y),
            (x + w, y + h),
            (0, 0, 255),
            3
        )

    # カメラ映像を表示
    cv2.imshow("Camera", display)

    # Escキーで終了
    if cv2.waitKey(1) == 27:
        break

# OpenCVウィンドウを閉じる
cv2.destroyAllWindows()

# カメラ停止
picam2.stop()

54行目の if len(faces) > 0: は、顔が1つ以上見つかったかどうかを確認する処理です。cascade.detectMultiScale() は、検出した顔の情報をまとめて返します。len(faces) は見つかった顔の数を表しており、顔が1つ以上見つかった場合はLEDを点灯し、顔が見つからなかった場合はLEDを消灯します。

55行目のLED点灯部分を変更すれば、ブザーを鳴らしたり、メールを送信したりといった多彩なアクションが実現できます。

カメラ映像からリアルタイムで物体を検出

使用したOS

ここから先のコードを実行するには、64ビット版のRaspberry Pi OS(Lgacy)が必要です。最新のOS「Trixie」では動作しない点に注意が必要です。

顔だけでなくさまざまな物体をリアルタイムで検出するプログラムをRaspberry Pi上で実行する方法を紹介します。MediaPipeのオブジェクト検出モデルを使用し、カメラ映像をもとに効率的な推論を行うものです。

そぞら
そぞら

MediaPipeはGoogleが提供するライブラリで、物体検出やジェスチャー認識などのビジョンタスクを簡単に実装できるツールです。

まずは、ターミナルを開き、以下のコマンドを実行していきます。

最初に、インストールできるソフトウェアの一覧を最新の状態に更新します。

sudo apt update

カメラ制御に必要な libcap ライブラリの開発用ヘッダーをインストールします。

sudo apt install libcap-dev

mpという名前のPython仮想環境を作成します。

python3 -m venv ~/mp --system-site-packages

チェックポイント

仮想環境とは、Python用の専用作業スペースのようなものです。Raspberry Pi本体のPython環境とは分離されているため、他のプログラムに影響を与えずにライブラリをインストールできます。


Bookworm以降のRaspberry Pi OSでは、システム領域へのPythonライブラリの直接インストールが制限されています。そのため、MediaPipeを利用するには仮想環境の使用が必要です。


コマンドが完了すると、「mp」フォルダが作成され、その中に専用のPython実行環境が用意されます。

仮想環境を有効化します。

source ~/mp/bin/activate

MediaPipeのサンプルコードを含むリポジトリをRaspberry Piにコピーします。

git clone https://github.com/googlesamples/mediapipe.git

Raspberry Pi用の物体検出のサンプルコードがあるディレクトリに移動します。

cd mediapipe/examples/object_detection/raspberry_pi

物体検出プログラムを実行するために必要な環境をセットアップします。32ビット版のRaspberry Pi OSでは、この段階でエラーが発生する場合があります。この場合は64ビット版の使用を推奨します

sh setup.sh

Thonnyを仮想環境に切り替える

Thonnyは初期設定のままだと、Raspberry Pi標準のPython環境(/usr/bin/python3)を使用します。そのため、仮想環境内にインストールしたMediaPipeを読み込もうとすると、エラーが発生します。そこで、Thonnyが仮想環境内のPythonを使用するよう設定を変更します。

Thonny画面右下の「Local Python 3 • /usr/bin/python3」をクリックして、表示されたメニューから「Configure interpreter…」をクリックします。

「Python executable」の右側にある「…」ボタンをクリックして、「ホーム」、「mp」の順に選択します。

「bin」、「python」、「OK」の順にクリックします。

検出した物体を表示する

以下のコードは、必ず/home/pi/mediapipe/examples/object_detection/raspberry_pi保存してから実行してください。

#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import sys
import time
from picamera2 import Picamera2
import cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from utils import visualize

# FPSを計算し推論を制御するためのグローバル変数
COUNTER, FPS = 0, 0
START_TIME = time.time()
is_inference_in_flight = False  # 推論中かどうかを制御するフラグ
latest_detection_result = None  # 最新の検出結果を格納する変数


def run(model, max_results, score_threshold):
    """カメラから取得した映像に対して継続的に推論を実行します。

    引数:
        model: TFLite物体検出モデルの名前。
        max_results: 検出結果の最大数。
        score_threshold: 検出結果のスコア閾値。
    """
    global is_inference_in_flight, latest_detection_result

    # カメラからの映像入力を開始
    picam2 = Picamera2()

    config = picam2.create_preview_configuration(
        main={"size": (800, 600)}
    )

    picam2.configure(config)
    picam2.start()

    # 表示用パラメータ
    row_size = 50  # ピクセル
    left_margin = 24  # ピクセル
    text_color = (0, 0, 0)  # 黒
    font_size = 1
    font_thickness = 1
    fps_avg_frame_count = 10

    def save_result(result: vision.ObjectDetectorResult, unused_output_image: mp.Image, timestamp_ms: int):
        """推論が完了した際に呼び出されるコールバック関数。"""
        global is_inference_in_flight, latest_detection_result, COUNTER, START_TIME, FPS

        # 推論が完了したことをマーク
        is_inference_in_flight = False

        # FPSを更新
        if COUNTER % fps_avg_frame_count == 0:
            current_time = time.time()
            FPS = fps_avg_frame_count / (current_time - START_TIME)
            START_TIME = current_time

        latest_detection_result = result
        COUNTER += 1

    # 物体検出モデルを初期化
    base_options = python.BaseOptions(model_asset_path=model)
    options = vision.ObjectDetectorOptions(
        base_options=base_options,
        running_mode=vision.RunningMode.LIVE_STREAM,
        max_results=max_results,
        score_threshold=score_threshold,
        result_callback=save_result
    )
    detector = vision.ObjectDetector.create_from_options(options)

    # カメラから継続的に映像を取得し推論を実行
    while True:

        # カメラ画像を取得
        rgb_image = picam2.capture_array()
        
        # XBGR8888 → RGB888
        rgb_image = cv2.cvtColor(
            rgb_image,
            cv2.COLOR_BGRA2RGB
        )        

        # MediaPipe用画像
        mp_image = mp.Image(
            image_format=mp.ImageFormat.SRGB,
            data=rgb_image
        )

        # 表示用画像
        image = rgb_image.copy()

        # 前回の推論が完了している場合のみ新しい推論を開始
        if not is_inference_in_flight:
            detector.detect_async(mp_image, time.time_ns() // 1_000_000)
            is_inference_in_flight = True

        # FPSを表示
        fps_text = 'FPS = {:.1f}'.format(FPS)
        text_location = (left_margin, row_size)
        current_frame = image
        cv2.putText(current_frame, fps_text, text_location, cv2.FONT_HERSHEY_DUPLEX,
                    font_size, text_color, font_thickness, cv2.LINE_AA)

        # 最新の検出結果がある場合は可視化
        if latest_detection_result is not None:
            current_frame = visualize(current_frame, latest_detection_result)

        cv2.imshow('object_detection', current_frame)

        # ESCキーが押されたらプログラムを終了
        if cv2.waitKey(1) == 27:
            break

    detector.close()
    picam2.stop()
    cv2.destroyAllWindows()


def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        '--model',
        help='物体検出モデルのパス。',
        required=False,
        default='efficientdet.tflite')
    parser.add_argument(
        '--maxResults',
        help='検出結果の最大数。',
        required=False,
        default=5)
    parser.add_argument(
        '--scoreThreshold',
        help='検出結果のスコア閾値。',
        required=False,
        type=float,
        default=0.25)

    args = parser.parse_args()

    run(args.model, int(args.maxResults), args.scoreThreshold)


if __name__ == '__main__':
    main()

プログラムの保存場所が異なると、モジュールの読み込み時にエラーが発生するので注意が必要です。

プログラムの保存場所

上記のプログラムはRaspberry Piに接続されたカメラを使い、MediaPipeを利用してリアルタイムで物体を検出し、その結果を画面に表示するものです。具体的には、カメラから取得した映像をTFLite(TensorFlow Lite)モデルに入力し、物体の検出を行っています。TFLiteは軽量かつ効率的な機械学習モデルを動作させるためのプラットフォームで、Raspberry Piのようなリソースの限られた環境での使用に最適化されています。

まず、カメラから映像を取得し、それをBGR形式からRGB形式に変換します。これはTFLiteモデルがRGB形式を要求するために必要です。その後、映像が物体検出モデルに送られ、推論によって検出された物体の情報が返されます。この情報をもとに、画面上に物体を囲む枠や名前を描画し、何が検出されたのかを表示します。

dining table、bottle、cupを検出

このプログラムは、サンプルコードとして提供されているdetect.pyを改良したものです。元のコードでは処理が追いつかず、動作が不安定になることがありました。そこで、前回の認識処理が終わるまで次の認識処理を開始しないようにし、安定して動作するよう改善しています。

このプログラムで検出できる物体の一例を次に示します。

  • 人(person)
  • 自動車(car)
  • バイク(motorcycle)
  • 自転車(bicycle)
  • バス(bus)
  • トラック(truck)
  • ボトル(bottle)
  • テレビ(tv)
  • ノートパソコン(laptop)
  • 椅子(chair)
  • ベッド(bed)
  • マウス(mouse)
  • 携帯電話(cellphone)

検出した物体をカウントする

特定の物体(今回は車)を検出した場合にその数をカウントすることもできます。以下のコードを、/home/pi/mediapipe/examples/object_detection/raspberry_piに保存します。

#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import sys
import time
from picamera2 import Picamera2
import cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from utils import visualize

# 任意の物体名を指定する変数(ここで変更可能)
target_object = "car"  # ここを好きな物体名に変更できる

# FPSを計算し推論を制御するためのグローバル変数
COUNTER, FPS = 0, 0
START_TIME = time.time()
is_inference_in_flight = False  # 推論中かどうかを制御するフラグ
latest_detection_result = None  # 最新の検出結果を格納する変数


def run(model, max_results, score_threshold):
    """カメラから取得した映像に対して継続的に推論を実行します。

    引数:
        model: TFLite物体検出モデルの名前。
        max_results: 検出結果の最大数。
        score_threshold: 検出結果のスコア閾値。
    """
    global is_inference_in_flight, latest_detection_result

    # カメラからの映像入力を開始
    picam2 = Picamera2()

    config = picam2.create_preview_configuration(
        main={"size": (800, 600)}
    )

    picam2.configure(config)
    picam2.start()

    # 表示用パラメータ
    row_size = 50  # ピクセル
    left_margin = 24  # ピクセル
    text_color = (0, 0, 0)  # 黒
    font_size = 1
    font_thickness = 1
    fps_avg_frame_count = 10

    def save_result(result: vision.ObjectDetectorResult, unused_output_image: mp.Image, timestamp_ms: int):
        """推論が完了した際に呼び出されるコールバック関数。"""
        global is_inference_in_flight, latest_detection_result, COUNTER, START_TIME, FPS

        # 推論が完了したことをマーク
        is_inference_in_flight = False

        # FPSを更新
        if COUNTER % fps_avg_frame_count == 0:
            current_time = time.time()
            FPS = fps_avg_frame_count / (current_time - START_TIME)
            START_TIME = current_time

        latest_detection_result = result
        COUNTER += 1

    # 物体検出モデルを初期化
    base_options = python.BaseOptions(model_asset_path=model)
    options = vision.ObjectDetectorOptions(
        base_options=base_options,
        running_mode=vision.RunningMode.LIVE_STREAM,
        max_results=max_results,
        score_threshold=score_threshold,
        result_callback=save_result
    )
    detector = vision.ObjectDetector.create_from_options(options)

    # カメラから継続的に映像を取得し推論を実行
    while True:

        # カメラ画像を取得
        rgb_image = picam2.capture_array()
        
        # XBGR8888 → RGB888
        rgb_image = cv2.cvtColor(
            rgb_image,
            cv2.COLOR_BGRA2RGB
        )        

        # MediaPipe用画像
        mp_image = mp.Image(
            image_format=mp.ImageFormat.SRGB,
            data=rgb_image
        )

        # 表示用画像
        image = rgb_image.copy()

        # 前回の推論が完了している場合のみ新しい推論を開始
        if not is_inference_in_flight:
            detector.detect_async(mp_image, time.time_ns() // 1_000_000)
            is_inference_in_flight = True

        # FPSを表示
        fps_text = 'FPS = {:.1f}'.format(FPS)
        text_location = (left_margin, row_size)
        current_frame = image
        cv2.putText(current_frame, fps_text, text_location, cv2.FONT_HERSHEY_DUPLEX,
                    font_size, text_color, font_thickness, cv2.LINE_AA)

        # 最新の検出結果がある場合は可視化
        if latest_detection_result is not None:
            current_frame = visualize(current_frame, latest_detection_result)
            
            # target_objectのカウントを計算
            target_count = sum(1 for detection in latest_detection_result.detections
                               if detection.categories[0].category_name == target_object)

            # target_objectのカウントを画面に表示
            count_text = f'{target_object} = {target_count}'
            count_location = (left_margin, row_size * 2)
            cv2.putText(current_frame, count_text, count_location, cv2.FONT_HERSHEY_DUPLEX,
                        font_size, text_color, font_thickness, cv2.LINE_AA)            
            

        cv2.imshow('object_detection', current_frame)

        # ESCキーが押されたらプログラムを終了
        if cv2.waitKey(1) == 27:
            break

    detector.close()
    picam2.stop()
    cv2.destroyAllWindows()


def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        '--model',
        help='物体検出モデルのパス。',
        required=False,
        default='efficientdet.tflite')
    parser.add_argument(
        '--maxResults',
        help='検出結果の最大数。',
        required=False,
        default=5)
    parser.add_argument(
        '--scoreThreshold',
        help='検出結果のスコア閾値。',
        required=False,
        type=float,
        default=0.25)

    args = parser.parse_args()

    run(args.model, int(args.maxResults), args.scoreThreshold)


if __name__ == '__main__':
    main()

24行目の「target_object」という変数で、カウントする物体(例えば「car」など)を指定しています。この物体を検出したときに、その数をカウントして画面に表示します。この変数を変更することで、他の物体をターゲットにできます。

このコードの応用例を考えてみます。例えば、カメラに映る人の数が一定以上になった際に特定の処理を実行できます。店舗内の混雑状況を把握したり、その情報をデータ化するなど、さまざまな用途に活用できそうです。

刃物(ナイフ)を検出したときにGmailで通知する

本項では応用として、カメラ映像内にナイフを検出した際に通知を送る方法を解説します。

以下のコードを実装することで、カメラ映像内にナイフが映った際にメール通知が送られるようになります。33行目のGoogleのアプリパスワードを取得する手順はアプリパスワードの取得を参考にしてください。

#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import argparse
import sys
import time
from picamera2 import Picamera2
import cv2
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
from utils import visualize
import datetime
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders

# Gmail送信の関数
def send_gmail(Discovery_time):
    YOUR_GMAIL_ADDRESS = "xxxxx@gmail.com"  # Gmailアドレス
    APP_PASSWORD = "xxxxxxxxxx"       # アプリパスワード
    TO_ADDRESS = YOUR_GMAIL_ADDRESS    # 送信先アドレス
    SMTP_SERVER = "smtp.gmail.com"          # SMTPサーバー
    SMTP_PORT = 587                         # SMTPポート番号
    EMAIL_SUBJECT = "ナイフ検知アラート"     # メールの件名
    EMAIL_BODY = f"ナイフが検出されました!\n検出時間: {Discovery_time}"  # メール本文

    try:
        # メール作成
        msg = MIMEMultipart()
        msg['From'] = YOUR_GMAIL_ADDRESS
        msg['To'] = TO_ADDRESS
        msg['Subject'] = EMAIL_SUBJECT

        # メール本文を追加
        msg.attach(MIMEText(EMAIL_BODY, 'plain'))

        # 画像ファイルを添付
        attachment = open("image.jpg", "rb")
        part = MIMEBase('application', 'octet-stream')
        part.set_payload(attachment.read())
        encoders.encode_base64(part)
        part.add_header('Content-Disposition', 'attachment; filename="image.jpg"')
        msg.attach(part)

        # メール送信
        connection = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
        connection.starttls()
        connection.login(YOUR_GMAIL_ADDRESS, APP_PASSWORD)
        connection.send_message(msg)
        connection.quit()
        print("メール送信成功")

    except Exception as e:
        print(f"メール送信失敗: {str(e)}")

# FPSを計算し推論を制御するためのグローバル変数
COUNTER, FPS = 0, 0
START_TIME = time.time()
is_inference_in_flight = False  # 推論中かどうかを制御するフラグ
latest_detection_result = None  # 最新の検出結果を格納する変数
last_detection_time = 0  # 最後にナイフを検出した時間
DETECTION_COOLDOWN = 60  # 再検出を抑制する時間(秒)

def run(model, max_results, score_threshold):
    """カメラから取得した映像に対して継続的に推論を実行します。

    引数:
        model: TFLite物体検出モデルの名前。
        max_results: 検出結果の最大数。
        score_threshold: 検出結果のスコア閾値。
    """
    global is_inference_in_flight, latest_detection_result, last_detection_time

    # カメラからの映像入力を開始
    picam2 = Picamera2()

    config = picam2.create_preview_configuration(
        main={"size": (800, 600)}
    )

    picam2.configure(config)
    picam2.start()

    # 表示用パラメータ
    row_size = 50  # ピクセル
    left_margin = 24  # ピクセル
    text_color = (0, 0, 0)  # 黒
    font_size = 1
    font_thickness = 1
    fps_avg_frame_count = 10

    def save_result(result: vision.ObjectDetectorResult, unused_output_image: mp.Image, timestamp_ms: int):
        """推論が完了した際に呼び出されるコールバック関数。"""
        global is_inference_in_flight, latest_detection_result, COUNTER, START_TIME, FPS

        # 推論が完了したことをマーク
        is_inference_in_flight = False

        # FPSを更新
        if COUNTER % fps_avg_frame_count == 0:
            current_time = time.time()
            FPS = fps_avg_frame_count / (current_time - START_TIME)
            START_TIME = current_time

        latest_detection_result = result
        COUNTER += 1

    # 物体検出モデルを初期化
    base_options = python.BaseOptions(model_asset_path=model)
    options = vision.ObjectDetectorOptions(
        base_options=base_options,
        running_mode=vision.RunningMode.LIVE_STREAM,
        max_results=max_results,
        score_threshold=score_threshold,
        result_callback=save_result
    )
    detector = vision.ObjectDetector.create_from_options(options)

    # カメラから継続的に映像を取得し推論を実行
    while True:

        # カメラ画像を取得
        rgb_image = picam2.capture_array()
        
        # XBGR8888 → RGB888
        rgb_image = cv2.cvtColor(
            rgb_image,
            cv2.COLOR_BGRA2RGB
        )        

        # MediaPipe用画像
        mp_image = mp.Image(
            image_format=mp.ImageFormat.SRGB,
            data=rgb_image
        )

        # 表示用画像
        image = rgb_image.copy()

        # 前回の推論が完了している場合のみ新しい推論を開始
        if not is_inference_in_flight:
            detector.detect_async(mp_image, time.time_ns() // 1_000_000)
            is_inference_in_flight = True

        # FPSを表示
        fps_text = 'FPS = {:.1f}'.format(FPS)
        text_location = (left_margin, row_size)
        current_frame = image
        cv2.putText(current_frame, fps_text, text_location, cv2.FONT_HERSHEY_DUPLEX,
                    font_size, text_color, font_thickness, cv2.LINE_AA)

        # 最新の検出結果がある場合は可視化
        if latest_detection_result is not None:
            current_frame = visualize(current_frame, latest_detection_result)
            
            # ナイフ検出のチェック(描画後のフレームに対して)
            current_time = time.time()
            if any("knife" in detection.categories[0].category_name.lower()
                   for detection in latest_detection_result.detections):
                if current_time - last_detection_time > DETECTION_COOLDOWN:
                    print("発見")
                    dt_now = datetime.datetime.now()
                    Discovery_time = dt_now.strftime('%Y年%m月%d日%H時%M分%S秒')
                    cv2.imwrite("image.jpg", current_frame)
                    
                    # Gmail送信
                    send_gmail(Discovery_time)
                    
                    last_detection_time = current_time
                else:
                    print("再検出(通知待機中)")             

        cv2.imshow('object_detection', current_frame)

        # ESCキーが押されたらプログラムを終了
        if cv2.waitKey(1) == 27:
            break

    detector.close()
    picam2.stop()
    cv2.destroyAllWindows()


def main():
    parser = argparse.ArgumentParser(
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument(
        '--model',
        help='物体検出モデルのパス。',
        required=False,
        default='efficientdet.tflite')
    parser.add_argument(
        '--maxResults',
        help='検出結果の最大数。',
        required=False,
        default=5)
    parser.add_argument(
        '--scoreThreshold',
        help='検出結果のスコア閾値。',
        required=False,
        type=float,
        default=0.25)

    args = parser.parse_args()

    run(args.model, int(args.maxResults), args.scoreThreshold)


if __name__ == '__main__':
    main()

このプログラムでは、ナイフを検出した際に、カメラの映像を保存し、それを送信する機能を実装しています。ナイフが検出されると、現在のフレームが cv2.imwrite() を用いて detected_knife.jpg というファイル名で保存されます。この画像を send_line_notification_with_image() 関数を使用して、通知を送る仕組みになっています。

DETECTION_COOLDOWN = 60 はナイフを検出した後、次の通知を送るまで最低60秒の間隔を空けるための設定です。これにより、同じナイフが短時間で繰り返し検出された場合に、不要な通知が連続して送信されるのを防ぎます。

この方法を使えば、リアルタイムで物体検出を行い、特定の物体(今回の場合はナイフ)を検出した際に画像付きで通知するシステムを構築できます。不審な人物の検出や、特定のエリアへの侵入監視などにも活用が期待できます。

コメント一覧

夏は暑い

こんにちは。
2つほど質問です。
1.「Raspberry Pi Camera V2」ですが、Aサイトだと評価がいまいちのようです。
(梱包もそうですが、カメラが認識しないという評価)
当たりはずれがあるのでしょうか?
(値段は高くなりますが高評価の「Raspberry Pi Raspberry Pi NoIR Camera V2」はどう判断されますか?)
2.「カメラを有効にする」の項目でデスクトップの説明が出てきますが、デスクトップ自体はどうやって準備(ソフト※出来ればハードの説明も)されたのでしょうか(OSはLinux系?)?
宜しくお願い致します。

返信する
そぞら

お問い合わせありがとうございます。
回答させていただきます。

1.「Raspberry Pi Camera V2」の評価の件、確認しました。
  カメラが認識しないというレビューが2件ありますので、不安になることと思います。
  恐縮ですが、以下のサイトを検討いただければと思います。同商品の取り扱いがあります。  
  https://akizukidenshi.com/catalog/g/gM-10518/
  Amazonではありませんが、電子工作愛好家に人気のパーツショップです。
  申し訳ございません。「NoIR Camera」につきましては、使用経験がないため
  紹介させていただくことができないです。

2. デスクトップにつきましては、Raspberry Pi OSを使用しています。
  Raspberry Pi OSはLinux系のOSです。
  準備方法は以下の記事で解説していますので、参考にしていただければと思います。
  https://sozorablog.com/raspberrypi_initial_setting/

返信する
夏は暑い

早々のお返事有難うございます。
ラズベリーパイと自作カメラは初めてなので、少しずつ試します。

返信する
なろす

こんにちは。
全くの初心者なので、頓珍漢な質問でしたら恐縮です。

このプログラムで動かしたところ、毎60秒ごとに静止画、もしくは動画が撮影されてしまいます。どうすればよいでしょうか。
ほかはちゃんと動いてます。
人感センサーが勝手に60秒ごとに反応しているかのとも思いましたが、そうでもないようです。

返信する
そぞら

お問い合わせありがとうございます。

60秒ごとというのが不思議なのですが、、、

人感センサー単体の動作は問題ないでしょうか。
以下のプログラムで正常に検知できることをご確認ください。

import RPi.GPIO as GPIO
import time

GPIO_PIN = 18

GPIO.setmode(GPIO.BCM)
GPIO.setup(GPIO_PIN,GPIO.IN)

while True:
if(GPIO.input(GPIO_PIN) == GPIO.HIGH):
print(“反応あり!”)
time.sleep( 1 )

else:
print(“反応なし”)
time.sleep( 1 )

返信する
なろす

プログラムまでいただき、ありがとうございます。

このプログラムでは毎分ごとに検出するような挙動はしていません。
とりあえず、センサーの感度調整で望まない検出頻度は下がるようなので、原因はよくわかりませんが、それで対応いたします。

ありがとうございました。

返信する
夏は暑い

こんにちは。
改めて質問致します。
「Raspberry Pi 3 model B」のセットが手ごろな価格で売られているので、それで監視カメラに挑戦しようと検討中です。
理由は「Raspberry Pi 4」のセットがベストだと思いますが、高性能より何とか動く程度でいいことと、ずばり値段が高い&在庫も少ないためです。
別記事で「Raspberry Pi 3 model B」だと性能が劣ると拝見しましたが、映像がかくかくするのでしょうか?

お返事頂ければ幸いです。

返信する
そぞら

お問い合わせありがとうございます。

「Raspberry Pi 3 model B」でもカメラを扱ったことがありますが
問題なく動作します。
映像がカクカクすることはないです。
「Raspberry Pi 3 model B」は現在生産されていないという
話を聞いています。
手ごろな価格で販売されているのは貴重だと思います。

返信する
夏は暑い

お世話になります。
いつもお返事有難うございます。
部品が揃ってセットアップをしていますが、最初の設定で「?」な箇所があります。
出来るところから進めようと思いSDカードにOSはインストールしておきましたが、
詳細な設定(ユーザー名とかホスト名とか)は飛ばしました。
youtubeで設定動画を見たのですが、恐らく細かい設定は後回しでやる動画だと思っています(OSと他設定を同時に進めない)が、合っていますか?
というのは、今一度Imagerを起動しましたが設定する中のホスト名とユーザー名の違いが分からず(他設定が必要なとこと不要の個所が不明)、動画内の後から設定だとそういうのはないようなので、そっちが楽かなと思っています。
お返事頂ければ幸いです。

返信する
そぞら

お問い合わせありがとうございます。

Imagerを使ってSDカードに書き込むときに設定をしないことは問題ありません。
ホスト名はデフォルトのraspberrypiのままで大丈夫です。
同一ネットワーク上に複数のラズパイがある場合、識別するために
ホスト名を変更する場合がありますが、後からでも変更可能です。

ユーザー名とパスワードは起動時に設定する必要があります。
パソコンからリモート操作するときに使うのでメモしておいてください。

返信する
夏は暑い

どーもです。
応用編までは行けました。
有難うございます。

返信する
夏は暑い

お世話になります。
動画保存について質問です。
恐らく私のハードウェア(本体)の問題(メモリ1Gなので動画処理に耐えられない?)と思っていますが、「test.m4v」はvlcで起動しますが数秒だとただ暗く、10秒以上保存でようやくちらつきますが、画面上の方1/4に頑張ってるちらつきが出て終わりです。
別の方の動画で設定の「gl driver」を「full kms gl driver」に変えればとありましたが、本体をRaspberryPi4にしてメモリは最低でも4Gで試すのが手っ取り早いでしょうか?(上記の場合パフォーマンス設定からGPUメモリも上げる必要ありのようです)。

返信する
そぞら

お世話になっております。

当方でもRaspberry Pi 3で動画保存を実行したところ
同じ不具合が確認されました。
Raspberry Pi 3で動画保存する方法を模索してみます。
何かわかったら連絡させていただきます。

返信する
夏は暑い

こんばんは。
質問の件、半分解決しました(コードは未修正です)。
普段使っているPCで試しに再生したら普通でした。
動画処理となると4にしないと厳しいかもです。
お手間をおかけしました。

返信する
そぞら

おつかれさまです。
こちらこそ、お役に立てず申し訳ないです。

返信する
Mr.kouki

こんにちは、初心者ですが
Pythonのpip3のOpenCVとVideoCaptureでも監視カメラを作ることは可能ですか?
私は、CentOS7で作ろうと思ってますが、必ずラズベリーパイのCMDじゃないといけないんですか?
私が所持している素材:
microSDカード
Raspberry Pi3 ※Model B
Elecomのランケーブル
買おうと思ってる素材:
raspberryPi3 のケース
ヒートシンク
やはりケースは必要ですか?

返信する
そぞら

こんにちは。
ブログを読んでいただきありがとうございます。

申し訳ございません。
CentOS7は使ったことがないため、分からないです。
ケースは破損防止のため用意した方が良いと思います。

返信する
くろえっち

本ページの人感センサーが反応したら静止画を保存するプログラムをコピーし実行したところ
最初に撮影された画像が更新されずに表示されます。
例えば、1回目の反応、撮影後、カメラの角度を変えて2回目の反応が起きても1回目の画像が保存されます。この際、画像ファイルの名前はセンサー反応時刻でちゃんとついております。

私は、センサーが反応するごとに異なる画像を撮影したいです。どのように修正すれば良いでしょうか?

返信する
そぞら

申し訳ございません。プログラムに不備がありました。

1回目の撮影後はrelease()をして画像を初期状態にする必要があります。
また、release()をするとカメラから画像が取得できなくなるため
ループの中にcv2.VideoCapture(0)を移動しました。

記事内のプログラムを修正しております。
お手数ですが、詳細はそちらをご確認ください。

返信する
cheese

こんにちは。
質問宜しいでしょうか。
コマンドからカメラを有効にしたのですが、インターフェイスからカメラを有効にしようとしても、そもそも項目が出てきません。
なぜでしょうか?
教えてくださると幸いです。

返信する
そぞら

お問い合わせありがとうございます。

raspi-configでカメラを有効化していれば
すでにカメラが使える状態になっているはずです。

なお、有効化してもインターフェイスの部分には
カメラ項目は出ません。

よろしくお願いします。

返信する
宮崎ヒロキ

こんにちは。
初心者質問で恐縮なのですが、人感センサーで反応したときにLINEbotに写真を送るという流れをラズパイとスマホ1対1ではなく、ラズパイ1に対して複数とのやり取りにすることは可能でしょうか。
 自転車の防犯システムとして、そのラズパイ前に自転車を止め、QRコードなどでLINEを追加し異変があれば通知するといったシステムを考えています。教えていただけると幸いです。

返信する
そぞら

ブログを読んでいただきありがとうございます。

複数のLINEアカウントに対して
写真を同時送信することは可能です。
その場合はプログラムの9~15行目をコピーして
下に追加します。

返信する
九州ヒロキ

 返信ありがとうございます。
 防犯システムということで、複数のLINEアカウントに登録してもらう場合、異変検知で全員に送信ではなく、最後に登録した人のみに送信するなどは可能でしょうか。
 また、Androidアプリを作成してラズパイを遠隔操作や送受信するなど可能でしょうか。LINEbotを活用するか、Androidアプリを作成しようと考えています。
重ね重ねの質問で申し訳ありません。ご教授いただけると幸いです。

返信する
そぞら

まずLINEアカウントの登録についてですが
登録自体を自動化するのでしょうか?
自動メッセージを送信するためには
「LINE Notify」のトークン取得が必要であり
ここを自動化するのは難しいと思われます。

あらかじめトークン取得してある人に送信するのであれば
何らかの方法で人を識別して、
その人に送信することはできると思います。

申し訳ございません。
Androidアプリは作成経験がなく
お答えできないです。

返信する
平林

こんにちは!
こちらの記事を参考にラズパイでOpenCVが動かせました。
情報をまとめていただいてありがとうございます!

返信する
そぞら

ブログをご覧いただきありがとうございます。
今後も皆様のお役に立てるよう改善を進めてまいります。

返信する
ナス

2つ質問があります。
ラズパイを使って犬の様子をチャットアプリに動画として送るのを作ろうとしているんですが、動画で送る方法とかはありますか?
そしてカメラのモジュールをできれば、モジュールを使わずにできないでしょうか?

返信する
そぞら

すみません。動画の送信は未経験です。
カメラモジュールをモジュールを使わずに使用する方法も分からないです。

返信する
ナス

モジュールを自作することは可能でしょうか?

返信する
さの

こんにちは。
このサイトを参考にしてLINEに画像を送るのをできました。ありがとうございます。

一つ質問があります。

カメラ画像を一旦画像ファイルに保存してから、send_messageにてそのファイルを読み込んでいますが、ファイルに一旦保存することなく送信できるようにするにはどのようにコードを変更したら良いでしょうか?

SDカードに画像を繰り返し書き込むと、SDカードが早く劣化しそうな気がするので、改善したいと思っています。

よろしくお願いします。

返信する
そぞら

以下のコードを試してみてください。
画像は一時的にメモリ上(RAM)に保存されますが、SDカードには保存されません。
このコードでは、cv2.imencode()を使用して画像データをJPEG形式にエンコードし、
そのバイト列を直接メモリ上のBytesIOオブジェクトに保存しています。
そして、そのBytesIOオブジェクトをsend_message関数に渡して、一時ファイルとしてLINEに送信します。

import RPi.GPIO as GPIO
import cv2
import time
import datetime
import requests
import numpy as np
from io import BytesIO

# LINEメッセージ送信の関数
def send_message(Discovery_time, img_bytes):
url = “https://notify-api.line.me/api/notify”
token = “トークンをここに入力”
headers = {“Authorization” : “Bearer “+ token}
files = {‘imageFile’: img_bytes}
message = (Discovery_time,”侵入者あり”)
payload = {“message” : message}
r = requests.post(url, headers = headers, params=payload, files=files)

# 検出時間の取得
dt_now = datetime.datetime.now()
Discovery_time = dt_now.strftime(‘%Y年%m月%d日%H時%M分%S秒’)
print(Discovery_time)

# カメラ画像を取得する
cap = cv2.VideoCapture(0)
ret, frame = cap.read()
cap.release()

# 画像をメモリに保存する
is_success, buffer = cv2.imencode(“.jpg”, frame)
img_bytes = BytesIO(buffer)

# LINEメッセージ送信
send_message(Discovery_time, img_bytes)

返信する
そぞら

すみません。
def send_message(Discovery_time, img_bytes):
の関数部分のインデントがおかしくなっていますので、ご注意ください。

返信する
さの

早速ご回答いただきましてありがとうございます😊

無事、ファイル書き込みなしでのLINE送信ができました。cv2.imencode、io.BytesIOを使うんですね。ググってもやり方を見つけられなかったので助かりました。ありがとうございました。

返信する
パセリ

こんにちは。
「Pythonプログラムでカメラ映像(動画)を表示する」について、プログラムをコピペして実行しましたが、エラーになり、numpyを最新にしてもダメです。
ラズパイ純正のカメラでないことと、他のmotion機能を自動起動に設定していることが疑われますが、解決方法を教えて頂けないでしょうか?

そぞら

ご質問いただきありがとうございます。

OpenCVを使ったカメラアクセスで問題が発生しているとのこと、大変お困りのことと思います。
ご指摘の通り、motionがカメラデバイスを占有していることが原因の可能性が高いです。

以下の手順で問題が解決するかもしれませんので、お試しください。

まず、motionを停止します。
ターミナルを開き、以下のコマンドを実行してください。
sudo service motion stop

次に、OpenCVを使ったプログラムを再実行してみてください。
motionが停止している間は、他のプログラムがカメラデバイスにアクセスできるはずです。

OpenCVの使用が終わったら、以下のコマンドでmotionを再開できます。
sudo service motion start

motionがカメラデバイスを占有していると、他のプログラムからはカメラへのアクセスが制限されるため、この手順で問題が解決する可能性があります。

もし問題が解決しない場合、エラー内容をコピペして返信していただけると、お手伝いできるかもしれません。
よろしくお願いいたします。

パセリ

そぞらさん、アドバイスありがとうございます。
motionをストップして、OpenCVを試してみましたが、以下のようなエラーでした。
初心者で理解できないエラーのため、アドバイス頂けると助かります。
※送信エラーのため、一部のエラーのみ貼り付けました。

Traceback (most recent call last):
File “/home/tama/.local/lib/python3.9/site-packages/numpy/core/__init__.py”, line 24, in
from . import multiarray
File “/usr/lib/python3/dist-packages/thonny/plugins/cpython_backend/cp_back.py”, line 263, in _custom_import
module = self._original_import(*args, **kw)
File “/home/tama/.local/lib/python3.9/site-packages/numpy/core/multiarray.py”, line 10, in
from . import overrides
File “/usr/lib/python3/dist-packages/thonny/plugins/cpython_backend/cp_back.py”, line 263, in _custom_import
module = self._original_import(*args, **kw)
File “/home/tama/.local/lib/python3.9/site-packages/numpy/core/overrides.py”, line 8, in
from numpy.core._multiarray_umath import (
File “/usr/lib/python3/dist-packages/thonny/plugins/cpython_backend/cp_back.py”, line 263, in _custom_import
module = self._original_import(*args, **kw)
ImportError: libopenblas.so.0: cannot open shared object file: No such file or directory

返信する
そぞら

エラーメッセージを拝見すると、numpy関連のエラーが発生しているようです。
ImportError: libopenblas.so.0: cannot open shared object file: No such file or directoryは、
OpenBLASという数学ライブラリが見つからない、または正しくインストールされていないことを示しています。
これを解決するためには、OpenBLASライブラリをシステムにインストールする必要があります。

以下の手順を試してみてください:

ターミナルを開き、以下のコマンドを実行します。

sudo apt install libopenblas-dev

変更を有効にするために、ラズパイを再起動してください。

返信する
パセリ

そぞらさん、アドバイスありがとうございます。OpenBLASをインストールすると、OpecCVをテストしてまエラーが無くなりましたが、画像が起動せず、固まったままです。
ターミナルでbumpyを最新にしても、ターミナルに以下のエラーです。何かアドバイスあればよろしくお願いします。
Looking in indexes: https://pypi.org/simple, https://www.piwheels.org/simple
Requirement already satisfied: numpy in /usr/local/lib/python3.9/dist-packages (1.26.3)
WARNING: Running pip as the ‘root’ user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv

返信する
そぞら

おそらく最新のOS「Bookworm」を使用されていることが原因と思われます。
デスクトップ画面の壁紙が鵜飼いのようなデザインではないでしょうか?
本記事が最新OSに対応しておらず申し訳ございません。

Bookwormでpipをする場合、仮想環境で実行しないとエラーが出ます。
※仮想環境はプロジェクトごとに異なるPythonのバージョンやライブラリを分けて管理できる、独立した作業スペースです。

この場合の対策は、

①OSを1世代古いものBullseye(Legacy)にインストールし直す(データは消えてしまいます)
②今のOSのまま仮想環境を作る

以上のどちらかになります。

仮想環境を作成しその中でライブラリーをインストールし、camera_test.pyを実行する手順を説明します。

ターミナルを開きます。
仮想環境を作成します。

myenvという名前で作成しますが、任意の名前に変更可能です。

python3 -m venv myenv

以下のコマンドを実行して、仮想環境をアクティブにします。

source myenv/bin/activate

pipを最新バージョンにアップグレードします。

python -m pip install –upgrade pip

OpenCVをインストールします。

pip install opencv-python==4.5.1.48

numpyをアップグレードします。

pip install -U numpy

camera_test.pyがあるディレクトリに移動します。

cdコマンドを使用して、そのファイルがあるディレクトリに移動します。
例えば、camera_test.pyがDocumentsフォルダにある場合は以下のようにします。

cd ~/Documents

以下のコマンドを実行して、camera_test.pyを実行します。

python camera_test.py

長くなってしまいましたが、よろしくお願いします。
また何かあればご質問いただければと思います。

返信する
パセリ

さぞらさん、アドバイスありがとうございます。最新OSでしたので仮想環境をトライしましたが、うまくいかなかったので、旧OSに再インストールしました。
OpenCVもエラーなく動作し、Line通知までたどり着きました。ありがとうございます。

返信する

こんにちは。
sudo pip3 install opencv-python==4.5.1.48
でopenCVを入れようとしたところ
WARNING: Running pip as the ‘root’ user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
という文が出てきてしまいました。仮想環境を作れということなのでしょうか?
OSはBullseyeです。

返信する
そぞら

お問い合わせいただきありがとうございます。

先ほど、最新のBullseyeでsudo pip3 install opencv-python==4.5.1.48を実行してみましたが
エラーが出ることなくインストールできました。

可能であれば、OSの再インストールをお試しください。
よろしくお願いいたします。

返信する
みみみ

こんにちは
大学の研究で参考にさせていただいています
電子工作の接続をなしで顔認識をした画像をLINEに送るようにプログラムをしたいのですが知識が足りずどのように変更すればいいのかわらないです
教えていただければ幸いです

返信する
そぞら

ブログを読んでいただいてありがとうございます。
カメラで顔を検出した際に、画像をLINEに送るプログラムを記事に追加しました。
不明点がありましたら、お気軽にお問い合わせください。
よろしくお願いいたします。

返信する
ぐら

コメント失礼致します。
そぞらさんのブログを参考にさせていただいております、大学生です。

「Pythonプログラムでカメラ映像(動画)を表示する」の段階で躓いています。
実行すると、以下のようなエラーが表示されるのですが、どう修正すればいいかわからないのでご享受いただきたいです。

Traceback (most recent call last):
File “”, line 9, in
cv2.error: OpenCV(4.5.1) /tmp/pip-req-build-zatnrcbe/opencv/modules/imgcodecs/src/loadsave.cpp:753: error: (-215:Assertion failed) !_img.empty() in function ‘imwrite’

他の質問者の犬さんと同じ警告が出ているのが問題なのでしょうか?以下サイトにて問題ない的なことが書かれており、現状は警告を無視しています。プログラム初心者なので常識的なことを質問しているかもしれませんが、ご回答のほどよろしくお願いいたします。
https://teratail.com/9uestions/358301

本体:Raspberry-Pi v4
OS:Raspberry-Pi OS(64-bit) Bullseye (2024/6/24インストール)
カメラモジュール:v3
Python:3.9.4
opencv:https://sozorablog.com/camera-module-v3/ を参考にインストール(上記と同じ警告が出る)

返信する
そぞら

連絡が遅くなり、申し訳ございません。

「【ラズベリーパイ】監視カメラの作り方|Pythonでカメラモジュールを自在に操作」で紹介しているプログラムは
カメラモジュールV3に対応していません。

カメラモジュールV3の場合は、Picamera2を使って画像を取得してからOpenCVで表示させる
という手順が必要になります。

https://sozorablog.com/camera-module-v3/
の内容を参考にしてもらえればと思います。

不明点がありましたら、お気軽にお問い合わせください。
よろしくお願いいたします。

返信する
グラン

初めまして。
コメント失礼いたします。
こちらの記事のラズベリーパイで監視カメラ【基礎編】からコードを入力して実行したところ

ModulNotFoundError: No module named ‘cv2’と出てしまいます。

改善点を教えて頂いてもよろしいでしょうか。
使用しているものは、
カメラモジュールV2
Raspberry Pi4 ModelB 4GB
です。

返信する
そぞら

初めまして。
コメントありがとうございます。

「ModulNotFoundError: No module named ‘cv2’」というエラーが出る場合、PythonにOpenCV(cv2)がインストールされていない可能性が高いです。これを解決するには、以下のコマンドをターミナルに入力してOpenCVをインストールしてみてください。
sudo pip3 install opencv-python==4.5.1.48
これでも解決しない場合は、「lsb_release -a」コマンドを実行してOSのバージョンを確認してみてください。
「Codename: bookworm」が表示される場合は、仮想環境の作成が必要です。

返信する
グラン

返信ありがとうございます。
OSを調べたところ「Codename: bookworm」が表示されました。

素人で恐縮ですが、仮想環境とはどのようなものでしょうか?

返信する
そぞら

bookworm で使用されているPython 3.11では、仮想環境を作成しないとパッケージのインストール時にエラーが発生します。
これはシステム全体に影響を与えないようにするための制限です。
ただし、カメラモジュールV2を使用されるのであれば、Raspberry Pi OS(Legacy, 32-bit)への再インストールを検討された方が良いかもしれません。
bookworm では仮想環境だけでなく、カメラを表示するプログラムも変更する必要があるためです。
先ほどの返信内容と矛盾する部分があることをお詫び申し上げます。

返信する
グラン

Raspberry Pi OS(Legacy, 32-bit)への再インストールをしたところ、OSがBullseyeになったので記事の通りに実行することが出来ました。

ご丁寧に解説していただきありがとうございました。

返信する
すなふきん おじん

Pythonプログラムで静止画を保存するを実行すると
cv2.error: OpenCV(4.5.1) /tmp/pip-req-build-zatnrcbe/opencv/modules/imgcodecs/src/loadsave.cpp:753: error: (-215:Assertion failed) !_img.empty() in function ‘imwrite’
がでてしまいます。

Pythonプログラムでカメラ映像(動画)を表示するはうまくいきました。
OSのVerは Bullseye ラズパイは p3 Model b+です。カメラモジュールは V3です。
インストールは当該サイトの手順どうりにしています。
但し、以下が違います。
raspi-configで「Glamor」を有効にする
/boot/config.txtの最下行に「dtoverlay=imx708」を追加
/boot/config.txtで「dtoverlay=vc4-kms-v3d」のコメントアウトを外す は libcameraコマンドが動作しなかったのでやっていません。

なにか わかることありましたら 教えて頂けますでしょうか。

ただ 64bit版の Bullseyeです。 32bit版でないといけないのでしょうか。

返信する
そぞら

カメラモジュールV3は、新しいIMX708センサーを使用しており、従来の方法(cv2.VideoCaptureを使用したOpenCVのコード)では正常に動作しません。
V3を使用するには、公式のライブラリであるPicamera2を使用する必要があります。
以下をお試しください。Raspberry Pi 3 Model A+で動作確認しました。
from picamera2 import Picamera2
# Picamera2のインスタンスを作成
picam2 = Picamera2()
# カメラ画像を保存
picam2.start_and_capture_file(“test.jpg”)

返信する
グラン

コメント失礼いたします。
本記事⑦の「検出した物体を表示する」のコードを実行すると、以下のようなエラーが表示されるのですが、どう修正すればいいかわからないのでご享受いただだいてもよろしいでしょうか。

Traceback (most recent call last):
File “”, line 21, in
ImportError: cannot import name ‘visualize’ from ‘utils’ (/home/guran/.local/lib/python3.9/site-packages/utils/__init__.py)

とエラーが出てしまいました。
Raspberry Pi OS(Legacy, 64-bit)を使用しています。

返信する
そぞら

コードを/home/pi/mediapipe/examples/object_detection/raspberry_piに保存されているでしょうか?
プログラムの保存場所が異なると、モジュールの読み込み時にエラーが発生します。
よろしくお願いいたします。

返信する
ゴロ助

はじめまして。コメント失礼します。
「カメラ映像からリアルタイムで物体を検出」で以下の手順を実施したのち、
もともと動作していた「Pythonプログラムでカメラ映像(動画)を表示する」で作成したプログラムで、cv2.imshow(“Frame”, frame)でフリーズするようになってしまいました。
何か考えられることがありましたが、教えていただけないでしょうか。

sudo apt install libcap-dev
git clone https://github.com/googlesamples/mediapipe.git
cd mediapipe/examples/object_detection/raspberry_pi
sh setup.sh

返信する
そぞら

お問い合わせいただき、ありがとうございます。
お手数ですが、以下の情報を教えていただけますか?
・お使いのラズパイのモデル名
・カメラのモデル名
・Raspberry Pi OSの種類(BullseyeまたはBookworm)
・もしお分かりになれば、OSのインストール時期
よろしくお願いいたします。

返信する
ゴロ助

返信が遅くなり、申し訳ございませんでした。
環境は、Raspberry Pi 4B、カメラのジュールV2、Bullseyeになります。
OSのインストール時期は、2024年7月頃になります。

「カメラ映像からリアルタイムで物体を検出」は実行すると下記のエラーが発生しており、調査中ですが、こちらの手順ミスか何かで、カメラ映像(動画)に影響を与えてしまったのかもしれません。

Error in cpuinfo: prctl(PR_SVE_GET_VL) failed
INFO: Created TensorFlow Lite XNNPACK delegate for CPU.
terminate called after throwing an instance of ‘cv::Exception’
what(): OpenCV(4.5.5) /tmp/bazel_build/opencv/modules/imgproc/src/imgwarp.cpp:1724: error: (-215:Assertion failed) dst.cols < SHRT_MAX && dst.rows < SHRT_MAX && src.cols < SHRT_MAX && src.rows < SHRT_MAX in function 'remap'
Process ended with exit code -6.

返信する
そぞら

まずはターミナルで以下のコマンドを実行して
カメラのプレビュー画面が表示できるか確認してみてください。
libcamera-hello –timeout 0

エラーについて
error: (-215:Assertion failed) dst.cols~の部分は
カメラ映像のサイズが大きすぎるために発生するようです。

カメラ映像(動画)を表示するコードにおいて
カメラの解像度を 640×480 に設定してみましょう。

camera = cv2.VideoCapture(0)の下に次の2行を追加してみてください。
camera.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

ちなみに、物体検出のコードは正常に動きますか?

返信する
ゴロ助

返信ありがとうございます。
「libcamera-hello –timeout 0」を実施したところ、
カメラが認識されていませんでした(ERROR: *** no cameras available ***)
もう一度、OSのインストールからやり直したのですが、
「sh setup.sh」コマンドを実行するまではカメラ撮影ができているのですが、「sh setup.sh」コマンドを実行した以降は、カメラ撮影のプログラムを実行すると、フリーズしてしまいます。
cv2.imshow(“Frame”, frame)
key = cv2.waitKey(1)
ちなみに、下記の2行を追加しても、物体検出は正常に動作しませんでした。エラー内容も同じものでした。
camera.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
camera.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)

そぞら

何らかのライブラリのアップデートの影響かもしれませんが、「cv2.VideoCapture(0)」のあとに
以下のコードを加えることで解決できるようです。
camera.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*’MJPG’))
または
cap.set(cv2.CAP_PROP_FOURCC, cv2.VideoWriter_fourcc(*’MJPG’))
上記の追加により、カメラの映像フォーマットを MJPG(Motion JPEG) に設定し
解像度の適用を確実にすることができます。
お手数ですが、こちらを追加して試していただければと思います。
また、ブログ本文も動作確認済みのコードに修正しました。
対応が遅くなり、ご迷惑をおかけしました。
問題をご報告いただいたおかげで、より正確な情報を提供できるようになりました。
ありがとうございました。

ゴロ助

ご丁寧にありがうございます。
カメラ撮影は正常に動作しました。

返信する
ABXY

初めまして。
コメント失礼いたします、初心者の学生です。
そぞらさんの分かりやすい説明に助けられています。本当にありがとうございます。

現在授業の課題に取り組んでいるのですが、
こちらのサイトの「カメラ映像からリアルタイムで物体を検出」を応用して、防犯カメラを作ろうと考えています。
具体的には、刃物を検知すると写真を撮って、LINEに通知を送れるようにしたいです。
この場合、プログラムをどのように書き換えればよいでしょうか。

お手数をおかけしますが、ご教示いただけますと幸いです。

返信する
そぞら

はじめまして。
とても興味深い企画ですね。記事に追加させていただきたいと思いますので、少々お待ちください。
また、LINE通知のサービスが3月31日で終了予定のため、Gmail通知への変更を検討しておりますが、LINE通知をご希望されますでしょうか?

返信する
ABXY

ご返信いただきありがとうございます。
Gmail通知で全く問題ありません。

返信する
ABXY

追加記事を拝見しました。
ご多忙の中本当にありがとうございます。
よろしければ今後も参考にさせていただきます。

返信する
MO

はじめまして。
ラズパイ初心者で両親の見守りカメラをline notify で運用しておりましたが、今月でサービス終了との事で代替サービスを探しております。Gmail通知の方法私も知りたいので是非記事にして頂けたら嬉しいです。
よろしくお願いいたします。

返信する
そぞら

リクエストいただきありがとうございます。
「Pythonプログラムで人感センサーが反応したとき写真をLINE通知する」の部分に
Gmail通知の方法を追加しました。
https://sozorablog.com/camera_shooting/#%E5%A4%96%E5%87%BA%E5%85%88%E3%81%8B%E3%82%89%E3%82%AB%E3%83%A1%E3%83%A9%E3%81%AE%E6%98%A0%E5%83%8F%E3%82%92%E7%A2%BA%E8%AA%8D%E3%81%99%E3%82%8B%E6%96%B9%E6%B3%95
ご確認いただけますと幸いです。

返信する
MO

ご案内ありがとうございます。週末じっくり改修チャレンジしてみます!

返信する

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です