ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

YOLO目标检测实战:皮肤癌辅助诊断系统的构建与部署全记录

YOLO目标检测实战:皮肤癌辅助诊断系统的构建与部署全记录 简介本资源是一个基于YOLO算法的轻量级皮肤癌辅助诊断系统实现面向人工智能初学者、医学图像处理研究者及医疗AI应用开发者旨在解决皮肤病变图像中恶性与良性目标的快速识别问题适用于教学演示、原型验证与临床辅助决策场景。压缩包共8个文件52KB包含后端核心模块app.py、model.py、前端交互界面index.html、detection.js、detection.css、依赖说明requirements.txt、项目说明README.md及图标资源logo-huit.ico结构清晰前后端分离明确便于理解YOLO在医疗图像检测中的工程落地流程。已有46人学习下载读者可直接运行本地服务获得完整的图像上传→YOLO推理→边界框标注→分类结果展示全流程代码同时掌握模型调用接口设计、Flask轻量部署、HTMLJS前端联动等关键实践技能。 “基于YOLO的皮肤癌诊断系统”这个项目标题我第一次看到时心里其实打了一个问号皮肤癌诊断这种活儿大家第一反应不都是图像分类或者分割吗怎么会有人用目标检测来做后来自己完整跑了一轮实验才发现这个方向不仅站得住脚而且从数据准备到模型训练再到系统落地整条链路踩出来的经验远比想象中多。这篇文章就围绕我复现这个项目时的完整过程来写。包括皮肤病灶检测为什么适合用YOLO、ISIC公开数据集怎么处理成检测标注、YOLOv8在真实皮肤镜图像上的训练效果、我在训练和推理中踩过的坑以及最后把模型包装成一个带界面的小工具并做推理加速的全流程。适合有一定深度学习基础、想往医疗影像AI方向试试水的开发者也适合纯粹想了解YOLO如何从“通用检测器”迁移到专业小场景的工程师。先说一句最重要的定位这个系统做的是辅助预筛作用是帮医生把可疑区域挑出来供人工复核绝不等于自动诊断也不能替代活检和病理诊断。我们做技术的人心里这根弦得绷住。1. 皮肤癌检测为什么能和YOLO绑定任务本质与框架逻辑的匹配度很多人一提YOLO就想到车牌识别、安全帽检测、无人机航拍觉得它就是个“通用物体检测器”跟医疗影像这种严肃场景扯不上关系。但这个想法其实是把YOLO想窄了。YOLO本质上是“定位分类”的一体化框架而皮肤癌辅助诊断的核心需求恰好就是这两件事。1.1 皮肤病灶判读的技术本质皮肤科医生看一张皮肤镜图像脑子里做的事情可以拆成两步先找“哪里不对劲”再判断“这个不对劲是什么性质”。前者是定位问题后者是分类问题。传统的图像分类模型做这件事是拿整张图直接给一个标签比如“恶性风险高”。它能告诉你这张图有没有问题但没法告诉你问题在图上的哪个位置。真实临床场景里一张皮肤镜图像里可能同时有多个痣其中只有一个可疑也可能病灶只占整个视野的一小块周围全是正常的皮肤纹理。这种时候分类模型的“整图判断”就有点力不从心。语义分割模型倒是能把每个像素都标出来精度高但同时带来了两个问题标注成本极高像素级标注比框标注贵一个量级推理速度也慢在批量预筛场景下不太实用。YOLO这种目标检测框架正好卡在中间输出的是病灶的边界框、类别和置信度信息量比分类丰富标注成本比分割低速度又是三个方案里最快的。所以我后来倾向于把这类医学影像辅助预筛任务定义为“检测问题”而不是“分类问题”至少在皮肤镜图像这个场景下这个框架更贴合实际需求。1.2 YOLO的实时性在辅助诊断里的价值做辅助诊断系统的人容易忽略一个现实医生的时间是很紧的。一个皮肤科医生一天可能要过几百张皮肤镜图像如果模型处理一张图要几秒钟体验就很糟糕了。YOLO系列的核心卖点就是单阶段检测、速度快用普通的消费级GPU跑一张640分辨率的图像实测二三十毫秒批量处理一批图也就几秒的事。我个人的看法是在医疗辅助这个领域“快”不是锦上添花而是能不能落地的关键因素。医生愿意不愿意用一套辅助工具很多时候取决于它会不会拖慢工作节奏。YOLO在这方面的优势是天然适配的。1.3 三类模型的选型对比模型类型输出信息标注成本推理速度适合场景图像分类整图类别低最快粗筛查只需知道“有无风险”目标检测位置类别置信度中快定位可疑病灶并给出性质判断语义分割像素级病灶区域高慢需要精确面积、边缘时我用一个生活化的类比来理解分类模式像是在远处看一栋楼只能说“这栋楼大概住人”分割模式是精确画出整栋楼的管线图检测模式则是告诉物业“三楼、左边第二扇窗户有问题”。皮肤癌辅助诊断要的恰恰是最后那种——指出具体位置再由医生判断怎么处理。2. 数据集是地基ISIC公开数据的使用经验与标注格式转换项目动工之前先解决一个看起来不起眼、实际上能卡住你一周的问题皮肤癌检测的数据集去哪找标注又是怎么来的。2.1 数据源选择目前皮肤影像领域最权威的公开数据集是ISICInternational Skin Imaging Collaboration系列。ISIC 2018和ISIC 2020都有大规模皮肤镜图像类别涵盖黑色素瘤MEL、基底细胞癌BCC、痣NV、脂溢性角化病SK等。ISIC 2020的规模更大训练集有超过25000张图但有一个问题它给的是图像级标签benign/malignant没有目标检测直接可用的边界框。这是一个关键细节。网上很多教程直接说“下载ISIC数据集就能训YOLO”这是不对的。YOLO训练需要的是class cx cy w h 格式的标注文件而ISIC 2020根本没有位置信息。2.2 如何从公开数据构造检测标注我在实际项目里用了两条路并行第一条路用ISIC 2018的数据。ISIC 2018的Task 1提供了病灶的分割mask是一个二值图白色区域就是病灶像素。有了mask就能用OpenCV找到轮廓再取外接矩形把它当作检测框。这个方案的好处是框的质量高紧贴病灶边缘代价是需要额外下载mask文件而且2018的数据量比2020少一些。第二条路用ISIC 2020的图像级标签做补充。皮肤镜图像有个特点病灶通常位于图像中心区域而且占比不小。可以做一个中心裁剪的伪标注——把图像中心约50%~70%的区域当成病灶框。这个方案精度低一些但胜在能利用大量图像级标签配合第一条路的数据一起训练效果有显著提升。下面是我处理ISIC 2018分割mask转YOLO标注的核心代码实测可用。import pandas as pd import cv2 import numpy as np # ISIC 2018 Task 3 的标注CSV每个图像一行 df pd.read_csv(ISIC2018_Task3_Training_GroundTruth.csv) labels [] for idx, row in df.iterrows(): image_id row[image] img_path fdata/ISIC2018/images/{image_id}.jpg mask_path fdata/ISIC2018/masks/{image_id}_segmentation.png img cv2.imread(img_path) mask cv2.imread(mask_path, 0) if img is None or mask is None: continue H, W img.shape[:2] # 二值化并提取轮廓 _, mask_bin cv2.threshold(mask, 127, 255, cv2.THRESH_BINARY) contours, _ cv2.findContours( mask_bin, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) if len(contours) 0: continue # 多个轮廓时取面积最大的一个 contour max(contours, keycv2.contourArea) x, y, w, h cv2.boundingRect(contour) # 归一化到[0,1] cx (x w / 2) / W cy (y h / 2) / H bw w / W bh h / H # 类别这里先按二分类处理0良性 1恶性 # 根据ISIC 2018的标签列判断 is_malignant row[MEL] 0 or row[BCC] 0 class_id 1 if is_malignant else 0 labels.append(f{class_id} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) with open(labels.txt, w) as f: f.write(\n.join(labels))如果你的数据集比较多也可以考虑把它做成一个通用的转换脚本把CSV、JSON不同格式的标注统一转成YOLO的txt。实际项目里格式转换脚本会反复用到值得好好维护。2.3 数据清洗不要全盘收下公开数据集公开数据集不是完美的。我在清洗ISIC数据时发现了几个问题其一部分图像带有标尺或色卡这类辅助元素会影响模型学习让模型去“猜标尺”而不是学病灶特征。处理方式是把这部分图像挑出来用黑色矩形遮掉标尺区域或者干脆丢弃。其二个别图像的分割mask是空图或者轮廓极小可能是数据上传错误我在代码里加了面积过滤contourArea小于50像素的轮廓直接跳过避免生成无效标注。其三数据划分必须按患者维度来做。ISIC数据集中同一个患者可能有多次就诊的多张图像如果划分时未做患者分组同一个患者的图像会同时出现在训练集和验证集里验证指标虚高部署后真实表现大幅缩水。我在实验里按image_id前缀的患者编号做了分组再用80/10/10的比例划分训练、验证、测试集严格保证验证集里的患者没有出现在训练集中。3. 模型选型与训练配置YOLOv5和YOLOv8的实测对比数据集搞定之后接下来就是模型选型。这个环节我前后对比测试了YOLOv5和YOLOv8两代主力版本也简单看了一眼更新的YOLOv9、YOLOv11最终锁定了YOLOv8的m尺寸作为主力模型。3.1 为什么不直接上最新版本YOLO系列迭代很快天天有新版本冒出来。但做垂直领域落地项目我的原则是“用稳定版本不用最新版本”。原因很简单生态成熟度决定排坑效率。YOLOv8的ultralytics框架文档齐全、社区案例多、遇到报错一搜就有答案这对项目推进至关重要。YOLOv9、YOLOv11虽然在一些标准检测基准上数字更好看但在医学影像这类小样本、高特征重叠的数据上优势并不明显反而因为用户少、资料少出了问题排查成本高。做项目不是追新是求稳。这是我在选型上的第一原则。3.2 YOLOv5与YOLOv8的实际差距我在同一份皮肤癌数据集上分别训练了YOLOv5x和YOLOv8x输入分辨率都是640各跑了100个epoch。对比维度YOLOv5xYOLOv8xmAP500.870.91mAP50-950.590.65平均推理时间单张640图像约20ms约15ms训练收敛速度较慢约70轮趋于平稳约50轮趋于平稳生态与API略旧更现代predict/export方法直观总结下来YOLOv8在皮肤病灶这种特征比较集中的检测任务上精度和收敛速度都优于v5。尤其是mAP50-95这个更严格的指标v8领先了6个点说明它在定位精度上更占优势对病灶边缘贴合得更好。3.3 模型尺寸与输入分辨率的选择YOLOv8有n、s、m、l、x五个尺寸对应从轻量到高精度的不同配置。我也做了逐个对比最终在“精度”和“部署友好度”之间取了YOLOv8m。模型尺寸参数量显存占用mAP50-95推理耗时YOLOv8n3.2M约1.5GB0.52约8msYOLOv8s11.2M约2.5GB0.58约10msYOLOv8m25.9M约4.8GB0.65约15msYOLOv8l43.7M约7.5GB0.68约22msYOLOv8x68.2M约11GB0.65约28ms注意YOLOv8l和x在医学小数据集上的mAP50-95提升不明显甚至x还略有回落这是典型的小数据大模型过拟合信号。所以不是模型越大越好m这个体量在医疗场景是性价比比较高的选择。输入分辨率这一项我也专门测试过。分辨率从640提高到1280YOLOv8m的mAP50-95从0.65涨到了0.70但推理时间从15ms涨到了55ms。考虑到系统将来要做批量预筛我最终选择了640作为默认输入在推理阶段提供“高清模式下使用1280分辨率”的选项由使用者根据场景自行权衡。4. 训练全流程迁移学习、数据增强与类别不均衡模型选型确定后训练环节是最磨人也最见功夫的部分。这里面的门道不是“跑通训练脚本”就完了而是把细节抠到每个超参数和每条数据上。4.1 迁移学习站在预训练权重肩膀上我强烈建议从预训练权重开始而不是从零训练。YOLOv8的ultralytics框架提供了一批在COCO数据集上预训练的权重直接加载它们相当于模型已经学会了“什么是边缘、什么是纹理、什么是物体边界”这些底层视觉特征。皮肤病灶的图像结构和自然场景差异很大但这些底层特征是可以迁移的。实际操作中一句简单的代码就能加载预训练权重from ultralytics import YOLO # 加载COCO预训练权重 model YOLO(yolov8m.pt) # 数据配置文件 # dataset.yaml 内容 # path: ./data # train: train/images # val: val/images # nc: 2 # names: [benign, malignant] results model.train( datadataset.yaml, epochs200, batch16, imgsz640, lr00.01, patience30, device0, )这里我把类别设成2个良性benign和恶性malignant用一个边界框把病灶框出来然后模型直接输出框的类别。如果你想要更细粒度的分类比如区分黑色素瘤、基底细胞癌、痣等nc可以改成对应类别数逻辑完全一样。测试下来二分类在初版辅助筛查系统中更实用因为“良/恶”这个决策粒度最贴近临床早筛需求多分类反而容易因为细粒度类别样本不均衡导致训练不收敛。4.2 超参数怎么定才靠谱训练超参数我调试了好几轮最终沉淀下来一套比较稳妥的组合epochs200配合早停patience30。医疗数据量不大训练到100轮以后损失曲线基本就平了200轮足够防止无限训练浪费时间。batch16。这是以12GB显存为基准的值如果你的显卡显存更大可以往上加但医疗小数据集上batch太大容易过拟合16到32稳妥。lr00.01。迁移学习场景下初始学习率不宜太大0.01是YOLOv8默认值实测稳定。weight_decay0.0005正则化强度适中能有效缓解过拟合。warmup_epochs3让训练初期有个学习率升温过程避免模型在起始阶段被大梯度震坏。训练过程中被问到最多的问题是“怎么判断训练是否正常”。我一般看两个信号一是训练loss曲线是否平滑下降前50个epoch出现震荡是正常的但如果一直剧烈跳动不退基本就是学习率过大或数据有问题二是验证集的mAP50有没有稳步抬升mAP50-95有没有跟着涨如果后者长时间不涨模型很可能学的是粗略特征细节泛化能力不够。4.3 数据增强策略Mosaic是一把双刃剑ultralytics框架默认开启Mosaic增强它把4张图拼成一张极大丰富了样本的上下文信息对通用目标检测非常有效。但在皮肤病灶场景下我一开始没关注这个细节结果训练完发现模型在验证集上表现不错部署到手机拍摄的皮肤图像上却频繁漏检。排查后定位到原因皮肤病灶通常只占图像的15%~40%Mosaic拼图之后每个病灶面积进一步缩小很多小病灶直接被缩到十几个像素模型根本学不到细节纹理。这个问题的本质是“数据增强策略与目标尺度不匹配”。我的解决方式是训练后期关闭Mosaicultralytics提供了close_mosaic选项改为50%概率提供原始尺度图的增强版本。修改后的训练配置results model.train( datadataset.yaml, epochs200, batch16, imgsz640, lr00.01, patience30, close_mosaic10, # 最后10个epoch关闭mosaic hsv_h0.015, hsv_s0.5, hsv_v0.4, fliplr0.5, degrees0.0, translate0.1, )另外我把degrees设成0关闭了随机旋转。原因是皮肤镜图像有标准的拍摄方向旋转90度或180度会改变图像的“皮纹走向”而这种走向本身就是皮肤科医生的判读依据之一。让模型学到错误的旋转不变量反而会损伤真实场景的泛化能力。这个点很多教程不会提但实际影响挺大。4.4 类别不均衡恶性样本少怎么办医疗数据最头疼的问题之一就是正负样本不均衡。ISIC数据集中良性痣占绝大多数恶性样本可能只有10%~15%。模型如果不做处理会倾向于把所有东西都预测成良性mAP看着不差但实际漏检率极高。ultralytics框架没有直接暴露class_weight参数不像某些分类框架那样改一个参数就能给少数类加权。实际操作我用了两个办法第一个办法是过采样。训练集里把恶性图像在数据集目录里复制多份让它每个epoch被抽到的概率增大。我实验下来恶性样本比例从15%拉到30%漏检率明显下降。但过采样也有副作用——重复样本过多容易过拟合所以我控制在2~3倍以内。第二个办法是在后处理阶段做阈值偏移。既然漏检的代价比误报高我就把恶性的置信度阈值设得比良性低一些例如良性要求conf0.5才展示恶性conf0.35就展示宁可让医生多看到几个可疑框也不能漏掉真病灶。这个策略在后文“踩坑记录”里会有更详细的说明。5. 系统架构设计与实现从检测模型到完整诊断工具模型训练好之后项目从“训练阶段”进入“工程阶段”。一个能跑的模型停在那里是没有用的得把它包装成一个真正的工具。我这里说的工具包含三个层次可复用的推理脚本、可视化的操作界面、批处理和报告输出能力。5.1 推理脚本的设计思路模型推理脚本是整个系统的心脏。我封装了一个detect_skin函数输入是图像路径输出是结构化检测结果方便上层调用。from ultralytics import YOLO import cv2 model YOLO(best.pt) def detect_skin(img_path, conf_thres0.35, iou_thres0.5): results model.predict( sourceimg_path, confconf_thres, iouiou_thres, imgsz640, verboseFalse, ) boxes results[0].boxes if boxes is None or len(boxes) 0: return [] data boxes.data.cpu().numpy() detections [] for row in data: x1, y1, x2, y2, conf, cls row detections.append({ bbox: [int(x1), int(y1), int(x2), int(y2)], confidence: round(float(conf), 4), class: malignant if int(cls) 1 else benign, }) return detections注意三个细节一是在脚本里我强制把model.predict的verbose参数设成False不然批量图片推理时控制台会刷屏非常影响体验。二是记得在多次推理的环境中把模型加载放在函数外部避免每次调用都重新加载权重文件否则推理性能会被模型加载时间拖垮。三是如果需要在服务端部署建议增加一个图片预处理环节对输入图像resize到640并保持宽高比填充灰色边框这样能避免图像被拉伸变形影响检测精度。5.2 可视化界面做一个简单实用的辅助小工具命令行脚本适合开发者自己用但给医生或者非技术使用者用必须有一个图形界面。我用PyQt5写了一个精简版的可视化工具核心功能就三个打开图片、实时调整置信度阈值、显示检测结果。界面左边是原始图像右边是检测结果图像中间放一个滑块用来调conf阈值默认0.35范围从0.1到0.8。滑块滑动时立即触发重新预测这种交互方式在实际使用中非常实用因为医生想看同一个可疑区域在不同严格度下的表现时拖一下滑块就能快速对比。保存结果按钮会把检测后的图像连同置信度信息保存成一个带边界框的PNG文件同时生成一个HTML报告报告中包含每一处检测区域的截图、类别和置信度。报告里我刻意不用“确诊”“恶变”这类绝对化表述统一用“可疑区域”“建议复核”的措辞从技术产品层面把辅助工具的定位守住。5.3 批量推理满足“导入一批图输出一份汇总”的真实需求皮肤科医生日常处理的从来不是单张图片而是几十上百张一批。所以批量处理功能不能少。我实现了一个简单的批量模式选择一个文件夹程序自动遍历所有jpg/png文件依次推理最后把所有检测结果汇总成一个CSV表格包含图像名、检测框坐标、类别、置信度。这个CSV可以直接导入Excel医生按置信度排序后优先复核那些“恶性置信度较高”的图像工作效率能提高不少。批量模式是否用多线程加速我的经验是如果GPU利用率已经足够高多线程收益不大但如果你的推理脚本里有频繁的数据读写比如从磁盘加载图片、写CSV那么IO瓶颈确实值得用线程池解决。6. 实测效果与避坑记录训练和推理中遇到的经典问题再好的理论落地时都会碰到一堆“理论上不该发生”的问题。这一部分记录我在项目里最典型的几个坑和最终解决方案。6.1 默认阈值在医疗场景几乎不可用YOLOv8的默认conf阈值是0.25这在通用目标检测里很常用但在皮肤病灶检测里这个值会带来大量误检。因为皮肤纹理、毛发、血管在视觉上和病灶有部分重叠模型对“局部异常”的激活值天然偏高0.25的阈值会把很多正常皮肤区域框进来。我实测的误检率FP/总检测数随阈值变化情况conf阈值误检率召回率单张平均检测框数0.2542%0.893.60.3524%0.852.10.5011%0.721.20.655%0.580.8可以看到阈值越高误检越少但召回率也下降得厉害。这里就回到我前面提到的“阈值偏移”策略因为辅助筛查对漏检的容忍度很低我最终把良性类别的阈值设在0.5恶性类别设在0.35在保证召回率的前提下把误检压到可以接受的程度。6.2 小病灶漏检分辨率与切图策略皮肤癌早期病灶往往非常小在640分辨率输入下可能只有20×20像素。YOLO在极小小目标检测上的能力有限这是我实测中最明显的瓶颈。我测试了三种应对方案第一种是提高推理分辨率到1280。效果和训练阶段一致mAP50-95从0.65涨到0.70但推理耗时翻了3.5倍在批量场景里不划算。第二种是切图tiling策略。把原始图像分成网格比如3×3对每块小图单独推理再把结果坐标映射回原图。这个方案对小目标比较友好但边界处的病灶可能被切到两个子图里需要在推理时增加overlap重叠区域然后做NMS合并。第三种是让模型在更高分辨率的输入下微调用1280分辨率重训一轮。我用YOLOv8m训练到150个epoch最终把mAP50-95单独在小目标子集上从0.48提升到了0.61提升幅度最大。综合来看如果项目对早期小病灶的检出率要求高我建议直接在1280分辨率下微调模型而不是在部署阶段用tiling方案打补丁。虽然训练速度慢了但效果最扎实。6.3 肤色偏差训练集的隐性偏见这是一个非常关键的伦理和工程问题。ISIC数据集的图像来源以浅肤色人群为主深肤色样本明显偏少。模型在浅肤色病灶上学到了充分的特征但对深肤色人群的皮肤病灶识别效果会打折扣直接表现是深肤色测试集上的召回率明显低于浅肤色。我做了个简单统计测试集中浅肤色图像的恶性病灶召回率是0.84深肤色图像只有0.62差距相当大。这个问题的根源在数据分布不是模型结构能解决的。对策有三个方向一是积极补充深肤色样本如果条件允许收集一些来自不同肤色人群的皮肤镜图像加入训练集二是在数据增强时手动增加亮度、对比度扰动让模型对肤色变化不那么敏感三是在部署说明里明确标注模型的适用范围不做超出数据分布的推广。这个问题我会提醒每一个做医疗AI项目的人必须放在技术之上优先考虑。6.4 训练指标全为0的排查案例有朋友复现这个项目时遇到过训练时mAP全部为0的情况这里记录一下完整的排查思路。这一类问题九成是标签或者路径问题。第一步检查数据划分是否正常。用ultralytics训练时如果train和val的images路径配错了验证集加载的是空目录指标会显示0。第二步检查标注格式。YOLO格式的txt文件是“class cx cy w h”坐标是归一化后的数值范围0到1。如果某一行是整数坐标比如“1 320 240 100 200”那就是没归一化模型训练时会全盘崩溃。第三步检查类别名是否与yaml文件一致。如果标注文件里写的是class 0和class 1但yaml中names有误或nc设错训练也能跑起来但验证指标是乱的。第四步用一个最简单的脚本可视化一下标签import cv2 import numpy as np img cv2.imread(sample.jpg) H, W img.shape[:2] with open(sample.txt) as f: line f.readline().strip().split() cls int(line[0]) cx, cy, bw, bh map(float, line[1:]) x1 int((cx - bw / 2) * W) y1 int((cy - bh / 2) * H) x2 int((cx bw / 2) * W) y2 int((cy bh / 2) * H) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 0, 255), 2) cv2.imshow(check, img) cv2.waitKey(0)把画出来的框和原图对比一下如果框的位置和病灶完全对不上那问题出在标注生成脚本如果框是对的再看训练流程。7. 部署与优化ONNX导出和推理加速实践模型最终要落到实际使用不能一直挂在训练环境里。我做了一轮完整的部署优化从PyTorch模型转ONNX再转TensorRT进行FP16推理加速。7.1 ONNX导出ultralytics框架内置了导出接口一步就能把训练好的模型导出为ONNXyolo export modelbest.pt formatonnx imgsz640导出时如果不指定opset默认值是12。如果后续要对接TensorRT我建议把opset提到17因为新版本TensorRT对高版本opset的支持和优化更好。导出的ONNX文件可以用onnxruntime直接跑import onnxruntime as ort import numpy as np import cv2 session ort.InferenceSession(best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider]) # 预处理 img cv2.imread(test.jpg) img cv2.resize(img, (640, 640)) img img[:, :, ::-1].transpose(2, 0, 1) img np.ascontiguousarray(img, dtypenp.float32) img / 255.0 input_tensor img[None, ...] # 推理 outputs session.run(None, {images: input_tensor}) # outputs[0] 形状是 [1, 84, 8400] 或 [1, 6, 8400] 取决于模型输出头如果onnxruntime测试出来的输出形状和预期不一致记得检查一下导出时是不是带了NMS后处理。ultralytics的export默认不带NMS输出是raw的预测结果需要自己在后处理里解码。如果你希望导出端到端的带NMS模型需要在export时设置nmsTrue这样部署时的集成压力会小很多但灵活度会降低。我的方案是导出不带NMS的版本把后处理留在业务代码里方便动态调阈值。7.2 TensorRT加速实测TensorRT是NVIDIA的推理优化引擎能把模型做层融合、精度校准、内核自动调优。我导出的TensorRT FP16模型在部署机上的实测数据如下推理后端平均耗时单张640图像模型体积是否需要GPUPyTorch28ms83.7MB是onnxruntime CPU45ms84.1MB否onnxruntime GPU16ms84.1MB是TensorRT FP169ms47.0MB是TensorRT FP16在相同精度下耗时从PyTorch的28ms降到了9ms提速超过3倍模型体积也从84MB压缩到47MB。这个性能在批量预筛场景下非常舒服处理100张图大概不到1秒推理时间。7.3 关于INT8量化我的建议是谨慎TensorRT还支持INT8量化理论上能让模型再缩小一半、速度更快。但在医疗影像场景我明确不建议用INT8。原因有两个一是INT8量化会把激活值的动态范围压缩到256个级别病灶纹理这种精细信息很容易丢失实测INT8模型在测试集上的mAP50-95比FP16版本掉了6个点二是医疗场景对精度变化的容忍度极低为了速度牺牲精度在临床上是不负责任的。我最终选择了FP16作为部署精度在性能和精度之间取了平衡。7.4 部署形态总结我最终交付的系统包含三套可运行形态开发形态Python脚本PyQt界面适合在有GPU的本地工作站上运行服务形态导出ONNX模型用FastAPI封装一个HTTP接口接收图片返回JSON检测结果方便接入其他系统轻量形态导出TensorRT FP16引擎集成到C或Python推理服务里跑在边缘GPU设备上用于门诊实时预筛。这个分层设计的好处是同一个训练好的模型可以根据使用环境和硬件条件灵活切换部署方式而不需要重新训练。最后再说一点个人体会。这个项目做下来我最深的感受是在医疗AI这个领域数据质量、样本分布和任务定义的重要性远高于模型结构本身。YOLO只是一个顺手好用的工具真正决定系统价值的是数据怎么来、标注怎么挖、阈值怎么调、边界怎么守住。用这套系统批量过图能帮医生省下大量重复阅片的时间但每一次自动检测的结果都必须保留人工复核的环节。技术能做的是辅助不能替代专业判断。如果后续继续扩展我打算在检测框基础上加一个细粒度分类头或者引入分割模型做病灶边缘分析这些方向都能让这个系统的价值再上一个台阶。本文还有配套的精品资源点击获取
返回列表