
1. 从USB相机到Aruco码定位的整体设计思路1.1 为什么选择USB相机加Aruco码这套组合做机器人视觉定位这几年我试过不少方案从最贵的工业相机到最便宜的树莓派摄像头都折腾过。最后发现对于大多数室内移动机器人、机械臂抓取、AGV对接这类场景USB相机加Aruco码的组合是性价比最高、落地最快的一条路。原因很简单USB相机即插即用驱动成熟Ubuntu下基本免配置Aruco码是开源库生成和识别都不需要额外授权打印出来贴上去就能用。这套方案解决的核心问题是让机器人知道自己相对于某个标记物的精确位姿。比如机械臂要去抓传送带上的工件工件上贴一个Aruco码相机看一眼就能算出工件在相机坐标系下的位置和姿态再通过手眼标定矩阵转换到机械臂基坐标系抓取路径就出来了。整个过程不需要昂贵的激光雷达也不需要复杂的深度学习训练一个普通USB相机加一张打印纸就能跑通。适合谁来参考如果你是做机器人竞赛的学生、做AGV小车的工程师、或者刚接触机器视觉想快速出效果的开发者这套方案能让你在半天内看到定位结果。前提是你得懂一点Linux操作会写简单的Python或C剩下的就是跟着步骤走。1.2 整体流程拆解从相机到Aruco码的完整链路整个链路我把它拆成四步每一步都有明确的输入和输出这样排查问题的时候能快速定位是哪一环出了毛病。第一步是相机标定。USB相机出厂时的内参是未知的镜头的焦距、畸变系数这些参数直接决定了后续Aruco码定位的精度。标定的过程就是拿一张棋盘格从不同角度拍十几张照片用算法算出相机的内参矩阵和畸变系数。这一步的输出是一个YAML文件里面存着相机的“身份证信息”。第二步是Aruco码生成与布置。根据你的实际场景选择字典类型和码的尺寸打印出来贴在目标物体上。这里有个坑码的尺寸必须和实际物理尺寸对应否则算出来的距离是错的。我一般用DICT_6X6_250这个字典码的边长根据相机分辨率和识别距离来定。第三步是aruco-ros节点配置与运行。aruco-ros是ROS下的一个功能包它订阅相机图像话题发布Aruco码的位姿话题。你需要配置相机内参文件路径、Aruco字典类型、码的物理尺寸这几个关键参数。跑起来之后在RViz里就能看到码的坐标系跟着实际码移动。第四步是位姿数据的使用与验证。拿到的位姿数据可以发给机械臂做抓取也可以发给导航模块做对接。验证的方法是把码放在已知距离上看输出的Z值是否和实际距离一致旋转码的角度看输出的姿态角是否跟着变。这四步环环相扣标定不准后面全白搭码的尺寸填错距离就飘。下面我逐步展开每个环节的实操细节。2. USB相机标定的核心细节与实操要点2.1 标定前的硬件准备与环境要求标定这件事硬件和环境决定了上限。我踩过的坑里至少一半是因为标定板没做好或者光照不均匀导致的。标定板的选择棋盘格标定板是最常用的OpenCV和ROS都支持。我建议用9x6的棋盘格方格边长根据你的工作距离来定。如果相机离标定板大概0.5米到1米方格边长用25mm到30mm比较合适。太小了角点检测容易飘太大了图像里放不下几个格子。标定板必须打印在平整的硬纸板上用胶水贴平不能有翘曲。我试过直接拿A4纸打印结果纸不平标定出来的畸变系数明显偏大。光照条件标定的时候光照要均匀避免反光和阴影。最好在室内用漫射光源不要有直射的强光打在标定板上。如果标定板上出现亮斑角点检测会失败或者偏移。我一般选在阴天靠窗的位置或者用两个柔光箱从两侧打光。相机设置USB相机标定前要固定好焦距。很多USB相机是自动对焦的标定过程中焦距一变内参就全废了。所以要么选手动对焦的相机要么用胶带把对焦环固定住。另外分辨率设置到相机支持的最高值这样角点检测的精度更高。帧率可以降到5fps标定不需要高帧率。注意标定过程中相机不能动标定板动。如果你反过来让相机动、标定板不动也可以但相机移动的轨迹要覆盖整个视野操作起来更麻烦。2.2 基于ROS的相机标定完整操作流程Ubuntu 18.04下用ROS Melodic做相机标定流程很成熟。我习惯用camera_calibration这个包它是ROS官方维护的比手写OpenCV代码省事。首先安装标定包sudo apt install ros-melodic-camera-calibration然后启动USB相机节点。假设你的相机是/dev/video0用usb_cam包roslaunch usb_cam usb_cam-test.launch这个launch文件默认发布/usb_cam/image_raw话题。确认图像能在image_view里正常显示后开始标定rosrun camera_calibration cameracalibrator.py --size 9x6 --square 0.025 image:/usb_cam/image_raw camera:/usb_cam这里的--size 9x6是棋盘格的内角点数注意不是方格数。9x6的棋盘格有8x5个内角点但ROS的参数写的是方格数减一所以写9x6。--square 0.025是方格边长单位是米对应25mm。标定界面出来后你要移动标定板让棋盘格出现在图像的左、右、上、下、远、近各个位置同时要有倾斜角度。界面右侧的四个进度条X、Y、Size、Skew都变绿了才能点CALIBRATE。我一般会拍30到40个有效样本太少了解算不稳定。标定完成后点SAVE会在/tmp目录下生成一个calibrationdata.tar.gz解压后里面有ost.yaml和ost.txt。YAML文件就是相机内参内容大概长这样image_width: 640 image_height: 480 camera_name: usb_cam camera_matrix: rows: 3 cols: 3 data: [615.2, 0, 320.5, 0, 614.8, 240.3, 0, 0, 1] distortion_model: plumb_bob distortion_coefficients: rows: 1 cols: 5 data: [-0.28, 0.09, 0.001, -0.002, 0]camera_matrix里的fx和fy是焦距像素单位cx和cy是光心。distortion_coefficients是畸变系数顺序是k1, k2, p1, p2, k3。2.3 标定结果验证与常见问题排查标定完了怎么知道准不准我一般做两个验证。重投影误差检查在标定界面上CALIBRATE之后会显示一个epipolar error或者reprojection error。这个值小于0.5像素算优秀小于1.0像素算合格大于1.5像素就得重新标。误差大的原因通常是标定板不平、光照不均、或者样本覆盖不够。实际测距验证把标定板放在离相机已知距离的位置比如正好1米然后用image_geometry包里的projectPixelTo3dRay函数算一下中心点的深度。如果算出来是0.98米到1.02米之间说明标定基本靠谱。偏差超过5%就得查原因。常见问题我整理了一个速查表问题现象可能原因解决方法角点检测失败光照太暗或反光调整光源避免直射标定误差大于2像素标定板不平或样本太少重新打印贴平增加样本到40张测距偏差大方格边长填错用卡尺量实际边长单位换算成米图像边缘畸变严重畸变系数没标定好确保标定时棋盘格覆盖图像四角标定后图像仍然弯曲用了错误的畸变模型USB相机一般用plumb_bob鱼眼用equidistant实操心得标定一次不要超过20分钟时间长了相机会发热内参可能漂移。如果标定过程中相机温度变化明显建议等相机冷却后再标。3. Aruco码生成与aruco-ros节点配置3.1 Aruco码的字典选择与生成方法Aruco码的本质是一个二进制矩阵外面加一圈黑色边框。字典决定了码的位数和容错能力。常用的字典有DICT_4X4_50、DICT_5X5_100、DICT_6X6_250、DICT_7X7_1000。数字越大码的位数越多能容纳的ID数量越多但识别距离越近。我一般选DICT_6X6_250理由是6x6的矩阵在1米距离上用640x480的相机能稳定识别250个ID够大多数场景用容错能力适中码被遮挡一角还能认出来。如果你的场景ID需求少但距离远选DICT_4X4_50如果ID需求多且距离近选DICT_7X7_1000。生成Aruco码有两种方式。一种是用在线生成器输入ID和尺寸直接下载PDF。另一种是用Python的opencv-contrib-python包import cv2 import numpy as np aruco_dict cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_250) marker_id 23 marker_size 200 # 像素 marker_image np.zeros((marker_size, marker_size), dtypenp.uint8) marker_image cv2.aruco.drawMarker(aruco_dict, marker_id, marker_size, marker_image, 1) cv2.imwrite(faruco_{marker_id}.png, marker_image)生成的图片打印出来实际边长要量一下。比如你打印时设置成5cm x 5cm那就用卡尺量一下黑色边框的外沿到外沿确保是50mm。这个尺寸后面要填到aruco-ros的配置里。注意Aruco码周围要留白至少留一个码的边长的空白。如果码贴在其他图案旁边识别率会下降。我一般会在码外面加一圈白边再打印。3.2 aruco-ros功能包的安装与参数配置aruco-ros在ROS Melodic下有二进制包直接apt安装sudo apt install ros-melodic-aruco-ros安装完之后功能包在/opt/ros/melodic/share/aruco_ros。它提供了几个launch文件最常用的是single.launch用于单个Aruco码的检测。启动之前要改几个参数。我一般复制一份launch文件到自己的工程里改不改原文件。关键参数有这几个markerId要检测的Aruco码ID比如23markerSize码的物理边长单位米比如0.05camera_frame相机坐标系的名字要和你的URDF或TF树里的一致reference_frame参考坐标系一般填相机坐标系image_is_rectified图像是否已经去畸变如果用的是image_proc节点输出的图像填true一个典型的launch配置如下launch arg namemarkerId default23/ arg namemarkerSize default0.05/ arg nameeye defaultleft/ arg namemarker_frame defaultaruco_marker_frame/ arg nameref_frame defaultcamera_link/ arg namecamera_frame defaultcamera_link/ arg nameimage_is_rectified defaulttrue/ node namearuco_single pkgaruco_ros typesingle remap from/camera_info to/usb_cam/camera_info/ remap from/image to/usb_cam/image_rect/ param nameimage_is_rectified value$(arg image_is_rectified)/ param namemarker_size value$(arg markerSize)/ param namemarker_id value$(arg markerId)/ param namereference_frame value$(arg ref_frame)/ param namecamera_frame value$(arg camera_frame)/ param namemarker_frame value$(arg marker_frame)/ /node /launch这里有个关键点/image话题要订阅去畸变后的图像image_rect而不是原始图像image_raw。去畸变由image_proc节点完成它会读取camera_info里的内参和畸变系数。所以启动顺序是先启动usb_cam再启动image_proc最后启动aruco_single。3.3 坐标系变换与TF树的正确配置aruco-ros发布的是Aruco码相对于相机坐标系的位姿话题是/aruco_single/pose类型是geometry_msgs/PoseStamped。同时它还会发布一个TF变换从camera_frame到marker_frame。如果你要在RViz里看到码的坐标系需要确保TF树是连通的。相机的TF一般由robot_state_publisher或static_transform_publisher发布。比如rosrun tf static_transform_publisher 0 0 0 0 0 0 base_link camera_link 100这条命令发布了一个从base_link到camera_link的静态变换。实际使用中这个变换应该由你的URDF文件定义或者由手眼标定结果给出。实操心得如果RViz里看不到Aruco码的坐标系先检查TF树。用rosrun tf view_frames生成TF树图看看camera_link和marker_frame之间有没有断链。常见问题是camera_frame参数填错了比如填了camera但TF树里是camera_link。4. 位姿数据的使用与精度验证4.1 从位姿话题到机械臂抓取的数据链路拿到/aruco_single/pose之后怎么用假设你有一个机械臂基坐标系是base_link相机固定在机械臂末端或者工作台上。你需要一个变换矩阵把Aruco码在相机坐标系下的位姿转换到机械臂基坐标系下。如果相机固定在机械臂末端eye-in-hand这个变换矩阵来自手眼标定。如果相机固定在工作台上eye-to-hand变换矩阵来自相机外参标定。手眼标定的方法我另开一篇讲这里假设你已经有了这个矩阵。转换的代码逻辑是import tf import geometry_msgs.msg listener tf.TransformListener() def pose_callback(msg): try: # 把Aruco码位姿从camera_link转到base_link transformed_pose listener.transformPose(base_link, msg) # 发给机械臂 send_to_robot(transformed_pose) except (tf.LookupException, tf.ConnectivityException): rospy.logwarn(TF变换不可用)这里的关键是transformPose函数它会自动查找TF树里从camera_link到base_link的变换链。前提是TF树里这条链是完整的。4.2 定位精度的实测验证方法精度验证我一般做三组测试。距离精度测试把Aruco码放在离相机0.5米、1.0米、1.5米的位置每个位置测10次看输出的Z值均值和标准差。我用DICT_6X6_250、5cm码、640x480分辨率实测1米处误差在±5mm以内1.5米处误差在±15mm以内。距离越远误差越大因为码在图像里占的像素少了。角度精度测试把码绕Z轴旋转0度、30度、45度、60度看输出的yaw角。实测误差在±2度以内。角度精度受码的尺寸和图像分辨率影响码越大、分辨率越高角度越准。重复性测试码固定不动连续测100次看位姿的波动。好的情况下位置波动小于2mm角度波动小于0.5度。如果波动大检查相机是否固定牢靠、光照是否稳定。测试项目测试条件实测误差备注距离精度1米5cm码±5mm640x480分辨率距离精度1.5米5cm码±15mm同上角度精度45度5cm码±2度同上重复性静止100次位置±2mm角度±0.5度光照稳定4.3 提升定位精度的几个实用技巧如果你觉得精度不够可以试这几个方法。提高相机分辨率从640x480升到1280x720码在图像里的像素翻倍角点检测精度提升明显。但要注意帧率会下降如果机械臂运动快可能跟不上。增大码的尺寸5cm码换成10cm码识别距离和精度都提升。但码太大可能贴不下或者影响外观。使用多个码在目标物体上贴多个Aruco码取平均位姿能降低随机误差。我试过贴4个码位置波动从±2mm降到±1mm。优化光照Aruco码识别对光照敏感。用主动光源比如LED环形灯从相机方向打光能提高对比度减少阴影干扰。亚像素角点检测OpenCV的Aruco检测默认用亚像素精度但你可以调cornerRefinementMethod参数。CORNER_REFINE_SUBPIX比默认的CORNER_REFINE_NONE精度更高但计算量稍大。注意如果码贴在反光表面上比如金属或玻璃识别率会大幅下降。解决办法是贴一层哑光膜或者把码打印在哑光纸上再贴上去。5. 常见问题与排查技巧实录5.1 Aruco码识别不稳定的排查思路识别不稳定是最常见的问题表现是RViz里码的坐标系时有时无或者位姿跳变。排查顺序我一般这样走第一步查图像质量。在image_view里看原始图像码是否清晰、对比度是否够。如果图像模糊调焦距如果太暗加光源如果反光换角度。第二步查字典类型。生成码用的字典和aruco-ros配置的字典必须一致。我遇到过用DICT_6X6_250生成但launch里写成了DICT_5X5_100结果死活识别不出来。检查方法是看aruco-ros的启动日志它会打印当前使用的字典。第三步查码的ID。launch里的markerId必须和实际码的ID一致。如果场景里有多个码每个码要单独起一个aruco_single节点或者用aruco_multi节点。第四步查图像话题。确认/image话题订阅的是去畸变后的图像。如果订阅了原始图像畸变会导致角点位置偏移识别率下降。第五步查TF树。如果位姿话题有数据但RViz里看不到多半是TF断了。用rostopic echo /aruco_single/pose确认数据在发然后用rosrun tf view_frames查TF树。5.2 位姿跳变与数据滤波处理位姿跳变的原因通常是角点检测在相邻帧之间发生了跳变。Aruco码的四个角点如果某一帧检测到了错误的角点组合位姿就会突然跳一下。解决办法有两个。一是加滤波用robot_localization包里的卡尔曼滤波或者简单点用滑动平均滤波。滑动平均的窗口大小取5到10能平滑掉大部分跳变但会引入延迟。二是加约束如果知道码只在平面上移动可以把Z值和roll、pitch角固定只让X、Y、yaw变化。这样即使角点检测有噪声位姿也不会乱跳。我一般用滑动平均加约束的组合。代码大概这样from collections import deque pose_buffer deque(maxlen5) def pose_callback(msg): pose_buffer.append(msg.pose) avg_pose average_poses(pose_buffer) # 约束Z和roll、pitch avg_pose.position.z 0 avg_pose.orientation constrain_orientation(avg_pose.orientation) publish_filtered_pose(avg_pose)实操心得滤波窗口不要太大5到10就够了。窗口太大机械臂响应会变慢抓取的时候对不准。5.3 多码场景下的ID管理与冲突解决一个场景里有多个Aruco码时ID管理很重要。我一般按功能分区分配ID0到49给工件50到99给工位100到149给AGV停靠点。这样一看ID就知道是什么东西。aruco-ros的single节点一次只能检测一个ID。如果要同时检测多个码用aruco_multi节点它会把图像里所有码的位姿都发布出来。但aruco_multi的话题格式和single不一样是/aruco_multi/pose里面是一个数组。如果两个码的ID相同aruco-ros会随机选一个发布导致位姿在两个码之间跳。所以ID绝对不能重复。生成码的时候用脚本批量生成每个ID对应一个文件避免手动搞错。问题现象排查步骤解决方法码时有时无查图像质量、字典、ID调光照、核对字典和ID位姿跳变查角点检测、加滤波滑动平均约束多码冲突查ID是否重复重新分配唯一ID距离偏差大查码的物理尺寸卡尺量实际边长更新配置RViz无显示查TF树补全TF链或修正frame名6. 从单目到双目与多传感器融合的扩展思路6.1 双目相机标定与Aruco码深度估计单目相机做Aruco码定位距离精度受码的尺寸影响很大。码的尺寸量不准距离就飘。双目相机能直接通过视差算深度不依赖码的物理尺寸精度更稳定。双目标定的流程和单目类似但要同时标定左右相机还要标定它们之间的外参。ROS下用camera_calibration包指定--camera_name为左相机再指定右相机它会输出一个包含左右内参和右相机到左相机变换的YAML文件。标定完之后用stereo_image_proc节点做立体匹配得到视差图再转成点云。Aruco码的角点在点云里找到对应的3D点就能算出码的位姿。这套流程比单目复杂但精度更高适合对距离要求严格的场景。6.2 相机与IMU联合标定的适用场景如果机器人是移动的相机在运动过程中拍Aruco码单帧定位会有运动模糊。这时候相机和IMU联合标定就有用了。IMU提供高频的姿态信息相机提供低频的绝对位置两者融合能提高动态定位的精度和鲁棒性。联合标定的关键是标定相机和IMU之间的外参也就是相机坐标系到IMU坐标系的旋转和平移。标定方法一般用kalibr工具需要采集一段同时包含相机图像和IMU数据的rosbag然后离线优化。这个过程比较耗时但标定一次可以用很久。注意相机和IMU联合标定对时间同步要求很高。如果相机和IMU的时间戳不同步标定结果会完全错误。采集数据前要确认两者用的是同一个时钟源。6.3 视觉引导机器人的典型应用案例最后说一个我实际做过的案例。一个四轴机械臂要从传送带上抓取工件工件上贴了Aruco码。相机固定在机械臂基座旁边俯视传送带。流程是相机拍传送带aruco-ros检测码的位姿通过手眼标定矩阵转换到机械臂基坐标系然后规划抓取路径。传送带是动的所以还要做传送带速度补偿。补偿的方法是检测到码的位姿后根据传送带编码器的速度预测码在机械臂到达时的位置。实测下来传送带速度0.1米/秒时抓取成功率95%以上。速度提到0.3米/秒成功率降到80%因为预测误差变大了。后来加了IMU做运动补偿0.3米/秒下成功率回到92%。这个案例说明Aruco码定位在静态或低速场景下非常可靠高速场景需要额外的传感器融合。但无论如何从USB相机标定到Aruco码定位这条链路是机器人视觉入门的最佳路径之一。先把这条链路跑通再根据实际需求加传感器、加算法一步步迭代比一上来就搞复杂的方案要踏实得多。