资讯动态

一套可运行的深度学习图像处理源码:分类与目标检测工程实战

发布时间:2026/9/16 14:45:07 来源:尧图企业网站定制
简介一套基于Python的深度学习图像处理设计源码面向计算机视觉研究者和开发者解决图像分类、目标检测及图像分割等场景下的模型训练、验证与部署问题。资源共436个文件以360个Python脚本为主辅以30个JSON配置、25个TXT说明、10张PNG图片和2个Markdown文档另含少量cfg、yaml、pbtxt等模型与运行配置文本与Markdown文件提供项目说明、使用指南和问题记录压缩包仅4.13MB。项目按pytorch_classification、pytorch_object_detection、pytorch_segmentation、deploying_service等模块组织分别涵盖图像分类器、基于PyTorch的Faster R-CNN与YOLO等检测算法、分割网络以及模型上线服务Python脚本中整理了训练、验证、测试代码与图像处理工具JSON文件保存训练参数和模型结构便于按需调整。已有351人学习下载适合需要快速搭建图像处理流程、学习PyTorch视觉项目或在二次开发中获取完整参考实现的工程师与研究者。1. 这个标题解决的是一套能跑的分类与目标检测源码工程打开GitHub搜索图像处理深度学习源码一半是教学用的Jupyter另一半是README写得很漂亮但pip install完跑不通的“僵尸仓库”。真正能在业务里用起来的不是某个孤立模型而是把数据加载、模型构建、训练、评估、导出按模块拆开让图像分类和目标检测共用同一套工程骨架的Python源码。这类源码要解决两类问题新接手的人能沿着文件路径快速复现训练老手想换数据集、换骨干网络或换检测头时不需要推倒重写。下文基于“分类检测”双任务给出一个可运行的源码工程设计覆盖目录怎么分、数据管线怎么统一、分类训练要动哪几个参数、目标检测如何从COCO标注起步以及最后怎么用统一接口验收浮点模型。适合正在搭Python视觉基础设施的算法工程师也适合想把深度学习图像处理能力接进服务里的后端开发。2. 基于Python的深度学习图像处理源码工程骨架与数据管线2.1 把源码拆成“数据、模型、训练、评估”四层而不是按业务拆图像分类和目标检测在训练层面的差异比很多人以为的小得多。两者都要做图像解码、归一化、增强、打包batch、计算loss、反向传播、保存最优权重。如果按照“分类项目”和“检测项目”拆两个独立仓库你会发现一半以上的工具函数是复制粘贴的后续改学习率策略或换评估指标时要改两遍。常见做法是把源码按照职责切层datasets只负责把磁盘上的图像变成Tensor和标签models只负责构建网络并输出预测trainer负责优化器、调度器、验证循环和checkpointevaluate负责分类准确率、目标检测mAP这类指标。这样分类和检测在“骨架”上完全一致只有数据格式、模型头和loss不同。代码量没有变多可维护性却翻了几倍。2.2 一个可直接git init的目录布局启动一个项目时我通常会先落成一个最小目录结构然后才写训练代码。参考布局如下image_processing_src/ ├── configs/ # 各任务的YAML/JSON配置 │ ├── classification.yaml │ └── detection.yaml ├── datasets/ │ ├── __init__.py │ ├── base_dataset.py # 统一图像Dataset │ └── transforms.py # 分类与检测共用的图像增强 ├── models/ │ ├── __init__.py │ ├── classifier.py # 分类模型构建与头替换 │ └── detector.py # detection模型构建与头替换 ├── trainer/ │ ├── __init__.py │ ├── train_classifier.py │ └── train_detector.py ├── evaluate/ │ ├── accuracy.py # top-1/top-5 │ └── coco_eval.py # mAP ├── utils/ │ ├── checkpoint.py # 保存/恢复断点 │ └── export_onnx.py ├── scripts/ │ ├── run_classify.sh │ └── run_detect.sh ├── inference.py # 统一推理入口 └── requirements.txtconfigs目录承担了“设计配置”数据集路径、模型名、epoch、学习率、图像尺寸、batch size都写在里面训练代码里不出现魔法数字。datasets与models之间通过固定的返回协议通信——分类返回(images, labels)检测返回(images, targets)后续章节会展开说明。2.3 用 Dataset 和 DataLoader 统一图像输入一个快速的起点是写一个BaseDataset让它同时支持“按文件夹读分类数据”和“按COCO JSON读检测数据”。代码里判断task_type对外暴露相同的__getitem__接口训练循环不需要知道自己正在跑哪个任务。# datasets/base_dataset.py from pathlib import Path from torch.utils.data import Dataset from PIL import Image class BaseDataset(Dataset): def __init__(self, root, task_typeclassification, transformNone, coco_jsonannotations.json): self.root Path(root) self.task_type task_type self.transform transform self.samples [] # 统一存 (image_path, label_or_target) self.class_names [] if task_type classification: self._load_classification() elif task_type detection: self._load_coco(coco_json) else: raise ValueError(fUnsupported task_type: {task_type}) def _load_classification(self): # 目录结构: root/class_name/*.jpg dirs sorted([p for p in self.root.iterdir() if p.is_dir()]) self.class_names [d.name for d in dirs] for label, d in enumerate(dirs): for img in d.glob(*.{[jJ][pP][gG],[pP][nN][gG]}): self.samples.append((str(img), label)) def _load_coco(self, coco_json): from pycocotools.coco import COCO self.coco COCO(str(self.root / coco_json)) for img_id in sorted(self.coco.getImgIds()): info self.coco.loadImgs(img_id)[0] ann_ids self.coco.getAnnIds(imgIdsimg_id) boxes [] labels [] for ann in self.coco.loadAnns(ann_ids): x, y, w, h ann[bbox] boxes.append([x, y, x w, y h]) labels.append(ann[category_id]) if boxes: self.samples.append((str(self.root / info[file_name]), {boxes: boxes, labels: labels})) def __len__(self): return len(self.samples) def __getitem__(self, idx): path, target self.samples[idx] image Image.open(path).convert(RGB) if self.transform: image self.transform(image) return image, target这段代码把“分类读目录、检测读COCO”两个逻辑收进一个类调用方只需要在创建Dataset时指定task_type。__getitem__里没有做归一化和Tensor转换我习惯把它放进transforms.py让任务无关的预处理独立出来方便之后统一调整。注意pycocotools是额外依赖requirements.txt里要加上pycocotools2.0.6否则实例化COCO会直接报错。# datasets/transforms.py from torchvision import transforms def get_classification_transform(trainTrue, input_size224): if train: return transforms.Compose([ transforms.RandomResizedCrop(input_size, scale(0.7, 1.0)), transforms.RandomHorizontalFlip(), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ]) return transforms.Compose([ transforms.Resize((input_size, input_size)), transforms.ToTensor(), transforms.Normalize(mean[0.485, 0.456, 0.406], std[0.229, 0.224, 0.225]) ])上面的RandomResizedCrop只做随机裁剪和翻转。你在业务里遇到的图像如果是细长条文本或卫星遥感图这两个增强就不够了需要换成RandomAffine或保持原始宽高比缩放。归一化均值和标准差沿用ImageNet统计量这是在PyTorch生态里最稳妥的默认值换预训练模型时不需要改。下表是分类任务常用增强参数的一个参考参数训练值验证值使用场景Resize 尺寸224/256/288224ResNet、MobileNet默认随机裁剪比例0.7~1.0无目标占比较大时RandomHorizontalFlipp0.5无非左右对称场景禁用Normalize均值0.485,0.456,0.406同左ImageNet预训练模型Normalize方差0.229,0.224,0.225同左保持分布一致需要强调验证集的Normalize参数必须与训练集完全一致。很多源码里验证集忘了写Normalize导致精度直接掉20个百分点这类问题在数据集更换时尤其隐蔽。3. 图像分类核心训练源码与3个必调参数3.1 模型构建torchvision 与 timm 的选择图像处理源码里分类部分最容易做“替身式开发”——换掉fc层接上自己的类别数。我一般会写一个build_classifier函数同时兼容torchvision.models和timm这样后续换骨干网络时只改配置不动训练代码。# models/classifier.py import torch.nn as nn import torchvision.models as tv_models def build_classifier(model_name, num_classes, pretrainedTrue): 支持 tv_resnet50 / resnet50.a1_in1k 这类名字。 if model_name.startswith(tv_): name model_name[3:] weights IMAGENET1K_V1 if pretrained else None model getattr(tv_models, name)(weightsweights) in_features model.fc.in_features model.fc nn.Linear(in_features, num_classes) return model import timm model timm.create_model(model_name, pretrainedpretrained, num_classesnum_classes) return model这个函数的关键是“拿到模型后只动最后一层”。对于ResNet分类头叫fc对于MobileNetV3分类头可能是classifier[1]。timm统一了接口create_model传入num_classes就能直接覆盖原头省去判断。tv_前缀是我自定义的命名规则目的是在配置里区分两个来源避免resnet50这样的名字模棱两可。3.2 训练循环验证与早停模型训练其实是一个“最小可跑训练器”加上验证逻辑。下面的代码刻意保持了简洁保留最核心的优化器、损失、余弦退火和早停保存。# trainer/train_classifier.py import torch import torch.nn as nn from torch.utils.data import DataLoader def train_classifier(model, train_loader, val_loader, epochs, lr, device): criterion nn.CrossEntropyLoss() optimizer torch.optim.SGD(model.parameters(), lrlr, momentum0.9, weight_decay5e-4) scheduler torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_maxepochs) best_acc, best_state 0.0, None for epoch in range(epochs): model.train() total_loss 0.0 for images, labels in train_loader: images, labels images.to(device), labels.to(device) optimizer.zero_grad() logits model(images) loss criterion(logits, labels) loss.backward() optimizer.step() total_loss loss.item() * images.size(0) # 验证 model.eval() correct total 0 with torch.no_grad(): for images, labels in val_loader: images, labels images.to(device), labels.to(device) preds model(images).argmax(dim1) correct (preds labels).sum().item() total labels.size(0) acc correct / total print(fepoch{epoch1} loss{total_loss/len(train_loader.dataset):.4f} val_acc{acc:.4f}) if acc best_acc: best_acc acc best_state {k: v.cpu().clone() for k, v in model.state_dict().items()} scheduler.step() return best_state, best_acc代码使用SGDmomentum而不是Adam因为图像分类在ImageNet预训练权重上微调时SGD的泛化通常更好。weight_decay5e-4是对ResNet等大模型常见的设置换成MobileNet这类小模型我一般降到1e-4。CosineAnnealingLR配合T_maxepochs会从初始lr平滑衰减到接近0省去手动调阶段的麻烦。注意这里没有做断点续训真实项目里建议每隔几个epoch保存带optimizer和scheduler的checkpoint防止训练到一半被机房断电打断。3.3 3个必调参数lr、batch_size、input_size新手最常陷入“换网络、调loss”的误区而精度问题往往来源于下面三个参数。参数推荐起点调试方向观察方法学习率 lrSGD: 0.01AdamW: 1e-4loss震荡则降10倍收敛太慢则升5倍记录每个epoch lossbatch_size单卡8~128按显存取最大偶数显存不足就梯度累积不提batch查看nvidia-smi占用input_size224ResNet/MobileNet小图用160/192大目标用320/384对比验证集准确率batch_size不直接影响单次模型质量但它决定BN的统计估计。当你把batch从64降到4会发现验证集准确率垮掉这是因为BN在少量样本上估算的均值方差不准此时要改用SyncBatchNorm或尽量增大batch。input_size必须与预训练模型匹配例如efficientnet_b0默认使用224efficientnet_b6对应528用错尺寸后模型会自动插值但会丢一部分预训练分布的信赖。3.4 导出与验证浮点模型训练完“设计源码”还没有闭环要把模型导出成ONNX并确认导出前后推理结果一致。导出的代码很短但陷阱集中在input_names和opset_version。# utils/export_onnx.py import torch def export_onnx(model, onnx_path, input_size(1, 3, 224, 224)): model.eval() dummy_input torch.randn(*input_size, devicecuda) torch.onnx.export(model, dummy_input, onnx_path, input_names[images], output_names[logits], opset_version17, dynamic_axes{images: {0: batch}, logits: {0: batch}})dynamic_axes允许推理时batch大小可变否则ONNX只能按input_size固定batch。导出后可以用onnxruntime做一次一致性校验比较PyTorch输出与ONNX输出的最大绝对误差参考值在1e-5以内。若差距过大优先检查模型里有没有nn.Dropout或trainingTrue的BN导出前必须保证模型处于eval状态。4. 目标检测应用从COCO标注到训练配置4.1 目标检测的预处理比分类更谨慎目标检测除了图像本身还要处理框的坐标。分类模型的输入是定尺寸224检测模型输入往往是不定尺寸的框架内部会做不同尺度的特征提取。因此设计检测训练源码时不要直接把分类的Resize((224,224))搬过来否则框坐标会错位。检测源码里图像的预处理通常只做短边缩放和长边限制例如短边至少800像素、长边不超过1333像素然后原图比例进入网络。这里有一个关键选择是自己实现Faster R-CNN的数据管线还是直接用Ultralytics YOLO那种训练器。如果你想保住“设计源码”的控制权我推荐先用torchvision自带的Faster R-CNN作为基线因为它的数据格式是标准COCOtargets结构改起来容易理解。4.2 用 torchvision 的 Faster R-CNN 做基线torchvision的fasterrcnn_resnet50_fpn支持预训练权重并且暴露了roi_heads.box_predictor可以直接替换成自己的分类器。下面这段代码就是检测源码里最常见的“换头”操作。# models/detector.py import torchvision from torchvision.models.detection.faster_rcnn import FastRCNNPredictor def build_detector(num_classes): model torchvision.models.detection.fasterrcnn_resnet50_fpn( weightsDETECTRON2_COCO ) in_features model.roi_heads.box_predictor.cls_score.in_features model.roi_heads.box_predictor FastRCNNPredictor(in_features, num_classes) return modelDETECTRON2_COCO权重在COCO上预训练num_classes需要把背景算进去也就是你的业务类别数加1。训练时数据loader返回的targets必须是包含boxes和labels的字典boxes格式为(x1, y1, x2, y2)labels从1开始0固定留给背景。训练循环比分类多一步loss汇总# trainer/train_detector.py def train_one_epoch(model, train_dataloader, optimizer, device): model.train() for images, targets in train_dataloader: images [img.to(device) for img in images] targets [{k: v.to(device) for k, v in t.items()} for t in targets] loss_dict model(images, targets) loss sum(v for v in loss_dict.values()) optimizer.zero_grad() loss.backward() optimizer.step() # loss_dict 包含 classification, box_reg等子项可打印观察loss_dict里的键典型有loss_classifier、loss_box_reg、loss_objectness、loss_rpn_box_reg。如果看到loss_box_reg始终不下降优先怀疑boxes坐标归一化有误尤其是从XML标注转过来时坐标可能还是像素值而模型内部归一化了。4.3 目标检测必调参数尺寸、RoI正负样本、NMS阈值检测模型有很多“潜藏”的参数训练代码里不一定显式看到但它们决定最终mAP。常用的参数配置如下参数默认推荐调整场景说明min_size / max_size800 / 1333小目标多时提高max_size到1600控制图像输入尺寸越大越耗显存rpn_batch_size_per_image256小目标密集场景降到128RPN采样正负样本总数rpn_positive_fraction0.5负样本过多时降到0.3避免模型全部预测背景box_nms_thresh0.5同类重叠多可调0.4非极大抑制的IoU阈值box_score_thresh0.05推理时提高至0.3~0.5过滤低置信度框修改这些参数要直接改model.roi_heads或model.rpn属性例如model.roi_heads.nms_thresh 0.4 model.roi_heads.score_thresh 0.05注意score_thresh在训练时应保持很低它只影响推理时的输出训练时改了会直接丢弃低质量框破坏正样本统计。4.4 评估用 COCOeval 计算 mAP检测的评估比分类复杂常见做法是先把预测结果整理成COCO提交格式再调用pycocotools的COCOeval# evaluate/coco_eval.py from pycocotools.coco import COCO from pycocotools.cocoeval import COCOeval def evaluate_coco(model, val_dataloader, coco_gt): model.eval() results [] with torch.no_grad(): for images, image_ids in val_dataloader: preds model([img.to(device) for img in images]) for img_id, pred in zip(image_ids, preds): boxes pred[boxes].cpu().numpy() scores pred[scores].cpu().numpy() labels pred[labels].cpu().numpy() for box, score, label in zip(boxes, scores, labels): x1, y1, x2, y2 box results.append({ image_id: int(img_id), category_id: int(label), bbox: [float(x1), float(y1), float(x2 - x1), float(y2 - y1)], score: float(score) }) coco_dt coco_gt.loadRes(results) coco_eval COCOeval(coco_gt, coco_dt, bbox) coco_eval.evaluate() coco_eval.accumulate() coco_eval.summarize() return coco_eval.stats[0] # mAP0.5:0.95注意那个bbox转换COCO要求的是(x, y, w, h)很容易写成x2, y2导致评估结果直接崩掉。这里coco_eval.stats[0]是mAP[0.5:0.95]stats[1]是mAP0.5。作为源码设计建议把stats里的10个指标都记录到日志不要只保留一个。5. 源码收官统一推理接口与一致性验签5.1 把分类和检测收敛到同一个run_inference()入口服务端不会关心模型内部是分类还是检测它只想要“给我一张图返回结构化结果”。因此在源码工程里加一个统一推理函数把两种任务的输出包装成一致的字典# inference.py import torch def run_inference(model, image_tensor, task_type): with torch.inference_mode(): if task_type classification: logits model(image_tensor.unsqueeze(0)) probs torch.softmax(logits, dim-1) return { task: classification, label: int(logits.argmax(dim-1)), probability: float(probs.max()) } elif task_type detection: pred model([image_tensor])[0] return { task: detection, boxes: pred[boxes].cpu().tolist(), scores: pred[scores].cpu().tolist(), labels: pred[labels].cpu().tolist() }这个函数要求输入已经是标准化后的Tensor。用它做推理时image_tensor的预处理必须和训练完全一致否则会出现“训练准确率95%实际接口预测却和随机差不多”的情况。常见做法是把预处理函数单独放到datasets/transforms.py并导出成JSON配置这样服务端调用时直接读取配置来决定是否强制缩放和Normalize。5.2 用 Profiler 定位预处理瓶颈图像处理源码上线后最常见的性能问题不在模型卷积而在图像解码和缩放。用torch.profiler可以快速定位from torch.profiler import profile, ProfilerActivity with profile(activities[ProfilerActivity.CPU, ProfilerActivity.CUDA]) as prof: for _ in range(20): run_inference(model, sample_tensor, classification) print(prof.key_averages().table(sort_bycpu_time_total, row_limit10))如果看到Resize和Normalize占CPU时间很长就把前置预处理挪到头像解码阶段或改用torchvision.transforms.functional配合GPU完成。如果DataLoader的num_workers为0训练速度会卡在IO应当至少设置4。5.3 浮点一致性校验让每一次导出都有据可查分类用ONNX对比logits的最大差值检测则对比同一张图上预测框的IoU。比较有效的方式是保存一个“黄金单测”固定三张测试图和期望输出任何一次训练代码改动都重新跑一遍保证没有回归。def verify_onnx_consistency(pt_model, ort_session, sample_input, task_type): with torch.inference_mode(): pt_out pt_model(sample_input) ort_out ort_session.run(None, {images: sample_input.numpy()})[0] if task_type classification: diff float((pt_out - torch.from_numpy(ort_out)).abs().max()) assert diff 1e-4, f浮点偏差过大: {diff} elif task_type detection: # 简化比较前5个预测框的IoU重叠阈值0.95 pass对于检测模型还要验证导出前后model.eval()状态是否遗漏。Faster R-CNN里如果有torchvision.ops.nmsONNX导出时opset_version需要≥11建议直接用17。把verify_onnx_consistency写进CI脚本以后换timm骨干或调整roi_heads参数时就不会让服务侧悄悄出现不可解释的精度下滑。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价