.. include:: /index.rst
:start-after: start_hello_message
:end-before: end_hello_message
.. _py_fun_camera:
4.1 カメラ
===================
**はじめに**
このプロジェクトでは、Raspberry Pi Zero を使用してシャッターボタン付きのシンプルなカメラシステムを作成する方法を紹介します。
ボタンを押すとカメラが写真を撮影し、その動作を示すために LED が点灯します。これは GPIO 制御や Raspberry Pi カメラモジュールの扱い方を実践的に学ぶのに最適なプロジェクトです。
----------------------------------------------
**必要なもの**
このプロジェクトで必要なコンポーネントは以下のとおりです。
.. 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_led`
- |link_led_buy|
* - :ref:`cpn_button`
- |link_button_buy|
* - :ref:`cpn_camera_module`
- |link_camera_buy|
* - :ref:`cpn_fusion_hat`
- \-
* - Raspberry Pi
- \-
----------------------------------------------
**回路図**
以下はこのプロジェクトで使用する GPIO ピンの接続図です。
.. image:: img/fzz/4.1.1_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.1_bb.png
:width: 80%
: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.1_Camera.py
#. プログラムを実行するとカメラが動作を開始します。
- ボタンを押すと写真が撮影されます。
- LED が点灯します。
- 1枚の写真が撮影され、 ``Pictures`` フォルダに保存されます
(例: ``photo_001.jpg`` 、 ``photo_002.jpg`` )。
- ボタンを離すと:
- LED が消灯します。
- プログラムは **Ctrl + C** を押すまで実行され続け、
終了時にはエラーメッセージを出さずに正常終了します。
.. note::
QT プレビューを使用するにはデスクトップ環境が必要です。
SSH などでプレビューが表示できない場合でも、写真の撮影と保存は正常に行われます。
----------------------------------------------
**コード**
以下はこのプロジェクトで使用する 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 LED and button
led = Pin(17, mode=Mode.OUT)
button = Pin(4, 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"\nCapturing: {filepath}")
camera.capture_file(filepath)
print("Saved.")
photo_index += 1
def main():
global preview_started
# Start preview only when a GUI display is available (remote SSH often has no DISPLAY)
preview_started = False
if os.getenv("DISPLAY"):
try:
camera.start_preview(Preview.QT)
preview_started = True
except Exception as e:
preview_started = False
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("Press the button to take a photo.")
print(f"Photos will be saved to: {PICTURES_DIR}")
print("Press Ctrl+C to exit.\n")
try:
while True:
if button.value(): # Button pressed (HIGH)
led.on() # LED on
take_photo() # Take photo
time.sleep(0.3) # Simple debounce (avoid multiple shots)
while button.value(): # Wait until button is released
time.sleep(0.01)
led.off() # LED off after release
time.sleep(0.01)
except KeyboardInterrupt:
print("\nExiting...")
finally:
# Turn off LED
try:
led.off()
except Exception:
pass
# Stop the camera first
try:
camera.stop()
except Exception:
pass
# Stop preview only if it was started
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`` / ``Preview``:Raspberry Pi カメラとプレビューの制御
- ``Pin`` / ``Mode`` / ``Pull``:Fusion HAT 経由で LED とボタンを制御
2. **保存ディレクトリの決定(sudo 対応)**
.. 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`` ディレクトリに保存されるようにします。
- スクリプトを ``sudo`` で実行した場合、 ``SUDO_USER`` は元のユーザーを指します。
- ``sudo`` を使用していない場合は、現在ログインしているユーザーが使用されます。
- ``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()
- ``photo_index`` は現在の写真番号を保持します。
- ``photo_lock`` は、ボタンが連続して押された場合でも
撮影処理が重複して実行されないようにします。
5. **プレビュー状態の追跡**
.. code-block:: python
preview_started = False
このフラグは、プレビューウィンドウが正常に開始されたかどうかを記録します。
これにより、プレビューが実際に開始された場合のみ停止処理が行われます。
6. **LED とボタンの設定**
.. code-block:: python
led = Pin(17, mode=Mode.OUT)
button = Pin(4, mode=Mode.IN, pull=Pull.DOWN)
- LED は GPIO17 に接続され、出力モードとして設定されています。
- ボタンは GPIO4 に接続され、内部プルダウン抵抗付きの入力として設定されています。
- HIGH 信号はボタンが押されたことを示します。
7. **写真撮影関数**
.. 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"\nCapturing: {filepath}")
camera.capture_file(filepath)
print("Saved.")
photo_index += 1
この関数は次の処理を行います。
- ``photo_001.jpg`` のようなファイル名を生成します。
- カメラを使用して画像を撮影します。
- 次の写真のためにインデックス番号を更新します。
8. **ローカル環境とリモート環境に対応したプレビュー**
.. code-block:: python
preview_started = False
if os.getenv("DISPLAY"):
try:
camera.start_preview(Preview.QT)
preview_started = True
except Exception as e:
preview_started = False
print(f"Preview start failed (continue without preview): {e}")
else:
print("No DISPLAY detected (running headless without preview).")
- グラフィカルなデスクトップ環境が利用可能な場合、QT プレビューウィンドウが表示されます。
- SSH などでディスプレイがない環境の場合、プレビューはスキップされます。
- いずれの場合でも写真撮影機能は正常に動作します。
9. **メインループとボタン処理**
.. code-block:: python
try:
while True:
if button.value(): # Button pressed (HIGH)
led.on() # LED on
take_photo() # Take photo
time.sleep(0.3) # Simple debounce (avoid multiple shots)
while button.value(): # Wait until button is released
time.sleep(0.01)
led.off() # LED off after release
time.sleep(0.01)
メインループでは次の処理が行われます。
- ボタンが押されると LED が点灯します。
- すぐに写真が撮影されます。
- ボタンが離されるまで待機し、連続撮影を防ぎます。
- ボタンが離されると LED が消灯します。
10. **安全な終了とリソースの解放**
.. code-block:: python
except KeyboardInterrupt:
print("\nExiting...")
finally:
# Turn off LED
try:
led.off()
except Exception:
...
``Ctrl+C`` が押されると、次の処理が行われます。
- LED を消灯します。
- カメラパイプラインを停止します。
- プレビューが開始されている場合のみ停止します。
- カメラのリソースを安全に解放します。
これにより、ローカルの画面付き環境でも、
ヘッドレスのリモート環境でもエラーなく終了できます。
----------------------------------------------
**トラブルシューティング**
1. **写真が撮影されない**
- **原因**:ボタン配線の誤り、またはカメラが初期化されていない可能性があります。
- **対処方法**:
- ボタンが GPIO4 と GND に接続されていることを確認してください。
- ``raspi-config`` でカメラが正しく有効化されていることを確認してください。
2. **LED が点灯しない**
- **原因**:LED の配線または GPIO 設定が正しくない可能性があります。
- **対処方法**:
- LED が適切な抵抗を介して GPIO17 に接続されていることを確認してください。
- LED 単体で動作するかテストしてください。
3. **カメラエラーでスクリプトが停止する**
- **原因**:カメラモジュールが認識されていない、または他のプロセスが使用している可能性があります。
- **対処方法**:
- カメラが正しく接続されているか確認し、Raspberry Pi を再起動してください。
- ``sudo lsof /dev/video*`` を実行して競合するプロセスがないか確認してください。
----------------------------------------------
**拡張アイデア**
1. **複数写真の撮影**:1 回のセッションで複数の写真を撮影し、それぞれ異なるファイル名で保存する。
.. code-block:: python
counter = 0
camera.capture_file(f'{user_home}/photo_{counter}.jpg')
counter += 1
2. **動画撮影**:ボタンを押したときに動画を録画するよう機能を拡張する。
.. code-block:: python
camera.start_recording(f'{user_home}/my_video.h264')
time.sleep(10)
camera.stop_recording()
3. **LED ステータス表示**:LED を使用してカメラの状態を示す。
- 点灯:準備完了
- 点滅:写真撮影中
4. **フォトギャラリー管理**:撮影した写真を日付やイベントごとにフォルダ分けして整理する。
5. **タイムラプス撮影**:一定間隔で写真を撮影してタイムラプスを作成する。
.. code-block:: python
for i in range(10):
camera.capture_file(f'{user_home}/timelapse_{i}.jpg')
time.sleep(5)
----------------------------------------------
**まとめ**
このプロジェクトでは、ボタンでシャッターを操作する基本的なカメラシステムを作成しました。
GPIO 制御と Picamera2 ライブラリを組み合わせることで、インタラクティブな Raspberry Pi プロジェクトを構築できます。
さらに機能を拡張することで、より高度で魅力的なアプリケーションへ発展させることが可能です。