
语义分割(Semantic Segmentation)为图像中的每个像素分配一个类标签从而生成覆盖整个场景的密集类映射。与区分单个对象的 实例分割不同语义分割将同一类的所有像素组合在一起而不管存在多少个不同的对象。语义分割是一项计算机视觉任务它通过为每个单独的像素分配特定的类别标签将图像划分为不同的区域。与图像分类为整幅图像分配一个标签或目标检测在对象周围绘制边界框这样更简单的任务不同语义分割提供了对场景的像素级理解。这种粒度分析对于物体精确形状和边界与其身份同样重要的应用至关重要。它使机器能够像人类一样“观察”世界能够区分出构成道路、行人或医学扫描中肿瘤的确切像素。1.语义分割的工作原理其核心在于语义分割将图像视为一个需要分类的像素网格。深度学习模型特别是卷积神经网络 (CNNs)是完成此任务的标准架构。一种典型的架构如广泛使用的U-Net采用了编码器-解码器结构。编码器压缩输入图像以提取高层特征如纹理和形状而解码器则将这些特征上采样回原始图像分辨率以生成精确的分割掩码。为了实现这一点模型会在大型标注数据集上进行训练在这些数据集中人工标注员已根据类别仔细地为每个像素着色。像Ultralytics Platform这样的工具通过提供自动标注功能促进了这一过程加快了高质量真值数据的创建。一旦训练完成模型会输出一个掩码其中每个像素值对应一个类别ID从而有效地用含义“绘制”图像。1、将标注的JSON 转换为 Mask 图像YOLO26 的语义分割任务要求标签必须是单通道 PNG 掩码图像而不是多边形坐标。你需要编写一个 Python 脚本读取 JSON 中的points并绘制成像素级掩码。Json2Mask.py转换代码如下import os import json import numpy as np from PIL import Image, ImageDraw # 配置路径 json_dir D:/YOLO26/ultralytics/datasets-sem/Json2Mask/Json mask_output_dir D:/YOLO26/ultralytics/datasets-sem/Json2Mask/Mask os.makedirs(mask_output_dir, exist_okTrue) # 定义类别映射必须与后续 YAML 一致 # 背景默认为 0目标从 1 开始 class_mapping { 1: 1, # other_class: 2 } def convert_json_to_mask(json_path, output_path): with open(json_path, r) as f: data json.load(f) # 获取图像尺寸 img_w data[imageWidth] img_h data[imageHeight] # 创建纯黑背景单通道 L 模式像素值 0 代表背景 mask Image.new(L, (img_w, img_h), 0) draw ImageDraw.Draw(mask) for shape in data[shapes]: label shape[label] points shape[points] if label in class_mapping: class_id class_mapping[label] # 将浮点坐标转为整数元组 polygon [(int(p[0]), int(p[1])) for p in points] # 填充多边形像素值设为类别 ID draw.polygon(polygon, fillclass_id) # 关键保存为 PNG严禁使用 JPG mask.save(output_path) # 批量处理逻辑 for file in os.listdir(json_dir): if file.endswith(.json): convert_json_to_mask( os.path.join(json_dir, file), os.path.join(mask_output_dir, file.replace(.json, .png)) )转后的图像用看图软件查看效果如下整个完全是黑色的原因重新生成掩码在绘制多边形时填充颜色fill设置为1而不是 255 或其他值。要想能看出掩码效果将对应的像素值乘以255即可相乘后的效果如下图所示2.数据集划分针对 YOLO 语义分割任务数据集划分的核心原则是“图像与掩码的严格对齐”。即无论怎么划分images/train里的图片必须能在masks/train里找到同名的掩码文件。以下是使用 Python 实现自动划分、移动并校验的完整代码不仅会分好文件夹还会自动检测“有图无码”或“有码无图”的错误数据防止训练报错。data_split_sem.py代码如下import os import shutil import random from collections import defaultdict # 配置区域 # 1. 原始数据路径 (所有jpg和png都在这两个文件夹里) ORIG_IMG_DIR r./datasets-sem/Json2Mask/Json # 存放所有 jpg 原图 ORIG_MASK_DIR r./datasets-sem/Json2Mask/mask # 存放所有 png 掩码 # 2. 输出数据集路径 (划分后的结果) OUTPUT_ROOT r./datasets-sem # 3. 划分比例 (训练集:验证集:测试集) # 如果不需要测试集可以设为 0.9, 0.1, 0.0 RATIO_TRAIN 0.9 #0.8 RATIO_VAL 0.1 #0.2 RATIO_TEST 0.0 # def split_and_move(): # 1. 获取所有文件名不带后缀 img_files {os.path.splitext(f)[0] for f in os.listdir(ORIG_IMG_DIR) if f.lower().endswith((.jpg, .jpeg, .png))} mask_files {os.path.splitext(f)[0] for f in os.listdir(ORIG_MASK_DIR) if f.lower().endswith(.png)} print(f 发现图片: {len(img_files)} 张) print(f 发现掩码: {len(mask_files)} 张) # 2. 数据一致性校验 (非常重要) missing_masks img_files - mask_files missing_imgs mask_files - img_files if missing_masks: print(f⚠️ 警告: 有 {len(missing_masks)} 张图片缺少对应的掩码 (例如: {list(missing_masks)[:3]})...) print( - 这些图片将被自动剔除不参与划分。) if missing_imgs: print(f⚠️ 警告: 有 {len(missing_imgs)} 个掩码缺少对应的图片...) print( - 这些掩码将被自动剔除。) # 取交集只保留配对成功的数据 valid_names list(img_files mask_files) print(f✅ 有效配对数据: {len(valid_names)} 组\n) # 3. 打乱并计算切分点 random.shuffle(valid_names) total len(valid_names) n_train int(total * RATIO_TRAIN) n_val int(total * RATIO_VAL) # 剩下的归为 test splits { train: valid_names[:n_train], val: valid_names[n_train : n_train n_val], test: valid_names[n_train n_val:] } # 4. 创建目录结构并移动文件 for split_name, file_list in splits.items(): if not file_list: continue img_dest os.path.join(OUTPUT_ROOT, images, split_name) mask_dest os.path.join(OUTPUT_ROOT, masks, split_name) os.makedirs(img_dest, exist_okTrue) os.makedirs(mask_dest, exist_okTrue) print(f 正在处理 {split_name} 集 ({len(file_list)} 组)...) for name in file_list: # 寻找原始文件的真实扩展名 (兼容 .JPG / .jpg) src_img_ext next((f for f in os.listdir(ORIG_IMG_DIR) if os.path.splitext(f)[0] name), None) src_mask_ext next((f for f in os.listdir(ORIG_MASK_DIR) if os.path.splitext(f)[0] name), None) if src_img_ext and src_mask_ext: shutil.copy2(os.path.join(ORIG_IMG_DIR, src_img_ext), os.path.join(img_dest, src_img_ext)) shutil.copy2(os.path.join(ORIG_MASK_DIR, src_mask_ext), os.path.join(mask_dest, src_mask_ext)) print(\n 划分完成目录结构如下) print(f {OUTPUT_ROOT}/) print(f ├── images/) print(f │ ├── train/ ({len(splits[train])})) print(f │ └── val/ ({len(splits[val])})) print(f └── masks/) print(f ├── train/ ({len(splits[train])})) print(f └── val/ ({len(splits[val])})) if __name__ __main__: split_and_move()3.data.yaml 配置文件在数据集根目录下创建一个data.yaml文件内容如下path: D:/YOLO26/ultralytics/datasets-sem # 数据集绝对路径 # 图像与掩码路径相对于 path train: images/train val: images/val # 类别数量 nc: 1 # 类别名称顺序必须与转换脚本中的 class_id 对应 names: # 0: background # 可选通常背景不需要写 # 1: 1 0: crack4.模型训练训练代码如下from ultralytics import YOLO # 1. 模型加载等准备工作可以放在外面 model YOLO(yolo26m-sem.pt) if __name__ __main__: # 2. 只有在这个代码块里的代码才会被子进程执行 # 所有的训练、验证、导出等操作都必须放在这里 model.train( datadatasets-sem/data.yaml, epochs100, imgsz640, batch16, device0, projectruns/sem, namesaw_blade_exp )5.训练过程集及模型测试训练过程如下图所示模型测试指令为yolo semantic predict modelbest.pt sourcetestfiles/1.jpg测试效果如下图所示绿色区域为识别效果6.PT模型转换为ONNX模型及OpenCV部署推理pt转Onnx模型命令为yolo export modelbest.pt formatonnx imgsz640 simplify注意模型推理时一定注意类别个数背景1如int total_classes 2; // 你的数据集总类别数包含1个背景1个类别