1. 为什么选Jetson Nano跑Dofbot的视觉分拣1.1 这套组合到底解决什么问题Dofbot是一台桌面级六轴机械臂官方标配的玩法通常是手柄示教或者固定轨迹抓放。但真正让它“长眼睛”的是给它接上一路摄像头让机械臂自己判断目标在哪、该往哪抓。我这次做的色块分拣就是最典型的入门级视觉抓取场景桌面上散落红、绿、蓝三种颜色的方块摄像头拍一张图程序算出每个色块的像素坐标换算成机械臂底座坐标系下的实际位置然后依次抓起来丢进对应颜色的盒子里。整套系统的核心硬件就三样Dofbot机械臂本体、Jetson Nano开发板、一个普通USB摄像头。软件栈是Ubuntu 18.04 OpenCV Python。选Jetson Nano的原因很直接——它自带GPU能跑轻量级神经网络虽然色块分拣用传统图像处理就够了但后续如果想升级成YOLO识别任意物体Nano的算力还能撑一撑。而且Nano的GPIO和USB接口跟Dofbot的舵机控制板对接很顺不需要额外转接。适合看这篇的人手上有Dofbot或者类似总线舵机机械臂、想入门视觉抓取的学生和爱好者正在做毕业设计需要机械臂视觉方案的以及已经跑通官方例程、想自己改点东西出来的朋友。如果你连Ubuntu都没装过建议先补一下Linux基础操作不然中间会遇到不少卡壳的地方。1.2 色块分拣的技术路线选择视觉抓取的技术路线大致分两种一种是端到端的深度学习方案直接输入图像输出抓取位姿另一种是传统的“检测-定位-映射-执行”流水线。我选的是后者原因有三个。第一色块分拣的目标特征极其明确——颜色。HSV色彩空间下红绿蓝三色的阈值范围很窄用cv2.inRange()就能干净地分割出来不需要训练数据不需要标注改颜色只要调几个数值。第二Jetson Nano跑深度学习推理虽然可行但帧率有限而传统图像处理在Nano上跑640x480的图像可以轻松到30fps以上响应更快。第三也是最重要的一点——可解释性。当抓取失败时我能明确知道是颜色没分割出来、还是坐标换算错了、还是机械臂运动学参数不对。深度学习方案出问题时排查起来就麻烦得多。提示如果你的场景里光照变化剧烈或者色块表面有反光HSV阈值法会很不稳定。这种情况要么加补光灯固定光照要么换用Lab色彩空间要么上深度学习。入门阶段先把光照控制住能省掉80%的麻烦。1.3 整体流程拆解从摄像头拍到图像到机械臂完成抓取中间要经过这些步骤图像采集USB摄像头通过V4L2接口读取一帧图像颜色分割BGR转HSV用阈值提取目标颜色区域轮廓处理找轮廓、算面积、过滤噪点、求最小外接矩形坐标计算取轮廓中心像素坐标通过标定矩阵换算到机械臂坐标系逆运动学求解根据目标三维坐标算出六个舵机的角度轨迹规划与执行控制机械臂移动到目标上方下降抓取抬升移动到对应颜色盒子释放这里面第4步和第5步是最容易出问题的。像素坐标到机械臂坐标的映射不是简单的线性关系因为摄像头有畸变、安装有倾角、机械臂底座和摄像头不在同一位置。我后面会详细讲怎么用仿射变换做近似标定以及为什么在桌面平面上这个近似足够用。2. Ubuntu 18.04环境搭建与OpenCV安装的坑2.1 Jetson Nano的系统准备Jetson Nano官方推荐的就是Ubuntu 18.04 LTSNVIDIA提供了专门的镜像文件。烧录过程不复杂用Etcher把镜像写到SD卡里就行但有几个细节要注意。SD卡至少32GBClass 10以上。我一开始用了一张16GB的卡系统跑起来后剩余空间不到2GB装完OpenCV直接满了。另外烧录完成后第一次启动要接HDMI显示器、键盘鼠标因为需要走一遍初始设置语言、时区、用户名密码。设置完成后建议立刻插网线或者配好WiFi因为后面装东西全靠网络。系统起来后第一件事是更新源和升级sudo apt update sudo apt upgrade -y这个过程在Nano上会比较慢因为eMMC或者SD卡的读写速度有限。我实测大概要20到30分钟耐心等。注意Jetson Nano的默认电源模式是5W性能受限。如果你用的是5V 4A的DC供电可以切换到10W模式sudo nvpmodel -m 0然后把风扇打开sudo jetson_clocks。这能让后续图像处理和机械臂通信都更流畅。2.2 OpenCV在Jetson Nano上的安装策略这是整个环境搭建里最容易踩坑的地方。Jetson Nano是ARM架构pip install opencv-python大概率会失败或者装上一个没有CUDA加速的版本。正确的做法有两种方案一用apt装系统包sudo apt install python3-opencv这个最省事但版本比较老Ubuntu 18.04自带的是OpenCV 3.2而且不带CUDA。对于色块分拣来说3.2的API完全够用cv2.inRange、cv2.findContours这些函数都有。如果你不打算用GPU加速图像处理这个方案最稳。方案二源码编译带CUDA的OpenCV如果你后续想用GPU跑深度学习推理或者需要OpenCV的CUDA模块比如cv2.cuda那就得源码编译。这个过程在Nano上大概要2到4个小时而且中间容易因为内存不足而编译失败。编译前先扩大swapsudo fallocate -l 4G /swapfile sudo chmod 600 /swapfile sudo mkswap /swapfile sudo swapon /swapfile然后装依赖sudo apt install build-essential cmake git pkg-config libgtk-3-dev \ libavcodec-dev libavformat-dev libswscale-dev libv4l-dev \ libxvidcore-dev libx264-dev libjpeg-dev libpng-dev libtiff-dev \ gfortran openexr libatlas-base-dev python3-dev python3-numpy \ libtbb2 libtbb-dev libdc1394-22-dev下载OpenCV源码我用的版本是4.5.4这个版本在Nano上编译成功率比较高git clone https://github.com/opencv/opencv.git git clone https://github.com/opencv/opencv_contrib.git cd opencv git checkout 4.5.4 cd ../opencv_contrib git checkout 4.5.4cmake配置的时候关键参数cmake -D CMAKE_BUILD_TYPERELEASE \ -D CMAKE_INSTALL_PREFIX/usr/local \ -D WITH_CUDAON \ -D CUDA_ARCH_BIN5.3 \ -D WITH_CUDNNON \ -D OPENCV_DNN_CUDAON \ -D ENABLE_FAST_MATHON \ -D CUDA_FAST_MATHON \ -D WITH_V4LON \ -D WITH_QTOFF \ -D WITH_OPENGLON \ -D OPENCV_EXTRA_MODULES_PATH../../opencv_contrib/modules \ -D BUILD_EXAMPLESOFF ..CUDA_ARCH_BIN5.3是Jetson Nano的算力版本写错了CUDA加速会失效。WITH_QTOFF是因为Qt在Nano上编译容易出问题用GTK就行。编译用make -j4四个核全开。如果中途报内存错误改成make -j2甚至make -j1。编译完成后sudo make install然后sudo ldconfig更新动态链接库。验证安装python3 -c import cv2; print(cv2.__version__); print(cv2.cuda.getCudaEnabledDeviceCount())如果输出版本号且CUDA设备数大于0说明带CUDA的OpenCV装好了。2.3 摄像头和串口权限配置USB摄像头在Ubuntu下通常是/dev/video0但Jetson Nano上可能枚举成/dev/video1或更高。用ls /dev/video*确认。测试摄像头sudo apt install v4l-utils v4l2-ctl --list-devicesDofbot的舵机控制板通过串口通信通常是/dev/ttyUSB0或/dev/ttyACM0。普通用户默认没有串口读写权限每次都要sudo很麻烦。把用户加到dialout组sudo usermod -a -G dialout $USER然后注销重新登录生效。这个坑我踩过——明明代码没问题就是打不开串口查了半天才发现是权限。提示如果你用的是虚拟机里的Ubuntu做开发USB设备透传经常出问题。建议要么直接装在物理机上要么用WSL2但注意WSL2的USB支持需要额外配置。机械臂控制对实时性有要求虚拟机方案只适合前期调试代码逻辑最终跑还是要物理机。3. 色块识别从BGR到HSV的完整处理链3.1 为什么必须转HSVOpenCV读进来的图像默认是BGR格式。BGR三个通道的值跟光照强度强相关——同一个红色块在亮处R200在暗处R120你没法用一个固定的R值范围把它框出来。HSV就不一样了H色调表示颜色种类S饱和度表示颜色深浅V明度表示亮度。红色块的H值不管在亮处还是暗处都在0到10或者170到180之间这就稳定多了。转换代码就一行hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV)但要注意OpenCV的H范围是0到179不是0到360。S和V都是0到255。红色在H轴上跨越了0和180两端所以要分两段取# 红色 lower_red1 np.array([0, 100, 100]) upper_red1 np.array([10, 255, 255]) lower_red2 np.array([170, 100, 100]) upper_red2 np.array([180, 255, 255]) mask_red cv2.inRange(hsv, lower_red1, upper_red1) cv2.inRange(hsv, lower_red2, upper_red2) # 绿色 lower_green np.array([40, 80, 80]) upper_green np.array([80, 255, 255]) mask_green cv2.inRange(hsv, lower_green, upper_green) # 蓝色 lower_blue np.array([100, 100, 100]) upper_blue np.array([130, 255, 255]) mask_blue cv2.inRange(hsv, lower_blue, upper_blue)这些阈值不是固定的跟你的光照环境、色块材质都有关系。我建议先用一个调试脚本把摄像头的HSV值实时打印出来然后手动调。具体做法用cv2.setMouseCallback绑定鼠标事件点击图像上某个像素打印它的HSV值。这样你点一下红色块就知道当前光照下红色的H、S、V大概是多少然后据此设定阈值范围。3.2 形态学处理去噪inRange出来的mask往往有噪点——桌面上可能有反光点、阴影边缘、或者色块表面的高光区域被排除后留下的空洞。形态学操作就是用来清理这些的。kernel np.ones((5, 5), np.uint8) mask cv2.morphologyEx(mask, cv2.MORPH_OPEN, kernel) # 开运算先腐蚀后膨胀去小白点 mask cv2.morphologyEx(mask, cv2.MORPH_CLOSE, kernel) # 闭运算先膨胀后腐蚀填小黑洞开运算的kernel大小很关键。太小了噪点去不干净太大了色块本身会被腐蚀掉。5x5是个比较通用的值但如果你的色块在图像里很小比如小于50个像素就要用3x3。反过来如果色块很大可以用7x7。我实际调试的时候发现闭运算比开运算更重要。因为色块表面经常有反光导致mask中间出现空洞如果不填上后面算轮廓中心的时候会偏。但闭运算也不能太狠否则相邻的两个色块可能被连在一起。3.3 轮廓筛选与中心点计算contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: area cv2.contourArea(cnt) if area 500: # 过滤太小的噪点 continue M cv2.moments(cnt) if M[m00] 0: continue cx int(M[m10] / M[m00]) cy int(M[m01] / M[m00]) # cx, cy 就是色块中心的像素坐标cv2.RETR_EXTERNAL只取最外层轮廓避免色块内部如果有纹理产生嵌套轮廓。CHAIN_APPROX_SIMPLE压缩轮廓点省内存。面积阈值500是我实测下来比较合适的值。640x480的图像里一个3cm见方的色块在30cm距离上大概占2000到3000个像素。设500能过滤掉大部分噪点又不会误杀真正的色块。但如果你的色块更小或者摄像头更远这个值要相应调低。注意cv2.moments算出来的中心是轮廓的几何中心不是最小外接矩形的中心。对于正方形色块两者差不多但如果色块被部分遮挡或者形状不规则几何中心可能偏。更稳的做法是用cv2.minAreaRect取最小外接矩形然后取矩形中心。我在实际项目里两种都用过色块完整的情况下差别不大但遮挡场景下minAreaRect更可靠。3.4 多色块同时识别的处理逻辑桌面上同时有红绿蓝三个色块时程序需要知道每个色块的颜色和位置。我的做法是分别对三个颜色生成mask各自找轮廓然后把结果汇总到一个列表里targets [] for color_name, mask in [(red, mask_red), (green, mask_green), (blue, mask_blue)]: contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) for cnt in contours: if cv2.contourArea(cnt) 500: continue M cv2.moments(cnt) cx int(M[m10] / M[m00]) cy int(M[m01] / M[m00]) targets.append({color: color_name, px: cx, py: cy})然后按某种策略排序——比如按y坐标从近到远或者按颜色优先级。我一般按距离机械臂底座最近的先抓这样运动路径最短效率最高。这里有个细节如果同一个色块在红色mask和蓝色mask里都出现了比如紫色块会被识别两次。实际用纯红绿蓝三色块不会遇到这个问题但如果你的色块颜色接近就要加一个互斥判断——同一个像素区域只归为一种颜色。4. 像素坐标到机械臂坐标的标定方法4.1 为什么不能直接用像素坐标摄像头看到的是二维图像机械臂工作在三维空间。像素坐标(cx, cy)只告诉你在图像里的位置不告诉你这个点在机械臂坐标系下的(x, y, z)。要把两者对应起来需要知道摄像头的内参焦距、主点、畸变系数和外参摄像头相对于机械臂底座的位置和姿态。完整的标定流程是用棋盘格标定板做相机标定得到内参矩阵和畸变系数然后做手眼标定得到摄像头到机械臂基座的变换矩阵。这套流程很标准但比较繁琐而且需要精确的标定板。对于桌面色块分拣这个场景我有一个简化方案假设所有色块都在同一个平面上桌面那么像素坐标到机械臂坐标的映射就是一个平面到平面的仿射变换或者透视变换。这个假设在色块分拣里完全成立——色块就放在桌面上高度固定。4.2 仿射变换标定的实操步骤仿射变换需要至少三对对应点。我的做法是在桌面上选三个点用机械臂末端去触碰记录下这三个点在机械臂坐标系下的坐标通过舵机角度和正运动学算出来或者直接用示教模式读出来把摄像头固定好在图像里找到这三个点对应的像素坐标用cv2.getAffineTransform算出变换矩阵但实际操作中用机械臂末端去精确触碰桌面上的点很麻烦。我换了一个更简单的方法用色块本身做标定。具体做法把红色色块放在桌面上一个已知位置比如机械臂底座正前方20cm处记录像素坐标然后移动色块到另一个已知位置再记录像素坐标。重复三次得到三对(像素坐标, 机械臂坐标)。然后用cv2.getAffineTransformpts_pixel np.float32([[cx1, cy1], [cx2, cy2], [cx3, cy3]]) pts_robot np.float32([[x1, y1], [x2, y2], [x3, y3]]) M cv2.getAffineTransform(pts_pixel, pts_robot)之后对于任何像素坐标(cx, cy)机械臂坐标就是robot_pt cv2.transform(np.array([[[cx, cy]]], dtypenp.float32), M)这个方法的精度取决于三个标定点的选取。我建议三个点尽量分散不要挤在一起而且不要共线。三角形的面积越大标定精度越高。提示仿射变换只能处理平移、旋转、缩放和剪切不能处理透视畸变。如果你的摄像头是斜着装的光轴不垂直于桌面仿射变换会有误差。这时候要用透视变换cv2.getPerspectiveTransform需要四对对应点。我实测下来摄像头光轴与桌面夹角在70度以上时仿射变换的误差在5mm以内对于3cm的色块来说完全够用。4.3 高度补偿与抓取姿态色块有厚度机械臂抓取时末端要下降到色块表面而不是桌面。所以z坐标要加上色块的高度。标准色块高度是3cm那么抓取时z 桌面高度 3cm。但这里有个问题仿射变换只给出了x和yz是固定的桌面高度。如果色块高度不同z就要相应调整。我的做法是在程序里维护一个颜色到高度的映射表红色块3cm绿色块3cm蓝色块3cm——都一样所以z固定。如果你的色块高度不同就要在标定时把高度也考虑进去或者用两个摄像头做立体视觉。抓取姿态方面Dofbot的末端夹爪默认是垂直向下的。对于桌面上的色块垂直向下抓是最稳的。但如果色块靠近机械臂底座垂直向下可能会碰到机械臂本体。这时候需要调整末端姿态让夹爪稍微倾斜。Dofbot的逆运动学求解器通常支持指定末端姿态我一般设成垂直向下遇到干涉再手动调。4.4 标定误差的验证与修正标定完成后一定要验证。我的验证方法是把色块放在桌面上五个不同位置程序算出机械臂坐标后控制机械臂移动到该坐标上方然后看末端是否对准色块中心。如果误差在1cm以内基本可以接受因为夹爪的开口有2cm左右有容错空间。如果误差超过2cm就要检查标定点是否准确重新标定摄像头是否移动过重新标定仿射变换是否适用改用透视变换机械臂运动学参数是否准确检查Dofbot的DH参数我遇到过一种情况标定完了误差很小但运行一段时间后误差变大。后来发现是摄像头支架松了轻微移位导致标定失效。所以摄像头一定要固定牢最好用螺丝锁死不要用夹子。5. 机械臂运动控制与抓取流程编排5.1 Dofbot的通信协议与Python控制Dofbot的舵机控制板通过串口接收指令协议通常是自定义的帧格式。官方Python SDK封装了底层通信直接调用就行。核心API大概是这样from dofbot import Dofbot arm Dofbot(port/dev/ttyUSB0, baudrate1000000) arm.set_servo_angle(servo_id1, angle90, speed50)servo_id从1到6对应六个关节angle是目标角度speed是运动速度。速度参数很重要——设太快机械臂会抖设太慢效率低。我一般用50到70之间的值。逆运动学求解方面Dofbot的SDK通常提供了set_position(x, y, z)这样的接口内部自动算逆解。但要注意逆解可能有多组解SDK一般会选一组“最自然”的。如果遇到奇异位形比如目标点在机械臂工作空间边缘逆解可能无解或者解出来的角度超出舵机范围。这时候要检查目标坐标是否在工作空间内。5.2 抓取动作的状态机设计抓取流程不是简单的“移动-抓-放”中间有很多状态转换和异常处理。我用一个状态机来管理状态动作转换条件IDLE等待检测到目标APPROACH移动到目标上方5cm到达位置DESCEND下降到抓取高度到达位置GRASP闭合夹爪夹爪闭合完成LIFT抬升到安全高度到达位置MOVE_TO_BOX移动到对应颜色盒子到达位置RELEASE打开夹爪夹爪打开完成RETURN回到IDLE到达初始位置每个状态都有超时检测。如果某个状态超过预期时间还没完成就报错并回到IDLE。比如APPROACH状态如果5秒内没到达可能是逆解失败或者舵机堵转需要人工检查。注意夹爪闭合后不要立刻抬升要等一小段时间比如0.3秒让夹爪完全夹紧。我一开始没加这个延时结果机械臂抬升时色块掉了。另外夹爪的闭合力度也要调——太松夹不住太紧可能把色块压变形。Dofbot的夹爪力度是通过舵机扭矩控制的一般设成中等偏上就行。5.3 多目标抓取的顺序规划桌面上有多个色块时抓取顺序会影响效率。最简单的策略是按距离排序——离机械臂底座最近的先抓。但还要考虑颜色盒子的位置。如果红色盒子在左边蓝色盒子在右边而桌面上红色块在右边、蓝色块在左边那先抓红色块就要横跨整个桌面效率低。我的做法是算一个代价函数代价 到色块的距离 从色块到对应盒子的距离。然后按代价从小到大排序。这样能减少机械臂的总运动距离。但实际测试下来对于三四个色块的小场景顺序优化的收益不大因为机械臂运动速度是瓶颈。我一般就用简单的距离排序够用了。5.4 异常处理与安全机制机械臂运动过程中可能遇到各种异常逆解失败、舵机堵转、串口通信超时、摄像头掉线。每一种都要有对应的处理。逆解失败时程序应该跳过这个目标继续处理下一个而不是卡死。舵机堵转时要立刻停止所有运动因为继续通电可能烧舵机。串口超时时重试三次还不行就报错退出。摄像头掉线时暂停抓取流程等待摄像头恢复。还有一个重要的安全机制急停。我在程序里绑定了一个键盘按键比如空格键按下后立刻发送停止指令给所有舵机。调试阶段这个功能救了我好几次——机械臂有时候会往奇怪的方向运动没有急停就只能拔电源。import keyboard if keyboard.is_pressed(space): arm.emergency_stop() print(急停触发)6. 实测中遇到的典型问题与解决思路6.1 颜色识别受光照影响严重这是最常见的问题。白天阳光从窗户照进来桌面上的色块颜色会偏晚上开日光灯颜色又不一样。我试过几种解决方案方案一固定光源。在摄像头旁边装一个LED补光灯让桌面光照恒定。这个最有效成本也低。我用的是一个USB供电的环形灯装在摄像头周围亮度可调。方案二自动白平衡。OpenCV可以通过cv2.xphoto.createSimpleWB()做白平衡但Jetson Nano上这个模块不一定编译进去了。更简单的做法是手动调摄像头的白平衡参数v4l2-ctl -d /dev/video0 --set-ctrlwhite_balance_temperature_auto0 v4l2-ctl -d /dev/video0 --set-ctrlwhite_balance_temperature4500方案三动态阈值。每次抓取前先拍一张桌面背景图算出背景的HSV均值然后根据背景调整色块阈值。这个方法比较复杂但适应性最强。我最终用的是方案一加方案二。固定光源保证了光照稳定手动白平衡消除了摄像头自动调整带来的波动。这两招下来颜色识别基本没再出过问题。6.2 机械臂抓取位置偏差标定完了程序也算出了机械臂坐标但抓的时候就是偏。可能的原因有几个原因一标定点不准。用色块做标定时色块本身的中心位置可能跟记录的位置有偏差。解决方法是标定时用尖头物体比如笔尖代替色块更精确。原因二机械臂重复定位精度不够。Dofbot用的是总线舵机重复定位精度大概在1到2度。对于20cm的臂展1度误差对应末端3到4mm的偏差。这个没办法完全消除只能通过软件补偿——记录每次抓取的实际偏差下次抓同一个位置时预先补偿。原因三逆运动学求解误差。Dofbot的DH参数如果标定不准逆解出来的角度会有偏差。这个需要重新标定DH参数比较麻烦。我一般先用软件补偿顶着有时间再重新标。原因四色块高度没算对。如果色块实际高度是3.5cm程序里按3cm算下降抓取时夹爪就会撞到色块或者抓空。用卡尺量一下色块实际高度更新到程序里。6.3 串口通信不稳定Jetson Nano的USB口供电能力有限如果同时接了摄像头和机械臂控制板可能出现供电不足导致串口掉线。我的解决方法是给Jetson Nano用独立的DC供电5V 4A不要只靠USB供电。另外串口线要用带屏蔽的长度不要超过1米太长容易受干扰。软件层面每次发送指令后加一个确认机制——机械臂控制板收到指令后返回一个ACK程序收到ACK才发下一条。如果超时没收到ACK重发。这个机制能过滤掉大部分偶发的通信错误。def send_command_with_retry(cmd, retries3): for i in range(retries): arm.send(cmd) if arm.wait_ack(timeout0.5): return True return False6.4 摄像头帧率与处理速度的平衡Jetson Nano跑640x480的HSV转换加轮廓查找单帧处理时间大概在15到20ms也就是50到60fps。但USB摄像头在Nano上经常只能跑到30fps。如果处理速度跟不上采集速度图像会堆积延迟越来越大。我的做法是采集线程和处理线程分开采集线程只负责从摄像头读帧并放入队列处理线程从队列取帧处理。队列长度设为1如果处理线程还没处理完上一帧采集线程就丢弃旧帧只保留最新帧。这样保证处理的永远是最新图像不会累积延迟。import queue import threading frame_queue queue.Queue(maxsize1) def capture_thread(): cap cv2.VideoCapture(0) while True: ret, frame cap.read() if not ret: continue if frame_queue.full(): frame_queue.get() # 丢弃旧帧 frame_queue.put(frame) def process_thread(): while True: frame frame_queue.get() # 处理frame这个模式在Jetson Nano上跑下来很稳延迟控制在50ms以内对于色块分拣来说完全够用。6.5 机械臂运动时的振动问题Dofbot的机械臂比较轻运动速度快的时候末端会抖动。抖动会导致两个问题一是抓取时位置不准二是摄像头如果装在机械臂上图像会模糊。解决方法是降低运动速度特别是在接近目标位置的时候。我一般把运动分成两段粗定位用高速speed80精定位用低速speed30。这样既保证了效率又保证了精度。另外机械臂底座一定要固定牢。我用的是螺丝把底座锁在桌面上如果只是放在桌上机械臂运动时底座会跟着晃精度根本没法保证。7. 从色块分拣延伸到更复杂的视觉抓取7.1 换成YOLO识别任意物体色块分拣跑通之后下一步很自然就是想识别更复杂的物体。Jetson Nano跑YOLOv5s是可行的用TensorRT加速后大概能到10到15fps。流程是把YOLO的输出框中心作为抓取点替代原来的颜色轮廓中心。但这里有个问题YOLO只能给出物体的二维框不知道物体的抓取姿态。对于规则物体比如杯子、盒子可以假设抓取点在框中心末端垂直向下。对于不规则物体就需要更复杂的抓取检测网络比如GG-CNN或者GR-ConvNet。这些在Nano上跑会比较吃力建议升级到Jetson Orin Nano。7.2 加入深度相机做三维抓取USB摄像头只有二维信息z坐标是假设固定的。如果换成深度相机比如Intel RealSense D435i就能直接得到每个像素的深度值z坐标不用假设。这样就能处理高度不同的物体也能做避障。RealSense在Jetson Nano上需要装librealsense SDK然后通过pyrealsense2读取深度图和对齐的彩色图。深度图跟彩色图对齐后每个彩色像素都有对应的深度值直接查表就能得到三维坐标。但深度相机也有坑透明物体、反光表面、黑色物体会导致深度值缺失。色块分拣用深度相机有点杀鸡用牛刀但如果你要做更复杂的场景深度相机是值得投资的。7.3 用ROS做系统集成如果项目变大比如多个传感器、多个执行器、需要远程控制那就该上ROS了。Dofbot有ROS驱动包可以把机械臂控制、摄像头采集、视觉处理都做成ROS节点通过topic通信。ROS的好处是模块化——视觉节点只负责发布目标位置运动规划节点订阅目标位置并控制机械臂。两个节点可以独立调试互不影响。而且ROS有rviz可视化工具能实时看到机械臂姿态和摄像头图像调试起来方便很多。但ROS也有学习成本而且Jetson Nano上跑ROS会占用不少资源。如果你的项目就是简单的色块分拣没必要上ROS。等你要做多机械臂协作或者移动抓取的时候再考虑。7.4 标定精度的进一步提升仿射变换标定的精度对于色块分拣够用但如果你要做精密装配比如把销钉插入孔里就需要更高的精度。这时候要做完整的相机标定加手眼标定。相机标定用棋盘格OpenCV有现成的cv2.calibrateCamera函数。手眼标定分两种eye-in-hand摄像头装在机械臂末端和eye-to-hand摄像头固定在外部。色块分拣用的是eye-to-hand标定方法是让机械臂末端移动到多个位置同时记录末端在机械臂坐标系的坐标和摄像头坐标系下的坐标然后用cv2.calibrateHandEye求解变换矩阵。这套流程我跑过精度能到1mm以内。但标定过程比较耗时需要采集20到30组数据。如果你的应用对精度要求不高仿射变换就够了。7.5 抓取策略的优化方向现在的抓取策略是“看到什么抓什么”没有考虑抓取的成功率。实际场景中有些色块可能被遮挡、有些可能靠近桌面边缘不好抓。更智能的策略是给每个目标算一个抓取成功率优先抓成功率高的。抓取成功率的评估因素包括色块是否完整轮廓面积是否接近标准值、是否在机械臂工作空间中心区域、周围是否有障碍物。这些可以用简单的规则实现也可以用机器学习的方法训练一个分类器。另外抓取失败后的重试策略也很重要。如果第一次抓取失败夹爪闭合后色块没被夹起来程序应该能检测到并重试。检测方法可以是夹爪闭合后再看一眼摄像头如果色块还在原地说明没抓起来。然后调整位置重试或者换个角度抓。8. 写在最后的一些实操体会这套Dofbot加Jetson Nano加OpenCV的色块分拣系统我从零搭起来大概花了三周时间其中环境搭建占了一周视觉处理占了一周机械臂控制和联调占了一周。中间踩的坑主要集中在OpenCV编译、串口权限、标定精度这三个方面。如果让我给后来者建议我会说先把环境搭稳再调视觉最后联调机械臂。不要一上来就想着把整个流程跑通那样出了问题都不知道是哪一环的错。每一步都单独验证——摄像头能出图、颜色能分割、坐标能算对、机械臂能走到指定位置——然后再串起来。还有一个很实用的技巧在程序里加一个调试模式把每一步的中间结果都保存成图片或者打印出来。比如HSV转换后的图、mask图、轮廓标注图、标定后的坐标值。出问题的时候看一眼中间结果就知道是哪一步不对。这个习惯帮我省了大量排查时间。最后机械臂的调试一定要慢。速度设低一点急停键放在手边工作空间里不要放易碎物品。安全永远是第一位的。
