“YOLO26-obb+语音控制” 机械臂抓取方案 (二、数据标注+模型训练)
好的,我们在上午提到了数据的采集,那么我们现在开始做数据标注的工作,标注完成后我们就可以开始进行模型训练了。
数据标注
我们采集完的数据均放在images/train文件夹下,等待标注完成,我们再划分数据集(以8:2的比例),由于我们需要使用yolo26-obb,我们选择CVAT标注工具。
CVAT标注
CVAT标注工具在线网页:https://app.cvat.ai/tasks
有很多同学可能没有接触过这个工具,当然我也是第一次使用,之前都是用Labelimg或者Labelme等工具,我尝试了一下觉得也挺方便的,大家一起跟着来试一下吧。
标注指南
打开网页我们可以看到右上角+号创建新任务
创建任务界面
在这里我们将自己训练集图片都导入进来,task的Name可以随便取不影响

任务创建完成后,我们可以看到Add label选项,我们可以根据图片内你想识别的物体类别进行添加,添加完成后,我们标注的准备工作就算完成了

开始进入标注,点击Jobs一栏的 Job #你的任务编号,进入标注界面
这里可以使用快捷键N来进行画框,就和labelimg使用w画矩形框一样

标注完成后,左上角点击Menu,选择Upload annotations

导出后可以根据跳出的只是click here进行跳转,也可以回到主页点击request进行查看

随后我们就可以进行Download,下载导出的xml文件,需要进行解压,打开后的xml文件内容大概为下图所示
我们可以看到你的label名臣,包括一些矩形框信息以及旋转角度的信息

那么好,我们的标注工作就算彻底结束了,但是我们不能直接使用这个xml文件,因为我们需要转换成yolo格式,让每一个label映射到每一张图像上进行对应,所以这里我们写一个xml转yolo的py文件
xml2yolo.py
在这里你只需要修改文件的路径以及类别映射中你的label名称
import os
import math
import xml.etree.ElementTree as ET
# =========================
# 路径配置(自己改)
# =========================
XML_PATH = "../annotations_val.xml"
IMG_DIR = "../dataset/images/val"
LABEL_DIR = "../dataset/labels/val"
os.makedirs(LABEL_DIR, exist_ok=True)
# =========================
# 类别映射
# =========================
class_map = {
"luckin_yellow": 0,
"luckin_blue": 1,
}
# =========================
# 计算旋转框四点
# =========================
def get_obb_points(cx, cy, w, h, angle, img_w, img_h):
angle_rad = math.radians(angle)
cos_a = math.cos(angle_rad)
sin_a = math.sin(angle_rad)
dx = w / 2
dy = h / 2
# 四个角(顺时针)
points = [
(-dx, -dy),
( dx, -dy),
( dx, dy),
(-dx, dy),
]
result = []
for x, y in points:
rx = x * cos_a - y * sin_a
ry = x * sin_a + y * cos_a
px = (cx + rx) / img_w
py = (cy + ry) / img_h
result.append((px, py))
return result
# =========================
# 解析XML
# =========================
tree = ET.parse(XML_PATH)
root = tree.getroot()
for image in root.findall("image"):
file_name = image.get("name")
width = float(image.get("width"))
height = float(image.get("height"))
label_path = os.path.join(
LABEL_DIR,
os.path.splitext(file_name)[0] + ".txt"
)
with open(label_path, "w") as f:
for box in image.findall("box"):
label = box.get("label")
# 跳过未知类别
if label not in class_map:
print(f"⚠️ 未知类别: {label}")
continue
cls_id = class_map[label]
xtl = float(box.get("xtl"))
ytl = float(box.get("ytl"))
xbr = float(box.get("xbr"))
ybr = float(box.get("ybr"))
# 中心点 + 宽高
cx = (xtl + xbr) / 2
cy = (ytl + ybr) / 2
w = (xbr - xtl)
h = (ybr - ytl)
# rotation(可能没有)
rotation_attr = box.get("rotation")
angle = float(rotation_attr) if rotation_attr else 0.0
# =========================
# 生成四点
# =========================
points = get_obb_points(cx, cy, w, h, angle, width, height)
# =========================
# 写入(保留6位小数)
# =========================
line = f"{cls_id} " + " ".join(
[f"{x:.6f} {y:.6f}" for x, y in points]
) + "\n"
f.write(line)
print(f"✅ 已生成: {label_path}")
print("🎉 全部转换完成!")
当控制台输出下面结果时,文件已经转换成功,可以对应点进去查看结果,这里就不给大家演示了

大家转换完txt文件后,不要忘记8:2的比例划分数据集,将图片和txt文件20%的数据放入val文件夹下保存
这里可以多做一次对于yolo格式的检查
data_check.py
import os
for file in os.listdir("../dataset/labels/train"):
with open(os.path.join("../dataset/labels/train", file)) as f:
for line in f:
if len(line.strip().split()) != 6:
print("错误:", file)
else:
print("yolo格式正常")
模型训练
我们已经完成了数据的标注,接下来我们将会使用yolo26-obb来进行训练
模型选择
我们从Ultralytics文档官网中下载obb模型,我这里下载的是yolo26n-obb.pt,现在模型都很轻量,下载都很快,就不提供网盘下载了。
官网链接:https://docs.ultralytics.com/zh/tasks/obb/#models

环境配置
在模型下载的同时我们也可以开始准备配置环境了,环境的配置可以参考https://blog.csdn.net/m0_56498637/article/details/159646240?spm=1001.2014.3001.5501
开始训练
Train.py
from ultralytics import YOLO
def main():
model = YOLO("yolo26n-obb.pt")
model.train(
data=r"C:\Users\16095\PycharmProjects\Yolov11-obb+Vosk\data\yolo26-obb.yaml",
epochs=100,
imgsz=640,
batch=8,
device=0
)
if __name__ == '__main__':
main()
训练过程
当出现这一步的时候就代表已经满足训练要求,已经开始训练了

