注釈
こんにちは、SunFounder Raspberry Pi & Arduino & ESP32 Enthusiast Community on Facebookへようこそ!他の愛好家と一緒に、Raspberry Pi、Arduino、ESP32の世界により深く入り込みましょう。
参加する理由
専門家サポート: 購入後の問題や技術的な課題を、コミュニティと私たちのチームの助けを借りて解決します。
学習と共有: ヒントやチュートリアルを交換して、スキルを向上させましょう。
限定プレビュー: 新製品の発表や先行プレビューに早期アクセスできます。
特別割引: 最新製品を特別割引でお楽しみいただけます。
季節限定キャンペーンとプレゼント: プレゼント企画やホリデーキャンペーンに参加しましょう。
👉 一緒に発見し、創造する準備はできましたか? [こちら] をクリックして、今すぐ参加しましょう!
11. パンチルトカメラによる物体追跡
1. 概要
この章では、MediaPipe の物体検出を拡張し、 パンチルトサーボプラットフォームを使った シンプルな 物体追跡システム を構築します。
このシステムは、指定した対象物
(たとえば banana)
を検出し、2 つのサーボを自動的に制御して、
対象がカメラ映像の中央付近に収まるように調整します。
このプロジェクトは、以下の要素を組み合わせています:
リアルタイム物体検出
サーボモータ制御
比例追跡ロジック
視覚的フィードバック表示
これは、コンピュータビジョンが リアルタイムで物理ハードウェアを直接駆動できることを示す 実践的な例です。
2. 動作の仕組み
追跡システムは次の手順で動作します:
パン・チルトサーボを中央位置に初期化する
Raspberry Pi カメラを動画ストリーミング用に設定する
物体検出用に EfficientDet Lite0 モデルを読み込む
各フレームで MediaPipe Tasks を使って物体を検出する
目標物体(例:
banana)を特定するフレーム中心に対する物体のずれ量を計算する
比例制御を使ってサーボ角度を調整する
追跡ガイドと状態情報を画面に表示する
このサンプルは、視覚フィードバックを使って ハードウェアの動きを動的に制御する方法を示しています。
3. コードの実行
重要
開始する前に、次の項目を確認してください:
パンチルトが組み立てられている
Raspberry Pi のデスクトップにアクセスできる
コードパッケージがインストールされている
Fusion HAT+ がインストールおよび設定されている
OpenCV がインストールされている
詳細な手順については 0. OpenCV のセットアップ を参照してください。
ターミナルを開き、次のコマンドを入力します:
sudo python3 ~/ai-lab-kit/mediapipe/mp_track_object.py
プログラムを実行すると、カメラウィンドウが開き、リアルタイム物体検出が始まります。
システムは指定された対象物(デフォルト:
banana)を探します。 画面中央には基準点として黄色の十字マーカーが表示されます。対象物がフレーム内に現れると:
MediaPipe が EfficientDet Lite0 モデルを使って物体を検出します。
検出されたバウンディングボックスの中心座標を計算します。
物体が中央のデッドゾーン外にある場合、パン・チルトサーボが段階的に動きます。
カメラが物理的に回転し、物体がフレーム中央付近に来るようにします。
物体の周囲に緑色の追跡ボックスが描画されます。
画面には次の情報が表示されます:
Tracking banana(状態表示)現在のサーボ角度(Pan / Tilt)
物体が検出されない場合:
サーボは動作を停止します。
状態表示は
No banana foundに切り替わります(赤色表示)。
追跡ロジックには、シンプルな 4 方向デッドゾーン制御を使用しています。 物体が中心から十分に離れたときだけサーボが動くため、 ジッターを防げます。
qを押すとプログラムを停止できます。終了時には:
2 つのサーボが中央位置に戻ります。
カメラが停止します。
表示ウィンドウが閉じます。
Tracking stopped. Servos centered.というメッセージが出力されます。
4. 完全なコード
#!/usr/bin/env python3
import cv2
import time
from fusion_hat.servo import Servo
from picamera2 import Picamera2
from pathlib import Path
# MediaPipe imports
import mediapipe as mp
from mediapipe.tasks import python
from mediapipe.tasks.python import vision
# -------------------- Configuration --------------------
TARGET = "banana" # Object to track
W, H = 640, 480 # Camera resolution
CX, CY = W // 2, H // 2 # Center coordinates
SCORE_THRESHOLD = 0.3 # Detection confidence threshold
DEADZONE = 50 # Pixels from center before moving
print(f"Tracking: {TARGET}")
# -------------------- Servo Initialization --------------------
pan = Servo(2) # Channel 2 for pan (horizontal)
tilt = Servo(3) # Channel 3 for tilt (vertical)
pan.angle(0) # Center position
tilt.angle(0) # Center position
time.sleep(1) # Allow servos to reach position
# -------------------- Camera Initialization --------------------
cam = Picamera2()
cam.configure(cam.create_preview_configuration(
main={"size": (W, H), "format": "XRGB8888"}
))
cam.start()
time.sleep(2) # Allow camera to stabilize
# -------------------- MediaPipe Detector Setup --------------------
model_path = str(Path(__file__).parent / "efficientdet_lite0.tflite")
options = vision.ObjectDetectorOptions(
base_options=python.BaseOptions(model_asset_path=model_path),
score_threshold=SCORE_THRESHOLD,
running_mode=vision.RunningMode.VIDEO
)
detector = vision.ObjectDetector.create_from_options(options)
print("Ready. Press 'q' to quit")
# -------------------- Tracking Logic --------------------
def simple_track(x, y):
"""Basic 4-direction tracking with deadzone"""
if x is None:
return 0, 0
pan_move = 0
tilt_move = 0
# Left/right movement decision
if x < CX - DEADZONE:
pan_move = 1 # Move right
elif x > CX + DEADZONE:
pan_move = -1 # Move left
# Up/down movement decision
if y < CY - DEADZONE:
tilt_move = -1 # Move down
elif y > CY + DEADZONE:
tilt_move = 1 # Move up
return pan_move, tilt_move
# -------------------- Main Tracking Loop --------------------
pan_pos = 0 # Current pan angle (-90° to +90°)
tilt_pos = 0 # Current tilt angle (-45° to +45°)
try:
while True:
# Capture frame from camera
frame = cam.capture_array()
frame = cv2.cvtColor(frame, cv2.COLOR_BGRA2BGR)
# Convert to RGB for MediaPipe
rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=rgb)
# Detect objects in frame
detections = detector.detect_for_video(mp_image, int(time.time() * 1000))
# Search for target object
obj_x = obj_y = None
for detection in detections.detections:
for category in detection.categories:
# Case-insensitive search for target
if TARGET.lower() in str(category.category_name).lower():
bbox = detection.bounding_box
# Calculate object center
obj_x = bbox.origin_x + bbox.width // 2
obj_y = bbox.origin_y + bbox.height // 2
break
# Process tracking if object found
if obj_x is not None:
pan_move, tilt_move = simple_track(obj_x, obj_y)
pan_pos += pan_move
tilt_pos += tilt_move
# Limit servo angles to safe ranges
pan_pos = max(-90, min(90, pan_pos))
tilt_pos = max(-45, min(45, tilt_pos))
# Send commands to servos
pan.angle(pan_pos)
tilt.angle(tilt_pos)
# Draw tracking box around object
cv2.rectangle(frame,
(obj_x - 30, obj_y - 30),
(obj_x + 30, obj_y + 30),
(0, 255, 0), 2)
status = f"Tracking {TARGET}"
color = (0, 255, 0) # Green for tracking
else:
status = f"No {TARGET} found"
color = (0, 0, 255) # Red for not found
# Draw center crosshair for reference
cv2.line(frame, (CX - 20, CY), (CX + 20, CY), (0, 255, 255), 2)
cv2.line(frame, (CX, CY - 20), (CX, CY + 20), (0, 255, 255), 2)
# Display status information
cv2.putText(frame, status, (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
cv2.putText(frame, f"Pan: {pan_pos:.0f} Tilt: {tilt_pos:.0f}",
(10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (255, 255, 0), 2)
cv2.putText(frame, "Press 'q' to quit", (10, 90),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 1)
# Show video window
cv2.imshow(f"Track: {TARGET}", frame)
# Exit on 'q' key press
if cv2.waitKey(1) & 0xFF == ord('q'):
break
finally:
# -------------------- Cleanup --------------------
pan.angle(0) # Return to center
tilt.angle(0) # Return to center
time.sleep(0.5) # Allow movement
cam.stop() # Stop camera
cv2.destroyAllWindows() # Close display
print("Tracking stopped. Servos centered.")
5. コードの説明
設定セクション
TARGET = "banana"
W, H = 640, 480
CX, CY = W // 2, H // 2
SCORE_THRESHOLD = 0.3
DEADZONE = 50
TARGET: 追跡する物体カテゴリ(COCO データセットのクラスに含まれている必要があります)W, H: カメラ解像度。速度と細部のバランスを取った設定ですCX, CY: 追跡基準となるフレーム中心座標SCORE_THRESHOLD: 有効な検出とみなす最小信頼度DEADZONE: サーボが動き始める中心からの距離(ジッター低減用)
サーボの初期化
from fusion_hat.servo import Servo
pan = Servo(2)
tilt = Servo(3)
pan.angle(0)
tilt.angle(0)
Servo(2)とServo(3)は Fusion HAT 上のチャンネルに対応しています.angle(0)でサーボを 0° の中央位置に設定しますtime.sleep(1)により、処理を続ける前にサーボが所定位置に到達する時間を確保します
カメラ設定
cam = Picamera2()
cam.configure(cam.create_preview_configuration(
main={"size": (W, H), "format": "XRGB8888"}
))
最新のカメラ API として Picamera2 ライブラリを使用しています
XRGB8888形式は 8-bit カラーチャンネルを提供しますtime.sleep(2)により、カメラセンサが安定するまで待機します
MediaPipe Detector
model_path = str(Path(__file__).parent / "efficientdet_lite0.tflite")
options = vision.ObjectDetectorOptions(
base_options=python.BaseOptions(model_asset_path=model_path),
score_threshold=SCORE_THRESHOLD,
running_mode=vision.RunningMode.VIDEO
)
同じディレクトリから EfficientDet Lite0 モデルを読み込みます
RunningMode.VIDEOは連続フレーム処理向けに最適化されていますdetect_for_video()は各フレームごとにタイムスタンプを必要とします
追跡関数
def simple_track(x, y):
if x < CX - DEADZONE:
pan_move = 1 # Object left → move right
elif x > CX + DEADZONE:
pan_move = -1 # Object right → move left
if y < CY - DEADZONE:
tilt_move = -1 # Object up → move down
elif y > CY + DEADZONE:
tilt_move = 1 # Object down → move up
シンプルな比例制御であり、厳密な PID ではありません
デッドゾーンにより、小さな揺れによるサーボのジッターを防ぎます
各軸について -1、0、1 の移動値を返します
メインループ処理
# Object detection
detections = detector.detect_for_video(mp_image, int(time.time() * 1000))
# Find target object
for detection in detections.detections:
for category in detection.categories:
if TARGET.lower() in str(category.category_name).lower():
bbox = detection.bounding_box
obj_x = bbox.origin_x + bbox.width // 2
obj_y = bbox.origin_y + bbox.height // 2
フレームを MediaPipe の画像形式に変換します
現在のタイムスタンプで物体検出を実行します
検出結果の中から目標物体を探します(大文字小文字は区別しません)
物体の中心座標を計算します
サーボ制御ロジック
if obj_x is not None:
pan_move, tilt_move = simple_track(obj_x, obj_y)
pan_pos += pan_move
tilt_pos += tilt_move
# Enforce safe angle limits
pan_pos = max(-90, min(90, pan_pos))
tilt_pos = max(-45, min(45, tilt_pos))
pan.angle(pan_pos)
tilt.angle(tilt_pos)
追跡関数から移動指令を取得します
現在位置の累積値を更新します
機械的な安全範囲内にクランプします
新しい角度をサーボに送信します
視覚的フィードバック
# Tracking box (green when tracking)
cv2.rectangle(frame, (obj_x-30, obj_y-30), (obj_x+30, obj_y+30), (0,255,0), 2)
# Center crosshair (yellow)
cv2.line(frame, (CX-20, CY), (CX+20, CY), (0,255,255), 2)
cv2.line(frame, (CX, CY-20), (CX, CY+20), (0,255,255), 2)
# Status text
cv2.putText(frame, status, (10,30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2)
緑のボックス: 現在追跡中の物体
黄色の十字マーカー: フレーム中央の基準位置
状態テキスト: 追跡状態とサーボ角度
クリーンアップ処理
finally:
pan.angle(0)
tilt.angle(0)
time.sleep(0.5)
cam.stop()
cv2.destroyAllWindows()
サーボを中央位置に戻します
カメラキャプチャを停止します
OpenCV ウィンドウを閉じます
エラーが発生しても必ず実行されます(
try...finally)
6. 設定オプション
追跡対象の変更
# Track different objects
TARGET = "person" # People tracking
TARGET = "cup" # Cup/glass tracking
TARGET = "book" # Book tracking
TARGET = "bottle" # Bottle tracking
追跡パラメータの調整
# Slower, smoother tracking
DEADZONE = 75 # Larger deadzone = less sensitive
# Faster, more responsive tracking
DEADZONE = 30 # Smaller deadzone = more sensitive
pan_move = 2 # Larger movement steps
サーボ可動範囲の制限
# Restrict movement range
pan_pos = max(-60, min(60, pan_pos)) # ±60° pan limit
tilt_pos = max(-30, min(30, tilt_pos)) # ±30° tilt limit
パフォーマンス調整
# Lower resolution for speed
W, H = 320, 240 # Faster processing
# Higher threshold for reliability
SCORE_THRESHOLD = 0.5 # Fewer false positives
7. パフォーマンス上の考慮点
Factor |
Effect on Performance |
Recommendation |
|---|---|---|
Camera Resolution |
高いほど検出は遅くなる |
640x480 が良いバランス |
Detection Threshold |
低いほど検出は増えるが誤検出も増える |
0.3-0.5 が最適 |
Deadzone Size |
大きいほど滑らかだが反応は鈍くなる |
40-60 ピクセル |
Servo Speed |
速いほど反応は良いがオーバーシュートしやすい |
加速度制御の導入を検討 |
Model Size |
Lite0 が最速、Lite2 が最高精度 |
リアルタイム追跡には Lite0 を推奨 |
想定パフォーマンス:
Raspberry Pi 4: 640x480 で 8-15 FPS
検出遅延: 100-200ms
サーボ応答時間: 1 度あたり 50-100ms
システム全体の遅延: 200-400ms
8. トラブルシューティングガイド
Issue |
Possible Cause |
Solution |
|---|---|---|
No object detection |
物体が COCO クラスに含まれていない |
対応している物体名を使用する |
Jerky servo movement |
デッドゾーンが小さすぎる |
DEADZONE を 60-80 に上げる |
Servo overshoot |
移動ステップが大きすぎる |
pan_move を 1 から 0.5 に変更する |
Low frame rate |
解像度が高すぎる |
320x240 に下げる |
Camera not working |
カメラが有効化されていない |
|
Servos not moving |
配線ミスまたは電源不足 |
接続と電源を確認する |
Object lost frequently |
閾値が高すぎる |
SCORE_THRESHOLD を 0.2 に下げる |
Incorrect tracking direction |
サーボの向きが逆 |
pan_move の符号を反転する |
デバッグのヒント:
サーボを個別にテストする:
pan.angle(45) # Should move right time.sleep(1) pan.angle(-45) # Should move left
物体検出を確認する:
print(f"Found: {category.category_name} {c.score:.2f}")
物体座標を確認する:
print(f"Object at: ({obj_x}, {obj_y}), Center: ({CX}, {CY})")
フレームレートを監視する:
import time start = time.time() # ... processing ... fps = 1 / (time.time() - start) print(f"FPS: {fps:.1f}")
9. 高度な改造
1. PID 制御の実装
class PIDController:
def __init__(self, kp=0.1, ki=0.01, kd=0.05):
self.kp, self.ki, self.kd = kp, ki, kd
self.prev_error = 0
self.integral = 0
def update(self, error, dt=1.0):
self.integral += error * dt
derivative = (error - self.prev_error) / dt
output = self.kp*error + self.ki*self.integral + self.kd*derivative
self.prev_error = error
return output
2. 複数物体の追跡
# Track closest object
best_dist = float('inf')
best_obj = None
for detection in detections.detections:
bbox = detection.bounding_box
obj_x = bbox.origin_x + bbox.width // 2
obj_y = bbox.origin_y + bbox.height // 2
dist = ((obj_x - CX)**2 + (obj_y - CY)**2)**0.5
if dist < best_dist:
best_dist = dist
best_obj = (obj_x, obj_y)
3. 距離に比例した速度制御
def adaptive_track(x, y):
if x is None:
return 0, 0
# Calculate distance from center
dx = x - CX
dy = y - CY
# Speed proportional to distance (with deadzone)
pan_move = 0
tilt_move = 0
if abs(dx) > DEADZONE:
pan_move = dx * 0.02 # 2% of distance per frame
if abs(dy) > DEADZONE:
tilt_move = dy * 0.02
return pan_move, tilt_move
4. 物体記憶(慣性追跡)
# Keep tracking briefly when object lost
OBJECT_TIMEOUT = 10 # frames
lost_counter = 0
if obj_x is not None:
last_x, last_y = obj_x, obj_y
lost_counter = 0
elif lost_counter < OBJECT_TIMEOUT:
obj_x, obj_y = last_x, last_y # Use last known position
lost_counter += 1
10. 応用と拡張
教育用途:
ロボティクスと自動化の原理学習
コンピュータビジョンの基礎
制御システム(P 制御 vs PID)
リアルタイムシステム設計
実用用途:
セキュリティカメラの自動追跡
ビデオ会議用カメラの自動化
野生動物の観察
追跡支援のための補助技術
拡張プロジェクト:
Web インターフェース: ブラウザ経由の遠隔操作
プリセット位置: よく使う追跡位置の保存 / 読み込み
物体学習: 独自の物体を学習させる
マルチカメラ: 複数追跡ユニットの連携
クラウド連携: 追跡データをアップロードして解析する
音声フィードバック: 追跡状態を音声で通知する
ジェスチャー制御: 手のジェスチャーで追跡を制御する
11. 安全上の注意とベストプラクティス
機械的安全性:
すべての可動部をしっかり固定する
ケーブルマネジメントを行う
指はさみの危険箇所を避ける
適切な角度制限を設定する
電気的安全性:
サーボには外部電源を使用する
適切なグラウンド接続を確保する
電源の過負荷を避ける
適切な太さの配線を使用する
ソフトウェア安全性:
終了時には必ずサーボを中央に戻す処理を入れる
緊急停止機構を実装する
デバッグ用にエラーログを残す
入力値と制限値を検証する
運用上の安全性:
可動機構に近づきすぎない
過熱がないか監視する
定期的に保守点検を行う
手動で介入できる手段を用意する
12. まとめ
この章では、以下を組み合わせた 完全な物体追跡システムを実装しました:
MediaPipe Tasks による高信頼な物体検出
パンチルトサーボ による物理的な追跡
シンプルな比例制御 による移動ロジック
OpenCV による視覚的フィードバックと表示
このシステムは、より高度な追跡アプリケーションの基礎となり、 リアルタイムコンピュータビジョン、制御システム、 組み込み Python プログラミングの重要な概念を実証しています。
追跡対象の変更、各種パラメータの調整、制御ロジックの拡張により、 このシステムは教育用デモから実用的な自動化ソリューションまで、 さまざまな用途に適応できます。
次のステップ:
より滑らかな追跡のために PID 制御を実装する
一時的な遮蔽に対応するため物体記憶を追加する
リモート監視用の Web インターフェースを作成する
ホームオートメーションシステムと統合する
独自の物体検出モデルを学習する