1. 图像预处理:从颜色提取到二值化
第一次参加RoboMaster比赛时,面对大能量机关的识别任务简直一头雾水。经过几个月的实战,我发现图像预处理是视觉识别的基石,就像盖房子前要打好地基。2023赛季的机关设计有变化,但核心逻辑不变——先把目标颜色从复杂环境中"揪出来"。
最常用的方法是HSV颜色空间转换。为什么不用RGB?因为HSV对光线变化更鲁棒。实际操作中,我常用inRange函数划定颜色范围:
python复制hsv_image = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)
lower_blue = np.array([100, 150, 50]) # 实际值需现场调试
upper_blue = np.array([130, 255, 255])
mask = cv2.inRange(hsv_image, lower_blue, upper_blue)
但实战中我发现,当场地灯光偏色严重时,单靠HSV容易漏检。这时可以试试通道相减法——用蓝色通道减去红色通道,能突出蓝标特征:
python复制channels = cv2.split(frame)
blue_minus_red = cv2.subtract(channels[0], channels[2])
二值化时有个坑:直接threshold可能把噪点也保留下来。我的经验是先做高斯模糊(GaussianBlur)降噪,阈值选择可以用大津法(THRESH_OTSU)自动计算:
python复制blurred = cv2.GaussianBlur(blue_minus_red, (5,5), 0)
_, binary = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY+cv2.THRESH_OTSU)
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 轮廓查找与特征分析
拿到二值图像后,真正的挑战才开始。findContours函数就像侦探的放大镜,但参数设置不对就会漏掉关键线索。经过多次翻车,我总结出几个关键点:
- 轮廓检索模式:一定要用
RETR_TREE,它能建立轮廓的族谱关系。曾经用RETR_EXTERNAL导致R标丢失,被队友吐槽了一整天 - 轮廓近似方法:新手建议用
CHAIN_APPROX_NONE保留所有点,虽然耗内存但精度高
找R标时有个巧妙特征:它是面积最小且有子轮廓的轮廓。代码实现时要注意异常保护:
python复制contours, hierarchy = cv2.findContours(binary, cv2.RETR_TREE, cv2.CHAIN_APPROX_NONE)
min_area = float('inf')
r_contour = None
for i in range(len(contours)):
area = cv2.contourArea(contours[i])
# 层级关系判断:hierarchy[0][i][2]!=-1表示有子轮廓
if 10 < area < min_area and hierarchy[0][i][2] != -1:
min_area = area
r_contour = contours[i]
箭头识别更考验耐心。我的方案是先做形态学膨胀合并分散区域:
python复制kernel = cv2.getStructuringElement(cv2.MORPH_ELLIPSE, (5,5))
dilated = cv2.dilate(binary, kernel, iterations=3)
但要注意!一定要在定位R标后再膨胀,否则R标特征会被破坏。识别箭头轮廓时,找面积最大且无父子关系的轮廓:
python复制max_area = 0
arrow_contour = None
for i in range(len(contours)):
if hierarchy[0][i][3]==-1 and hierarchy[0][i][2]==-1: # 无父子
area = cv2.contourArea(contours[i])
if area > max_area:
max_area = area
arrow_contour = contours[i]
3. 目标点定位的实战技巧
定位目标点就像玩几何游戏,核心是两点确定一条直线。但实际操作中会遇到三个典型问题:
- 坐标偏移:因为摄像头安装有倾角,需要做透视校正
- 动态补偿:机关在旋转,需要预测运动轨迹
- 距离换算:像素距离转实际距离要考虑镜头畸变
我的解决方案分三步走:
第一步:计算几何中心
python复制M = cv2.moments(arrow_contour)
cx = int(M['m10']/M['m00'])
cy = int(M['m01']/M['m00'])
第二步:建立坐标系
以R标中心为原点,计算箭头中心的相对坐标。这里要注意象限判断:
python复制dx = cx - r_center[0]
dy = r_center[1] - cy # 图像y轴向下为正
第三步:倍率延长
官方规则提示打击点位于R标与箭头连线延长线上,实测1.5倍距离最稳定:
python复制target_x = int(r_center[0] + dx * 1.5)
target_y = int(r_center[1] - dy * 1.5) # 注意y轴方向
遇到机关旋转时,可以加入角速度预测。我常用的方法是记录前5帧位置,用最小二乘法拟合运动轨迹:
python复制# positions是前几帧的位置队列
A = np.vstack([np.arange(len(positions)), np.ones(len(positions))]).T
kx, bx = np.linalg.lstsq(A, [p[0] for p in positions], rcond=None)[0]
ky, by = np.linalg.lstsq(A, [p[1] for p in positions], rcond=None)[0]
predict_x = kx * len(positions) + bx
predict_y = ky * len(positions) + by
4. 调试与优化的血泪经验
调参过程就像中医把脉,需要望闻问切。分享几个让我少走弯路的工具:
可视化调试工具:
python复制debug_img = cv2.cvtColor(binary, cv2.COLOR_GRAY2BGR)
cv2.drawContours(debug_img, [r_contour], -1, (0,0,255), 2)
cv2.circle(debug_img, target_point, 5, (0,255,0), -1)
cv2.imshow('Debug', debug_img)
参数自动化调整:
用滑动条实时观察效果,存为JSON配置文件:
python复制cv2.createTrackbar('H_min', 'panel', 100, 180, lambda x: None)
h_min = cv2.getTrackbarPos('H_min', 'panel')
# 保存配置
config = {'h_min':h_min, 'thresh':120}
json.dump(config, open('params.json','w'))
性能优化技巧:
- 图像缩放:识别前先缩放到固定尺寸(如640x480)
- ROI设置:只在机关可能出现区域处理
- 多线程:图像采集和计算分离
记得有次比赛前夜发现识别率骤降,原来是场馆的钠灯导致颜色失真。临时解决方案是在HSV空间加大S分量下限,同时启用自适应阈值:
python复制binary = cv2.adaptiveThreshold(blurred, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY, 11, 2)
这些经验都是用无数个通宵换来的。最后给新手建议:多准备几套参数预案,赛场灯光永远比实验室复杂。完整代码我放在GitHub仓库里,包含更多异常处理逻辑,比如轮廓为空时的保护判断,这对比赛稳定性至关重要。
