.. include:: /index.rst
:start-after: start_hello_message
:end-before: end_hello_message
.. _py_fun_camera2:
4.2 自動撮影カメラ
===================================
**はじめに**
留守中に、好奇心旺盛な小さなリスが窓辺を訪れたらどうでしょうか。このプロジェクトでは、そんな愛らしい瞬間を自動で撮影できるカメラを作成します。モーションセンサーを使って動きを検出し、一定間隔で写真を撮影します。
----------------------------------------------
**必要なもの**
このプロジェクトで必要なコンポーネントは以下のとおりです。
.. list-table::
:widths: 30 20
:header-rows: 1
* - COMPONENT INTRODUCTION
- PURCHASE LINK
* - :ref:`cpn_breadboard`
- |link_breadboard_buy|
* - :ref:`cpn_wires`
- |link_wires_buy|
* - :ref:`cpn_resistor`
- |link_resistor_buy|
* - :ref:`cpn_pir`
- |link_pir_buy|
* - :ref:`cpn_camera_module`
- |link_camera_buy|
* - :ref:`cpn_fusion_hat`
- \-
* - Raspberry Pi
- \-
----------------------------------------------
**回路図**
以下はこのプロジェクトの回路図です。
.. image:: img/fzz/4.1.2_sch.png
:width: 80%
:align: center
----------------------------------------------
**配線図**
#. カメラモジュールを便利に使用するために、:ref:`assemble_fusion_hat_pan_tilt` を推奨します。
.. note::
パンチルトを組み立てると一部のピンが隠れてしまう場合があるため、カメラを使用するときだけ組み立てるか、組み立て後に外側へ配置することをおすすめします。
.. image:: ../quick_start/img/gimbal_assemble.png
#. 以下の配線図に従って回路を組み立ててください。
.. image:: img/fzz/4.1.2_bb.png
:width: 80%
:align: center
#. 最適な動作のために、PIRモジュール上の2つの可変抵抗を調整してください。1つは感度、もう1つは検出距離を調整します。両方とも反時計回りいっぱいまで回してください。
.. image:: ../python/img/PIR_TTE.png
:width: 400
:align: center
----------------------------------------------
**サンプルの実行**
#. Raspberry Pi のデスクトップにアクセスします。
* :ref:`remote_desktop`: **VNC** を使用して完全なデスクトップ環境を利用します。
* |link_rpi_connect|: **Raspberry Pi Connect** を使用して、任意のブラウザから安全に Pi にアクセスします。
#. ターミナルを開き、コードフォルダへ移動します。
.. raw:: html
.. code-block:: shell
cd ~/ai-lab-kit/python
#. スクリプトを実行してカメラを起動します。
.. raw:: html
.. code-block:: shell
sudo python3 4.2_AutomaticCaptureCamera.py
#. スクリプトを実行すると、次のように動作します。
* カメラが起動し、GPIO17 に接続された PIR モーションセンサーを継続的に監視します。
* PIR センサーが動きを検出して出力が HIGH になると、カメラは1枚の写真を撮影し、連番付きのファイル名で Pictures フォルダに保存します。
* 撮影後は、連続撮影を防ぐために少し待機します。
* システムは引き続き動きを監視し、``Ctrl + C`` を押すまで動作し続けます。
----------------------------------------------
**コード**
以下は、このプロジェクトで使用する Python コードです。
.. raw:: html
.. code-block:: python
#!/usr/bin/env python3
import os
import time
import threading
from picamera2 import Picamera2, Preview
from fusion_hat.pin import Pin, Mode, Pull
# Resolve the correct user's home directory (works with sudo)
REAL_USER = os.getenv("SUDO_USER") or os.getlogin()
USER_HOME = f"/home/{REAL_USER}"
PICTURES_DIR = os.path.join(USER_HOME, "Pictures")
os.makedirs(PICTURES_DIR, exist_ok=True)
# Initialize camera
camera = Picamera2()
camera.configure(camera.create_preview_configuration(main={"size": (800, 600)}))
# Photo counter with thread safety
photo_index = 1
photo_lock = threading.Lock()
# Track whether preview was started successfully
preview_started = False
# Initialize PIR sensor (GPIO 17)
pir = Pin(17, mode=Mode.IN, pull=Pull.DOWN)
def take_photo():
"""Capture one photo and increment the index."""
global photo_index
with photo_lock:
filepath = os.path.join(PICTURES_DIR, f"photo_{photo_index:03d}.jpg")
print(f"\nMotion detected! Capturing: {filepath}")
camera.capture_file(filepath)
print("Saved.")
photo_index += 1
def main():
global preview_started
# Start preview only when a GUI display is available
preview_started = False
if os.getenv("DISPLAY"):
try:
camera.start_preview(Preview.QT)
preview_started = True
except Exception as e:
print(f"Preview start failed (continue without preview): {e}")
else:
print("No DISPLAY detected (running headless without preview).")
camera.start()
print("Camera is running.")
print("PIR sensor monitoring on GPIO 17.")
print(f"Photos will be saved to: {PICTURES_DIR}")
print("Press Ctrl+C to exit.\n")
try:
while True:
if pir.value(): # PIR detects motion (HIGH)
take_photo() # Take one photo
time.sleep(2) # Delay to avoid repeated shots
time.sleep(0.1)
except KeyboardInterrupt:
print("\nExiting...")
finally:
try:
camera.stop()
except Exception:
pass
if preview_started:
try:
camera.stop_preview()
except Exception:
pass
try:
camera.close()
except Exception:
pass
if __name__ == "__main__":
main()
----------------------------------------------
**コードの解説**
1. **インポート**
.. code-block:: python
import os
import time
import threading
from picamera2 import Picamera2, Preview
from fusion_hat.pin import Pin, Mode, Pull
このスクリプトでは、ファイル処理( ``os`` )、時間制御( ``time`` )、スレッド安全性( ``threading`` )、カメラ操作( ``Picamera2`` )、GPIO 制御( ``Pin`` )のためのライブラリをインポートしています。
2. **パスの設定**
.. code-block:: python
REAL_USER = os.getenv("SUDO_USER") or os.getlogin()
USER_HOME = f"/home/{REAL_USER}"
PICTURES_DIR = os.path.join(USER_HOME, "Pictures")
os.makedirs(PICTURES_DIR, exist_ok=True)
この部分では、正しいユーザーのホームディレクトリを取得し、写真保存用の ``Pictures`` フォルダが存在することを確認します。
3. **カメラの初期化**
.. code-block:: python
camera = Picamera2()
camera.configure(camera.create_preview_configuration(main={"size": (800, 600)}))
カメラを初期化し、プレビュー解像度を 800 × 600 に設定しています。
4. **写真カウンターとスレッドロック**
.. code-block:: python
photo_index = 1
photo_lock = threading.Lock()
連番付きファイル名を作成するために写真カウンターを使用し、ロックによって撮影処理を安全に行えるようにしています。
5. **PIR センサーの初期化**
.. code-block:: python
pir = Pin(17, mode=Mode.IN, pull=Pull.DOWN)
PIR モーションセンサーは GPIO17 に接続され、プルダウン抵抗付きの入力として設定されています。
6. **写真撮影関数**
.. code-block:: python
def take_photo():
"""Capture one photo and increment the index."""
global photo_index
with photo_lock:
filepath = os.path.join(PICTURES_DIR, f"photo_{photo_index:03d}.jpg")
print(f"\nMotion detected! Capturing: {filepath}")
camera.capture_file(filepath)
print("Saved.")
photo_index += 1
この関数は動きが検出されるたびに1枚の写真を撮影し、 ``Pictures`` ディレクトリへ保存します。
7. **プレビュー処理とカメラ起動**
.. code-block:: python
preview_started = False
if os.getenv("DISPLAY"):
try:
camera.start_preview(Preview.QT)
preview_started = True
except Exception as e:
print(f"Preview start failed (continue without preview): {e}")
else:
print("No DISPLAY detected (running headless without preview).")
グラフィカルな表示環境が利用できる場合のみカメラプレビューを開始し、それ以外の場合はヘッドレスモードで動作します。
8. **メインループ(動き検出)**
.. code-block:: python
while True:
if pir.value():
take_photo()
time.sleep(2)
プログラムは PIR センサーを継続的に監視します。動きが検出されると(HIGH)、1枚の写真を撮影し、その後短い待機時間を入れて連続撮影を防ぎます。
9. **終了処理とクリーンアップ**
.. code-block:: python
except KeyboardInterrupt:
camera.stop()
camera.close()
ユーザーが ``Ctrl + C`` を押すと、プログラムは安全に終了し、カメラを停止して閉じます。
----------------------------------------------
**トラブルシューティング**
1. **写真が撮影されない**
- **原因**:PIR モーションセンサーが動作していない、または配線が正しくありません。
- **対処方法**:
- PIR センサーが GPIO17、電源、GND に正しく接続されていることを確認してください。
- 必要に応じて、PIR センサーの感度と遅延時間の可変抵抗を調整してください。
2. **PIR センサーが常に動きを検出する**
- **原因**:PIR センサーの不具合、または周囲環境による過剰な干渉です。
- **対処方法**:
- マルチメーターでセンサーを確認し、正しく動作しているかテストしてください。
- 誤検出を避けるため、安定した環境で使用してください。
3. **カメラエラーでスクリプトが停止する**
- **原因**:カメラが正しく初期化されていない、または他のプロセスが使用中です。
- **対処方法**:
- カメラが正しく接続され、 ``raspi-config`` で有効化されていることを確認してください。
- Raspberry Pi を再起動して、競合しているプロセスを解放してください。
----------------------------------------------
**拡張アイデア**
1. **タイムスタンプ付きファイル名**:整理しやすいように、写真をタイムスタンプ付きのファイル名で保存します。
.. code-block:: python
timestamp = time.strftime("%Y%m%d-%H%M%S")
camera.capture_file(f'{user_home}/capture_{timestamp}.jpg')
2. **LED インジケーター**:PIR センサーが動きを検出したときに LED を点灯させます。
.. code-block:: python
from fusion_hat import Pin
led = Pin(27)
if pir.value() == 1:
led.on()
else:
led.off()
3. **フォトギャラリー管理**:撮影した写真を日付やイベントごとにフォルダ分けして自動整理します。
----------------------------------------------
**まとめ**
このプロジェクトでは、PIR モーションセンサーと Raspberry Pi カメラモジュールを使って、自動撮影カメラシステムを構築する方法を紹介しました。思いがけない瞬間を記録するのに最適であり、IoT やコンピュータビジョンの可能性を広げる良い題材でもあります。さらに動画録画やクラウドアップロードなどの機能を追加して、リアルタイム監視システムへ発展させてみてください。