注釈
こんにちは、SunFounder Raspberry Pi & Arduino & ESP32 Enthusiast Community on Facebookへようこそ!他の愛好家と一緒に、Raspberry Pi、Arduino、ESP32の世界により深く入り込みましょう。
参加する理由
専門家サポート: 購入後の問題や技術的な課題を、コミュニティと私たちのチームの助けを借りて解決します。
学習と共有: ヒントやチュートリアルを交換して、スキルを向上させましょう。
限定プレビュー: 新製品の発表や先行プレビューに早期アクセスできます。
特別割引: 最新製品を特別割引でお楽しみいただけます。
季節限定キャンペーンとプレゼント: プレゼント企画やホリデーキャンペーンに参加しましょう。
👉 一緒に発見し、創造する準備はできましたか? [こちら] をクリックして、今すぐ参加しましょう!
4.16 パン・チルトカメラ制御システム
はじめに
このプロジェクトでは、ジョイスティックを使用してカメラをパン(水平移動)およびチルト(垂直移動)できるカメラ制御システムを作成します。サーボモーターに取り付けられたカメラの向きを遠隔操作し、リアルタイムで映像をプレビューしながら、ジョイスティックのボタンを押すことで写真を撮影することができます。このプロジェクトは、監視用途、写真撮影プロジェクト、またはサーボモーター制御やカメラ統合の学習に最適です。
必要なもの
このプロジェクトには、以下のコンポーネントが必要です。
COMPONENT INTRODUCTION |
PURCHASE LINK |
|---|---|
- |
|
- |
|
Raspberry Pi |
- |
回路図
配線図
カメラモジュールをより便利に使用するために、パン・チルトの組み立て(カメラ用) の組み立てを推奨します。
注釈
パン・チルト機構を組み立てると、一部のピンが隠れてしまう場合があります。そのため、カメラを使用する場合のみ組み立てるか、組み立て後に外側に配置することを推奨します。
次の配線図に従って回路を接続してください:
サンプルの実行
Raspberry Pi デスクトップにアクセスします:
リモートデスクトップ : VNC を使用してフルデスクトップ環境にアクセスします。
Raspberry Pi Connect : Raspberry Pi Connect を使用して、任意のブラウザから安全に Raspberry Pi にアクセスできます。
ターミナルを開き、コードフォルダへ移動します:
cd ~/ai-lab-kit/python
スクリプトを実行してカメラを起動します:
sudo python3 pan_tilt_camera.py
スクリプトを実行すると、パン・チルトカメラシステムが起動し、カメラとサーボが初期化されます。
ディスプレイが利用可能な場合は、リアルタイムのカメラプレビューが表示されます。ディスプレイがない場合でも、プログラムはヘッドレスモードで正常に動作します。
ジョイスティックを左右に動かすとカメラが水平(パン)方向に回転し、上下に動かすと垂直(チルト)方向に動きます。
ジョイスティックのボタンを押すとカメラが写真を撮影し、
Pictures/camera_pan_tiltディレクトリにphoto_001.jpgのような連番ファイル名で保存されます。プログラムは Ctrl + C を押して停止するまで、ユーザー入力に応答しながら動作を続けます。
コード
以下は、このプロジェクトで使用する Python スクリプトです:
#!/usr/bin/env python3
import os, time
from picamera2 import Picamera2, Preview
from fusion_hat.adc import ADC
from fusion_hat.pin import Pin, Mode, Pull
from fusion_hat.servo import Servo
# Servo channels for pan (horizontal) and tilt (vertical)
PAN_CHANNEL, TILT_CHANNEL = 2, 3
# Joystick ADC pins (X/Y axis) and button pin
X_PIN, Y_PIN = "A1", "A0"
BTN_PIN = 17
# Angle limits to protect servos
PAN_MIN, PAN_MAX = -90, 90
TILT_MIN, TILT_MAX = -45, 45
# Deadzone ignores small joystick movement
DEADZONE = 15
MOVE_SPEED = 3
LOOP_DELAY = 0.05
# Photo save directory (works with sudo)
REAL_USER = os.getenv("SUDO_USER") or os.getlogin()
PHOTO_DIR = os.path.join(f"/home/{REAL_USER}", "Pictures", "camera_pan_tilt")
os.makedirs(PHOTO_DIR, exist_ok=True)
# Initialize servos
pan_servo = Servo(PAN_CHANNEL)
tilt_servo = Servo(TILT_CHANNEL)
# Initialize joystick and button (active-low)
x_adc = ADC(X_PIN)
y_adc = ADC(Y_PIN)
joystick_button = Pin(BTN_PIN, mode=Mode.IN, pull=Pull.UP) # pressed -> 0
# Initialize camera
camera = Picamera2()
camera.configure(camera.create_preview_configuration(main={"size": (1280, 720)}))
preview_started = False
photo_count = 1
current_pan = 0
current_tilt = 0
last_button_state = 1 # Used for edge detection
def clamp(v, vmin, vmax):
# Limit value to a safe range
return max(vmin, min(vmax, v))
def map_value(value, in_min, in_max, out_min, out_max):
# Map ADC value to a new range
return (value - in_min) * (out_max - out_min) / (in_max - in_min) + out_min
def apply_deadzone(v, dz):
# Ignore small joystick movement
return 0 if (-dz < v < dz) else v
def read_joystick():
# Read joystick X/Y position
x = map_value(x_adc.read(), 0, 4095, -100, 100)
y = map_value(y_adc.read(), 0, 4095, -100, 100)
return x, y
def check_button_press():
# Detect button press (HIGH -> LOW)
global last_button_state
current_state = joystick_button.value()
if last_button_state == 1 and current_state == 0:
last_button_state = current_state
return True
last_button_state = current_state
return False
def take_photo():
# Capture and save one photo
global photo_count
filename = f"photo_{photo_count:03d}.jpg"
filepath = os.path.join(PHOTO_DIR, filename)
camera.capture_file(filepath)
print("Saved:", filepath)
photo_count += 1
def start_preview_if_available():
# Start camera preview only if a display is available
global preview_started
preview_started = False
if os.getenv("DISPLAY"):
try:
camera.start_preview(Preview.QT)
preview_started = True
except Exception:
preview_started = False
def cleanup():
# Safely stop camera and release resources
try:
camera.stop()
except Exception:
pass
if preview_started:
try:
camera.stop_preview()
except Exception:
pass
try:
camera.close()
except Exception:
pass
def main():
global current_pan, current_tilt
start_preview_if_available()
camera.start()
# Center camera at startup
pan_servo.angle(0)
tilt_servo.angle(0)
try:
while True:
# Read joystick and move camera
x, y = read_joystick()
x = apply_deadzone(x, DEADZONE)
y = apply_deadzone(y, DEADZONE)
new_pan = current_pan + (MOVE_SPEED if x > DEADZONE else -MOVE_SPEED if x < -DEADZONE else 0)
new_tilt = current_tilt + (MOVE_SPEED if y > DEADZONE else -MOVE_SPEED if y < -DEADZONE else 0)
new_pan = clamp(new_pan, PAN_MIN, PAN_MAX)
new_tilt = clamp(new_tilt, TILT_MIN, TILT_MAX)
if new_pan != current_pan:
current_pan = new_pan
pan_servo.angle(current_pan)
if new_tilt != current_tilt:
current_tilt = new_tilt
tilt_servo.angle(current_tilt)
# Take photo when button is pressed
if check_button_press():
take_photo()
time.sleep(LOOP_DELAY)
except KeyboardInterrupt:
pass
finally:
cleanup()
if __name__ == "__main__":
main()
コードの解説
ハードウェアの初期化
2 つのサーボモーターを初期化し、カメラのパン(水平)およびチルト(垂直)の動きを制御します
ジョイスティックは ADC チャンネルを使用して X 軸と Y 軸のアナログ値を読み取り、GPIO ピンでボタン入力を検出します
カメラモジュールはプレビューモード用に初期化され、ディスプレイあり/なしの両方の環境に対応します
ジョイスティック入力の読み取りと処理
read_joystick()はジョイスティックの X 軸と Y 軸のアナログ値を取得しますmap_value()は ADC 値(0–4095)を −100〜100 の範囲に変換しますapply_deadzone()は小さな入力を無視し、意図しないカメラの微動を防ぎます
カメラの動作制御
ジョイスティック入力をパン角度とチルト角度の増分に変換します
clamp()により角度を安全範囲内に制限し、サーボモーターを保護します角度が変化した場合のみサーボを更新することで、滑らかで安定した動作を実現します
ボタン押下の検出
ジョイスティックのボタンはプルアップ抵抗を使用したアクティブロー入力として設定されています
check_button_press()はエッジ検出(HIGH → LOW)によってボタン押下を検出しますこれにより、ボタンを押し続けても 1 回の押下につき 1 枚の写真だけが撮影されます
写真の撮影と保存
take_photo()はカメラモジュールを使用して画像を撮影します写真は
photo_001.jpgのような連番ファイル名で保存されますすべての画像はユーザーの
Pictures/camera_pan_tiltディレクトリに保存されます
カメラプレビューの処理
グラフィカルディスプレイが利用可能な場合のみ、ライブカメラプレビューが開始されます
ディスプレイがない環境でもスクリプトは正常に動作します
メインループと終了処理
メインループではジョイスティック入力を継続的に読み取り、リアルタイムでカメラを制御します
Ctrl + Cでプログラムを終了すると、カメラが安全に停止しますすべてのハードウェアリソースが適切に解放され、正常に終了します
トラブルシューティング
サーボが動作しない:
原因:サーボ接続の誤り、または電源の問題
解決方法:
サーボが正しいチャンネル(2 と 3)に接続されていることを確認する
Fusion HAT が正しく給電されていることを確認する
サーボ配線に緩みがないか確認する
カメラプレビューが表示されない:
原因:カメラモジュールが検出されていない、または設定が正しくない
解決方法:
カメラケーブルが CSI ポートにしっかり接続されていることを確認する
Raspberry Pi の設定でカメラが有効になっているか確認する
カメラモジュールの互換性を確認する
ジョイスティックが反応しない:
原因:ピン設定の誤り、または ADC の問題
解決方法:
ジョイスティックが A0、A1、および GPIO 17 に正しく接続されているか確認する
簡単な print 文で ADC の値を確認する
Fusion HAT の ADC が正常に動作しているか確認する
写真が保存されない:
原因:権限の問題、またはディレクトリの問題
解決方法:
ユーザーのホームディレクトリに Pictures フォルダが存在するか確認する
写真保存フォルダの書き込み権限を確認する
権限問題が続く場合は sudo で実行してみる
サーボが不安定に動く:
原因:電源の不安定、またはソフトウェアのタイミング問題
解決方法:
Fusion HAT への電源供給が安定しているか確認する
MOVE_SPEEDや遅延値を調整する必要に応じてサーボ電源ラインにコンデンサを追加する
発展アイデア
動画録画機能:録画の開始/停止を制御できる動画録画機能を追加します:
def start_recording(): timestamp = time.strftime("%Y%m%d_%H%M%S") video_path = os.path.join(VIDEO_DIR, f"video_{timestamp}.mp4") camera.start_recording(video_path) print(f"Recording started: {video_path}") def stop_recording(): camera.stop_recording() print("Recording stopped")
プリセット位置:カメラを素早く移動できるプリセット位置を設定します:
PRESETS = { 'center': (0, 0), 'left': (-45, 0), 'right': (45, 0), 'up': (0, 30), 'down': (0, -30) } def goto_preset(preset_name): if preset_name in PRESETS: pan, tilt = PRESETS[preset_name] pan_servo.angle(pan) tilt_servo.angle(tilt)
まとめ
このプロジェクトでは、Raspberry Pi、サーボモーター、カメラモジュールを組み合わせて高度なパン・チルトカメラ制御システムを構築する方法を紹介しました。ハードウェア制御、リアルタイム映像処理、ユーザー入力処理を統合した実用的なシステムです。このプロジェクトは、監視システム、撮影ロボット、インタラクティブアート装置など、より高度な応用へ発展させるための基礎となります。