ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

深度学习图像处理源码实战:从分类到目标检测的完整复现与避坑指南

2026/9/28 22:57:27 拓冰建站 浏览量
深度学习图像处理源码实战:从分类到目标检测的完整复现与避坑指南 简介面向图像分类与目标检测的Python深度学习源码项目基于PyTorch实现同时覆盖图像分割与模型部署场景适合希望从源码层面系统学习深度学习图像处理的开发者与研究人员。资源共436个文件以360个Python脚本为主干承担模型训练、验证、测试及图像处理工具函数另有30个JSON配置文件管理训练参数25个文本说明提供使用指南10个PNG图像与少量cfg/yaml/checkpoint等辅助文件构成完整配套压缩包整体约4.13MB。目录按pytorch_classification、pytorch_object_detection、pytorch_segmentation、deploying_service等模块划分分别对应分类、检测、分割与部署任务结构清晰便于按需查阅。目标检测涉及Faster R-CNN、YOLO等经典算法分割部分包含FCN、U-Net类网络实现配置与说明文档可直接对照调整超参。目前已有351人学习下载适用于学术实验、课程设计或工业场景快速原型验证整体工程参考价值完整且便于二次扩展。1. 源码给你的是骨架不是成品为什么一张图分类和检测项目跑起来总差一口气很多人在拿到一份基于Python的深度学习图像处理源码时第一反应是去翻模型定义文件。看到几百行卷积、残差、注意力机制觉得挺像回事然后开始训练结果验证集精度上不去检测框飘得离谱。这个现象我已经见过太多次问题往往不在模型结构而在数据管线、训练参数和推理预处理这些被忽视的环节里。我要讲的就是一份涵盖了分类与目标检测等应用的Python深度学习图像处理源码从下载到跑通、再到改成自己数据的完整路径以及中间那些不看到报错就不会长记性的坑。这篇文章适合手里已经有源码、或者正准备去下载一份来学的从业者不管是大学生做课程设计还是刚转到算法岗的工程师都能照着一路复现。目标很具体让你不再对着别人的代码发愣而是能改、能调参数、能用自己的数据训练出来。2. 读懂一份图像处理源码目录结构、数据流与训练管线2.1 源码里最常见的目录划分不是文件多就高级一份正经的深度学习图像处理源码目录划分通常是有规律可循的。常见的是按功能拆分data放数据集和加载代码models放网络结构定义utils放工具函数例如可视化、指标计算、锚框生成train.py和inference.py是入口脚本configs或options放超参数配置。你打开源码后先别急着点运行而是花十分钟沿这个思路把文件归类就能避免后面出现“不知道改了哪个文件导致的玄学问题”。很多开源项目会把数据集加载、数据增强、训练循环写在一个train.py里头这样对初学者友好但改起来痛苦。我看到过一份经典源码所有数据集处理都堆在main.py里大概八百多行。你想换一种数据增强方式要在函数之间跳来跳去还可能改错缩进。所以我的习惯是拿到源码先画一张数据流图心里面想清楚图片从硬盘怎么进内存进内存怎么变成张量张量怎么进网络网络输出怎么和标签算损失。这一条链路捋顺了后面所有问题都能定位到某个环节。数据流里最容易翻车的是“图片进网络之前”这一段。源码里经常有自定义Dataset类实现了__getitem__方法里面做读图、解码、缩放、归一化、转张量。你看到的 PIL、OpenCV、albumentations 脚本都只是表面真正的隐形规则是数据增强应该在读取之后、归一化之前做。如果顺序反了比如先归一化再做随机裁剪那裁剪后的像素分布就不满足预训练模型的输入习惯精度下降几个点都很正常。2.2 数据加载这一段决定了你后面少踩一半的坑用 PyTorch 写源码的团队基本都会基于torch.utils.data.Dataset和DataLoader来做数据加载。我在检查源码时先挑三类文件看图像读取方式、标签解析方式、返回的张量形状。图像读取最常见的是cv2.imread和PIL.Image.open它们有个区别OpenCV 读出来是 BGR 通道顺序PIL 是 RGB。如果源码里模型是用 PIL 训练的你推理时用 OpenCV 读图直接喂进去分类结果大概率错得离谱因为通道被调换了。解决方案很简单要么用 PIL 读要么cv2.cvtColor(img, cv2.COLOR_BGR2RGB)转一下。再说DataLoader的collate_fn和num_workers。目标检测源码里每张图的标注框数量不一样模型通常返回一个元组列表每个元素是图像张量和标注字典。默认的collate_fn只能拼接形状一致的张量所以检测源码里一定会重写这个函数把不同长度的 label 列表放进同一个 batch然后填充或者按帧处理。num_workers也不是越大越好Windows 系统的 Python 在 Windows 下跑多进程数据加载如果把num_workers设成 4 以上又用了spawn启动方式会在训练一开始就报错或者卡死。我一般建议源码丢到 Linux 服务器上跑如果只能在 Windows 上调试num_workers设成 0 最稳妥。数据增强也是源码里常出问题的地方。分类源码里常见RandomResizedCrop、RandomHorizontalFlip和ColorJitter。检测源码里常见 Mosaic、MixUp 和随机仿射变换。不要小看增强它直接影响模型泛化能力。有一份公开发的目标检测源码训练集只有一万张图片但 Mosaic 增强把每轮的上下文信息混合起来硬是把 mAP 提高了两个点。所以读懂源码时不要觉得增强只是“锦上添花”。2.3 训练管线的四个环节预处理、前向、损失、反向把训练循环拆开看主要有四个环节。预处理除了包括数据增强还包括归一化。常用的归一化均值和标准差是从 ImageNet 上计算出来的例如均值[0.485, 0.456, 0.406]标准差[0.229, 0.224, 0.225]。如果你的图片是 0-255 范围的整数就要先除以 255 再减均值除方差如果源码已经在你毫不知情的情况下用了别的归一化方式训练和推理效果就会对不上。我在排查推理效果差的问题时十次有八次是漏了这一步。前向传播相对固定但需要注意模型是训练模式还是推理模式。训练模式下要打开Dropout和BatchNorm否则模型会偷偷用移动平均值推理模式下要用model.eval()关闭这些随机行为并且最好在torch.no_grad()里跑。损失函数这一块分类用交叉熵检测用分类损失加回归损失加权组合常见的权重是box0.05, cls0.5, dfl1.5这一类。反向传播就是loss.backward()加optimizer.step()。我看到一份源码在每轮迭代里把梯度清零写在了loss.backward()之后于是梯度在 backward 后被清零参数根本没更新训练损失一直不下降。这种 bug 不仔细看很难发现。源码里还经常有学习率调度器例如ReduceLROnPlateau、余弦退火。使用余弦退火时要注意总迭代次数如果total_steps设置得和实际训练步数不一致学习率曲线会提前降到 0后面的训练就变成在“乱走”。我一般会在训练脚本里加一段代码把每个 epoch 的 lr 打印出来确认调度行为符合预期。这些都属于训练管线里的细节但它们才是一份源码能不能被复现的底气。3. 分类任务源码怎么落地迁移学习、训练参数与评价指标3.1 用 torchvision 把预训练模型换成自己的类别开源世界里的分类源码十有八九是直接在 ImageNet 预训练模型上加一个全连接层。你拿到的源码可能默认是 1000 类输出而你的任务只有 10 类。这时候不要从头训练整个网络我一般做法是保留预训练权重只把最后一层线性层替换成适合自己类别数的结构。PyTorch 官方的 torchvision 模型在models包里提供了现成接口比如 ResNet、EfficientNet、ConvNeXt。你可以这样改import torch import torch.nn as nn from torchvision import models num_classes 10 model models.resnet50(weightsmodels.ResNet50_Weights.IMAGENET1K_V1) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) for name, param in model.named_parameters(): if name.startswith(fc.): param.requires_grad True else: param.requires_grad False这段代码先加载了 ImageNet 预训练权重然后把最后的全连接层替换成输出维度为num_classes的新线性层。后面再遍历所有参数只让fc.前缀的参数参与梯度更新主干网络被冻结。这样做的理由是预训练模型的主干已经学会了边缘、纹理和几何结构等通用特征在你自己的小数据集上微调时只要训分类头就够了能大幅降低过拟合风险。如果你有足够的训练数据可以把requires_grad改回全部True或者设置一个frozen_epochs先在冻结状态下训练几个 epoch再解冻主干接着训。如果你拿到的源码是直接用torch.load整个模型对象而不是state_dict那换模型结构时要小心。我建议你统一使用state_dict保存权重因为它在模型类别或结构发生变化时更灵活。加载时如果有键名不匹配例如源码里fc.weight和你的新fc.weight尺寸不一致不要硬 load先执行一次匹配再手动忽略权重维度不一致的层。3.2 训练脚本里的学习率、批次和图像尺寸怎么设分类任务的超参数很多人偷懒全部沿用源码默认值。默认值在源码作者的数据集上有效换到你自己数据上不一定。学习率是最敏感的。我见过有人把初始学习率设置成 0.01然后用 MobileNet 训练五分类结果训练集精度 50%验证集精度更低。换成 0.001 之后三个 epoch 就看到了明显提升。为什么因为 MobileNet 的结构和 BatchNorm 对学习率更敏感预训练模型的特征空间已经很好了不需要用大步长去改变主干的权重。常见的做法是初始学习率取 0.001配合batch_size来调整。一个经验公式是如果你的batch_size是 64学习率 0.001batch_size减半学习率最好也乘 0.5。还有一种更稳的预热策略前几个 epoch 让学习率从很小的值线性升到目标值再用余弦衰减降到接近零。源码里如果已经写了lr_scheduler你至少要把T_max或者step_size改到和你的总训练步数一致。总训练步数等于总样本数 / batch_size * epochs这个很容易算但经常被忽略。图像尺寸也不建议随意改。源码训练时用 224x224你推理时用 256x256模型结构里的 Global Average Pooling 不会报错但直接影响了特征分布。特别是对目标检测模型输入尺寸一变锚框的感受野映射全变了抖动会非常大。如果你想要更高精度训练和推理应该使用同一个尺寸。我一般会在config里统一管理IMG_SIZE变量训练、验证、预测脚本全部引用它避免有一处写死。批次大小和显存强相关。一块 RTX 3060 12G 显存ResNet50 224x224 用batch_size64会显存溢出。遇到这种情况不要只看报错信息而是手动算一下模型参数量和单张图片的特征图大小。可以用这个脚本观察模型前向的内存占用import torch from torchvision import models model models.resnet50() img torch.randn(1, 3, 224, 224) model.eval() torch.cuda.empty_cache() with torch.no_grad(): y model(img) print(f模型参数量: {sum(p.numel() for p in model.parameters()) / 1e6:.2f}M)这里输入是一张随机图片目的是验证当前显存余量能否跑一次前向。如果这一步都爆显存说明模型本身太大或者输入尺寸不合理。实际训练时你可以在代码里加入梯度累积模拟更大的 batch比如实际 batch 16累积 4 步和 batch 64 的梯度效果相近。源码里如果没有这个功能可以自己在外层循环里判断iteration % accumulation_steps 0再 step。3.3 分类指标别只看 accuracy查准率与召回率在样本不平衡时怎么用很多源码训练完只输出一个accuracy对白酒瓶盖瑕疵检测或者皮肤病变分类这类类别极不平衡的任务这个指标没有说服力。比如你有 99% 的负样本和 1% 的正样本全预测成负样本也有 99% 准确率但模型实际毫无使用价值。我一般在训练脚本里加入混淆矩阵计算输出每个类别的 precision、recall 和 F1-score。from sklearn.metrics import classification_report import numpy as np def evaluate_model(model, dataloader, device, class_names): model.eval() all_preds, all_labels [], [] with torch.no_grad(): for images, labels in dataloader: images, labels images.to(device), labels.to(device) outputs model(images) _, preds torch.max(outputs, dim1) all_preds.extend(preds.cpu().tolist()) all_labels.extend(labels.cpu().tolist()) report classification_report(all_labels, all_preds, target_namesclass_names, digits4) print(report)这段代码在验证阶段收集所有预测和真实标签最后用classification_report打印每一项指标。digits4让输出保留四位小数比较精细。当你发现某个类别的 recall 很低而 precision 很高说明模型倾向于不预测这个类别那就要考虑增加该类别的样本权重或者做欠采样。源码里如果只给一个混淆矩阵你要自己去算这些值。这也是把源码改造成可靠系统的关键一步模型在测试集上的表现必须要和实际业务场景的代价对齐。4. 目标检测任务源码怎么改从 YOLO 系源码到自己的数据4.1 先分清检测源码的流派anchor-based 和 anchor-free 的差异目标检测源码这几年更新很快但无论怎么变核心思想分成两派。以 YOLOv5、YOLOv7 为代表的 anchor-based 方法会在特征图的每个位置预设一组不同大小和宽高比的锚框模型负责预测锚框和真实目标之间的偏移以 YOLOv8、RT-DETR 为代表的 anchor-free 方法直接预测目标的中心点和尺寸省掉了锚框聚类这一步。你在读源码时首先要搞清楚它属于哪一派因为后处理逻辑完全不同。如果拿到的是老一点的核心源码里面很可能包含anchors文件或者自动锚框计算逻辑。自动锚框的典型实现是使用 K-Means 聚类对训练集的标注框重新计算预设尺寸。源码里常见的代码长这样import numpy as np from sklearn.cluster import KMeans def auto_anchor(widths, heights, n_anchors9): wh np.stack((widths, heights), axis1) kmeans KMeans(n_clustersn_anchors, random_state0, n_init20) kmeans.fit(wh) return kmeans.cluster_centers_.tolist()这里输入是所有目标框的宽和高列表输出是 9 组锚框的宽高。为什么 YOLOv5 训练前都要跑这段脚本因为初始锚框如果和真实目标的尺度差距太大模型前期很难收敛需要多好多个 epoch 才能把预测偏移拉到合理范围。我建议你把自己数据的标注框统计好后直接算一次聚类结果替换掉源码里默认锚框。锚框个数量级是参数不能算完不管还要注意把中心点坐标转成宽高格式时保持尺度一致。anchor-free 的检测源码例如 YOLOv8则不需要聚类锚框模型直接回归目标框的边界。你更需要关注的是正负样本分配策略比如 TaskAlignedAssigner。这个策略会根据分类得分和 IoU 联合决定哪些位置是正样本不同源码实现差异较大。想改也简单但入口不是锚框而是损失函数里的assigner参数。如果源码里没暴露出来训练时可调的参数就少了这时你的重点应该放到数据质量上。4.2 把自己的数据集转成 YOLO 格式标注脚本与目录组织从源码下载到跑通最大的坑其实在数据准备。很多目标检测源码默认数据是 COCO 格式或者 YOLO 格式。COCO 用 JSON 文件组织标注信息YOLO 用每个图片对应一个 txt 文件每行表示一个目标类别编号、归一化后的中心点 x、中心点 y、宽、高。YOLO 格式直观也更容易并行读取所以我一般会先把 COCO 标注转成 YOLO 格式。下面是一个转换脚本你会经常用到import json import os coco_json annotations/instances_train.json output_dir labels/train os.makedirs(output_dir, exist_okTrue) with open(coco_json, r) as f: coco json.load(f) img_id_to_info {img[id]: img for img in coco[images]} anns_by_img {} for ann in coco[annotations]: anns_by_img.setdefault(ann[image_id], []).append(ann) for img_id, anns in anns_by_img.items(): img_info img_id_to_info[img_id] width, height img_info[width], img_info[height] lines [] for ann in anns: if ann.get(iscrowd, 0): continue category_id ann[category_id] - 1 # 取决于你的类别文件从0还是1开始 bbox ann[bbox] # COCO格式: x, y, w, h x, y, w, h bbox cx (x w / 2) / width cy (y h / 2) / height cw w / width ch h / height lines.append(f{category_id} {cx:.6f} {cy:.6f} {cw:.6f} {ch:.6f}) txt_path os.path.join(output_dir, img_info[file_name].replace(.jpg, .txt)) with open(txt_path, w) as f: f.write(\n.join(lines))脚本逻辑是遍历 COCO 标注里的每条 annotation提取目标框坐标并归一化。注意category_id - 1这行很多 COCO 数据集合的类别编号从 1 开始而 YOLO 格式要求从 0 开始。如果你的源码data.yaml里类别列表顺序跟 COCO 的类别 ID 不是一一对应那这里就要改成你自己的映射字典否则训练出来的模型预测类别全乱。转换完以后建议随机抽查几个 txt 文件把坐标乘以图片宽高还原成像素坐标再画框到原图上检查框有没有飞到图外这是最容易被忽视的步骤。4.3 训练参数imgsz、batch、epochs 和 mAP 的计算逻辑目标检测源码里imgsz是输入图像的边长YOLO 系列代码通常会要求它是 32 的倍数因为网络下采样多次最终特征图大小需要是整数。常见取 640但如果你做的是航拍图像切割目标很小可以把imgsz调到 960 或者更高。代价是显存和训练时间翻倍。有个实用经验当你发现验证集 mAP 不涨先查是不是输入分辨率太低导致小目标在特征层上只剩一个点。在 GPU 显卡显存不足时宁可使用更大的imgsz加更小的batch也不要盲目把imgsz降到 320。batch参数和workers参数在目标检测源码里更敏感。检测任务的输入图像一般比分类大显存占用也大默认 16 可能直接 OOM。除了调 batch还有一个方法是在源码里开启缓存例如 YOLOv5 的--cache ram把图像在内存中预处理好后直接喂给模型减少数据读取等待但会占内存。这个参数对 Windows 系统支持不好Linux 下可以用。如果你要融合多个数据集也要先统一成同样的标注格式和类别编号再合并到同一个 data 目录里。mAP 是目标检测最常用的指标源码里一般分mAP0.5和mAP0.5:0.95。前者只计算 IoU 阈值为 0.5 时的平均精度后者把阈值从 0.5 到 0.95 按 0.05 步长取十个档位然后平均。后者更严格也更注重定位准确度。我一般先看mAP0.5是否够用再看mAP0.5:0.95的差距。如果你只关心检测是否存在而不关心精确位置mAP0.5就够了。如果你要做测量、机械定位这类任务必须把mAP0.5:0.95拉高。源码里默认权重只评估一个指标你可以在验证脚本里修改 IoU 阈值列表让它同时输出两个值避免反复训练。5. 深度学习图像处理源码常见问题与避坑从环境到显存再到数据标注5.1 CUDA 和 PyTorch 版本不匹配报错却让你去查显卡驱动现象在本地跑一份源码刚导入 torch 就报RuntimeError: CUDA error: no kernel image is available for execution on the device。原因读源码的人直接pip install torch装上了最新版 PyTorch但电脑显卡驱动或者 CUDA 版本太老新的 PyTorch 编译进去的算子没有包含对应的 GPU 微架构。这种报错和你的显卡是不是“太老”没有必然关系本质是 CUDA 运行时和驱动不兼容。解决先用nvidia-smi看驱动支持的 CUDA 版本再用nvcc -V看本机 CUDA 版本最后到源码requirements.txt里看它锁定的 PyTorch 版本。我常见到的做法是pip install torch2.0.1cu118 torchvision0.15.2cu118来安装匹配的版本或者直接在 PyTorch 官网按标签安装。如果你不想重装环境用虚拟环境重新建一套比在原有环境里折腾少很多可能出现的意外。5.2 显存不够不是调小 batch 就完事现象训练报CUDA out of memory把batch_size从 32 调成 4很快就再次 OOM甚至第 1 个 epoch 都撑不完。原因显存峰值不仅由 batch 决定还来自特征图、优化器状态、梯度累积时的缓存。一些源码在训练时开启了torch.backends.cudnn.benchmark True搜索最佳卷积算法会临时占用额外显存。还有源码在每次验证后没有正常清理缓存导致显存碎片化。解决先保证代码里有无泄漏用torch.cuda.memory_summary()打印详细分配。然后在训练循环中定期执行torch.cuda.empty_cache()或者减少输入尺寸。如果调小 batch 和输入尺寸都不行检查是不是在forward里做了太多中间变量比如每帧图像都保留到列表里做可视化这种写法万万要不得。实在不行就把标注好的半精度训练打开PyTorch 里可以有model.half()但要注意 BatchNorm 层在小 batch 下用半精度更容易波动通常需要混合精度策略而不是全模型半精度。5.3 数据集路径里带中文或空格训练中断得莫名其妙现象训练前几轮正常到了某个 epoch 就报FileNotFoundError而且地址里明显有中文目录名。原因Python 在 Windows 系统下处理多语言路径有时会因为编码不对找不到文件而数据集放在带空格的目录下部分框架的cv2.imread也会直接返回None不会报错进一步导致后续处理时崩溃。解决把数据集复制到纯英文路径下路径里不要有空格、中文、特殊符号比如E:/projects/datasets/defect就比E:/我的数据集/缺陷检测/稳得多。如果数据集实在太大不能移动可以用cv2.IMREAD_UNCHANGED配合np.fromfile读文件但大部分源码不会这么写。更省事的方式是创建符号链接把英文路径链接到真实数据目录。这个问题在跨平台复现源码时尤其明显Linux 上中文路径问题少见Windows 上几乎是过不去的坎。5.4 标注框和图片尺寸不一致检测器训练时 loss 不降现象目标检测训练时每次 loss 下降一点又弹回原高度甚至一直震荡验证集 mAP 在 0.1 左右徘徊。原因训练时每张图按imgsz做了缩放而标注坐标没有同步缩放或者标注坐标系本身是 0-100 的百分比代码却按 0-1 除了一遍。还有更隐蔽的场景部分源码在 Mosaic 增强时会把几张图拼在一起如果标注没有跟着偏移和缩放模型会把原来框的位置学成一个错乱坐标损失函数当然没法收敛。解决设置一个验证入口在训练循环的最前面取一个 batch 的数据把图像和标注框画出来保存成一张图片看框是否和真实目标对齐。我这个习惯救过多次翻车现场。画框代码可以放在数据加载和增强后输出的图片会直接告诉你标注转换是否正确。如果发现框偏了一个偏移量很可能是图像 padding 时没有记录ratio和dx/dy导致后面映射失败。检查源码里有没有padded参数没有就补上。5.5 训练时 loss 下降验证时 mAP 为 0现象训练 loss 一路下降到很低的水平但验证阶段 mAP 始终显示 0或者置信度全是负的。原因一种可能是验证脚本里模型还在训练模式没有用model.eval()导致 BatchNorm 还在更新统计量推理输出不稳定。另一种可能是标签类别编号和推理时使用的类别编号不对齐验证脚本把所有框都归到了错误的类别。还有一种情况是后处理里置信度阈值设成了 0.9而模型输出普遍在 0.5 左右所有预测都被过滤掉了。解决先检查model.eval()有没有被调用再看源码里 NMS 的置信度阈值调低到 0.05 重新验证。如果 mAP 从 0 变成 0.2说明阈值问题如果还是 0就用上一小节提到的可视化方法把预测框画出来看看框到底有没有输出以及坐标是否正确。mAP 指标本身不会骗人但它对阈值、类别映射和数据格式特别敏感排查时按这个顺序来很少遇到解决不了的情况。6. 把源码改造成自己的应用验证集划分、推理脚本和可视化6.1 固定随机种子让实验结果可以复现源码跑通了接下来要做的事不是急着加功能而是固定随机种子。深度学习训练过程中数据加载顺序、权重初始化、数据增强都会引入随机性不固定种子的话你昨天训练和今天训练的结果可能相差好几个点。我在自己的工程里一般会写这样一段工具函数import random import numpy as np import torch def set_seed(seed42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic True torch.backends.cudnn.benchmark Falsecudnn.benchmark False是为了避免卷积算法选择的随机性代价是稍微降低一点训练速度。固定种子之后多轮训练的结果应该在很小的范围内浮动。这样你后面改任何一个参数比如把学习率从 0.001 改成 0.0008才能确定精度差异来自这个改动而不是来自随机波动。6.2 写一个最小推理脚本带上置信度过滤和 NMS源码里自带的推理代码一般会有太多依赖比如要加载训练配置文件、动态导入模型类、绑定 GPU。我要做的是绕开这些直接使用训练好的权重和简单的预处理流程。下面是一个分类和检测通用的推理骨架思路以分类为例import torch from PIL import Image from torchvision import transforms def infer_single_image(model, image_path, device, class_names): model.eval() transform transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]), ]) img Image.open(image_path).convert(RGB) tensor transform(img).unsqueeze(0).to(device) with torch.no_grad(): output model(tensor) prob torch.softmax(output, dim1) conf, idx torch.max(prob, dim1) print(f预测类别: {class_names[idx]}, 置信度: {conf.item():.4f})推理时最容易犯的错误是忘记把图片做和训练时一致的预处理并且没有unsqueeze(0)增加 batch 维度。对于检测模型推理脚本要多做一步后处理把模型输出的检测框还原到原图坐标。源码常见的 NMS 函数会接收框列表和得分列表你要注意传入的框是像素坐标还是归一化坐标这直接决定了最终框的位置。我不建议为了让脚本更“通用”而把所有后处理都堆到一个文件里还是按模型结构分开写便于出问题时单独测试。6.3 可视化预测结果别只盯着控制台的数字最后说一个我自己的习惯无论分类还是检测推理完一定要把结果画出来保存成图片。分类模型可以画在图片左上角标签和置信度检测模型直接在原图上画框和标签。肉眼看到的结果往往能暴露出指标看不出的问题比如框与目标偏移了几个像素或者置信度不高但位置很准。import cv2 def draw_detections(image, boxes, scores, class_names, conf_threshold0.5): for box, score, cls in zip(boxes, scores, class_names): if score conf_threshold: continue x1, y1, x2, y2 [int(v) for v in box] cv2.rectangle(image, (x1, y1), (x2, y2), (0, 255, 0), 2) label f{cls}: {score:.2f} cv2.putText(image, label, (x1, y1 - 5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) return image这里conf_threshold控制了最终输出的召回率画图前先对比多个阈值下的效果再选择一个适合你业务场景的平衡点。我见过很多同学训练完模型只打印验证集 mAP 然后就去写报告结果现场部署时发现框画到了背景上。可视化是最后的校验也是你的后悔药可以让你在交付前提前发现那些指标没体现过的问题。希望这些踩坑经验能帮你节省几天时间希望帮到你。本文还有配套的精品资源点击获取