在机器人竞赛的赛场上,视觉系统往往决定着智能小车的"智商上限"。参加过三届工创赛的老队员都清楚,从物料识别到二维码扫描,从色环定位到动态码垛,每一个环节都可能成为翻车现场。本文将分享一套经过省赛、国赛验证的视觉方案选型思路,以及那些用时间和汗水换来的实战经验。
轮趣C100和花雀广角摄像头是我们测试过的十余款设备中脱颖而出的选手。参数表往往只能说明部分问题:
| 关键指标 | 轮趣C1080P | 花雀200万广角 | 普通USB摄像头 |
|---|---|---|---|
| 视角范围 | 92度 | 130度 | 78度 |
| 最低照度 | 0.5lux | 0.3lux | 1.0lux |
| 曝光控制 | 自适应 | 手动/自动 | 固定 |
| 抗频闪能力 | 优秀 | 良好 | 无 |
| 帧率(1080P) | 30fps | 25fps | 15fps |
实际测试中发现,广角镜头带来的边缘畸变需要通过OpenCV的校正矩阵来补偿。这段代码可以快速评估摄像头畸变程度:
python复制import cv2
import numpy as np
def check_distortion(cam_index):
cap = cv2.VideoCapture(cam_index)
ret, frame = cap.read()
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
# 使用棋盘格进行标定
criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001)
objp = np.zeros((6*9,3), np.float32)
objp[:,:2] = np.mgrid[0:9,0:6].T.reshape(-1,2)
objpoints = []
imgpoints = []
ret, corners = cv2.findChessboardCorners(gray, (9,6), None)
if ret:
corners2 = cv2.cornerSubPix(gray,corners, (11,11), (-1,-1), criteria)
imgpoints.append(corners2)
objpoints.append(objp)
# 计算畸变参数
ret, mtx, dist, rvecs, tvecs = cv2.calibrateCamera(
objpoints, imgpoints, gray.shape[::-1], None, None)
print(f"径向畸变系数(k1,k2): {dist[0][:2]}")
print(f"切向畸变系数(p1,p2): {dist[0][2:4]}")
cap.release()
提示:省赛现场的光照条件往往与实验室差异巨大,建议准备三档曝光参数的预设文件,现场通过串口指令快速切换。
初赛阶段我们使用OpenMV进行扫码,直到省赛前一周的模拟赛才暴露出致命问题——在强光环境下,OpenMV的自动曝光算法会导致二维码区域过曝。紧急测试了三种替代方案:
OpenCV扫码方案:
python复制qr_decoder = cv2.QRCodeDetector()
data, bbox, _ = qr_decoder.detectAndDecode(frame)
if bbox is not None:
# 增强显示
cv2.polylines(frame, [np.int32(bbox)], True, (0,255,0), 2)
ZBar扫码库:
bash复制sudo apt-get install libzbar-dev
pip install pyzbar
专用扫码模块:测试了5款市售模块,识别距离普遍要求30cm以内
最终采用的组合方案是:花雀摄像头+OpenCV直方图均衡预处理。这段增强代码使我们的识别率从60%提升到98%:
python复制def enhance_qr_image(img):
# 分通道处理避免颜色失真
channels = cv2.split(img)
eq_channels = []
for ch in channels:
clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8,8))
eq_channels.append(clahe.apply(ch))
return cv2.merge(eq_channels)
传统方案通常先进行HSV色域分割,但在赛场强光干扰下,我们开发了基于形态学梯度的创新算法:
python复制def color_circle_detect(img):
# 形态学梯度增强边缘
kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
gradient = cv2.morphologyEx(img, cv2.MORPH_GRADIENT, kernel)
# 多尺度高斯模糊组合
blur1 = cv2.GaussianBlur(gradient, (5,5), 1)
blur2 = cv2.GaussianBlur(gradient, (9,9), 2)
blended = cv2.addWeighted(blur1, 0.7, blur2, 0.3, 0)
# 自适应阈值
thresh = cv2.adaptiveThreshold(
blended, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
# 霍夫圆检测优化参数
circles = cv2.HoughCircles(
thresh, cv2.HOUGH_GRADIENT_ALT, 1.5, 50,
param1=100, param2=0.9, minRadius=20, maxRadius=60)
return circles
这个算法的关键突破在于:
对于运动中的物料盘,我们采用卡尔曼滤波预测目标位置。这是初始化代码:
python复制class KalmanFilter:
def __init__(self):
self.kf = cv2.KalmanFilter(4, 2)
self.kf.measurementMatrix = np.array([[1,0,0,0],[0,1,0,0]], np.float32)
self.kf.transitionMatrix = np.array([
[1,0,1,0],
[0,1,0,1],
[0,0,1,0],
[0,0,0,1]], np.float32)
self.kf.processNoiseCov = np.eye(4, dtype=np.float32) * 0.03
def predict(self, coordX, coordY):
measured = np.array([[np.float32(coordX)], [np.float32(coordY)]])
self.kf.correct(measured)
predicted = self.kf.predict()
return predicted
实际应用中需要调整的关键参数:
processNoiseCov:影响系统对快速移动的响应速度measurementNoiseCov:决定系统对测量结果的信任程度稳定的串口通信是视觉-控制系统的生命线。我们的架构包含:
接收线程:持续监听下位机状态
python复制def uart_listener():
while True:
if ser.in_waiting >= 4:
data = ser.read(4)
process_data(data)
发送队列:避免多线程冲突
python复制from queue import Queue
send_queue = Queue(maxsize=10)
def uart_sender():
while True:
packet = send_queue.get()
ser.write(packet)
心跳机制:每500ms发送心跳包维持连接
将比赛流程抽象为状态机,每个状态对应特定的视觉算法:
python复制class StateMachine:
def __init__(self):
self.states = {
'SCAN_QR': self.scan_qr,
'LOCATE_CIRCLE': self.locate_circle,
'TRACK_OBJECT': self.track_object
}
self.current_state = 'SCAN_QR'
def run(self):
while True:
frame = get_camera_frame()
self.states[self.current_state](frame)
def scan_qr(self, frame):
# 二维码识别逻辑
if qr_found:
self.current_state = 'LOCATE_CIRCLE'
注意:状态切换时需要添加50-100ms的延时,避免硬件响应不及时导致的误判。
赛场光照条件的复杂性远超实验室环境,我们总结的应对策略:
硬件层面:
算法层面:
python复制def dynamic_white_balance(img):
lab = cv2.cvtColor(img, cv2.COLOR_BGR2LAB)
l, a, b = cv2.split(lab)
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8,8))
limg = clahe.apply(l)
return cv2.cvtColor(cv2.merge((limg,a,b)), cv2.COLOR_LAB2BGR)
现场调试:
图像处理流水线优化:
python复制# 不好的实践:连续创建临时Mat对象
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
blur = cv2.GaussianBlur(gray, (5,5), 0)
# 优化方案:复用内存空间
workspace = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
cv2.GaussianBlur(workspace, (5,5), 0, dst=workspace)
避免不必要的图像显示:
多摄像头切换策略:
异常处理规范:
python复制try:
result = process_frame(frame)
except cv2.error as e:
log_error(f"OpenCV error: {e}")
return default_value
except Exception as e:
send_alert(f"Critical error: {e}")
raise
资源释放保障:
python复制import atexit
@atexit.register
def cleanup():
cv2.destroyAllWindows()
for cap in cameras:
cap.release()
在国赛现场,我们遇到过USB接口松动导致摄像头掉线的问题。最终的解决方案是在代码中增加硬件检测循环:
python复制def check_camera_health():
while True:
if not cap.isOpened():
log_error("Camera disconnected!")
os.system("sudo uhubctl -l 1-1 -p 2 -a 1") # USB电源重置
time.sleep(2)
cap.open(camera_index)
time.sleep(1)
这些经验背后是无数次凌晨三点的调试和赛场上的惊心动魄。记得省赛前夜,当我们发现色环识别算法在特定角度会失效时,团队连续工作了18小时重写核心算法。这段经历告诉我们:在机器人竞赛中,没有完美的方案,只有不断迭代的勇气。