单目视觉伺服机械臂抓取:原理与Python实现

单目视觉伺服机械臂抓取:原理与Python实现 简介基于Python实现的单目视觉伺服抓取系统面向毕业设计、课程设计及实际项目开发场景主要用于解决静态物体的视觉识别与机械臂自动抓取这一核心问题。整套压缩包共收录47个文件以14个Python源码脚本作为主体另有样例样本、可扩展标记语言配置、版本管理记录与说明文档等辅助内容包体总大小仅59KB结构紧凑便于本地快速查阅与运行调试。该资源目前已有67人学习下载受到初学者与进阶开发者的关注。源码内容覆盖逆运动学求解、相机测距、目标定位和抓取控制等关键环节从图像采集、距离计算到机械臂动作输出形成完整技术链路同时包含机械臂查找、抓取控制、服务端通信等多个功能模块支持网络联动与伺服调试。配合测试脚本与项目说明文档可帮助学习者理解单目视觉伺服的基本原理与工程实现并在此基础上进行功能扩展与实际改造适合具备一定编程基础并希望快速搭建抓取系统原型的开发者用作参考。1. 单目视觉伺服抓取为什么一个摄像头也能闭环单目视觉伺服抓取看起来像在“作弊”——只有一个摄像头没有深度信息怎么抓得准关键在于把整个抓取过程做成闭环拍照、识别、估算偏差、控制机械臂移动、再拍照确认。每一步不追求精确但每一轮都在收敛最后停在目标上。这个思路比一次性求取三维坐标更符合工程直觉也避开了单目方案在深度估计上的先天短板。这套基于 Python 的源码把流程落成了一个个可单独调试的脚本shot.py 负责采集图像arm_find.py 负责识别目标位置测距camera.py 与测距pic.py 分别从实时画面和静态图片估算距离arm_xy.py 将像素坐标映射到机械臂基坐标系inverse_kinematic.py 做逆运动学求解control.py 和 server.py 负责闭环控制与角度指令下发。适合作为 Python 视觉抓取方向的毕业设计、课程设计也适合想低成本验证视觉伺服概念的个人项目。2. 单目测距原理与像素坐标解算2.1 针孔模型与单目测距的适用条件单目测距的所有公式都建立在针孔相机模型上空间中一个点经过光心投影到成像平面得到唯一像素坐标 [u, v]。反过来一个像素坐标只能确定一条射线无法唯一确定空间点这就是单目相机缺乏深度信息的原因。要让单目系统具备测距能力通常要引入先验约束最常见的是以下两种一是目标物体尺寸已知利用成像大小反推距离二是相机安装高度固定且光轴与地面夹角已知利用几何投影三角关系求距离。这套项目里的测距camera.py走的正是第二种思路。摄像头固定安装在机械臂上方高度 h 和俯仰角 pitch 在安装后不变此时目标在图像中的 v 坐标就携带了距离信息。目标越靠近画面底部说明它在相机视野中离得越近。这种约束适合桌面级抓取场景因为机械臂的工作平面基本是水平的目标也放在同一平面上几何关系稳定计算量小非常适合实时伺服。2.2 相机内参标定与畸变矫正无论用哪种测距公式第一步都是把焦距和畸变系数标出来。OpenCV 的 calibrateCamera 是通用做法打印一张棋盘格在不同角度拍 15 张左右图片然后执行下面的标准流程import cv2 import numpy as np import glob CHECKERBOARD (9, 6) # 棋盘格内角点数 criteria (cv2.TERM_CRITERIA_EPS cv2.TERM_CRITERIA_MAX_ITER, 30, 0.001) objp np.zeros((CHECKERBOARD[0] * CHECKERBOARD[1], 3), np.float32) objp[:, :2] np.mgrid[0:CHECKERBOARD[0], 0:CHECKERBOARD[1]].T.reshape(-1, 2) objpoints, imgpoints [], [] for fname in glob.glob(calib/*.jpg): img cv2.imread(fname) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) ret, corners cv2.findChessboardCorners(gray, CHECKERBOARD, None) if ret: objpoints.append(objp) corners2 cv2.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria) imgpoints.append(corners2) ret, mtx, dist, rvecs, tvecs cv2.calibrateCamera( objpoints, imgpoints, gray.shape[::-1], None, None) np.savez(camera_params.npz, mtxmtx, distdist) print(mtx)这段代码里cornerSubPix 用亚像素精度细化角点坐标标定结果 mtx 中 fx、fy 就是焦距的像素单位值dist 包含径向和切向畸变系数。后续所有像素坐标参与运算前应该先用 cv2.undistort 或 cv2.undistortPoints 对图像做去畸变否则画面边缘的坐标误差会直接传导到机械臂抓取点。标定参数保存为 npz 文件后可以直接在测距、识别模块里复用换摄像头时只需要重新跑一次标定。2.3 像素坐标到距离的几何换算标定完成后的核心换算逻辑如下。假设相机光轴相对水平地面有一个俯仰角 pitch光心离工作平面高度为 h目标在图像中的 v 像素坐标与图像中心 vc 的差值反映了目标相对光轴的偏离角import numpy as np class MonoRanger: def __init__(self, focal_px, height_mm, pitch_deg): self.f focal_px self.h height_mm self.pitch np.deg2rad(pitch_deg) def distance(self, v_pixel, vc): # 目标相对光轴的夹角 alpha np.arctan2(v_pixel - vc, self.f) # 光线与水平面的夹角 theta self.pitch alpha # 水平距离 d self.h / np.tan(theta) return d这段代码就是测距camera.py 这类模块的核心。alpha 是目标偏离光轴的角度theta 是目标光线与水平面的夹角最后用高度 h 除以 tan(theta) 得到水平距离。需要注意 pitch 的符号相机向下倾斜时pitch 是正值还是负值取决于坐标系定义建议用一个已知距离的物体先做验证测出来偏大就把 pitch 反号再试。如果目标是横向尺寸已知的矩形物体也可以用简单的相似三角形成像公式d f * real_width / pixel_width其中 pixel_width 是目标在图像里占的像素宽度real_width 是目标真实宽度。这个公式没有考虑俯仰角只适用于相机光轴水平或目标正好位于光心附近的场景精度上限比较低。实测下来同样的硬件用几何三角法在 0.5 米处误差约 ±8mm直接用相似三角形会到 ±20mm 以上这就是差距。2.4 标定焦距、高度和俯仰角标定参数建议按下表准备参数符号来源焦距像素fcalibrateCamera 返回的 fx光心高度h尺子量镜头光心到工作平面俯仰角pitch用两个已知距离反推图像中心 v 坐标vcmtx 里的 cy俯仰角不一定要用角度仪量可以在工作平面上放两个已知距离的标记物读出对应的 v 坐标反代上面的 distance 公式求 pitch。我一般会用三组距离求平均避免单一数据把角度带偏。需要强调的是这套几何公式只对“目标放在同一水平面”成立如果目标被垫高或者机械臂倾斜距离解算会整体失真。3. 目标识别与机械臂坐标系对齐3.1 基于 HSV 的目标分割与中心提取抓静态物体最常用的方案是颜色阈值或形状匹配。HSV 比 RGB 更抗光照变化因为色调 H 相对稳定亮度 V 变化不会直接影响 H。arm_find.py 中典型的识别逻辑如下import cv2 import numpy as np def find_target(frame, lower_hsv, upper_hsv, min_area500): hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, lower_hsv, upper_hsv) # 开运算先腐蚀后膨胀去掉细小噪点 mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, np.ones((5, 5), np.uint8)) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if not contours: return None c max(contours, keycv2.contourArea) if cv2.contourArea(c) min_area: return None M cv2.moments(c) if M[m00] 0: return None return (int(M[m10] / M[m00]), int(M[m01] / M[m00]))识别步骤可以拆成四步先将 BGR 转 HSV再用 inRange 做阈值分割接着用开运算去掉孤立噪点最后取最大轮廓的一阶矩作为目标中心。min_area 的作用是过滤掉小面积干扰数值与相机安装高度相关相机抬得越高目标在画面里越小这个阈值要跟着调低。HSV 阈值的选取我习惯用滑动条实时调试把 H、S、V 上下限分别绑定到 trackbar在画面里同时显示 mask调到目标区域刚好全白、背景基本全黑为止。只调 H 往往不够室内不同光源下 S 和 V 的浮动很大尤其是阳光从窗户进来的时候V 通道会整体抬升。3.2 像素坐标到机械臂基坐标的透视变换目标中心拿到的是像素坐标而机械臂控制器需要的是基坐标系下的 x、y。如果相机光轴与工作台不垂直简单等比例缩放是无效的必须做单应变换。arm_xy.py 的常见实现思路是在工作台上放四个已知机械臂坐标的标记点记录它们在图像中的像素坐标用 getPerspectiveTransform 求单应矩阵。src np.array([(u1, v1), (u2, v2), (u3, v3), (u4, v4)], dtypenp.float32) dst np.array([(x1, y1), (x2, y2), (x3, y3), (x4, y4)], dtypenp.float32) H cv2.getPerspectiveTransform(src, dst) def pixel_to_robot(u, v): p np.array([[[u, v]]], dtypenp.float32) out cv2.perspectiveTransform(p, H) return out[0][0]这里 src 是图像上的像素点dst 是机械臂基坐标系下的坐标点H 是 3x3 单应矩阵。四个标定点不要布成狭长三角形区域尽量覆盖整个抓取工作范围否则外推区域误差会很大。还有一种做法是用多个点拟合仿射变换但相机如果有明显俯仰角透视变换比仿射更准确。提示单应矩阵只在“目标位于同一平面”时严格成立。如果抓取物高度变化明显需要在测距环节先算出高度再对 x、y 做高度补偿否则远处和近处的映射误差会不一致。3.3 抓取点偏移与工具中心点补偿一个容易被忽略的坑视觉识别返回的是物体在图像中的形心但机械臂末端执行器比如吸盘或夹爪的 TCP 并不等于视觉检测点。如果夹爪中心与 TCP 有固定偏移必须在 pixel_to_robot 输出后加上偏移量。比如吸盘装在法兰盘下方 50mm、前方 20mm 处那实际抓取点就是视觉坐标沿夹爪方向平移后的结果。对于细长物体形心也不一定是抓取点。识别到轮廓后可以用 cv2.minAreaRect 求出物体的旋转角度再把抓取点从质心偏置到物体的几何中心。这个偏置量建议写成配置文件不要硬编码在识别函数里换物体时只改参数不动逻辑。4. 逆运动学解算与视觉伺服闭环4.1 两连杆平面机械臂的逆解公式inverse_kinematic.py 解决的是已知末端在基坐标系下的 (x, y)反求两个关节角。对平面两连杆机械臂先用余弦定理求第二关节角 theta2再用几何关系求第一关节角 theta1。设杆长分别为 L1、L2末端到基座的距离为 r sqrt(x^2 y^2)则cos(theta2) (x^2 y^2 - L1^2 - L2^2) / (2 * L1 * L2) theta1 atan2(y, x) - atan2(L2 * sin(theta2), L1 L2 * cos(theta2))这里 theta2 实际上有两个解肘部向上和肘部向下。对桌面抓取场景通常选择肘部朝上的那一组避免机械臂与桌面碰撞同时也让末端的姿态更稳定。4.2 用 numpy 实现逆运动学下面是一份可以直接使用的逆运动学实现接口设计为输入目标坐标和杆长输出两个关节角import numpy as np def inverse_kinematic(x, y, L1120.0, L2120.0): r np.hypot(x, y) # 可达性检查 if r L1 L2 or r abs(L1 - L2): raise ValueError(目标点超出工作空间) cos2 (x * x y * y - L1 * L1 - L2 * L2) / (2.0 * L1 * L2) cos2 np.clip(cos2, -1.0, 1.0) theta2 np.arccos(cos2) theta1 np.arctan2(y, x) - np.arctan2( L2 * np.sin(theta2), L1 L2 * np.cos(theta2) ) return np.rad2deg(theta1), np.rad2deg(theta2)r 的计算同时承担了可达性检查如果目标点离基座太远或太近直接抛异常。cos2 用 clip 限制在 [-1, 1]避免浮点误差导致 arccos 返回 nan。返回的是角度制方便直接下发给舵机或步进电机控制板。如果你的机械臂是三连杆或带有末端俯仰关节可以在保持函数签名不变的前提下扩展输入 (x, y, phi)其中 phi 是末端期望姿态角先按三连杆几何关系解出中间关节再用姿态约束求末端关节。这样视觉伺服循环代码可以完全不动只替换运动学模块。4.3 视觉伺服闭环控制循环control.py 的核心是一个迭代循环识别目标、映射坐标、求逆解、下发角度、等待稳定、再识别。每轮循环都以“目标在图像中的像素位移”作为收敛判据而不是要求一次到位import time import numpy as np def visual_servo(cam, arm, target_hsv, max_iter6, tol10): for i in range(max_iter): frame cam.read() center find_target(frame, target_hsv) if center is None: break u, v center x, y pixel_to_robot(u, v) theta1, theta2 inverse_kinematic(x, y) arm.set_joint_angles(theta1, theta2) time.sleep(0.6) # 等待机械臂运动到位 frame cam.read() new_center find_target(frame, target_hsv) if new_center is None: break err np.hypot(new_center[0] - u, new_center[1] - v) if err tol: break这个循环里tol 是像素偏差阈值max_iter 是最大迭代次数。常见的调参误区是把 tol 设得太小导致机械臂在目标附近反复震荡。建议先放宽到 15 像素跑通流程再逐步收紧。time.sleep(0.6) 的时长取决于机械臂响应速度舵机慢就加长步进电机快就缩短但不要取消否则伺服环会在一帧图像的运动模糊里判断失误。4.4 上下位机通信与角度指令下发如果机械臂是串口或网络控制需要把关节角序列发给下位机。server.py 和 push_to_send.py 就是做这层封装的。我用过比较简单可靠的 TCP 报文格式是前导码、关节数、各关节角度乘以 100 后的低字节和高字节最后加一个校验和。import socket arm_sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) arm_sock.connect((192.168.1.120, 8080)) def send_angles(theta1, theta2): t1 int(theta1 * 100) t2 int(theta2 * 100) payload bytes([ 0xAA, # 前导码 2, # 关节数量 t1 0xFF, (t1 8) 0xFF, t2 0xFF, (t2 8) 0xFF ]) checksum sum(payload) 0xFF arm_sock.send(payload bytes([checksum]))角度乘以 100 是为了保留一位小数精度因为很多舵机控制板只接受整数角度。校验和取所有字节累加的低 8 位下位机收到后重新计算不匹配就丢弃这一帧避免串口干扰导致机械臂突然乱动。如果你的机械臂走 CAN 总线换成 pyserial 或 python-can 的发送函数即可循环控制部分不需要改。提示如果机械臂在闭环过程中出现来回振荡优先检查控制周期和角度下单幅值而不是只调视觉阈值。把每轮最大角度变化限制在 5 度以内能有效抑制高频抖动。5. 实战验证方法与参数调整5.1 用固定靶标验证闭环是否收敛拿到源码后建议先把目标物体固定在一个位置连续跑 20 次抓取记录每次闭环结束时的像素偏差。把数据用 matplotlib 画出来如果偏差曲线呈下降趋势并稳定在一个小范围内说明视觉、坐标映射、运动学三个模块的误差没有互相放大如果曲线发散或来回跳优先检查单应矩阵标定点是否覆盖了目标实际位置。5.2 常见问题与修正表现象可能原因调整方向目标识别频繁跳变HSV 阈值范围太宽缩小 S 和 V 范围提高 min_area距离计算忽远忽近pitch 标定不准用三组已知距离反推 pitch 取平均机械臂到位后目标仍在原地转逆解选了解的另一支固定 theta2 的符号解抓取点系统性偏左TCP 偏移未补偿测量夹爪中心与法兰中心偏差闭环越追越偏透视变换矩阵不准重新布标定点扩大覆盖范围5.3 把单目方案移植到自己的机械臂换机械臂时最省事的做法是保持接口不变inverse_kinematic 的输入统一为 (x, y, tool_offset)输出关节角数组视觉模块只输出像素中心由坐标映射层负责转换成机械臂基座标。这样你只需要重写逆运动学函数和通信层识别与闭环逻辑可以直接复用。把抓取点设在 TCP 正下方 ±5mm 内能显著降低抓偏概率这是手眼标定之外最容易被忽略的一件事。本文还有配套的精品资源点击获取