用Python+Arduino做个会‘看’会‘动’的小玩意:手把手教你复现颜色跟踪云台(附完整代码)
·
用Python+Arduino打造智能视觉追踪云台:从零件到成品的全流程实战
最近在创客圈里,视觉追踪系统越来越受欢迎——它能自动锁定并跟随目标物体,像是给机器装上了"眼睛"。这种技术听起来高大上,但其实用常见的Arduino和Python就能实现。今天我们就来动手做一个二自由度颜色追踪云台,从硬件组装到代码编写,一步步带你体验完整的开发过程。
1. 项目准备:硬件选型与搭建
1.1 核心组件清单
要完成这个项目,你需要准备以下材料:
| 组件名称 | 规格要求 | 数量 | 备注 |
|---|---|---|---|
| Arduino开发板 | Uno或Nano | 1 | 建议使用正版保证稳定性 |
| 舵机 | SG90或MG995 | 2 | 需180度旋转范围 |
| USB摄像头 | 720P及以上分辨率 | 1 | 支持OpenCV调用 |
| 云台支架 | 二自由度 | 1套 | 可3D打印或购买成品 |
| 杜邦线 | 公对公/公对母 | 若干 | 建议不同颜色区分信号和电源线 |
| 电源 | 5V 2A | 1 | 单独给舵机供电更稳定 |
提示:舵机工作时电流较大,建议使用独立电源供电,避免通过Arduino板直接供电导致电压不稳。
1.2 机械结构组装
组装云台时要注意几个关键点:
- 底座固定 :将第一个舵机水平安装在底座上,负责左右旋转(Yaw轴)
- 上层结构 :第二个舵机垂直安装在上层支架,负责俯仰运动(Pitch轴)
- 摄像头安装 :确保摄像头牢固固定在第二个舵机的活动部件上
- 走线规划 :用扎带固定线缆,避免运动时缠绕
# 简单的舵机角度测试代码
from pyfirmata import Arduino, SERVO
import time
board = Arduino('COM3') # 替换为你的实际端口
servo_pin = 9
board.digital[servo_pin].mode = SERVO
def rotate_servo(pin, angle):
board.digital[pin].write(angle)
time.sleep(0.015)
# 测试0到180度转动
for i in range(0, 180):
rotate_servo(servo_pin, i)
for i in range(180, 0, -1):
rotate_servo(servo_pin, i)
这段代码可以帮助你测试单个舵机是否正常工作。如果发现转动不顺畅,可能是机械结构有干涉或供电不足。
2. 开发环境配置
2.1 Python环境搭建
视觉处理部分我们使用Python+OpenCV方案,需要安装以下库:
pip install opencv-python numpy pyserial
对于更完整的开发环境,推荐使用Anaconda创建独立环境:
conda create -n tracking python=3.8
conda activate tracking
conda install -c conda-forge opencv numpy pyserial
2.2 Arduino开发环境
- 下载安装Arduino IDE(最新稳定版)
- 安装必要的库:
- Servo库(通常已内置)
- 可能需要安装Firmata库用于高级控制
注意:确保Python和Arduino IDE的串口通信使用相同的波特率,通常9600或115200都是常用选择。
3. 视觉识别核心算法
3.1 颜色空间转换与阈值处理
OpenCV处理颜色识别的基本流程:
import cv2
import numpy as np
def color_tracking(frame):
# 转换到HSV颜色空间
hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
# 定义红色范围(示例值,需根据实际目标调整)
lower_red = np.array([0, 120, 70])
upper_red = np.array([10, 255, 255])
mask1 = cv2.inRange(hsv, lower_red, upper_red)
lower_red = np.array([170, 120, 70])
upper_red = np.array([180, 255, 255])
mask2 = cv2.inRange(hsv, lower_red, upper_red)
# 合并两个红色范围的掩膜
mask = mask1 + mask2
# 形态学操作去除噪声
kernel = np.ones((5,5), np.uint8)
mask = cv2.erode(mask, kernel, iterations=1)
mask = cv2.dilate(mask, kernel, iterations=2)
return mask
3.2 目标中心点计算
获取目标轮廓并计算中心坐标:
def find_contour_center(mask):
contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
if len(contours) > 0:
# 找到最大轮廓
largest_contour = max(contours, key=cv2.contourArea)
# 计算轮廓的矩
M = cv2.moments(largest_contour)
# 计算中心坐标
if M["m00"] != 0:
cX = int(M["m10"] / M["m00"])
cY = int(M["m01"] / M["m00"])
return (cX, cY)
return None
4. 控制系统设计与实现
4.1 坐标映射与舵机控制
将摄像头画面坐标映射到舵机角度是关键步骤。假设摄像头分辨率为640x480:
def map_coordinates_to_servo(cX, cY, img_width=640, img_height=480):
# 计算与画面中心的偏移量
offset_x = cX - img_width // 2
offset_y = cY - img_height // 2
# 将偏移量转换为角度(比例系数需要根据实际云台调整)
angle_x = offset_x * 0.1 # 每像素偏移对应0.1度
angle_y = offset_y * 0.1
# 限制角度范围
angle_x = max(min(angle_x, 30), -30) # 限制在±30度内
angle_y = max(min(angle_y, 30), -30)
return angle_x, angle_y
4.2 串口通信协议设计
Python与Arduino之间需要定义简单的通信协议:
- 格式:'X角度,Y角度\n'(例如:"15.5,-10.2\n")
- 波特率:115200(高速传输减少延迟)
- 校验:可添加简单校验和
Arduino端对应的解析代码:
#include <Servo.h>
Servo servo_x; // 水平方向舵机
Servo servo_y; // 垂直方向舵机
void setup() {
Serial.begin(115200);
servo_x.attach(9);
servo_y.attach(10);
// 初始化位置
servo_x.write(90);
servo_y.write(90);
delay(1000);
}
void loop() {
if (Serial.available() > 0) {
String data = Serial.readStringUntil('\n');
// 解析X,Y角度
int commaIndex = data.indexOf(',');
if (commaIndex != -1) {
float angleX = data.substring(0, commaIndex).toFloat();
float angleY = data.substring(commaIndex+1).toFloat();
// 转换为舵机角度(假设90度为中间位置)
servo_x.write(90 + angleX);
servo_y.write(90 - angleY); // 注意Y轴方向可能需要反转
}
}
}
5. 系统集成与优化技巧
5.1 主控制循环实现
完整的Python控制程序框架:
import cv2
import serial
import time
# 初始化串口
ser = serial.Serial('COM3', 115200, timeout=1)
time.sleep(2) # 等待Arduino初始化
# 初始化摄像头
cap = cv2.VideoCapture(0)
cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
while True:
ret, frame = cap.read()
if not ret:
break
# 颜色识别
mask = color_tracking(frame)
center = find_contour_center(mask)
if center:
cX, cY = center
# 绘制中心点和十字线
cv2.circle(frame, (cX, cY), 5, (0, 255, 0), -1)
cv2.line(frame, (cX-10, cY), (cX+10, cY), (0, 255, 0), 2)
cv2.line(frame, (cX, cY-10), (cX, cY+10), (0, 255, 0), 2)
# 计算舵机角度并发送
angle_x, angle_y = map_coordinates_to_servo(cX, cY)
command = f"{angle_x:.1f},{angle_y:.1f}\n"
ser.write(command.encode())
# 显示结果
cv2.imshow("Tracking", frame)
cv2.imshow("Mask", mask)
if cv2.waitKey(1) & 0xFF == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
ser.close()
5.2 常见问题解决方案
-
舵机抖动问题 :
- 增加硬件滤波电容(100-470μF)
- 软件上采用移动平均滤波
- 降低舵机响应速度
-
延迟优化技巧 :
- 降低摄像头分辨率(如320x240)
- 缩小颜色识别区域ROI
- 使用多线程处理图像和串口通信
-
提高追踪稳定性 :
- 实现简单的预测算法(如卡尔曼滤波)
- 设置目标丢失时的搜索模式
- 添加死区控制(小偏移不响应)
# 简单的移动平均滤波实现
class MovingAverageFilter:
def __init__(self, window_size=5):
self.window_size = window_size
self.values = []
def update(self, new_value):
self.values.append(new_value)
if len(self.values) > self.window_size:
self.values.pop(0)
return sum(self.values) / len(self.values)
# 使用示例
x_filter = MovingAverageFilter()
y_filter = MovingAverageFilter()
# 在发送命令前滤波处理
filtered_x = x_filter.update(angle_x)
filtered_y = y_filter.update(angle_y)
command = f"{filtered_x:.1f},{filtered_y:.1f}\n"
这个项目最有趣的部分是看着云台自动追踪目标物体时的反应,就像有了生命一样。在实际测试中,我发现用乒乓球涂成红色作为追踪目标效果特别好——它移动速度快,能很好地测试系统的响应能力。当一切正常工作,云台平滑地跟随目标转动时,那种成就感是看多少教程都替代不了的。
更多推荐
所有评论(0)