
简介面向需要攻克小目标检测的计算机视觉开发者这套演示源码将YOLOv5与SAHI模块结合起来覆盖超分辨率增强、模型推理和结果可视化等完整流程可在Windows与PyCharm环境中直接复现。包内共4个文件1个Python主程序、1个YOLOv5s6预训练权重、1份Markdown运行说明和1张用于验证的测试图像整体文件大小23.05MB结构紧凑适合快速上手或作为二次开发起点。项目以PyTorch 1.7.1与CUDA 10.1为基础重点展示了SAHI先对输入图像做放大处理、再由YOLOv5完成检测的思路说明文档对环境配置、数据集组织、训练与推理调用方式均有交代能帮助读者理解数据增强、超分辅助检测等关键环节。已有503人学习对想系统掌握小目标检测工程实现或拓展YOLOv5应用场景的读者可直接参考源码、权重和操作说明减少环境搭建和调参摸索的时间。1. 基于 yolov5 SAHI 模块做超分辨率与小目标检测演示问题在哪方案怎么选做小目标检测的人都有这种体验一张航拍图里明明有几十辆车yolov5 愣是只给你检出两三辆把整图缩到 640 分辨率后一个 30×30 像素的目标直接变成 8×8特征全糊了。基于 yolov5 SAHI 模块完成超分辨率以及小目标检测演示核心思路就是不去硬改检测头而是先在输入侧做两件事用超分辨率重建把细节拉回来再用 SAHI 切片推理把大图拆成小块让模型分别“看”。这套组合适合无人机俯视图、监控摄像头远景、工业质检里的微小缺陷以及“训练集目标挺大、实际场景目标特别小”这类迁移应用。思路对了yolov5 的权重甚至不用重新训练。2. SAHI 切片推理大图预测为什么会漏切图预测为什么会稳2.1 切片推理的坐标系还原与 NMS 合并逻辑SAHI 全称 Slicing Aided Hyper Inference它解决的不是“模型不行”而是“输入尺度不匹配”。yolov5 推理时会把任意尺寸图片缩放到网络的输入尺寸比如 640×640。一张 4000×3000 的航拍图缩到 640 后一个原本 40×40 的目标只剩 6×6backbone 前面几层就已经把它抽象没了。直接提高输入分辨率如 1280显存翻倍不说目标相对面积也没变大多少。常见做法是照搬 SAHI 的“切片—预测—还原—合并”流程把大图按设定好的宽高切成若干张有重叠的切片每张切片按正常尺度送进 yolov5预测结果里,每个边界框坐标要从切片坐标系映射回原图坐标系最后对所有切片预测出的框做一次全局 NMS 合并去掉重复框。这里的关键点有两个。一是切片重叠区域必须保留否则目标恰好落在切片边缘时会被切掉一半漏检率暴增二是合并时的后处理不能简单用 torchvision 的 nms因为同一目标可能出现在多个切片上置信度不同、位置略有偏移需要用带匹配阈值的方法如 batched_nms 或 SAHI 默认的postprocess_match_threshold来收敛。坐标还原也容易踩坑SAHI 返回的object_prediction_list里的bbox已经是原图坐标系不需要手工换算但如果你自己写切片逻辑就必须记录每个切片的偏移量否则画框的位置会错得离谱。还有一个很少有人提的细节切片推理不只是为了小目标它还能让你在有限显存下跑更高分辨率的模型。同样一张 4000×3000 的图整图推理必须把整张图载入显存切片推理只需一张切片占显存显存占用从“跟图成正比”变成“跟切片大小成正比”。很多实际项目里SAHI 带来的提升甚至主要来自“输入图像不再被粗暴压缩”。2.2 跑通 SAHI 的最小命令参数与返回结构假设你手上已经有一个训练好的 yolov5 权重SAHI 的调用不算复杂。下面这段是完整的最小推理脚本建议先跑通它再改自己的数据。from sahi.model import Yolov5DetectionModel from sahi.predict import get_sliced_prediction # 1. 先创建检测模型实例内部会直接加载 yolov5 权重 detection_model Yolov5DetectionModel( model_pathweights/yolov5s.pt, # 换成你自己的权重 confidence_threshold0.25, # 低一点能留住弱目标但误检也会变多 image_size640, # 切片会缩放到这个尺寸送入模型 devicecuda:0, # CPU 就填 cpu ) # 2. 对整张大图做切片推理 result get_sliced_prediction( imagedemo/aerial_001.jpg, detection_modeldetection_model, slice_height512, # 切片高度越小越省显存但也越慢 slice_width512, # 切片宽度 overlap_height_ratio0.2, # 垂直方向重叠比例0.2 是个人常用的起点 overlap_width_ratio0.2, # 水平方向重叠比例 postprocess_match_threshold0.5, # 合并框的 IoU 匹配阈值 verbose1, # 打印每张切片耗时 ) # 3. 把预测框画回原图上注意坐标已经是原图坐标系 result.export_visuals( export_dirruns/sahi_result/, file_nameaerial_001_pred, hide_labelsFalse, ) # 4. 拿所有目标框做后续分析 for pred in result.object_prediction_list: print(pred.category.name, pred.bbox.to_cxywh())这段脚本的逻辑是先实例化 yolov5 模型然后get_sliced_prediction内部完成“切图→逐片预测→坐标还原→NMS 合并”最后export_visuals会画好标注图。值得注意的参数是postprocess_match_threshold它决定两个切片预测出的框算不算同一个目标调太低会留下大量重复框调太高会漏掉跨切片的目标。初次运行建议verbose1你会看到每张切片单独跑了一次前向对理解整个过程很有帮助。2.3 切片参数原则与整图推理的对照实验切片参数不是随便填的我一般按目标尺度反推切片尺寸至少要大于最大目标的 2 倍并且保证目标在切片内占比不超过 30%。下面给一组常用区间参数推荐值作用与边界slice_height / width320640小于 320 会让大量切片没有目标纯增加耗时大于 640 显存压力大切片本身也可能需要缩放overlap_height_ratio / width_ratio0.10.3决定了漏检率和重复框率的平衡0.2 起步边缘目标多就提到 0.25postprocess_match_threshold0.30.6合并阈值低于 0.3 重复框多高于 0.6 会把同一个目标的几个切片框拆成多个检测结果confidence_threshold0.150.3小目标置信度天然偏低0.25 左右合适需要严格精确率就调回 0.4想确认参数有没有白调做一个对照组就行同一张测试大图分别跑一次整图推理和一次 SAHI 推理对比检测框数量和小目标召回。我见过很多项目只盯着 mAP 看结果切片推理把 mAP 提了 3 个点但可视化里该漏的还在漏——这时候要检查是不是overlap_height_ratio设成了 0.05 以下相当于没留重叠。3. 超分辨率模块放大不是目的检测精度才是3.1 超分辨率在检测流程里的位置预处理、框内放大、两段式很多人以为超分辨率重建就是把图像变清楚然后直接丢进检测器。实际上超分模块在检测流程里通常放在三个位置第一检测前的整图预处理对整张大图先超分再进 SAHI第二检测后的框内放大先用低阈值检出候选区域再对每个候选框裁出小图做超分把目标细节恢复后二次分类第三两段式串联先整图超分再切片检测这是演示代码里最省事、最不容易出 bug 的做法。放在不同位置收益和代价完全不同。整图超分最简单但超分模型如果太强比如直接把 640×640 放大到 2560×2560检测速度会断崖式下降框内放大只处理潜在目标区域精度更高但依赖第一轮检测不能漏漏了就彻底没机会。实际演示项目里我一般会先做整图超分因为它的“后悔药”最容易吃——输出一张中间图随时可以对比超分前后的检测差异排查起来也直观。有一点必须说清楚超分在这里解决的并不是“图像变美”而是“让目标在缩放过程中保留可辨识的边缘和纹理”。yolov5 是卷积网络对小目标的响应本来就弱插值放大虽然也能把像素变大但边缘是糊的超分网络则是通过学习先验把高频细节补回来。比如一张 CT 影像里的微小病灶或者遥感图里的车辆直接插值放大会让边缘发虚而基于学习的超分辨率重建能给出更锐利的边界。3.2 轻量超分模型选型与参数化建议演示项目里不需要一上来就上 Real-ESRGAN 这种吃显存的庞然大物。常见做法是先用 ESPCN 或 EDSR 这种轻量模型做 2 倍超分跑通流程之后再换大模型验证收益。ESPCN 的优势在于亚像素卷积做放大速度快适合 CPU 也能跑EDSR 精度更高但参数多一倍以上。如果你的业务场景是连续的视频帧还可以叠加时序信息不过在单张图片的小目标检测 demo 里这属于过度设计。选型上我有一个比较稳的组合主体用 EDSR 2 倍超分输入尺寸限制在 1280×1280 以内目标特别小、且图像本身纹理清晰时才升到 4 倍。超分模型的参数要注意scale和rgb_rangescale必须是模型训练时的放大倍数rgb_range常见是 255但有些模型内部归一化到 1.0接错会导致输出图像整体偏暗或者发白。3.3 一个可复现的对照脚本插值超分与超分模型对比为了确认超分模块真的有效我习惯先把“插值超分”当成下限再跟模型超分对比。下面这个脚本可以同时输出两种放大结果直接喂给 SAHI 看检测效果。from PIL import Image # 加载原始小图 img Image.open(demo/crop_006.png).convert(RGB) w, h img.size # 方案 A双线性插值放大 2 倍作为基线 img_baseline img.resize((w * 2, h * 2), Image.BILINEAR) img_baseline.save(demo/crop_006_baseline.png) # 方案 B先用超分模型放大 2 倍EDSR 思路 # 常见开源实现会把模型封装成 SR 类这里标出关键步骤 from sr_model import load_sr_model, forward_sr # 假设演示包提供封装 sr_net load_sr_model(weights/edsr_2x.pt, scale2) img_sr forward_sr(sr_net, img) # 返回 PIL Image img_sr.save(demo/crop_006_sr.png) print(baseline size:, img_baseline.size) print(sr size:, img_sr.size)这段代码里的load_sr_model和forward_sr是演示包常见的封装。逻辑说明先跑插值放大目的不是跟超分模型比美而是确立一个“只放大不补细节”的下限。如果超分后的检测结果连插值都打不过那问题多半不在超分模型而在放大倍数或检测阈值。参数说明scale2决定了输出的长宽是输入的 2 倍一般不要设置成模型没训练过的倍数。4. 拿到演示源码包后怎么跑通目录梳理、运行命令、必调参数4.1 解压后的目录惯常结构与依赖项这类 zip 解压后目录结构一般包括权重文件、测试图片、推理脚本和运行说明文档。常见结构如下小目标检测演示/ # 解压后根目录 ├── weights/ │ ├── yolov5s.pt # 检测权重 │ └── edsr_2x.pt # 超分权重 ├── demo/ │ ├── aerial_001.jpg # 演示原图 │ └── crops/ # 切片或裁剪后的目标小图 ├── runs/ # 输出目录推理结果会写到这 ├── main.py # 主推理演示脚本 ├── split_and_sr.py # 切片 超分预处理脚本 ├── requirements.txt └── README.md # 运行说明依赖项一般都在requirements.txt里核心是torch、yolov5和sahi。我建议先装 torch 再装 sahi因为 sahi 安装时会自动匹配 torchvision 版本顺序反了容易出现 cuda 版本不一致的报错。虚拟环境是必须的别图省事直接装到系统 Python 里后面升级 yolov5 或者换超分模型时你会感谢当初的自己。4.2 运行主脚本从图片到检测结果主脚本通常把“超分→切片→检测→合并→可视化”串成一条链。用命令行跑是最省心的方式# 解压到当前目录并进入根目录 unzip 小目标检测演示.zip -d tiny_demo cd tiny_demo # 创建虚拟环境并激活 python -m venv .venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate # 安装依赖国内网络建议加镜像源提速 pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple # 跑推理 python main.py \ --source demo/aerial_001.jpg \ --det-weights weights/yolov5s.pt \ --sr-weights weights/edsr_2x.pt \ --save-dir runs/demo_out逻辑说明--source是输入图片路径也可以是文件夹脚本会逐张处理--det-weights和--sr-weights分别指定检测权重和超分权重--save-dir是结果输出目录。跑通后runs/demo_out里至少应该出现三样东西超分后的原图、画了检测框的可视化图、一个inference_time.txt或 JSON 格式的耗时记录。注意源码包里的main.py不一定叫这个名字也可能是detect_demo.py看 README 开头用哪个入口就行。4.3 三个必调参数与一个开关这个 demo 里有三个参数几乎每次都得起slice_size、overlap_ratio、sr_scale。下面根据我的实践给出一个稳妥的初始配置参数我所用值设置逻辑slice_size512平衡“目标占比”和“显存开销”多数演示图和权重在这个值下不会出现目标被过度压缩overlap_ratio0.2保留边缘目标同时控制重复框数量后续微调幅度建议在 0.10.25 之间sr_scale2超分放大倍数放大 4 倍视觉冲击强但检测收益通常不涨反跌伪影也会干扰分类还有一个容易被忽略的开关是save_crops或export_crops它会把检测到的每个小目标单独裁图保存。第一次跑建议打开这样你能直观看到每个目标在原始分辨率下到底长什么样判断是“目标本身就模糊”还是“超分没做好”。关掉它只留整图可视化排查问题时相当于少了一只眼睛。4.4 输出怎么判断“跑通了”跑通不是不报错就算完要检查三点第一runs/demo_out里有没有生成的图片第二可视化图里检测框坐标是否跟原图对齐如果框飘到目标旁边大概率是切片坐标还原的偏移量算错第三超分前后的检测数量有没有明显变化如果两者一样说明目标尺度还没被压到模型失明的地步SAHI 和超分的收益没体现出来。可以用一段简单的 Python 脚本自查import os, glob out_dir runs/demo_out imgs glob.glob(os.path.join(out_dir, *.jpg)) assert len(imgs) 0, 输出目录为空检查 --save-dir 或 main.py 是否成功执行 # 检查可视化文件名确认是超分后处理过的图 pred_files [f for f in imgs if pred in f] print(f发现 {len(pred_files)} 张预测可视化图)5. 避坑与常见问题排查切片重叠、重复框、显存不足、超分负优化5.1 切片后仍然漏掉小目标现象用 SAHI 跑完后输出图里没有新增小目标和整图推理结果几乎一样。原因有两种可能一是切片尺寸设得太大比如slice_size1024切片缩放后小目标占比依然很低等于换汤不换药二是重叠比例设太小比目标尺寸还小目标恰好被切分到两张切片的边缘。解决把slice_height和slice_width降到 512 甚至 384overlap_height_ratio提到 0.25重新跑一遍。如果还是漏去检查confidence_threshold小目标置信度经常在 0.2 到 0.35 之间0.5 会直接过滤掉这也是最常见的翻车点。5.2 检测框大量重复现象切片推理结果里同一个目标被框了三四次框的位置还差几个像素。原因postprocess_match_threshold太低放宽了合并条件也可能是重叠率很高同一目标在多个切片上被反复预测而 NMS 合并阶段没有把高 IoU 的框压掉。解决逐步调高postprocess_match_threshold从 0.5 往 0.7 试观察重复框数量。注意这个阈值不是越大越好0.8 以上会把紧挨着的真实目标合并成一个框这个只能靠可视化判断没有绝对标准。5.3 显存、内存不足与速度过慢现象跑大图时 GPU 显存没爆但运行到切片阶段内存持续上涨最后整个程序被系统杀掉。原因SAHI 默认会先把所有切片切成 np.ndarray 列表存放大图切片数量多时内存直接翻车。解决改用sahi.predict.predict里的batch_size参数控制一次推理的切片张数常见做法是把batch_size降到 4 或 2同时用devicecuda:0强制让切片直接走 GPU。速度过慢则检查是否对整张 8000×6000 的图做了 4 倍超分输出变成 32000×24000后面切片数量跟着指数上涨。先降sr_scale到 2再考虑批处理优化。5.4 超分之后检测精度下降现象加了超分模块后检测框数量变多但很多框打在背景纹理上错检反而上升。原因超分模型会把高频噪声也当成细节放大特别是地面纹理和树冠区域特征被强化后yolov5 更容易误激活。解决把超分后的图像转为检测输入之前降低一次输入分辨率比如超分 2 倍后再缩到 1.5 倍相当于只用了超分的一部分细节另外把confidence_threshold从 0.25 提到 0.35过滤掉被放大的伪影。这个属于吃一堑长一智的问题超分不是万能的它只对“目标本身有一定边缘只是被过采样糊掉”的场景有效。6. 进阶验证用 RA 指标和小目标模板评估效果别只看 mAP6.1 RA 指标与小目标区域人工计数评估很多人跑完 demo 只看可视化图觉得“看起来框多了”就收工。我会建议自己动手算一个 RA 指标Recall per Area也就是在小目标区域单独统计召回率。做法是从测试图里挑出小目标最密集的 3 到 5 个区域手动数清楚目标总数然后统计检测框命中了其中几个。下面是我常用的一段统计脚本# 把人工标注的框存成列表格式为 [x1, y1, x2, y2] gt_boxes [[120, 340, 152, 372], [480, 210, 511, 241]] # 从 SAHI 结果里取预测框 from sahi.utils.coco import CocoPrediction pred_boxes [] for pred in result.object_prediction_list: if pred.category.name car: bx pred.bbox.to_xyxy() pred_boxes.append(bx) # 计算命中数IoU 超过 0.5 算命中 def iou(a, b): x1, y1 max(a[0], b[0]), max(a[1], b[1]) x2, y2 min(a[2], b[2]), min(a[3], b[3]) inter max(0, x2 - x1) * max(0, y2 - y1) area_a (a[2] - a[0]) * (a[3] - a[1]) area_b (b[2] - b[0]) * (b[3] - b[1]) return inter / (area_a area_b - inter 1e-6) hit 0 for gt in gt_boxes: if any(iou(gt, pb) 0.5 for pb in pred_boxes): hit 1 print(f小目标召回率: {hit}/{len(gt_boxes)})这个脚本的价值在于它把评估目标固定在最容易翻车的小目标上比看整图 mAP 诚实得多。mAP 会被大量容易检测的大目标拉高小目标表现差也可能被藏住。我自己的习惯是每次改切片参数都重算一次 RA确认那些手工标注的疑难样本有改善而不是只盯着总 AP。6.2 把流程整装部署的落地做法如果这套流程在 demo 上验证有效下一步就是把三块逻辑做成独立服务超分模块、SAHI 切片推理、结果合并。检测部分可以先尝试把main.py里固定写死的模型路径和阈值提成配置项再考虑把 yolov5 导出成 ONNX。在实际项目里我一般先保证流程稳定再谈优化速度很多人在 demo 上追求实时结果切片加超分叠在一起帧率掉到个位数最后又退回去做整图检测得不偿失。最后一个实战习惯每次调完参数把原始图、超分后的中间图、检测结果图都保存下来做成一个“病例库”。下次遇到新的小目标检测情况先回翻病例库而不是重新开始调参。这个习惯帮我避开了很多重复踩坑也希望帮到你。本文还有配套的精品资源点击获取