简介:面向计算机科学与技术专业毕业设计或课程作业的完整项目包,聚焦基于机器视觉的机械臂智能跟随系统,涵盖图像采集、目标检测与跟踪、机械臂控制、软硬件接口等关键环节。压缩包共616个文件,约52.72MB,类型以Python脚本(173个py)、YOLO等视觉模型配置(68个yaml、43个crf)、STM32嵌入式C/C++源码(c/h/cpp/cu)及编译产物(o/axf/hex)为主,另含23个md、15个txt说明、docx/pdf设计文档和mp4/gif演示、jpg/png图片,目录结构清晰,兼顾算法分析与底层实现。目前已有178人学习下载,资料中嵌入式源码显示对STM32F4系列外设有完整驱动。读者可据此复现目标识别到机械臂跟随的完整链路,掌握视觉与运动控制间的通信协议和坐标变换方法,同时设计文档、演示录屏和脚本能直接支撑课程汇报或二次开发。
1. 一个“机器视觉 + 机械臂智能跟随”的毕设,难在哪里
如果你打开这套“基于机器视觉的机械臂智能跟随系统”的压缩包,第一眼看到的通常是三个目录:视觉识别、机械臂控制、标定数据。这其实就是完整的技术栈——先用相机认出目标,再把目标坐标换算到机械臂的坐标系里,最后让机械臂实时追着目标运动。很多人栽在第二步和第三步的衔接上:视觉识别跑通了、单点抓取也成功了,但目标一旦动起来,机械臂要么反应慢半拍,要么直接往错误方向冲。这个项目的价值,是把“看到”和“抓到”之间的那根线——坐标变换、轨迹预测、实时控制——完整地补上了。适合正在做毕设或课程设计、手里有一套机械臂(哪怕是 3D 打印的六轴或总线舵机臂)和一块普通 USB 相机的同学照着做、做出来、答辩时有东西演示。
2. 把“眼睛”装到机械臂上:相机选型与手眼标定
2.1 两种手眼关系:Eye-in-Hand 与 Eye-to-Hand 怎么选
视觉跟随系统里,相机和机械臂的相对位置决定了整个系统的数学模型。常见做法是分两种:Eye-in-Hand(相机装在机械臂末端,跟着手臂一起动)和Eye-to-Hand(相机固定在工作区上方,看着机械臂和目标)。
跟在身后的场景,我一般推荐Eye-to-Hand。原因很简单:跟随的目标是独立运动的物体,相机固定时视野稳定,像素坐标和机械臂基座坐标之间的变换关系是一次标定、长期有效的。Eye-in-Hand 的好处是能近距离贴近目标、不容易被机械臂本体遮挡,但代价是每次机械臂关节一动,相机位姿就变,变换矩阵必须实时从正运动学状态里重新计算,实时性要求高,调试期容易翻车。如果你手里是遨博这类商用臂,示教器里自带手眼标定向导,用 Eye-in-Hand 倒也省事;但自己搭的毕设系统,还是固定相机更可控。
固定相机的安装位置也有讲究:尽量让相机光轴与机械臂基座坐标系的 Z 轴近似平行,俯视安装。这能把标定矩阵的耦合程度降低,后面转换坐标时误差更小。别斜着四五十度装,看着能覆盖工作空间,实际一算误差全放大在 Z 方向上。
2.2 最小可复现的标定流程与代码
手眼标定的本质,是求解相机坐标系到机械臂基座坐标系的变换矩阵 T_cam2base。我们用 OpenCV 加 ArUco 标定板,不用买昂贵的棋盘格。ArUco 的好处是单张图片就能给出位姿,检测鲁棒性比棋盘格好,尤其适合不规则的打光环境。
#!/usr/bin/env python3 # 程序目标:采集机械臂在多个位姿下的标定板图片,估计相机外参 import cv2 import numpy as np aruco_dict = cv2.aruco.Dictionary_get(cv2.aruco.DICT_6X6_250) params = cv2.aruco.DetectorParameters_create() camera_matrix = np.load("camera_matrix.npy") # 由棋盘格标定得到 dist_coeffs = np.load("dist_coeffs.npy") # 机械臂末端在基座坐标系下的位姿,由示教器/ROS tf 读取 end_effector_poses = load_from_robot_controller() rvecs_all, tvecs_all = [], [] for img_path in ["calib1.jpg", "calib2.jpg", "calib3.jpg"]: img = cv2.imread(img_path) corners, ids, _ = cv2.aruco.detectMarkers(img, aruco_dict, parameters=params) if ids is not None: rvec, tvec, _ = cv2.aruco.estimatePoseSingleMarkers(corners, 0.05, camera_matrix, dist_coeffs) rvecs_all.append(rvec[0]) tvecs_all.append(tvec[0]) # 用 PnP 求解每张图片的相机外参,再结合末端位姿求解手眼矩阵 # 这里以固定相机为例,T_cam2base 由多组对应关系通过 AX=XB 求解 # 常见做法是用 Python 的 transform3d 库或手写四元数线性方程采集数据时有个血泪经验:机械臂末端位姿差异要够大。如果你只让机械臂在同一个区域附近小幅移动,采集了十几张图,解出来的 T_cam2base 在数学上没问题,实际一用就偏。我一般会让末端走一个“大立方体”的八个顶点,每个顶点再旋转几个角度,保证旋转和平移的激励都充分。共采集 15~20 张图,然后看一眼重投影误差,超过 3 个像素就重来。
2.3 标定结果怎么检查:重投影误差与矩阵物理含义
标定完不是直接丢进程序就完事。要做的第一件事,是把矩阵打印出来看一眼量级:
[[ 0.9987 0.0151 -0.0462 0.312 ] [-0.0148 0.9996 0.0231 0.178 ] [ 0.0466 -0.0224 0.9987 0.625 ] [ 0. 0. 0. 1. ]]最后一行必须是 0 0 0 1,平移项的单位是米,量级应该和你相机到机械臂基座的实际距离对得上。如果平移量是几十上百,那不是标定错了就是坐标系单位不一致。另一个检查办法:把标定板放在机械臂工作空间的正中间,用视觉算出一个坐标,再手动示教机械臂末端到这个点,看两个坐标差。差在 1~2 厘米内,毕设够用;超过 5 厘米,别急着调跟踪算法,先回头查标定。
3. 让机械臂“认出”目标:检测、坐标转换与轨迹预测
3.1 从颜色阈值到深度学习:目标检测方案怎么选
跟随系统的目标通常很明确:要么是颜色显著的小球、方块,要么是带 ArUco 码的标签,要么是任意物体(需要深度学习)。毕设场景里,千万不要一上来就用 YOLO。先问自己一个问题:演示环境能否控制?如果能,用颜色阈值或 ArUco 是性价比最高的方案——没有训练成本、检测稳定性好、帧率高。
颜色阈值的问题在于光照敏感,解决办法是把 RGB 转到 HSV 空间,并且加一个“面积过滤”和“形态学开闭运算”。ArUco 码则对光照鲁棒许多,还能直接给出目标的姿态角,缺点是目标必须贴有清晰的码,不能太远。如果标题里的“机器视觉”明确要求体现深度学习,可以把 YOLO 检测作为备选方案,但真正跑跟随控制时仍然建议用 ArUco——深度学习推理速度慢、框抖动大,直接喂给控制器,机械臂会跟着抖到怀疑人生。
3.2 像素坐标到机械臂基座坐标的完整链路
目标被识别出来后,输出的是像素坐标 (u, v)。要变成机械臂能用的坐标,得走完这条链路:像素坐标 → 相机坐标系(用相机内参反投影)→ 机械臂基座坐标系(用 T_cam2base 变换)。关键诀窍是:单目相机只能给出一个方向的射线,没法直接得到三维坐标。所以固定相机方案下,目标的三维位置通常靠“假设目标在一个已知平面上”来解,例如桌面高度 z=0。这样射线与平面的交点就是目标坐标。
import numpy as np def pixel_to_base(u, v, z_plane=0.0, camera_matrix=None, t_cam2base=None): fx, fy = camera_matrix[0, 0], camera_matrix[1, 1] cx, cy = camera_matrix[0, 2], camera_matrix[1, 2] # 反投影:像素 -> 相机坐标系的归一化射线 x_cam = (u - cx) / fx y_cam = (v - cy) / fy z_cam = 1.0 # 射线与目标平面的交点(目标平面在相机坐标系下的高度由标定获得) # 常见做法是先在相机坐标系下算交点,再变换到基座坐标系 ray = np.array([x_cam, y_cam, z_cam], dtype=np.float64) # 相机坐标系到基座坐标系的变换 tvec = t_cam2base[:3, 3] rmat = t_cam2base[:3, :3] # 现在把射线变换到基座坐标系,再求与 z=0 平面的交点 ray_base = rmat @ ray origin_base = tvec.copy() # 射线参数方程: origin + t * ray, 令 z=0,解出 t if abs(ray_base[2]) < 1e-6: return None t = (z_plane - origin_base[2]) / ray_base[2] point_base = origin_base + t * ray_base return point_base[:2]这里的 z_plane 不是随便填 0。它表示目标在机械臂基座坐标系下的高度,由你安装相机时实际测量。测量误差 1 厘米,在平面交点处就会产生几厘米的位置偏移,这是“看着明明对准了,机械臂伸过去却偏了”的经典原因。
3.3 卡尔曼滤波:让跟随不抖、不滞后
裸的坐标流直接喂给机械臂,你看到的将是末端疯狂抖动。因为颜色分割和 ArUco 检测都存在几个像素的随机噪声,换算到空间坐标就是毫米级抖动,机械臂控制周期又比视觉帧率高,抖动被放大了。常规做法是在坐标流后面加一个卡尔曼滤波器,对目标的运动状态做估计,输出更平滑的预测位置。如果你用 ROS,robot_localization 包里的 ukf 节点可以直接用;自己写也不难。
from filterpy.kalman import KalmanFilter kf = KalmanFilter(dim_x=4, dim_z=2) kf.F = np.array([[1, 0, 1, 0], [0, 1, 0, 1], [0, 0, 1, 0], [0, 0, 0, 1]]) # 匀速运动模型 kf.H = np.array([[1, 0, 0, 0], [0, 1, 0, 0]]) kf.P *= 100 kf.R = np.eye(2) * 0.03 # 测量噪声,像素噪声越大,这里越大 kf.Q = np.eye(4) * 0.005 # 过程噪声,目标运动越机动,这里越大 def update_target(z): kf.predict() kf.update(z) return kf.x[0], kf.x[1]参数上,R 和 Q 的比值决定了滤波器的“保守程度”。R 设得大,滤波输出更平滑但滞后明显;Q 设得大,滤波器更相信测量值,响应快但可能仍有抖动。我一般先设 R=0.03、Q=0.005,然后让目标做快速直线移动,看末端跟不跟得上。跟不住就降 Q,抖得厉害就升 R。
4. 机械臂跟随控制:位置跟随还是速度跟随
4.1 位置跟随:MoveIt + Rviz/Gazebo 同步的最小流程
很多同学拿到这个项目,第一步就是把视觉坐标发布到 ROS,然后调move_group的go()接口让机械臂跑过去。这在仿真里看着挺对——Rviz 里机械臂确实一下一下地跳着追目标。但真机和 Gazebo 同步好的 MoveIt2 配置下,你会发现两个问题:一是go()每次调用要先做运动规划,规划耗时 100~300 毫秒,目标早就跑了;二是频繁打断当前轨迹会让机械臂走走停停,像机器人没充满电。
位置跟随适合目标移动缓慢或间歇停止的场景。核心配置是缩小规划时间、固定参考系为机械臂基座:
# move_group 参数调整示例 follow_joint_trajectory: # 允许关节目标被新目标打断 num_joints: 6 # 规划超时缩短 planning_time: 0.3 # 末端速度上限 max_velocity_scaling_factor: 0.8 max_acceleration_scaling_factor: 0.6如果只是课程展示,位置跟随足够应付。但在 Rviz 和 Gazebo 里看着“同步”,并不代表标定关系正确——仿真里相机是虚拟的,标定矩阵是代码里写死的,所以你永远不会发现手眼标定的坑。这也是“仿真是个放大器,不是避坑器”的含义:仿真越顺,真机越要谨慎。
4.2 速度跟随:视觉伺服里更常用的控制律
快速移动目标必须用速度跟随。思路是每帧视觉计算出目标在基座坐标系下的位置,与当前机械臂末端位置求误差,再把误差乘一个比例系数,输出为末端速度,发送给机器人控制器(或 MoveIt 的move_group.set_goal_tolerance()配合笛卡尔路径)。这叫位置-速度混合视觉伺服(Position-Based Visual Servoing)。
import rospy from geometry_msgs.msg import TwistStamped from std_msgs.msg import Header def follow_control(target_pos, cur_pos, kp=0.8): # 目标与末端的空间误差 err = target_pos - cur_pos # 限制最大速度,避免目标突变导致末端飞出去 max_vel = 0.15 # m/s vel = np.clip(kp * err, -max_vel, max_vel) return vel # 在控制循环里 pub = rospy.Publisher('/arm/cartesian_velocity', TwistStamped, queue_size=1) while not rospy.is_shutdown(): target = get_filtered_target() # 视觉线程 + 卡尔曼滤波 cur = get_end_effector_pos() # 从 /tf 读取 vel = follow_control(target, cur) twist = TwistStamped() twist.header = Header() twist.header.stamp = rospy.Time.now() twist.twist.linear.x = vel[0] twist.twist.linear.y = vel[1] twist.twist.linear.z = vel[2] pub.publish(twist) rate.sleep()关键参数有三个:比例增益 kp、最大速度上限、控制周期。kp 一般从 0.5~1.0 起步,调太大会让末端在目标附近振荡;最大速度限制一定要设,否则目标点偶尔跳变(例如遮挡后 Kalman 输出异常),机械臂会给出一个“飞走”的轨迹,直接打到限位。控制周期尽量和视觉刷新率解耦:视觉 30Hz,控制 100Hz 以上,缓存最近一次目标坐标,控制线程插值使用。
4.3 参数表:跟随模式该怎么选
| 参数 | 位置跟随 | 速度跟随 |
|---|---|---|
| 目标运动速度 | < 0.05 m/s | < 0.2 m/s |
| 控制周期 | 500 ms 级 | 10~20 ms 级 |
| 视觉帧率要求 | 15 Hz 够用 | 30 Hz 以上 |
| 规划器 | MoveIt 关节空间规划 | 笛卡尔速度指令 |
| 抗遮挡能力 | 差(直接停) | 较好(Kalman 续估计) |
| 真机风险 | 低 | 中,需限位保护 |
毕设答辩演示,我建议两套都实现,默认跑速度跟随,展示时先让目标慢速移动,再切换成快速移动,体现性能差异。这一下就能把项目和“拍照-抓取”的简单系统区分开。
5. 避坑与常见问题排查
5.1 现象:目标快速移动时突然丢帧,机械臂朝最后坐标猛冲
原因:视觉线程丢帧后,控制线程还在用上一帧坐标,误差突然变大,速度指令饱和输出。
解决:在视觉解析后加一个“新鲜度校验”,超过 100 毫秒没有新坐标,就让机械臂进入保持状态而不是继续追;同时 Kalman 预测继续运行 200 毫秒,挡住瞬时遮挡:
last_update_time = 0 FRESH_THRESHOLD = 0.1 # 秒 def is_fresh(): return rospy.Time.now().to_sec() - last_update_time < FRESH_THRESHOLD5.2 现象:Gazebo 仿真里一切正常,真机一跑就抖
原因:仿真相机没有畸变,没有曝光波动,目标识别稳定得不可思议;真机相机帧率抖动、分辨率不同、运动模糊。
解决:先录一段 rosbag,把真机相机话题和仿真相机话题分别回放,对比检测框的抖动幅度。真机上把曝光时间固定(v4l2-ctl -c exposure_absolute=300),关掉自动白平衡,并在检测前做一次高斯模糊。
5.3 现象:逆解输出关节角突然跳变,机械臂像抽搐
原因:六轴机械臂逆解存在多组解,两个相邻时刻可能取到了不同的解分支。
解决:在上一次关节角的基础上选最近的一组解。用 MoveIt 的setStartState加setJointValueTarget会帮你选;直接走 IKFast 或自写解析逆解时,必须自己加“最近解”逻辑,把历次关节角缓存下来,对每组解做距离比较。
5.4 现象:颜色阈值在演示厅灯光下失效,目标直接消失
原因:室内灯光色温和照度变化让 HSV 分量整体漂移,固定阈值过不去了。
解决:改用 ArUco 作为主要检测目标,颜色识别只作为辅助展示模块。如果必须用颜色,把 HSV 阈值做成动态可调,并在程序里放一个滑条(cv2.createTrackbar),演示现场调。别嫌土,这个滑条能救你毕设现场一命。
5.5 现象:机械臂夹爪控制板导致的控制线程阻塞
原因:不少总线舵机或夹爪控制板只有一个串口,控制反馈与运动指令在同一串口上轮询,某条指令超时会阻塞 200 毫秒,视觉线程被卡住。
解决:把夹爪控制单独拆线程,运动控制指令和夹爪指令走两个队列;串口通信设置读超时(serial.Serial(..., timeout=0.05)),并在每次指令前清空串口缓冲。
6. 怎么验证跟随做得好不好:画圆轨迹测试
最有效的验证方法,不是让目标走直线,而是让目标画圆。拿一张纸,打印一个半径 15 cm 的圆,贴在平板上,让目标(ArUco 码)沿着圆周匀速移动。机械臂末端会跟着画出一个圆,这时你把末端轨迹从/tf里录下来,在 Rviz 里用PlotJuggler或直接导出 CSV,对比两个圆的半径差和相位滞后。
合格的标准:半径误差在 2 cm 以内,相位滞后不超过 100 毫秒。如果发现半径一致但滞后明显,优先调大卡尔曼滤波的 Q 值;如果半径时大时小,查手眼标定矩阵的平移项是否准确。这一步做完,答辩时直接放轨迹对比图,效果比任何 PPT 截图都直观。
从毕设往深处走,这个系统可以自然延伸成机械臂动态抓取:加入深度相机(比如 RealSense D435),把目标从 2D 平面升级到 3D 空间,跟随末端再加上夹爪控制——这就接近“具身智能机械臂”的入门形态了。真到了那一步,你会发现标定、滤波、速度控制这套底子全都复用得上,只是传感器从单目换成了 RGB-D,控制从笛卡尔速度换成了阻抗控制。我自己的经验是,这套项目做完后,把 bag 数据和标定脚本整理成一套“视觉跟随测试台”,后续调任何机械臂项目都会先跑一遍画圆测试,省下大量调参时间。希望这些分析和踩坑记录能帮你的毕设少走几段弯路,做完之后,记得把轨迹数据留好——那是你整个系统最有力的证据。
本文还有配套的精品资源,点击获取