YOLOv10 在机器人中的应用:实时目标检测与部署教程 2026
YOLOv10 在机器人视觉中的应用实战:从模型训练、优化到边缘设备部署(Jetson Nano/树莓派),涵盖目标检测、跟踪、避障、抓取,附完整代码与性能优化技巧
YOLOv10 在机器人中的应用:实时目标检测与部署教程 2026
YOLO(You Only Look Once)系列是实时目标检测的标杆算法。2026 年,YOLOv10 凭借更高的精度、更快的速度和更小的模型体积,成为机器人视觉系统的首选方案。
本文将系统讲解 YOLOv10 在机器人领域的应用,从模型训练、优化到边缘设备部署,涵盖目标检测、跟踪、避障、抓取等典型场景,并提供完整的代码示例和性能优化技巧。
你将学到:
- ✅ YOLOv10 架构改进与性能优势
- ✅ 机器人数据集准备与模型训练
- ✅ 模型优化:量化、剪枝、TensorRT 加速
- ✅ 边缘设备部署(Jetson Nano、树莓派、ESP32)
- ✅ 实际应用:目标跟踪、避障、抓取定位
- ✅ 性能优化:从 30 FPS 到 100+ FPS
前置知识:
1. YOLOv10 架构与优势
1.1 YOLO 系列演进
| 版本 | 发布年份 | mAP@50 | FPS (V100) | 参数量 | 核心改进 |
|---|---|---|---|---|---|
| YOLOv3 | 2018 | 55.3 | 65 | 61.5M | 多尺度检测 |
| YOLOv4 | 2020 | 65.7 | 65 | 64.0M | CSPDarknet |
| YOLOv5 | 2020 | 56.7 | 140 | 7.0M | 工程优化 |
| YOLOv7 | 2022 | 56.8 | 161 | 36.9M | E-ELAN |
| YOLOv8 | 2023 | 56.9 | 198 | 11.2M | Anchor-free |
| YOLOv10 | 2024 | 58.5 | 230 | 8.5M | NMS-free |
1.2 YOLOv10 核心改进
1. NMS-Free 设计
- 移除后处理 NMS(Non-Maximum Suppression)
- 使用一致性双重分配策略
- 推理延迟降低 30%
2. 高效架构设计
- 轻量级分类头(减少计算量)
- 空间-通道分离注意力
- 大核卷积(增强感受野)
3. 多任务优化
- 同时支持检测、分割、姿态估计
- 统一的特征金字塔
- 更好的小目标检测
1.3 机器人场景优势
在 具身 AI 技术栈 中,视觉感知是最核心的模块之一。YOLOv10 的特性使其成为机器人场景的理想选择:
| 特性 | 对机器人的价值 |
|---|---|
| 实时性 | 快速避障、动态跟踪 |
| 高精度 | 准确抓取、精细操作 |
| 小模型 | 边缘设备部署 |
| 多任务 | 一个模型完成多个感知任务 |
| 易训练 | 快速适配新场景 |
2. 数据集准备
2.1 机器人常用数据集
公开数据集:
- COCO:通用目标检测(80 类)
- Object365:大规模物体检测(365 类)
- Open Images:多样化场景(600+ 类)
机器人专用数据集:
- YCB Video:物体抓取(21 类日常物体)
- KITTI:自动驾驶(车辆、行人)
- RoboCup:足球机器人(球、球员、球门)
2.2 自定义数据集
数据收集流程:
#!/usr/bin/env python3
"""机器人数据采集脚本"""
import cv2
import os
import json
from datetime import datetime
class RobotDataCollector:
def __init__(self, save_dir="dataset"):
self.save_dir = save_dir
os.makedirs(f"{save_dir}/images", exist_ok=True)
os.makedirs(f"{save_dir}/labels", exist_ok=True)
self.cap = cv2.VideoCapture(0)
self.annotations = []
self.frame_count = 0
def capture_frame(self):
"""采集一帧"""
ret, frame = self.cap.read()
if not ret:
return None
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S_%f")
filename = f"{timestamp}.jpg"
# 保存图像
cv2.imwrite(f"{self.save_dir}/images/{filename}", frame)
# 显示标注界面
annotated = frame.copy()
cv2.putText(annotated, f"Frame: {self.frame_count}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
cv2.imshow("Data Collection", annotated)
self.frame_count += 1
return filename, frame
def add_annotation(self, filename, bbox, class_id, class_name):
"""添加标注"""
# YOLO 格式:class x_center y_center width height (归一化)
h, w = 480, 640 # 图像尺寸
x_center = (bbox[0] + bbox[2]) / 2 / w
y_center = (bbox[1] + bbox[3]) / 2 / h
width = (bbox[2] - bbox[0]) / w
height = (bbox[3] - bbox[1]) / h
# 保存标签
label_filename = filename.replace(".jpg", ".txt")
with open(f"{self.save_dir}/labels/{label_filename}", "w") as f:
f.write(f"{class_id} {x_center:.6f} {y_center:.6f} {width:.6f} {height:.6f}\n")
self.annotations.append({
"image": filename,
"bbox": bbox,
"class_id": class_id,
"class_name": class_name
})
def run(self):
"""主循环"""
print("按 'c' 采集,'q' 退出")
while True:
filename, frame = self.capture_frame()
key = cv2.waitKey(100) # 10 FPS
if key == ord('q'):
break
elif key == ord('c'):
# 这里可以集成自动标注或手动标注
print(f"采集: {filename}")
self.cap.release()
cv2.destroyAllWindows()
# 保存标注元数据
with open(f"{self.save_dir}/annotations.json", "w") as f:
json.dump(self.annotations, f, indent=2)
print(f"共采集 {len(self.annotations)} 个标注")
if __name__ == "__main__":
collector = RobotDataCollector()
collector.run()
数据增强策略:
# 机器人场景数据增强
import albumentations as A
from albumentations.pytorch import ToTensorV2
transform = A.Compose([
# 几何变换
A.HorizontalFlip(p=0.5),
A.ShiftScaleRotate(shift_limit=0.1, scale_limit=0.2, rotate_limit=15, p=0.5),
# 颜色变换(模拟不同光照)
A.RandomBrightnessContrast(p=0.3),
A.HueSaturationValue(p=0.3),
# 噪声(模拟传感器噪声)
A.GaussNoise(p=0.2),
A.MotionBlur(p=0.2),
# 裁剪
A.RandomResizedCrop(height=640, width=640, scale=(0.8, 1.0), p=0.5),
# 归一化
A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
ToTensorV2()
], bbox_params=A.BboxParams(format='yolo', label_fields=['class_labels']))
2.3 数据集划分
from sklearn.model_selection import train_test_split
import os
def split_dataset(dataset_dir, train_ratio=0.8, val_ratio=0.1, test_ratio=0.1):
"""划分训练/验证/测试集"""
images = os.listdir(f"{dataset_dir}/images")
# 分层采样(确保每个类别分布均匀)
train, temp = train_test_split(images, train_size=train_ratio, random_state=42)
val, test = train_test_split(temp, train_size=val_ratio/(val_ratio+test_ratio), random_state=42)
# 创建目录
for split in ['train', 'val', 'test']:
os.makedirs(f"{dataset_dir}/{split}/images", exist_ok=True)
os.makedirs(f"{dataset_dir}/{split}/labels", exist_ok=True)
# 移动文件
import shutil
for split, files in [('train', train), ('val', val), ('test', test)]:
for img in files:
src_img = f"{dataset_dir}/images/{img}"
src_label = f"{dataset_dir}/labels/{img.replace('.jpg', '.txt')}"
dst_img = f"{dataset_dir}/{split}/images/{img}"
dst_label = f"{dataset_dir}/{split}/labels/{img.replace('.jpg', '.txt')}"
shutil.move(src_img, dst_img)
shutil.move(src_label, dst_label)
print(f"Train: {len(train)}, Val: {len(val)}, Test: {len(test)}")
split_dataset("dataset")
3. 模型训练
3.1 安装 YOLOv10
# 克隆官方仓库
git clone https://github.com/THU-MIG/yolov10.git
cd yolov10
# 安装依赖
pip install -r requirements.txt
pip install -e .
# 验证安装
yolo predict model=yolov10n.pt source='ultralytics/assets/bus.jpg'
3.2 训练配置
# robot_detect.yaml
task: detect
mode: train
model: yolov10n.pt # 预训练模型
data: robot_dataset.yaml
# 训练参数
epochs: 100
batch: 16
imgsz: 640
device: 0 # GPU
# 优化器
optimizer: AdamW
lr0: 0.001
lrf: 0.01
momentum: 0.9
weight_decay: 0.0005
# 数据增强
hsv_h: 0.015
hsv_s: 0.7
hsv_v: 0.4
degrees: 10.0
translate: 0.1
scale: 0.5
flipud: 0.0
fliplr: 0.5
mosaic: 1.0
mixup: 0.1
# 保存
project: runs/detect
name: robot_v1
save: true
save_period: 10 # 每10轮保存
# robot_dataset.yaml
path: ./dataset # 数据集路径
train: train/images
val: val/images
test: test/images
# 类别定义(根据实际需求修改)
names:
0: red_cube
1: blue_cube
2: green_cube
3: screwdriver
4: wrench
5: person
6: robot_arm
7: obstacle
3.3 开始训练
# 命令行训练
yolo train data=robot_dataset.yaml model=yolov10n.pt epochs=100 batch=16
# Python API 训练
from ultralytics import YOLO
model = YOLO("yolov10n.pt")
results = model.train(
data="robot_dataset.yaml",
epochs=100,
batch=16,
imgsz=640,
device=0
)
3.4 训练监控
# 实时监控训练
import matplotlib.pyplot as plt
from ultralytics.utils.plotting import plot_results
# 绘制训练曲线
plot_results("runs/detect/robot_v1/results.csv")
# 查看验证结果
model = YOLO("runs/detect/robot_v1/weights/best.pt")
metrics = model.val(data="robot_dataset.yaml")
print(f"mAP@50: {metrics.box.map50:.3f}")
print(f"mAP@50-95: {metrics.box.map:.3f}")
print(f"Precision: {metrics.box.mp:.3f}")
print(f"Recall: {metrics.box.mr:.3f}")
4. 模型优化
4.1 模型量化
INT8 量化(TensorRT):
#!/usr/bin/env python3
"""TensorRT INT8 量化"""
import torch
from ultralytics import YOLO
from torch.utils.data import DataLoader
# 加载模型
model = YOLO("runs/detect/robot_v1/weights/best.pt")
# 准备校准数据(300 张图像)
calibration_data = []
for i in range(300):
img = load_image(f"dataset/calibration/{i}.jpg")
calibration_data.append(img)
# 导出为 TensorRT
model.export(
format="engine",
half=False, # FP32
int8=True, # INT8 量化
data="robot_dataset.yaml",
workspace=4, # GB
nms=True
)
print("模型已导出为 TensorRT INT8")
性能对比:
| 精度 | 模型大小 | 推理时间 (Jetson Nano) | mAP |
|---|---|---|---|
| FP32 | 32 MB | 45 ms | 58.5 |
| FP16 | 16 MB | 28 ms | 58.3 |
| INT8 | 8 MB | 15 ms | 57.8 |
4.2 模型剪枝
# 结构化剪枝
from ultralytics import YOLO
import torch.nn.utils.prune as prune
model = YOLO("runs/detect/robot_v1/weights/best.pt")
# 剪枝 30% 的卷积层
for name, module in model.model.named_modules():
if isinstance(module, torch.nn.Conv2d):
prune.l1_unstructured(module, name='weight', amount=0.3)
prune.remove(module, 'weight')
# 微调
model.train(data="robot_dataset.yaml", epochs=20, batch=16)
# 导出剪枝后的模型
model.export(format="onnx")
4.3 ONNX 导出
# 导出为 ONNX(跨平台)
model = YOLO("runs/detect/robot_v1/weights/best.pt")
model.export(
format="onnx",
dynamic=True, # 动态输入尺寸
simplify=True,
opset=12
)
# 验证 ONNX 模型
import onnx
model_onnx = onnx.load("best.onnx")
onnx.checker.check_model(model_onnx)
print("ONNX 模型验证通过")
5. 边缘设备部署
5.1 Jetson Nano 部署
环境准备:
# 安装 JetPack(包含 CUDA、cuDNN、TensorRT)
# 从 NVIDIA 官网下载 JetPack 4.6+
# 安装依赖
sudo apt update
sudo apt install python3-pip cmake libopenblas-dev
# 安装 PyTorch(Jetson 版本)
pip3 install torch torchvision torchaudio --extra-index-url https://download.pytorch.org/whl/cu102
# 安装 TensorRT Python API
pip3 install tensorrt
# 安装 ONNX Runtime(GPU 版本)
pip3 install onnxruntime-gpu
推理代码:
#!/usr/bin/env python3
"""Jetson Nano 推理"""
import cv2
import numpy as np
import tensorrt as trt
import pycuda.driver as cuda
import pycuda.autoinit
from ultralytics import YOLO
class TensorRTInference:
def __init__(self, engine_path):
# 加载 TensorRT 引擎
with open(engine_path, "rb") as f:
runtime = trt.Runtime(trt.Logger(trt.Logger.WARNING))
self.engine = runtime.deserialize_cuda_engine(f.read())
self.context = self.engine.create_execution_context()
# 分配内存
self.inputs = []
self.outputs = []
self.bindings = []
for binding in self.engine:
size = trt.volume(self.engine.get_binding_shape(binding))
dtype = trt.nptype(self.engine.get_binding_dtype(binding))
host_mem = cuda.pagelocked_empty(size, dtype)
device_mem = cuda.mem_alloc(host_mem.nbytes)
self.bindings.append(int(device_mem))
if self.engine.binding_is_input(binding):
self.inputs.append({"host": host_mem, "device": device_mem})
else:
self.outputs.append({"host": host_mem, "device": device_mem})
def preprocess(self, image):
"""预处理"""
# 调整尺寸
input_shape = self.engine.get_binding_shape(0)
image = cv2.resize(image, (input_shape[3], input_shape[2]))
# 归一化
image = image.astype(np.float32) / 255.0
image = np.transpose(image, (2, 0, 1)) # HWC -> CHW
image = np.expand_dims(image, axis=0)
return image
def infer(self, image):
"""推理"""
# 预处理
input_data = self.preprocess(image)
np.copyto(self.inputs[0]["host"], input_data.ravel())
# 传输到 GPU
for inp in self.inputs:
cuda.memcpy_htod(inp["device"], inp["host"])
# 执行推理
self.context.execute(2, self.bindings)
# 传输回 CPU
for out in self.outputs:
cuda.memcpy_dtoh(out["host"], out["device"])
return [out["host"] for out in self.outputs]
def postprocess(self, outputs, conf_threshold=0.5):
"""后处理"""
# 解析输出(根据模型结构)
boxes = outputs[0].reshape(-1, 6) # x1, y1, x2, y2, conf, class
# 过滤低置信度
mask = boxes[:, 4] > conf_threshold
boxes = boxes[mask]
return boxes
# 使用示例
engine = TensorRTInference("best.engine")
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
# 推理
outputs = engine.infer(frame)
boxes = engine.postprocess(outputs)
# 绘制结果
for box in boxes:
x1, y1, x2, y2, conf, cls = box
cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
cv2.putText(frame, f"{conf:.2f}", (int(x1), int(y1)-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
cv2.imshow("Detection", frame)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
性能优化:
# 使用 CUDA Stream 并行处理
import pycuda.driver as cuda
class OptimizedInference(TensorRTInference):
def __init__(self, engine_path):
super().__init__(engine_path)
self.stream = cuda.Stream()
def infer_async(self, image_queue):
"""异步推理(流水线)"""
while True:
image = image_queue.get()
if image is None:
break
# 异步传输
input_data = self.preprocess(image)
cuda.memcpy_htod_async(self.inputs[0]["device"], input_data.ravel(), self.stream)
# 异步推理
self.context.execute_async_v2(self.bindings, self.stream.handle)
# 异步传输回
for out in self.outputs:
cuda.memcpy_dtoh_async(out["host"], out["device"], self.stream)
self.stream.synchronize()
yield [out["host"] for out in self.outputs]
5.2 树莓派部署
环境准备:
# 安装 OpenCV
sudo apt install python3-opencv
# 安装 ONNX Runtime
pip3 install onnxruntime
# 或使用 OpenVINO(Intel NCS2 加速棒)
pip3 install openvino-dev
ONNX Runtime 推理:
#!/usr/bin/env python3
"""树莓派 ONNX Runtime 推理"""
import cv2
import numpy as np
import onnxruntime as ort
class ONNXInference:
def __init__(self, model_path):
# 加载模型
self.session = ort.InferenceSession(model_path)
self.input_name = self.session.get_inputs()[0].name
self.input_shape = self.session.get_inputs()[0].shape
print(f"输入形状: {self.input_shape}")
def preprocess(self, image):
"""预处理"""
# 调整尺寸
target_size = (self.input_shape[3], self.input_shape[2])
image = cv2.resize(image, target_size)
# 归一化
image = image.astype(np.float32) / 255.0
image = np.transpose(image, (2, 0, 1))
image = np.expand_dims(image, axis=0)
return image
def infer(self, image):
"""推理"""
input_data = self.preprocess(image)
outputs = self.session.run(None, {self.input_name: input_data})
return outputs
def postprocess(self, outputs, conf_threshold=0.5):
"""后处理(简化版)"""
# YOLOv10 输出格式
predictions = outputs[0]
# 解析边界框和置信度
boxes = []
for pred in predictions[0]:
conf = pred[4]
if conf > conf_threshold:
x1, y1, x2, y2 = pred[:4]
cls = np.argmax(pred[5:])
boxes.append([x1, y1, x2, y2, conf, cls])
return np.array(boxes)
# 使用示例
model = ONNXInference("best.onnx")
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
outputs = model.infer(frame)
boxes = model.postprocess(outputs)
for box in boxes:
x1, y1, x2, y2, conf, cls = box
cv2.rectangle(frame, (int(x1), int(y1)), (int(x2), int(y2)), (0, 255, 0), 2)
cv2.imshow("Detection", frame)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
性能优化(OpenVINO):
# 转换为 OpenVINO IR 格式
mo --input_model best.onnx --output_dir openvino_model --data_type FP16
# 使用 OpenVINO 推理
pip3 install openvino
from openvino.runtime import Core
ie = Core()
model = ie.compile_model("openvino_model/best.xml", "CPU")
# 推理
input_layer = model.input(0)
output_layer = model.output(0)
result = model.infer_new_request({input_layer: input_data})
5.3 ESP32 部署(轻量级)
模型转换(TFLite):
# 转换为 TFLite
pip install tensorflow
# Python 转换
import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_onnx_model("best.onnx")
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_model = converter.convert()
with open("model.tflite", "wb") as f:
f.write(tflite_model)
ESP32 推理(简化版):
// ESP32 + TensorFlow Lite Micro
#include <TensorFlowLite_ESP32.h>
#include "model_data.h"
tflite::MicroErrorReporter tflite_error_reporter;
tflite::MicroInterpreter* interpreter = nullptr;
void setup() {
// 加载模型
static tflite::MicroMutableOpResolver<10> resolver;
resolver.AddConv2D();
resolver.AddDepthwiseConv2D();
resolver.AddReshape();
const tflite::Model* model = tflite::GetModel(g_model);
static uint8_t tensor_buffer[20 * 1024];
static tflite::SimpleTensorAllocator allocator(tensor_buffer, sizeof(tensor_buffer));
interpreter = new tflite::MicroInterpreter(
model, resolver, &allocator, &tflite_error_reporter);
interpreter->AllocateTensors();
}
void loop() {
// 获取摄像头图像
camera_fb_t* fb = esp_camera_fb_get();
// 预处理
TfLiteTensor* input = interpreter->input(0);
preprocess_image(fb->buf, input->data.int8);
// 推理
interpreter->Invoke();
// 后处理
TfLiteTensor* output = interpreter->output(0);
process_detections(output->data.int8);
esp_camera_fb_return(fb);
delay(100); // 10 FPS
}
6. 实际应用
6.1 目标跟踪(DeepSORT)
#!/usr/bin/env python3
"""YOLOv10 + DeepSORT 目标跟踪"""
import cv2
import numpy as np
from ultralytics import YOLO
from deep_sort_realtime.deepsort_tracker import DeepSort
class RobotTracker:
def __init__(self):
# 检测模型
self.detector = YOLO("best.pt")
# 跟踪器
self.tracker = DeepSort(
max_age=30, # 最大跟踪帧数
n_init=3, # 初始化所需帧数
nms_max_overlap=0.5, # NMS 阈值
embedder="mobilenet" # 特征提取器
)
# 轨迹历史
self.track_history = {}
def detect_and_track(self, frame):
"""检测 + 跟踪"""
# 检测
results = self.detector(frame, conf=0.5, verbose=False)
detections = []
for result in results:
boxes = result.boxes.xyxy.cpu().numpy()
confs = result.boxes.conf.cpu().numpy()
classes = result.boxes.cls.cpu().numpy()
for box, conf, cls in zip(boxes, confs, classes):
detections.append(([box[0], box[1], box[2]-box[0], box[3]-box[1]], conf, int(cls)))
# 跟踪
tracks = self.tracker.update_tracks(detections, frame=frame)
# 更新轨迹历史
for track in tracks:
if not track.is_confirmed():
continue
track_id = track.track_id
ltrb = track.to_ltrb()
if track_id not in self.track_history:
self.track_history[track_id] = []
self.track_history[track_id].append(
((ltrb[0]+ltrb[2])/2, (ltrb[1]+ltrb[3])/2) # 中心点
)
# 保留最近 50 帧
if len(self.track_history[track_id]) > 50:
self.track_history[track_id].pop(0)
return tracks
def draw_tracks(self, frame, tracks):
"""绘制跟踪结果"""
for track in tracks:
if not track.is_confirmed():
continue
track_id = track.track_id
ltrb = track.to_ltrb()
# 绘制边界框
cv2.rectangle(frame, (int(ltrb[0]), int(ltrb[1])),
(int(ltrb[2]), int(ltrb[3])), (0, 255, 0), 2)
# 绘制 ID
cv2.putText(frame, f"ID: {track_id}", (int(ltrb[0]), int(ltrb[1])-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), 2)
# 绘制轨迹
if track_id in self.track_history:
points = np.array(self.track_history[track_id], dtype=np.int32)
cv2.polylines(frame, [points], False, (255, 0, 0), 2)
return frame
# 使用示例
tracker = RobotTracker()
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
tracks = tracker.detect_and_track(frame)
frame = tracker.draw_tracks(frame, tracks)
cv2.imshow("Tracking", frame)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
6.2 机器人避障
#!/usr/bin/env python3
"""基于 YOLOv10 的机器人避障"""
import cv2
import numpy as np
from ultralytics import YOLO
import time
class ObstacleAvoidance:
def __init__(self):
self.detector = YOLO("best.pt")
# 相机参数(需要标定)
self.focal_length = 600 # 像素
self.real_width = 0.1 # 目标实际宽度(米)
# 安全距离
self.safe_distance = 0.5 # 米
# 控制参数
self.kp_linear = 0.5
self.kp_angular = 1.0
def estimate_distance(self, box_width):
"""基于边界框宽度估计距离"""
# 针孔相机模型
distance = (self.real_width * self.focal_length) / box_width
return distance
def detect_obstacles(self, frame):
"""检测障碍物"""
results = self.detector(frame, conf=0.5, verbose=False)
obstacles = []
for result in results:
boxes = result.boxes.xyxy.cpu().numpy()
confs = result.boxes.conf.cpu().numpy()
for box, conf in zip(boxes, confs):
x1, y1, x2, y2 = box
width = x2 - x1
center_x = (x1 + x2) / 2
# 估计距离
distance = self.estimate_distance(width)
obstacles.append({
'bbox': (x1, y1, x2, y2),
'center_x': center_x,
'distance': distance,
'confidence': conf
})
return obstacles
def compute_velocity(self, obstacles, frame_width):
"""计算避障速度"""
if not obstacles:
# 无障碍物,前进
return 0.3, 0.0
# 找到最近的障碍物
closest = min(obstacles, key=lambda x: x['distance'])
# 判断方向
frame_center = frame_width / 2
obstacle_center = closest['center_x']
# 计算角速度(远离障碍物)
if obstacle_center < frame_center:
angular = self.kp_angular * (frame_center - obstacle_center) / frame_center
else:
angular = -self.kp_angular * (obstacle_center - frame_center) / frame_center
# 计算线速度(根据距离)
if closest['distance'] < self.safe_distance:
linear = 0.0 # 停止
else:
linear = self.kp_linear * min(1.0, closest['distance'] / 2.0)
return linear, angular
def run(self):
"""主循环"""
cap = cv2.VideoCapture(0)
while True:
ret, frame = cap.read()
if not ret:
break
# 检测障碍物
obstacles = self.detect_obstacles(frame)
# 计算速度
linear, angular = self.compute_velocity(obstacles, frame.shape[1])
# 发送控制指令(这里需要连接到机器人)
# robot.set_velocity(linear, angular)
# 可视化
for obs in obstacles:
x1, y1, x2, y2 = map(int, obs['bbox'])
color = (0, 0, 255) if obs['distance'] < self.safe_distance else (0, 255, 0)
cv2.rectangle(frame, (x1, y1), (x2, y2), color, 2)
cv2.putText(frame, f"{obs['distance']:.2f}m", (x1, y1-10),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2)
# 显示速度
cv2.putText(frame, f"Linear: {linear:.2f}", (10, 30),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
cv2.putText(frame, f"Angular: {angular:.2f}", (10, 60),
cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 255, 255), 2)
cv2.imshow("Obstacle Avoidance", frame)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
avoidance = ObstacleAvoidance()
avoidance.run()
6.3 抓取定位
抓取定位是机器人操作的核心技能。结合 宇树 Go2 ROS2 开发指南 中的相机标定方法,可以实现精准的 3D 抓取定位。
#!/usr/bin/env python3
"""YOLOv10 抓取定位"""
import cv2
import numpy as np
from ultralytics import YOLO
class GraspPlanner:
def __init__(self):
self.detector = YOLO("best.pt")
# 相机标定参数
self.camera_matrix = np.array([
[600, 0, 320],
[0, 600, 240],
[0, 0, 1]
])
self.dist_coeffs = np.zeros((4, 1))
# 相机到机械臂基座的变换(需要标定)
self.camera_to_base = np.array([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 1, 0.3], # 相机高度 0.3m
[0, 0, 0, 1]
])
def detect_objects(self, frame):
"""检测可抓取物体"""
results = self.detector(frame, conf=0.6, verbose=False)
objects = []
for result in results:
boxes = result.boxes.xyxy.cpu().numpy()
confs = result.boxes.conf.cpu().numpy()
classes = result.boxes.cls.cpu().numpy()
for box, conf, cls in zip(boxes, confs, classes):
x1, y1, x2, y2 = box
center_x = (x1 + x2) / 2
center_y = (y1 + y2) / 2
objects.append({
'bbox': (x1, y1, x2, y2),
'center_2d': (center_x, center_y),
'confidence': conf,
'class_id': int(cls)
})
return objects
def estimate_pose(self, obj, depth_image):
"""估计物体 3D 位姿"""
center_x, center_y = obj['center_2d']
# 从深度图获取深度值
depth = depth_image[int(center_y), int(center_x)] / 1000.0 # mm -> m
# 像素坐标转 3D 坐标
x = (center_x - self.camera_matrix[0, 2]) * depth / self.camera_matrix[0, 0]
y = (center_y - self.camera_matrix[1, 2]) * depth / self.camera_matrix[1, 1]
z = depth
# 转换到机械臂基座坐标系
pos_camera = np.array([x, y, z, 1])
pos_base = self.camera_to_base @ pos_camera
return pos_base[:3]
def plan_grasp(self, obj, pose_3d):
"""规划抓取位姿"""
# 简化版:直接抓取物体中心
grasp_pose = {
'position': pose_3d,
'orientation': [0, 0, 0, 1], # 四元数
'approach_vector': [0, 0, -1] # 从上方接近
}
return grasp_pose
def run(self):
"""主循环"""
cap = cv2.VideoCapture(0)
# 假设深度相机
# depth_cap = cv2.VideoCapture(1)
while True:
ret, frame = cap.read()
if not ret:
break
# 模拟深度图(实际应从深度相机获取)
depth_image = np.random.randint(500, 1500, (480, 640), dtype=np.uint16)
# 检测物体
objects = self.detect_objects(frame)
# 选择置信度最高的物体
if objects:
target = max(objects, key=lambda x: x['confidence'])
# 估计位姿
pose_3d = self.estimate_pose(target, depth_image)
# 规划抓取
grasp_pose = self.plan_grasp(target, pose_3d)
print(f"目标: {target['class_id']}")
print(f"3D 位置: {pose_3d}")
print(f"抓取位姿: {grasp_pose}")
# 可视化
x1, y1, x2, y2 = map(int, target['bbox'])
cv2.rectangle(frame, (x1, y1), (x2, y2), (0, 255, 0), 2)
cv2.circle(frame, (int(target['center_2d'][0]), int(target['center_2d'][1])),
5, (0, 0, 255), -1)
cv2.imshow("Grasp Planning", frame)
if cv2.waitKey(1) == ord('q'):
break
cap.release()
cv2.destroyAllWindows()
if __name__ == "__main__":
planner = GraspPlanner()
planner.run()
7. 性能优化技巧
7.1 推理加速
1. 批处理推理
# 批量推理(提高吞吐量)
images = [img1, img2, img3, img4]
results = model(images, batch=4)
2. 多线程预处理
from concurrent.futures import ThreadPoolExecutor
class ParallelInference:
def __init__(self, model):
self.model = model
self.executor = ThreadPoolExecutor(max_workers=4)
def preprocess_batch(self, images):
"""多线程预处理"""
futures = [self.executor.submit(self.preprocess, img) for img in images]
return [f.result() for f in futures]
3. CUDA Stream 并行
import torch.cuda
stream1 = torch.cuda.Stream()
stream2 = torch.cuda.Stream()
with torch.cuda.stream(stream1):
output1 = model(input1)
with torch.cuda.stream(stream2):
output2 = model(input2)
torch.cuda.synchronize()
7.2 内存优化
1. 模型卸载
# 将不常用的层卸载到 CPU
model.model.to('cpu')
model.model.layer1.to('cuda') # 只保留关键层在 GPU
2. 内存池
# PyTorch 内存池
torch.cuda.memory.set_per_process_memory_fraction(0.8) # 限制 GPU 内存使用
7.3 精度-速度权衡
| 优化策略 | 速度提升 | 精度损失 | 适用场景 |
|---|---|---|---|
| FP16 推理 | 2x | <0.5% | 通用 |
| INT8 量化 | 4x | 1-2% | 边缘设备 |
| 模型剪枝 | 1.5x | 1-3% | 内存受限 |
| 降低输入分辨率 | 2-4x | 2-5% | 实时性要求高 |
| 减少后处理 | 1.2x | 0% | 所有场景 |
总结
YOLOv10 为机器人视觉系统提供了强大的目标检测能力。通过本文,你已掌握:
- ✅ YOLOv10 架构与优势
- ✅ 机器人数据集准备与训练
- ✅ 模型优化(量化、剪枝、TensorRT)
- ✅ 边缘设备部署(Jetson、树莓派、ESP32)
- ✅ 实际应用(跟踪、避障、抓取)
- ✅ 性能优化技巧
下一步建议:
- 针对你的具体场景微调模型
- 探索多模态融合(视觉 + 深度 + IMU)
- 研究端到端学习(视觉直接到控制)
- 关注 YOLO 系列最新进展
祝你的机器人视觉项目成功!🤖👁️
FAQ
YOLOv10 和 YOLOv8 有什么区别?
YOLOv10 最大的改进是移除了 NMS(非极大值抑制)后处理,推理延迟降低约 30%。同时采用了更高效的架构设计,在相同精度下模型体积更小,更适合边缘设备部署。
YOLOv10 能在树莓派上实时运行吗?
可以。使用 ONNX Runtime 或 OpenVINO 优化后,YOLOv10n 在树莓派 5 上可达到 5-10 FPS。如需更高帧率,建议使用 Jetson Nano 配合 TensorRT,可达 30+ FPS。
如何提升 YOLOv10 在机器人上的检测速度?
主要优化手段包括:1) INT8 量化(4x 加速);2) TensorRT 引擎优化;3) 降低输入分辨率(如 640→320);4) 批处理推理;5) 多线程预处理流水线。
YOLOv10 适合哪些机器人应用场景?
YOLOv10 适用于:目标检测与分类、物体抓取定位、自主避障、人员跟踪、工业质检等。特别适合需要实时响应的移动机器人和协作机器人场景。