训练时,我们主要查看的值就是各类损失是否降低,查看mAP50和mAP50-95的值,我们不讲究的话,一般情况下这两个值基本上就对应了我们模型的识别准确率

训练后的结果我们可以在runs文件夹下查看

测试模型
图片测试
test_model_image.py
from ultralytics import YOLO
import cv2
import numpy as np
import math
MODEL_PATH = "../runs/obb/train/weights/best.pt"
names = {
0: "luckin_yellow",
1: "luckin_blue"
}
model = YOLO(MODEL_PATH)
img = cv2.imread("../test_images/1775027354.png")
results = model(img)
# =========================
# 核心函数:带方向的抓取角度
# =========================
def get_grasp_angle_with_direction(pts):
"""
pts: (4,2) numpy array
return: angle in degrees (-90 ~ 90)
"""
edges = []
for i in range(4):
p1 = pts[i]
p2 = pts[(i + 1) % 4]
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
length = (dx ** 2 + dy ** 2) ** 0.5
edges.append((length, dx, dy))
# 找最长边
_, dx, dy = max(edges, key=lambda x: x[0])
# 统一方向(让dx为正)
if dx < 0:
dx = -dx
dy = -dy
angle = math.degrees(math.atan2(dy, dx))
return angle
for r in results:
if r.obb is None:
continue
boxes = r.obb.xyxyxyxy.cpu().numpy()
cls_ids = r.obb.cls.cpu().numpy()
scores = r.obb.conf.cpu().numpy()
for box, cls_id, score in zip(boxes, cls_ids, scores):
cls_id = int(cls_id)
pts = box.reshape((4, 2))
pts_int = pts.astype(int)
# =========================
# 计算角度
# =========================
p1, p2 = pts[0], pts[1]
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
angle = get_grasp_angle_with_direction(pts)
# =========================
# 画框
# =========================
cv2.polylines(img, [pts_int], True, (0, 255, 0), 1)
# =========================
# 文本(类别+置信度+角度)
# =========================
label = f"{names[cls_id]} {score:.2f} {angle:.1f}deg"
x, y = pts_int[0]
cv2.putText(img, label, (x, y),
cv2.FONT_HERSHEY_SIMPLEX,
0.5,
(0, 255, 0),
1,
cv2.LINE_AA)
cv2.imshow("result", img)
cv2.waitKey(0)
cv2.destroyAllWindows()
视频流检测
test_model_video.py
from ultralytics import YOLO
import pyrealsense2 as rs
import numpy as np
import cv2
import math
# =========================
# 模型路径
# =========================
MODEL_PATH = "../runs/obb/train/weights/best.pt"
names = {
0: "luckin_yellow",
1: "luckin_blue"
}
model = YOLO(MODEL_PATH)
# =========================
# RealSense 初始化
# =========================
pipeline = rs.pipeline()
config = rs.config()
config.enable_stream(rs.stream.color, 640, 480, rs.format.bgr8, 30)
pipeline.start(config)
print("✅ RealSense 已启动")
# =========================
# 核心函数:带方向的抓取角度
# =========================
def get_grasp_angle_with_direction(pts):
"""
pts: (4,2) numpy array
return: angle in degrees (-90 ~ 90)
"""
edges = []
for i in range(4):
p1 = pts[i]
p2 = pts[(i + 1) % 4]
dx = p2[0] - p1[0]
dy = p2[1] - p1[1]
length = (dx ** 2 + dy ** 2) ** 0.5
edges.append((length, dx, dy))
# 找最长边
_, dx, dy = max(edges, key=lambda x: x[0])
# 🔥 关键:统一方向(让dx为正)
if dx < 0:
dx = -dx
dy = -dy
angle = math.degrees(math.atan2(dy, dx))
return angle
# =========================
# 主循环
# =========================
try:
while True:
frames = pipeline.wait_for_frames()
color_frame = frames.get_color_frame()
if not color_frame:
continue
frame = np.asanyarray(color_frame.get_data())
# YOLO推理
results = model(frame, verbose=False)
for r in results:
if r.obb is None:
continue
boxes = r.obb.xyxyxyxy.cpu().numpy()
cls_ids = r.obb.cls.cpu().numpy()
scores = r.obb.conf.cpu().numpy()
for box, cls_id, score in zip(boxes, cls_ids, scores):
cls_id = int(cls_id)
pts = box.reshape((4, 2))
pts_int = pts.astype(int)
# =========================
# 计算抓取角度
# =========================
angle = get_grasp_angle_with_direction(pts)
if 0 <= angle <= 90:
angle = angle - 90
elif -90 < angle < 0:
angle = angle + 90
# =========================
# 画OBB框
# =========================
cv2.polylines(frame, [pts_int], True, (0, 255, 0), 2)
# =========================
# 文本信息
# =========================
label = f"{names[cls_id]} {score:.2f} {angle:+.1f}deg"
x, y = pts_int[0]
cv2.putText(frame, label, (x, y),
cv2.FONT_HERSHEY_SIMPLEX,
0.5, # 字体小一点
(0, 255, 0),
1, # 更细
cv2.LINE_AA)
cv2.imshow("RealSense OBB Detection", frame)
if cv2.waitKey(1) & 0xFF == 27:
break
finally:
pipeline.stop()
cv2.destroyAllWindows()
模型验证结果

总结
至此,我们的yolo26-obb模型就已经完成了训练,并且验证成功,下一步我们会通过服务端来可视化结果,方便后期的接口调用,整体和先前的SAM3+点云的步骤差不多,大家动手试一下吧!
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)