注釈

こんにちは、SunFounder Raspberry Pi & Arduino & ESP32 Enthusiast Community on Facebookへようこそ!他の愛好家と一緒に、Raspberry Pi、Arduino、ESP32の世界により深く入り込みましょう。

参加する理由

  • 専門家サポート: 購入後の問題や技術的な課題を、コミュニティと私たちのチームの助けを借りて解決します。

  • 学習と共有: ヒントやチュートリアルを交換して、スキルを向上させましょう。

  • 限定プレビュー: 新製品の発表や先行プレビューに早期アクセスできます。

  • 特別割引: 最新製品を特別割引でお楽しみいただけます。

  • 季節限定キャンペーンとプレゼント: プレゼント企画やホリデーキャンペーンに参加しましょう。

👉 一緒に発見し、創造する準備はできましたか? [こちら] をクリックして、今すぐ参加しましょう!

4.1 カメラ

はじめに

このプロジェクトでは、Raspberry Pi Zero を使用してシャッターボタン付きのシンプルなカメラシステムを作成する方法を紹介します。 ボタンを押すとカメラが写真を撮影し、その動作を示すために LED が点灯します。これは GPIO 制御や Raspberry Pi カメラモジュールの扱い方を実践的に学ぶのに最適なプロジェクトです。


必要なもの

このプロジェクトで必要なコンポーネントは以下のとおりです。

COMPONENT INTRODUCTION

PURCHASE LINK

ブレッドボード

購入

ジャンパーワイヤー

購入

抵抗器

購入

LED

購入

ボタン

購入

カメラモジュール

購入

Fusion HAT+

-

Raspberry Pi

-


回路図

以下はこのプロジェクトで使用する GPIO ピンの接続図です。

../_images/4.1.1_sch.png

配線図

  1. カメラモジュールを便利に使用するため、パン・チルトの組み立て(カメラ用) の組み立てを推奨します。

    注釈

    パンチルトを組み立てると一部のピンが隠れる場合があります。そのため、カメラを使用する場合のみ組み立てるか、組み立て後に外側へ配置することを推奨します。

    ../_images/gimbal_assemble.png
  2. 以下の配線図に従って回路を組み立ててください。

    ../_images/4.1.1_bb.png

サンプルの実行

  1. Raspberry Pi のデスクトップにアクセスします。

  2. ターミナルを開き、コードフォルダへ移動します。

    cd ~/ai-lab-kit/python
    
  3. スクリプトを実行してカメラを起動します。

    sudo python3 4.1_Camera.py
    
  4. プログラムを実行するとカメラが動作を開始します。

    • ボタンを押すと写真が撮影されます。

      • LED が点灯します。

      • 1枚の写真が撮影され、 Pictures フォルダに保存されます (例: photo_001.jpgphoto_002.jpg )。

    • ボタンを離すと:

      • LED が消灯します。

    • プログラムは Ctrl + C を押すまで実行され続け、 終了時にはエラーメッセージを出さずに正常終了します。

    注釈

    QT プレビューを使用するにはデスクトップ環境が必要です。 SSH などでプレビューが表示できない場合でも、写真の撮影と保存は正常に行われます。


コード

以下はこのプロジェクトで使用する 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. インポートと目的

    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 対応)

    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. カメラの初期化と設定

    camera = Picamera2()
    camera.configure(camera.create_preview_configuration(main={"size": (800, 600)}))
    

    カメラを初期化し、プレビューおよび写真撮影用に設定します。

    • プレビュー解像度は 800 × 600 に設定されています。

    • この設定は、プレビューウィンドウが表示される場合と表示されない場合の両方で動作します。

  4. 写真インデックスとスレッド安全性

    photo_index = 1
    photo_lock = threading.Lock()
    
    • photo_index は現在の写真番号を保持します。

    • photo_lock は、ボタンが連続して押された場合でも 撮影処理が重複して実行されないようにします。

  5. プレビュー状態の追跡

    preview_started = False
    

    このフラグは、プレビューウィンドウが正常に開始されたかどうかを記録します。 これにより、プレビューが実際に開始された場合のみ停止処理が行われます。

  6. LED とボタンの設定

    led = Pin(17, mode=Mode.OUT)
    button = Pin(4, mode=Mode.IN, pull=Pull.DOWN)
    
    • LED は GPIO17 に接続され、出力モードとして設定されています。

    • ボタンは GPIO4 に接続され、内部プルダウン抵抗付きの入力として設定されています。

    • HIGH 信号はボタンが押されたことを示します。

  7. 写真撮影関数

    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. ローカル環境とリモート環境に対応したプレビュー

    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. メインループとボタン処理

    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. 安全な終了とリソースの解放

    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 回のセッションで複数の写真を撮影し、それぞれ異なるファイル名で保存する。

    counter = 0
    camera.capture_file(f'{user_home}/photo_{counter}.jpg')
    counter += 1
    
  2. 動画撮影:ボタンを押したときに動画を録画するよう機能を拡張する。

    camera.start_recording(f'{user_home}/my_video.h264')
    time.sleep(10)
    camera.stop_recording()
    
  3. LED ステータス表示:LED を使用してカメラの状態を示す。

    • 点灯:準備完了

    • 点滅:写真撮影中

  4. フォトギャラリー管理:撮影した写真を日付やイベントごとにフォルダ分けして整理する。

  5. タイムラプス撮影:一定間隔で写真を撮影してタイムラプスを作成する。

    for i in range(10):
        camera.capture_file(f'{user_home}/timelapse_{i}.jpg')
        time.sleep(5)
    

まとめ

このプロジェクトでは、ボタンでシャッターを操作する基本的なカメラシステムを作成しました。 GPIO 制御と Picamera2 ライブラリを組み合わせることで、インタラクティブな Raspberry Pi プロジェクトを構築できます。 さらに機能を拡張することで、より高度で魅力的なアプリケーションへ発展させることが可能です。