ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

YOLOv11实例分割实战:牛肉分级与脂肪占比计算

YOLOv11实例分割实战:牛肉分级与脂肪占比计算 简介基于YOLOv11的牛肉分级分割设计项目聚焦图像识别与人工智能交叉方向面向毕业设计、课程设计以及机器学习入门者。方案利用YOLOv11将目标检测转化为回归问题对牛肉的肉色、纹理、脂肪分布等特征进行实时定位与品质分级有效替代传统人工判断。资源包共23个文件其中包含18张不同等级牛肉的示例图片、3个Python脚本覆盖预测、验证与UI交互界面以及README.md和README.docx两份说明文档压缩包总体积仅2.39MB轻量但流程完整。目前已有31人学习下载内容从数据集构建、目标框标注到模型训练中的网络结构、损失函数、优化器配置均有系统阐述并附可运行的界面脚本用于直观展示分级结果对于需要理解YOLO系模型在农业食品领域落地原理的开发者这是一份兼顾代码与文档的实用工程参考。1. 基于YOLOv11的牛肉分级分割这份毕业设计资源到底能做什么把牛肉分级和图像分割放在同一个标题里很多人的第一反应是“这不就是个目标检测换了个皮”实际完全不是。YOLOv11在实例分割方向上的输出不是框而是每个牛肉切面的像素级掩膜mask也就是说你能拿到每一块脂肪、每一块瘦肉的精确轮廓和面积占比再按这些数据去套分级规则。这套毕设资源的做法是用YOLOv11-seg模型训练牛肉切面数据集推理时同时输出类别、置信度和分割掩膜最后通过掩膜统计完成牛肉的大理石纹等级判定。相比传统检测模型只能告诉你“这里有一块牛肉”分割模型能告诉你“这块牛肉里脂肪占了多少比例、分布在哪”后者才真正支撑得起分级逻辑。这份资源适合两类人一是正在做图像识别、机器学习方向课程设计或毕业设计的学生需要一个能完整跑通数据标注、训练、验证、推理全流程的项目二是想快速上手YOLOv11实例分割任务的开发者拿现成的牛肉数据集和脚本作为切入点省掉从零标注和调参的漫长过程。文章后面会按数据准备、环境配置、训练参数、踩坑记录、分级逻辑落地依次展开每一步都给到可以直接抄走的命令和代码。2. 用实例分割而不是检测做牛肉分级先搞懂任务定义和数据格式2.1 为什么检测模型在这里不够用牛肉分级的核心指标是“大理石纹”——也就是肌肉内脂肪的分布密度和面积。传统目标检测模型输出的是矩形框但脂肪在牛肉切面上是不规则分布的有的细碎如雪花有的成片连成条矩形框一框就把背景和瘦肉一起框了进来完全没法统计脂肪的真实面积。语义分割模型能对每个像素做类别预测但这类任务里前后两块牛肉、不同等级的样本会同时出现在一张图上实例分割比语义分割多一个“区分不同个体”的能力这对后期按单块牛肉分别出等级非常关键。YOLOv11-seg 输出的是一组多边形坐标点而不是整张图的分割图。训练时每一块牛肉标注一个 mask推理时模型会输出每个实例的掩膜坐标集合再配合置信度过滤和 NMS最终得到若干个独立的牛肉实例。每个实例都可以单独计算面积、周长、脂肪占比正好匹配分级需求的流程。除非你只做单块牛肉的简单分类否则不建议退回到纯检测或纯分类的路线上。2.2 数据集组织方式与标注格式YOLO 分割任务的数据集目录结构和检测任务基本一致唯一的区别在 labels 目录下的 txt 文件内容。检测格式一行是class_id cx cy w h分割格式一行是class_id x1 y1 x2 y2 ... xn yn后面的坐标点按顺序构成一个多边形坐标值全部做归一化除以图片宽高取值在 0 到 1 之间。标注工具常用 Labelme导出的是 JSON 文件需要写一个转换脚本把 JSON 转成 YOLO 分割格式。import json import os def labelme_to_yolo_seg(json_path, save_path, class_map): with open(json_path, r, encodingutf-8) as f: data json.load(f) img_w data[imageWidth] img_h data[imageHeight] lines [] for shape in data[shapes]: label shape[label] if label not in class_map: continue points shape[points] # 将所有坐标点归一化到 [0,1] norm_points [] for x, y in points: nx x / img_w ny y / img_h norm_points.append(f{nx:.6f} {ny:.6f}) line f{class_map[label]} .join(norm_points) lines.append(line) with open(save_path, w, encodingutf-8) as f: f.write(\n.join(lines)) class_map {fat: 0, lean: 1} json_dir labelme_json out_dir yolo_labels for file in os.listdir(json_dir): if file.endswith(.json): labelme_to_yolo_seg( os.path.join(json_dir, file), os.path.join(out_dir, file.replace(.json, .txt)), class_map )这个脚本每次只会读取一个 JSON 文件按图片实际宽高对每个多边形顶点做归一化。class_map 里我想强调一点牛肉分级一般不把脂肪和瘦肉标成两个独立类更常见的是只标“脂肪”一个类瘦肉作为背景留白因为分级判定的核心指标就是脂肪面积占比背景和瘦肉怎么划分会直接影响后续面积统计口径。如果你要同时输出脂肪和瘦肉两个类务必在统计时重新定义清楚“脂肪占整块牛肉面积的比例”到底是脂肪掩膜除以瘦肉掩膜还是除以两者之和这个口径不一致是后期最容易翻车的地方。2.3 数据增强的分割专属操作检测模型做数据增强很随意随机翻转、旋转、缩放都行但分割模型有个铁律标注的掩膜坐标必须和图片做完全相同的几何变换否则掩膜和图像内容就对不上了。用 Albumentations 库的时候必须把图片和掩膜放进同一个 pipeline 里做同步变换。import albumentations as A import cv2 aug A.Compose([ A.RandomRotate90(p0.5), A.HorizontalFlip(p0.5), A.RandomBrightnessContrast(p0.3), ], bbox_paramsNone, keypoint_paramsNone) # 读入原图和掩膜掩膜和原图同尺寸 image cv2.imread(beef.jpg) mask cv2.imread(beef_mask.png, cv2.IMREAD_GRAYSCALE) augmented aug(imageimage, masks[mask]) aug_image augmented[image] aug_mask augmented[masks][0]这里的关键点是 Compose 里的 masks 参数它要求你传入一个掩膜列表Albumentations 会自动把所有几何变换同时作用到每个掩膜上。我一般会把多个掩膜一起传进去比如一张图上如果标注了脂肪和瘦肉两个类就传两个掩膜。颜色类增强比如 HSV 变换不作用于掩膜Albumentations 也知道这一点所以你在 pipeline 里混用几何变换和颜色变换是安全的。唯一要注意的是别在 pipeline 外面单独对图片做了一次增强、又对掩膜做另一次增强那样数据就废了。3. 环境配置与训练参数从零跑通 YOLOv11-seg 的关键设置3.1 Ultralytics 环境的版本选择YOLOv11 的官方实现来自 Ultralytics 仓库安装方式很简单但版本坑也埋在这里。Ultralytics 对 Python 和 PyTorch 的版本要求有明确的范围Python 3.8 到 3.12 都支持PyTorch 建议 2.0 以上。但凡你之前装过旧版 Ultralytics或者机器上已经存在一个 TensorFlow 环境很容易出现torch.cuda.is_available()返回 False、模型训练时一直走 CPU 的问题。conda create -n yolov11 python3.10 -y conda activate yolov11 pip install ultralytics装完后立刻验证环境是否正常这个验证步骤能省掉后面连带出现的所有 CUDA 报错python -c import torch; print(torch.__version__); print(torch.cuda.is_available())如果输出False说明你装到了 CPU 版 PyTorch。最常见的原因是 conda 默认源把 PyTorch 解析成了 CPU 版或者机器上原本有老版本 PyTorch 被 Ultralytics 依赖占用。我一般会直接从 PyTorch 官方源重装命令是pip install torch torchvision --index-url https://download.pytorch.org/whl/cu121CUDA 版本和你本机驱动匹配即可。这一步做完再验证一次看到True再继续往下走。3.2 数据集配置与 yaml 文件写法Ultralytics 训练分割模型需要一份 yaml 配置文件里面指定数据集路径和类别信息。注意 path 字段建议写绝对路径相对路径在切换工作目录后经常导致数据集找不到的报错。path: /home/user/beef_seg_dataset train: images/train val: images/val names: 0: fat 1: lean这份 yaml 里的 train 和 val 都是相对于 path 的路径。我的习惯是在项目根目录建一个 data.yaml训练命令里用datadata.yaml引用防止在别的终端里跑时找不到文件。图片和标签目录要严格对应图片放在images/train/xxx.jpg标签放在labels/train/xxx.txt文件名前缀必须一致。如果出现大量警告说找不到标签文件基本就是目录组织错了。3.3 训练命令与关键参数对照表Ultralytics 训练分割模型的主命令格式是yolo segment train模型结构用yolo11n-seg.pt或yolo11s-seg.ptn 是 nano 尺寸s 是 small。毕设场景下显卡显存不超过 8G 的话建议用 nano显存够用再上 small。下面是一份可以直接复制的训练命令yolo segment train \ databeef.yaml \ modelyolo11n-seg.pt \ epochs120 \ imgsz640 \ batch8 \ device0 \ projectbeef_runs \ nameseg_exp1参数说明我按优先级列一个表方便你先理解再改参数建议值说明epochs80150牛肉分割数据量通常不大100 轮左右足够收敛过大容易过拟合imgsz640训练分辨率如果牛肉纹理细碎可以提到 800但显存占用随之上升batch416受显存限制8G 显存跑 nano 建议 8不够就降到 4device0指定 GPU 序号只有 CPU 的机器填 cpu但训练速度会很慢patience20早停轮数验证集损失连续 20 轮不下降就自动停止防止浪费时间训练开始后终端会滚动显示每一轮的 box_loss、seg_loss、cls_loss、dfl_loss这些都是模型收敛的观测指标。分割任务里 seg_loss 是最核心的如果它一直不降或者反复震荡优先检查标注坐标有没有超出 0 到 1 的范围。训练结束后在project/name目录下的weights/best.pt是验证集上表现最好的权重后续推理就用它。3.4 训练日志里哪些数字值得盯Ultralytics 训练结束后会打印 P精确率、R召回率、mAP50 和 mAP50-95 四类指标。对于牛肉分割mAP50 达到 0.85 以上算基线可用mAP50-95 因为 IoU 阈值更严格一般比 mAP50 低 15 到 20 个点属正常现象。真正决定分级结果质量的不是这张图的平均 mAP而是每个实例掩膜的形状质量。我习惯在训练完后把 val 目录里的每张图存一份可视化推理结果肉眼看脂肪边缘是否贴合实际轮廓比如明显的锯齿状边界或小块漏检比纯看数字更能定位问题。4. 避坑指南YOLOv11 牛肉分割数据集从标注到训练的四条踩坑记录4.1 训练时 seg_loss 不降反升检查标签坐标是否越界现象是前几轮 loss 还能正常下降到十几轮后 seg_loss 开始突然飙升甚至直接 nan。第一次遇到时我以为是学习率的问题调了半天一点用没有。后来把某一张图片对应的 txt 打开看发现里面出现了1.2345这样的坐标值大于 1.0。原因是标注时在 Labelme 多边形上重复点了同一个点转换脚本没有做去重和越界裁剪导致归一化后坐标越界。解决的方法是在转换脚本里对每个坐标点加一个 clamp 操作强制压到 0 到 1 之间同时对连续重复的点做去重否则模型训练时算 loss 会拿到异常数值中后期发散。这个检查脚本我每次转换完都会跑一遍对所有标签做边界校验。import os label_dir yolo_labels for file in os.listdir(label_dir): path os.path.join(label_dir, file) with open(path, r) as f: for line in f: parts line.strip().split() for coord in parts[1:]: val float(coord) if val 0 or val 1: print(file, 越界坐标:, val)4.2 验证集 mAP50 不低但推理时脂肪边缘松散我在第一版模型上遇到过一种典型情况mAP50 在 0.9 左右看起来不错但部署到推理脚本里输出的掩膜边缘有明显的“毛刺”一层像素往外扩散。原因是数据增强里开了A.RandomBrightnessContrast同时保留了 HSV 色彩抖动牛肉脂肪在颜色上和瘦肉边缘本来就存在相似的像素段颜色抖动让模型学到了错误的边界判断。解决方法是把增强策略改成只保留空间几何变换旋转、翻转去掉颜色抖动相关的增强项或者严格降低颜色强化的概率。对分割任务空间一致性比颜色多样性重要得多这一点和检测任务差别特别大。4.3 训练时显存溢出应该改 batch 而不是换小模型显存溢出时报错形如CUDA out of memory后面会跟一个建议词 “Try decreasing batch size”。很多新手的第一反应是换更小的模型比如从 s 换到 n但这样做会损失精度尤其在脂肪细碎边缘的分割上表现很明显。正确顺序是先调 batch 降到 4再看显存占用如果还溢就把 imgsz 从 640 降到 512。批量减少带来的代价是训练轮数要适当增加梯度更新次数少了收敛速度变慢是正常的。4.4 推理结果文件为空或只有框没有掩膜训练完跑yolo segment predict时输出结果里只有检测框没有任何掩膜很多人以为模型坏了。其实大概率是用错了命令入口检测和分割在 Ultralytics 里的命令行指令前缀不同。分割推理必须用yolo segment predict如果用的是yolo detect predict模型就只输出检测类结果。另外输出的 txt 保存文件里如果看不到 mask 相关字段也要检查保存参数和推理命令。正确做法如下yolo segment predict \ modelbest.pt \ sourcetest_images/ \ save_txtTrue \ save_confTrue5. 推理与牛肉分级逻辑落地从掩膜到等级判定的完整流程5.1 单张图片推理与结果保存方式训练完的模型做单图推理时Ultralytics 会自动生成带掩膜标注的结果图保存在 runs/segment/predict 目录下。如果你需要把分割结果以坐标格式导出就要用到 save_txt 参数。注意推理时输入图片最好和训练时保持同一个尺寸如果有缩放Ultralytics 内部会自动完成预处理但为了掩膜边缘质量建议直接用原图尺寸推理让模型在尽可能多的图像细节下工作。yolo segment predict modelbest.pt sourcetest_images/img01.jpg save_txtTrue save_imgTrue生成的结果图会把每个实例的掩膜半透明叠在原图上不同类别用不同颜色区分。做毕业设计答辩时直接用这张图展示效果即可。如果前面分类只设了脂肪一个类那么掩膜只有一种颜色展示时信息密度略低但计算指标时更清爽。5.2 从掩膜坐标计算脂肪面积占比这是整个牛肉分级逻辑最关键的一步。把掩膜坐标转成像素级布尔数组时最常见的错误是用轮廓面积列公式只计算多边形面积而忽略掩膜内部的空洞比如脂肪块中间有瘦肉点。正确做法是先构建像素级掩膜再用像素计数来算面积占比这样才不会把空洞一起算进脂肪面积。import numpy as np import cv2 def mask_coords_to_binary(coord_list, img_shape): mask np.zeros(img_shape[:2], dtypenp.uint8) coords np.array(coord_list, dtypenp.int32).reshape(-1, 2) cv2.fillPoly(mask, [coords], 1) return mask def compute_fat_ratio(fat_coords, lean_coords, img_shape): fat_mask mask_coords_to_binary(fat_coords, img_shape) lean_mask mask_coords_to_binary(lean_coords, img_shape) whole_mask cv2.bitwise_or(fat_mask, lean_mask) fat_pixels np.sum(fat_mask 1) whole_pixels np.sum(whole_mask 1) return fat_pixels / whole_pixels fat_ratio compute_fat_ratio(fat_coords, lean_coords, image.shape)这段代码里 fat_coords 来自模型输出的第一个实例掩膜坐标lean_coords 来自第二个实例。由于 YOLOv11 返回的坐标是归一化后的浮点数用之前一定要先乘回图片宽高再转成 int32 传给 fillPoly不然画出来的掩膜位置全错。脂肪面积占比是分级的第一输入变量整块牛肉面积越大同样的脂肪像素数占比就越低所以不能用绝对像素数作为等级依据必须归一化到比例。实际项目中我一般还会加一步只保留掩膜面积大于全图 1% 的实例过滤掉预测置信度低的小碎片避免噪声掩膜干扰面积统计。5.3 批量推理与分级汇总表生成毕设演示时通常要对几十张测试图片逐一输出分级结果手写循环麻烦不说还容易漏。更高效的做法是用 Python 脚本循环读取图片逐张调用模型推理再把结果写入 CSV 文件。Ultralytics 在 Python 接口里封装得非常简洁先用 results[0].masks.xy 提取所有实例的坐标序列再按上面 5.2 的写法逐实例算脂肪占比。from ultralytics import YOLO import csv model YOLO(beef_runs/seg_exp1/weights/best.pt) image_list [test_01.jpg, test_02.jpg, test_03.jpg] rows [] for img_path in image_list: results model.predict(sourceimg_path, save_txtFalse) result results[0] if result.masks is None: rows.append((img_path, -1, -1)) continue masks result.masks.xy # 最简处理取置信度最高的实例作为主目标 main_coords masks[0] area estimate_instance_area(main_coords, result.orig_shape) rows.append((img_path, area, len(masks))) with open(grading_summary.csv, w, newline) as f: writer csv.writer(f) writer.writerow([image, fat_area, instance_count]) writer.writerows(rows)这里有一个很实际的取舍result.masks.xy 返回的是每个实例的多边形坐标列表同一个实例可能有多个多边形比如掩膜中间有空洞时直接用 masks[0] 拿到的只是其中一块。稳妥的做法是把某个实例的所有多边形合并后再构像素掩膜不然面积会被明显低估。这个尺度和刚才 5.2 节强调的“空洞不能直接算面积”是同一类问题的两个相反面。5.4 分级规则的软件落地与可视化牛肉分级的输出格式要直观不能只给一个比例数字。我习惯把等级映射写成一个函数输入脂肪面积占比输出等级描述字符串。等级数可以按需求定义成 3 级或 5 级与国标中的具体等级编号不一定完全对应但毕设展示中完全够用。def grade_by_fat_ratio(ratio): if ratio 0.10: return 等级低脂肪面积占比过少 elif ratio 0.20: return 等级中脂肪分布一般 elif ratio 0.30: return 等级较高脂肪分布较均匀 return 等级高大理石纹丰富按不同的脂肪占比区间给等级描述比直接打印数字更能体现图像识别和机器学习的应用价值。答辩的时候把这些输出结果整理成表格配合牛肉原图和掩膜叠加图整个项目从数据标注到最终判级的闭环就能讲清楚了。6. 掩膜统计阶段的边缘处理技巧别让边界误差毁掉整个分数分割模型输出的掩膜在不同实例之间偶尔会出现 1 到 2 个像素的交叠区这个是网络在边缘预测上的天然误差。如果在统计面积时不处理交叠像素会被重复计数。最通用的做法是做一个按像素的类别优先级合并比如脂肪的优先级高于瘦肉交叠位置整块按脂肪算这样三个类别脂肪、瘦肉、背景互斥全图每个像素只归属一个类别统计出来的面积才保证完全一致。这是一个非常值得做一下的小优化。fat_pixels np.sum(fat_mask 0) lean_only cv2.bitwise_and(lean_mask, cv2.bitwise_not(fat_mask)) lean_pixels np.sum(lean_only 0) whole_pixels fat_pixels lean_pixels这里有一个容易忽视的细节如果在训练配置里只标注了脂肪一个类、瘦肉当作背景那么 lean_mask 不存在whole_pixels 的计算口径就要改成用整块牛肉的外部轮廓掩膜而不是脂肪加瘦肉。这个外部轮廓掩膜在推理时一般拿不到需要在数据标注阶段就额外保存一份“牛肉整体轮廓”的标签否则后续统计时只能手动框选目标区域误差会大很多。每一套数据集都应该有一个固定的面积统计口径这句话写在笔记第一页也不过分。另外一个经验是关于置信度阈值的。推理参数里的 conf置信度阈值默认是 0.25但分割任务里小碎片的置信度普遍偏低如果调到 0.5 以上细小脂肪块会被当成背景过滤掉脂肪占比整体下降等级会被误判到低档。我实测过同一张图 conf0.25 和 conf0.5 的面积统计结果可以差 20% 以上。对分级场景调低一点宁可多保留碎片也不要在统计阶段丢细节。从那以后我每次做掩膜统计前都会强制走一遍流程先把 conf 降到 0.15再把所有掩膜按面积过滤掉小于全图 0.5% 的碎片最后再归一化计算占比。这套流程在牛肉数据集上稳定复现如果你也被面积统计不准折磨希望这段经验帮到你。本文还有配套的精品资源点击获取
返回列表