A pan-tilt camera tracker with servo motors is a classic mechatronics project that combines computer vision with hardware control. Using OpenCV’s face detection and a Raspberry Pi, you can build an auto-tracking camera that follows faces in real time. This tutorial covers the servo pan-tilt mechanism, PID controller implementation, OpenCV face detection integration, and building a complete face-tracking security camera system for India home and office use.
Table of Contents
- Pan-Tilt Hardware Setup
- Servo Control with Raspberry Pi
- Face Detection with OpenCV
- PID Controller for Smooth Tracking
- Complete Face Tracking Code
- Calibration and Tuning
- Enclosure and Mounting
- FAQ
Pan-Tilt Hardware Setup
A pan-tilt mechanism uses two servo motors: one for horizontal rotation (pan, 0-180 degrees) and one for vertical tilt (tilt, 60-120 degrees to prevent camera cables tangling). The camera mounts on the tilt servo bracket, which itself mounts on the pan servo. Standard SG90 or MG90S servos handle the small load of a Pi camera.
Wiring: Both servo signal wires to Pi GPIO (pan = GPIO 12, tilt = GPIO 13). Power servos from external 5V supply rated at least 1A – NOT from Pi 5V pin as servo transients cause Pi to reset. In India, a 5V 2A mobile charger with USB-to-barrel connector works perfectly.
Arducam IMX219 8MP Camera Module
Lightweight 8MP IMX219 camera – ideal for pan-tilt mounts where weight matters. Compact form factor fits standard pan-tilt brackets. CSI interface gives low-latency feed to Raspberry Pi for real-time tracking.
Waveshare IMX219-77 Camera Module
77-degree FOV provides ideal capture angle for face tracking – wide enough to catch faces before they exit frame, narrow enough for good face detail. Lightweight for servo mounting.
Servo Control with Raspberry Pi
import pigpio
import time
# Connect to pigpio daemon (must be running: sudo pigpiod)
pi = pigpio.pi()
PAN_GPIO = 12
TILT_GPIO = 13
# Servo pulse width ranges (microseconds)
SERVO_MIN = 500 # 0 degrees
SERVO_MID = 1500 # 90 degrees
SERVO_MAX = 2500 # 180 degrees
def angle_to_pulse(angle):
# Map 0-180 degrees to 500-2500 us
return int(SERVO_MIN + (angle / 180.0) * (SERVO_MAX - SERVO_MIN))
def set_pan(angle):
angle = max(0, min(180, angle))
pi.set_servo_pulsewidth(PAN_GPIO, angle_to_pulse(angle))
def set_tilt(angle):
angle = max(60, min(120, angle)) # Limit tilt range to prevent cable damage
pi.set_servo_pulsewidth(TILT_GPIO, angle_to_pulse(angle))
# Centre both servos
set_pan(90)
set_tilt(90)
time.sleep(1)
# Start pigpio daemon before running:
# sudo pigpiod
Face Detection with OpenCV
import cv2
# Load Haar cascade face detector
face_cascade = cv2.CascadeClassifier(
cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
def detect_faces(frame):
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(
gray,
scaleFactor=1.1,
minNeighbors=5,
minSize=(80, 80) # Minimum face size (filters small false positives)
)
return faces
def get_largest_face(faces):
if len(faces) == 0:
return None
# Return the largest face (closest to camera)
areas = [(w*h, i) for i, (x,y,w,h) in enumerate(faces)]
areas.sort(reverse=True)
return faces[areas[0][1]]
PID Controller for Smooth Tracking
class PIDController:
def __init__(self, Kp=0.1, Ki=0.001, Kd=0.05):
self.Kp, self.Ki, self.Kd = Kp, Ki, Kd
self.prev_error = 0
self.integral = 0
def update(self, error, dt=0.033):
self.integral += error * dt
self.integral = max(-30, min(30, self.integral)) # Anti-windup
derivative = (error - self.prev_error) / dt
output = self.Kp*error + self.Ki*self.integral + self.Kd*derivative
self.prev_error = error
return output
pan_pid = PIDController(Kp=0.08, Ki=0.0005, Kd=0.03)
tilt_pid = PIDController(Kp=0.08, Ki=0.0005, Kd=0.03)
Complete Face Tracking Code
from picamera2 import Picamera2
import cv2, pigpio, time
pi = pigpio.pi()
PAN_GPIO, TILT_GPIO = 12, 13
pan_angle, tilt_angle = 90.0, 90.0
def set_servos(pan, tilt):
pan = max(0, min(180, pan))
tilt = max(60, min(120, tilt))
pi.set_servo_pulsewidth(PAN_GPIO, int(500 + pan/180*2000))
pi.set_servo_pulsewidth(TILT_GPIO, int(500 + tilt/180*2000))
picam2 = Picamera2()
picam2.configure(picam2.create_preview_configuration(
main={'size': (640, 480), 'format': 'BGR888'}))
picam2.start()
time.sleep(2)
face_cascade = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
while True:
frame = picam2.capture_array()
h, w = frame.shape[:2]
cx, cy = w//2, h//2
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
faces = face_cascade.detectMultiScale(gray, 1.1, 5, minSize=(80,80))
if len(faces) > 0:
x, y, fw, fh = max(faces, key=lambda f: f[2]*f[3])
fx, fy = x + fw//2, y + fh//2
ex = fx - cx # Horizontal error (+ve = face is right of centre)
ey = fy - cy # Vertical error (+ve = face is below centre)
# Update servo angles
pan_angle += pan_pid.update(ex) * 0.1
tilt_angle += tilt_pid.update(ey) * 0.1
set_servos(pan_angle, tilt_angle)
cv2.rectangle(frame, (x,y), (x+fw,y+fh), (0,255,0), 2)
cv2.imshow('Face Tracker', frame)
if cv2.waitKey(1) & 0xFF == ord('q'): break
picam2.stop()
Calibration and Tuning
Tune PID gains by testing with a face at centre, then moving it side to side:
- Too slow to respond: Increase Kp
- Overshoots and oscillates: Reduce Kp, increase Kd
- Steady-state offset (never quite centres): Increase Ki
- Jerky movement: Reduce Kp and Kd, use RPi.GPIO’s hardware PWM via pigpio
Enclosure and Mounting
Pan-tilt kits in India: Available at Robu.in for Rs 150-300 for SG90-compatible acrylic kits. Mount the Pi separately (not on the moving head) to reduce inertia. Use 15cm ribbon cable between Pi and camera. For outdoor use in India, use an IP54 junction box housing with a clear acrylic window in front of the camera.
FAQ
Can I use PWM from Raspberry Pi GPIO instead of pigpio?
RPi.GPIO software PWM works but jitters due to Linux scheduling. For smooth tracking, use pigpio with hardware PWM on GPIO 12 and 13 (pins with hardware PWM support). Run sudo pigpiod at boot.
What face detector is more accurate than Haar cascade?
OpenCV’s DNN face detector (res10_300x300_ssd) is more accurate and robust to partial occlusion and Indian skin tones. Runs at 5-10 FPS on Pi 4 – fast enough for tracking if you run detection every 3rd frame.
Can I add multiple face tracking?
For pan-tilt, it makes sense to track one face at a time – the largest or most centred face. For multi-face scenarios, add a display overlay showing all detected faces but only command servos to follow the primary target.
Add comment