航拍屋顶识别用YOLOv8:数据、训练到RK3588部署全指南

发布时间:2026/10/11 22:24:13

航拍屋顶识别用YOLOv8:数据、训练到RK3588部署全指南 简介面向航拍影像中的屋顶目标识别任务提供了一份基于YOLOv8的完整项目代码适合无人机遥感、地理信息处理与计算机视觉方向的学习者、研究人员及工程技术人员参考复现。压缩包共467个文件整体大小约23.41MB内含基于Python的训练与推理脚本、用于调整网络结构和超参数的配置文件、训练好的模型权重、便于工程落地的C部署示例以及命令行辅助脚本和交互式示例笔记本同时附带说明文档和环境依赖清单可方便地梳理代码模块、复现网络训练与推断流程。目前已有127人学习下载。借助预训练权重可直接完成屋顶目标检测推理通过参数配置可调整训练策略C示例则为后续部署提供参考按环境依赖清单配置好运行环境后即可使用也能在此基础上替换自有航拍数据集进行迁移学习、二次开发是轻量但完整的目标检测实战范本。1. 航拍屋顶识别用 YOLOv8先想清楚这四件事再开工做城市规划普查、屋顶光伏评估或者灾后房屋损毁摸底时你手头往往是一张几千像素见方的航拍正射影像。拿 YOLOv8 做目标检测模型本身不会因为你喂的是航拍图就自动学会“屋顶”长什么样。我见过太多人把无人机影像直接缩到 640×640 丢进默认配置结果 mAP50 看着有 0.85放大一看蓝色铁皮棚一个没框住道路画得比屋顶还整齐。航拍屋顶识别真正要解决的不是“YOLOv8 跑不跑得动”而是俯视视角下目标尺度极端、背景纹理复杂、类别边界模糊这三件事。这篇文章按数据准备、训练调参、推理部署、踩坑记录的顺序给你一条能直接照着走通的落地路径。适合刚接触目标检测、手头已有一批航拍影像但没做过标注和训练的从业者也适合被小屋顶漏检折磨过、想搞清楚参数为什么这么设的熟手。2. 准备航拍屋顶数据集公开数据与自建标注怎么取舍2.1 航拍屋顶和普通目标检测数据差在哪航拍屋顶识别训练一个可靠模型首先解决数据定义问题。自然场景的目标检测数据集大都是平视视角目标边缘清楚类别之间差异大到靠颜色就能区分。航拍屋顶是纯俯视视角问题一下子变复杂了。第一个差异是尺度分布极度不均衡。同样是“屋顶”一栋大型厂房可能占 800×600 像素一间临时彩钢棚可能只有 40×30 像素两者在图像里的尺度差了两个数量级。YOLOv8 的检测头本身是尺度敏感的你在 640×640 输入下训练小目标特征在深层 feature map 里已经几乎消失。很多教程教你把图缩放到 640 输入但航拍屋顶识别我一般不建议这么做。第二个差异是类别模糊。沥青屋顶、水泥平顶、彩钢瓦屋顶在可见光影像里颜色和纹理非常接近道路、停车场、硬化地面。这导致误检率天然偏高。如果你用的是红绿蓝三通道影像而航拍影像里包含多光谱波段能把近红外通道一起用上屋顶和植被的区分会容易得多。第三个差异是航拍图通常极大。单张正射影像少则 4000×3000大则超过两万像素不可能整张喂进模型。切块策略——也就是常说的切图——不是可选项是必须项。数据来源方面常见做法是两条腿走路。公开数据集方面可以找遥感建筑分割或航空目标检测类的开源数据比如某些城市航空影像标注集但公开数据里“屋顶”的类别定义和你业务里的“屋顶”往往对不上——有的标成 building有的标成 roof而你的业务可能要区分平顶、坡顶、彩钢瓦。另一条路是自建标注。我的经验是如果标注预算有限优先保证覆盖度和负样本而不是一味追求数量。500 张充分覆盖不同屋顶材质和拍摄角度的图比 5000 张单一住宅区的图对最终模型的提升大得多。2.2 把 VOC 标注转成 YOLO 训练格式转换脚本与四个边界坑不管你用什么标注工具——LabelImg、X-AnyLabeling 或者在线平台——最常见的导出格式是 VOC XML 或者 COCO JSON。YOLOv8 用的是 Ultralytics 格式每张图片对应一个同名 .txt 文件每行是“类别编号 x_center y_center width height”全部归一化到 0 到 1。下面这个脚本处理 VOC XML 转 YOLO 格式我在实践中反复用到注意几个边界条件它也一并处理了。pythonimport os import xml.etree.ElementTree as ET from pathlib import Path def voc_to_yolo(xml_path, img_width, img_height, class_names, out_dir): 把单个VOC XML标注转成YOLO格式TXT文件。 xml_path: VOC标注文件路径 img_width, img_height: 原图尺寸用来归一化坐标 class_names: 类别列表索引即类别编号必须与最终训练配置一致 tree ET.parse(xml_path) root tree.getroot() # 优先读取XML里的图像尺寸防止外部传入尺寸不一致 size root.find(size) if size is not None: img_width int(size.find(width).text) img_height int(size.find(height).text) yolo_lines [] for obj in root.findall(object): cls_name obj.find(name).text if cls_name not in class_names: # 遇到标注文件里出现类别表外的标签跳过而不是报错 print(f跳过未知类别 {cls_name}文件 {xml_path}) continue cls_id class_names.index(cls_name) bnd obj.find(bndbox) xmin float(bnd.find(xmin).text) ymin float(bnd.find(ymin).text) xmax float(bnd.find(xmax).text) ymax float(bnd.find(ymax).text) # 边界裁剪部分标注工具的框会略微超出图像范围不裁剪会导致 # YOLO归一化坐标1训练时直接被过滤或引发NaN损失 xmin max(0, min(xmin, img_width - 1)) ymin max(0, min(ymin, img_height - 1)) xmax max(0, min(xmax, img_width - 1)) ymax max(0, min(ymax, img_height - 1)) # 过滤退化框有些误标注产生的框宽或高为0直接跳过 if xmax xmin or ymax ymin: print(f跳过退化框: {xml_path} - {cls_name}) continue x_center (xmin xmax) / 2 / img_width y_center (ymin ymax) / 2 / img_height box_width (xmax - xmin) / img_width box_height (ymax - ymin) / img_height # 个别标注框的某个坐标恰好等于图像尺寸时归一化值可能算成1.0 # 虽然概率很低但1.0边界值在某些增强策略下会越界统一钳位到1 x_center min(x_center, 0.999999) y_center min(y_center, 0.999999) box_width min(box_width, 0.999999) box_height min(box_height, 0.999999) yolo_lines.append(f{cls_id} {x_center:.6f} {y_center:.6f} {box_width:.6f} {box_height:.6f}) if yolo_lines: out_path Path(out_dir) / (Path(xml_path).stem .txt) with open(out_path, w) as f: f.write(\n.join(yolo_lines))这段脚本有四个值得注意的边界处理。第一是 xmax/ymax 超过原图尺寸的情况VOC 格式允许标注框略微溢出边界但不裁剪会让归一化坐标超过 1Ultralytics 训练时这些框会被当作无效标注丢掉导致你以为标了 2000 个框实际只有 1800 个参与训练。第二是退化框过滤有些标注者手滑拉出一个零宽度框这是纯噪声留着会搅乱损失计算。第三是未知类别处理一个数据集中偶尔混入别的标注工具导出的类别名建议跳过并打印出来而不是直接崩溃方便排查。第四是对坐标精确值做 0.999999 钳位这个在绝大多数情况下不会触发但一旦触发能省掉你训练到一半发现损失变成 NaN 的排查时间。2.3 训练集/验证集划分与 classes.txt 的坑数据格式转换完成后接下来是划分训练集和验证集。YOLOv8 的目录结构约定是 images 和 labels 两个根目录下面各自有 train 和 val 子目录。常见做法是把数据按 8:2 或 9:1 划分。航拍数据有个特殊性同一块区域的重叠影像不能同时出现在训练集和验证集。如果一张图切出来的多个瓦片来自同一片区域按文件随机划分会把高度相似的样本拆到两边验证集分数虚高测试时面对真正的新区域效果直接打折。正确的做法是先按地理网格或影像来源分组再按组划分。类别配置文件里也有一个高频踩坑点。data.yaml 中的 names 列表顺序必须和你转换标注时用的 class_names 列表顺序完全一致。很多人标注时把“平屋顶、彩钢瓦屋顶、坡屋顶”三轮标注分别导出最后合并时才去排序 classes.txt结果是编号 0 在训练集里代表平屋顶在验证集里代表彩钢瓦屋顶训练过程完全不报错但 mAP50 一塌糊涂。我一般转换完数据后会写一行命令抽查几个 txt 文件人工看一眼每行开头的编号是否符合预期。这个检查三十秒能完成却能省掉几十次的返工。yaml# data.yaml - 修改前一定要逐行核对names顺序 # 注意names列表顺序必须与标注转换时的class_names完全一致 names: 0: flat_roof # 平屋顶 1: steel_roof # 彩钢瓦屋顶 2: sloped_roof # 坡屋顶如果说数据准备阶段只能记住一句话那就是先定类别列表再写标注和转换脚本最后才碰训练配置。顺序反了你多半会体验到 YOLOv8 在训练日志里安静地给你一个 0.5 的 mAP而你想不通问题出在哪的那种无助感。3. 用 YOLOv8 训练屋顶识别模型模型选型、参数配置与损失曲线判读3.1 选 n/s/m 还是 l航拍屋顶场景的算力与精度权衡模型选型没有绝对标准但航拍屋顶识别有自己的规律。YOLOv8 系列从 n 到 x参数量和计算量递增精度也随之上升。关键问题是你的输入分辨率打算用多大以及推理设备是谁。如果最终要部署到边缘盒子——比如有人提到把 yolov8 部署到 rk3588 这类 NPU 设备——那模型大小直接决定部署是否可行。yolov8n 在 NPU 上可以跑到不错的帧率但航拍小屋顶的召回率往往不够yolov8m 或 l 精度好但 NPU 的算力占用和内存占用都会明显上升。我的经验是训练阶段用 yolov8m 或 l 拿到一个更高的精度上界蒸馏或直接换小模型到部署端是航拍目标检测更稳妥的思路。表里列一下常见选择的差异。模型参数量640 输入 mAP50 参考优点典型场景yolov8n约 3.2M中推理快NPU 友好在线实时预览yolov8s约 11.2M较高精度与速度均衡常规训练首选yolov8m约 25.9M高小目标召回率更好航拍离线分析yolov8l约 43.7M很高精度上界离线高精度标注辅助这里不写精确值因为 YOLOv8 的 mAP 随数据集和输入分辨率变化很大公开 COCO 指标对航拍屋顶没有太多参考价值。我一般会按“验证集上 mAP50 从 0.85 提到 0.90模型体积翻三倍值不值”来决策。如果是给城市规划部门做一次性普查离线批处理直接上 l如果要放到无人机上做实时检测s 几乎是上限。3.2 训练命令与关键参数imgsz、epochs、batch、优化器训练自己数据集的命令很简单难的是参数为什么这么设。下面是一条我在航拍屋顶数据上常用到的训练命令注释写清了每个参数的选择理由。bash# 在航拍屋顶数据集上训练YOLOv8s # data.yaml 里指定了训练集、验证集和类别列表 # 关键点在于 imgsz航拍图像小目标多保持高输入分辨率比加大模型更有效 yolo detect train \ dataroof.yaml \ # 数据集配置路径、类别数量、names顺序 modelyolov8s.pt \ # 用预训练权重做迁移学习收敛更快 epochs200 \ # 航拍数据量不大200轮足以看到收敛趋势 imgsz1536 \ # 输入分辨率航拍小目标的核心参数 batch8 \ # 由GPU显存决定6GB显存跑s模型大约只能放8张 optimizerauto \ # 自动选择优化器新手不折腾就是最优解 patience30 \ # 30轮无改善则早停防止后期过拟合浪费时间 pretrainedTrue \ # 加载COCO预训练权重 cos_lrTrue # 余弦退火学习率对航拍数据这类小数据集更稳定imgsz1536 是航拍屋顶识别最值得讲的一个参数。COCO 平视检测任务常用 640但航拍影像里的屋顶动辄只占几十个像素1536 意味着模型在前处理时把原图放大再切块小目标保留的特征信息更多。代价是显存占用按平方增长——同一个模型640 输入能放 64 张图的 batch1536 输入可能只能放 8 张。看到这里你不要慌6GB 显存的 GTX1660Ti 跑 yolov8s 配 imgsz1536 是可以跑的batch 调到 2 到 4 即可代价只是训练时间拉长。梯度累计可以在不增加显存的情况下提高有效 batch sizeUltralytics 有现成的参数。epochs 设 200 看起来不多但航拍数据一般只有几百到两三千张配合早停完全够用。很多人一上来设 500 甚至 1000训练集损失已经收敛到 0.05验证集损失从第 80 轮就开始回升——这就是典型的过拟合模型把训练集里某一片特定小区的外形背下来了。watch 验证集损失是否回升比无脑堆 epoch 有用得多。3.3 损失曲线怎么看画出来、看趋势、找拐点很多初学者训练完只看最终的 mAP 数值但训练过程中间发生了什么更有诊断价值。Ultralytics 会把每次迭代的指标写入 runs/detect/train 目录下的 results.csv里面有 epoch、train/box_loss、train/cls_loss、train/dfl_loss、metrics/precision、metrics/recall、metrics/mAP50 等列。画损失函数曲线图不需要额外工具pandas 加 matplotlib 就够了。pythonimport pandas as pd import matplotlib.pyplot as plt # 读取results.csv这是YOLOv8训练过程中自动记录的指标 df pd.read_csv(runs/detect/train/results.csv) # 注意新版Ultralytics的列名可能带前导空格先做清理 df.columns df.columns.str.strip() epochs df[epoch] fig, axs plt.subplots(1, 3, figsize(15, 4)) # 左边box_loss回归框位置误差训练和验证曲线差距过大说明过拟合 axs[0].plot(epochs, df[train/box_loss], labeltrain box_loss) axs[0].plot(epochs, df[val/box_loss], labelval box_loss) axs[0].set_title(Box Loss) axs[0].legend() # 中间cls_loss分类误差航拍屋顶误检率高时重点关注这条曲线 axs[1].plot(epochs, df[train/cls_loss], labeltrain cls_loss) axs[1].plot(epochs, df[val/cls_loss], labelval cls_loss) axs[1].set_title(Cls Loss) axs[1].legend() # 右边mAP50与loss趋势对照看loss降mAP涨才是正常现象 axs[2].plot(epochs, df[metrics/mAP50(B)], labelmAP50, colorgreen) axs[2].plot(epochs, df[metrics/mAP50-95(B)], labelmAP50-95, colororange) axs[2].set_title(mAP) axs[2].legend() plt.tight_layout() plt.savefig(loss_curve.png, dpi150) print(损失曲线已保存到 loss_curve.png)画出来只是第一步怎么读是更关键的能力。三个信号要盯住。信号一是 train loss 和 val loss 在后期同步下降说明模型还在从数据里学东西此时早停不会触发正常训练即可。信号二是 train loss 持续降但 val loss 第某个 epoch 后反弹这是过拟合的前兆如果你训练日志里没触发早停要人工检查 patience 参数是否设得过大。信号三是 train loss 在训练初期根本不降这个往往不是模型问题而是数据问题——请先检查标注文件是否为空、类别是否都在 names 列表里、图像是否有大量纯黑或纯白区域。训练完成后最值得做的事情是把最佳权重备份到一个独立目录。Ultralytics 会在每个 epoch 结束后保存 last.pt并在训练结束时保留 best.pt但很多人删除 runs 目录释放磁盘空间时把 best.pt 一起丢了。我吃过这个亏后面养成了训练一结束就立刻把 best.pt 复制到单独权重目录的习惯。4. 推理验证与部署到 RK3588从 PyTorch 权重到板端跑通4.1 用训练好的权重对航拍图做推理conf、iou、save 三个参数训练完拿到 best.pt第一件事不是急着导出部署而是先在本地验证集上跑一轮推理用肉眼确认模型学会了什么、漏掉了什么。bash# 对验证集图片做推理并保存可视化结果 # conf0.25 是偏低阈值宁可多出误检框先看全模型学到的东西 # 后面正式使用时再上调conf误检和漏检本来就是一对矛盾 yolo detect predict \ modelruns/detect/train/weights/best.pt \ sourcedata/roof/val/images \ conf0.25 \ # 置信度阈值低于此值的预测框被丢弃 iou0.5 \ # NMS的IoU阈值两个重叠框IoU大于此值只保留置信度高者 saveTrue \ # 保存可视化标注图 imgsz1536 # 必须与训练时的imgsz保持一致推理阶段两个参数的调节逻辑和训练完全不同。conf 控制“敢不敢框”iou 控制“密集目标能不能分开”。航拍屋顶有一个非常典型的场景一片工业区里几十个彩钢瓦屋顶紧密排列几乎贴在一起。如果 iou 设成 0.7NMS 会把两个高度重叠的框合并成一个漏掉其中一个如果 iou 设成 0.3同一个屋顶会被框出三四个彼此交叠的框看起来一片混乱。我一般先固定 conf0.25 观察再调 iou 从 0.3 到 0.7 各跑一次看哪个阈值下主观判断的误检和漏检最平衡。没有标准答案但这个组合实验成本很低值得花二十分钟做。另外一个容易被人忽略的点是 imgsz。推理时的输入尺寸如果不等于训练时的尺寸模型输出的目标尺寸分布会变化mAP 也会下降。很多人训练时用 1536推理时命令行默认成 640结果大吃一惊——召回率怎么掉了一半。这不是模型坏了是输入分布变了。4.2 导出 ONNX 与 RK3588 部署思路rknn 格式转换流程验证完精度如果目标设备是 RK3588 这类边缘计算平台需要把 PyTorch 权重转成板端能用的格式。RK3588 的 NPU 不能直接跑 Ultralytics 的 PyTorch 权重中间要过一道 ONNX再转成 RKNN。这套流程是国产边缘部署的标准路径Ultralytics 自带的导出命令可以完成第一步。bash# 导出ONNX格式注意opset和动态轴的设置 yolo export \ modelruns/detect/train/weights/best.pt \ formatonnx \ imgsz1536 \ opset12 \ dynamicFalse # 固定输入尺寸部署端预处理难度低很多导出 ONNX 时有几个参数要特别留意。dynamicFalse 意味着导出模型只接受固定尺寸输入RK3588 部署时不需要处理动态 shape能少踩很多格式转换的坑。opset12 是一个兼容性较好的选择新版本 ONNX Runtime 都支持且对 NPU 转换工具更友好。如果你的部署包要求更高版本 opset可以在导出后先用 ONNX Runtime 跑一下确认输出的坐标和置信度与 PyTorch 推理结果一致。ONNX 转 RKNN 这一步我一般用 Rockchip 官方提供的 RKNN-Toolkit2 工具链。转换前先确认板端的 rknn-toolkit-lite 版本宿主机一致这是一个很容易被忽略的坑宿主机上装的是 2.0 版本板端装的是 1.6 版本加上 NPU 驱动版本不匹配推理时的报错会以各种奇怪的面目出现。转换时还需要提供一张代表性图片做量化校准这张图片建议从训练集以外找而且要包含不同尺度的屋顶目标而不是随便一张风景图。RK3588 常见做法是把模型转成 rknn 格式后配合板端推理框架实现实时检测。部署到 rk3588 的工程师往往会把 yolov8 的预处理、后处理——letterbox 缩放、归一化、解码坐标、NMS 过滤——全部从 Python 挪到 C 里因为 Python 的运行时开销在边缘盒子上很吃亏。4.3 板端推理的预处理对齐形状、顺序、归一化三个细节模型转换到板端之后最容易翻车的地方反而不是模型本身而是预处理对不齐。PyTorch 侧Ultralytics 的推理管线会读入图像后用 letterbox 方式缩放到模型输入尺寸保持长宽比并填充灰色边缘也会把 BGR 顺序转换到 RGB再除以 255 归一化。你在板端手写推理代码时这三步任何一步跟训练端不一致都会出现“模型在电脑上好好的上板后精度崩了”的怪现象。我踩过的血泪坑是 BGR 和 RGB 的顺序。OpenCV 读图得到的是 BGRUltralytics 内部会自动转 RGB但板端如果用 C 写推理时OpenCV 读图后直接喂给 NPU就会得到一张 RGB 顺序错乱的图。解决方法是逐通道检查——取一张只有红色物体的图分别打印三通道像素均值手动确认顺序。这个检查值得做否则你会发现模型把红色屋顶全部漏掉而原因只是通道顺序反了。另一个频繁出现的坑是 letterbox 填充值。训练时 Ultralytics 默认填充一个特定的灰度值通常是 114板端如果用 0 填充模型输入分布被改变推理精度同样下滑。不同版本的 Ultralytics 对 letterbox 参数处理略有差异导出模型前最好从源码里确认一下具体填充值保持板端一致。5. 航拍屋顶识别常见问题排查与避坑五个高复现率的问题5.1 模型把道路和停车场误检成屋顶现象推理结果里平整的柏油道路、水泥停车场被框成屋顶置信度还高达 0.7 以上。原因训练数据里屋顶和道路-停车场之间缺乏足够的负样本。屋顶的视觉特征是“规则的矩形区域 均一的材质纹理”而道路和停车场恰好也满足这些特征。模型学到的不是“屋顶”而是“一块边界清晰的平坦区域”。解决三个动作依次做。第一补充负样本把没有屋顶的区域——注意是完整影像块不是单纯裁剪背景——加入训练集以降低误检第二在标注层面细分屋顶子类比如把“水泥屋顶”和“道路”从锚定特征上区分开类别越细类间差异越清晰第三推理时把 conf 阈值从 0.25 上调到 0.4 或 0.5过滤掉大多数低置信度的误检框。三个动作配合起来效果最好。5.2 小屋顶漏检输入分辨率不是越大越好现象独立的小型彩钢棚、临时板房——目标只占几十个像素——完全没被检测出来大屋顶表现正常。原因这是航拍目标检测最典型的问题。模型输入分辨率固定为 1536但原始航拍图切块后小块里的目标如果本身占像素过少特征提取层经过多次下采样小目标信息几乎被抹平。单纯把 imgsz 提高到 2560 往往也无效——计算量翻倍且超出的分辨率带来的收益递减。解决把小屋顶和大屋顶分开建模或者采用切图推理策略。切图推理的做法是把大图切成 512×512 或 640×640 的小块每块独立推理再把结果合并回原图。虽然增加了推理次数但小目标在切块后的相对尺度显著增大。第 6 章会展开讲具体实现。另外训练阶段把输入分辨率调到 1920 并配合马赛克增强对小目标召回率也有提升。5.3 误检框堆积conf 和 iou 需要分开调现象同一片工业区相邻屋顶之间输出了几十个高度重叠的框可视化结果没法看。原因iou 阈值设置过高NMS 没能把重叠框合并掉。尤其是航拍影像里屋顶密集排列、且标注框本身大量重叠时模型输出的候选框天然密集。解决把 iou 从默认的 0.5 调低到 0.3 左右让 NMS 更激进地合并重叠框。同时把 conf 阈值适当上调。这两个参数是一对组合不要把精力只放在一个上面。我一般先固定 iou0.3然后跑一组 conf 从 0.2 到 0.6 的网格用肉眼挑一个“误检少、漏检可接受”的平衡点。5.4 训练时 loss 为 NaN先查数据再查参数现象训练跑到第 20 轮train/box_loss 变成 nan训练进程崩溃。原因最常见的情况是某一批训练数据里含有损坏的图片——比如零字节文件、不能被 OpenCV 解码的图像或者归一化坐标越界的标注。少数情况是 batch size 过小导致学习率相对过大梯度爆炸。解决先用脚本遍历 images 目录用 OpenCV 逐个尝试解码所有图片把解码失败的找出来删掉。再检查 labels 目录里的 txt 文件用 grep 找出任何包含大于 1.0 或小于 0.0 数值的行。如果数据没问题就把 learning rate 设成显式的较低值比如 0.001而不是完全依赖 lr0 默认参数。5.5 验证集精度高但新区域掉点数据划分方式的问题现象在训练集划分出的验证集上 mAP50 能到 0.92但换到另一批从未见过的城市航拍图上mAP 直接掉到 0.6。原因训练集和验证集来自同一片区域的相邻切块空间自相关导致验证集分数虚高。模型实际只是在记忆训练区域的颜色纹理没学到跨区域的泛化特征。解决把数据按地理区域分组比如把整个城市按网格划分成若干组组内切块只能同时进训练集或验证集不能拆散。航拍数据的划分逻辑和普通图像数据集有本质区别一定要按分区、按飞行架次划分。同时适当增加数据增强特别是色彩抖动和模糊让模型少依赖单一区域的色调特征。6. 进阶技巧大图切块推理与多尺度验证把召回率再拉一截训练做的再好航拍图像最终落地时都是大图。把大图直接缩到模型输入尺寸推理是一种懒办法——我第一版就这么干的小屋顶漏检率接近 40%。切块推理能补回大部分召回损失代码不复杂核心思路是滑窗裁剪、逐块推理、坐标还原、合并 NMS。pythonimport numpy as np import cv2 from ultralytics import YOLO def infer_tiled(model, img_path, tile_size640, overlap0.2, conf_thres0.3, iou_thres0.4): 对大幅航拍图做滑窗推理。 tile_size: 切块边长推理速度与召回率之间的权衡点 overlap: 相邻切块的重叠比例防止屋顶正好压在切块边界上被截断 img cv2.imread(img_path) h, w img.shape[:2] stride int(tile_size * (1 - overlap)) all_boxes [] # 按行、列滑窗四个方向的坐标都要记录方便还原 for y in range(0, h, stride): for x in range(0, w, stride): # 最后一块可能不够尺寸用边缘填充而非缩放保持目标比例不变 tile np.zeros((tile_size, tile_size, 3), dtypenp.uint8) tile[:] 114 # 填充值与训练时letterbox一致 y_end min(y tile_size, h) x_end min(x tile_size, w) tile[0:y_end-y, 0:x_end-x] img[y:y_end, x:x_end] results model.predict(tile, confconf_thres, iouiou_thres, imgsztile_size, verboseFalse) boxes results[0].boxes if boxes is None or len(boxes) 0: continue xyxy boxes.xyxy.cpu().numpy() confs boxes.conf.cpu().numpy() cls_ids boxes.cls.cpu().numpy() # 还原到原图坐标只需要把偏移量加回去 for i in range(len(xyxy)): x1 xyxy[i][0] x y1 xyxy[i][1] y x2 xyxy[i][2] x y2 xyxy[i][3] y all_boxes.append([x1, y1, x2, y2, confs[i], cls_ids[i]]) # 全局NMS跨切块的重叠框在这里被合并 if len(all_boxes) 0: return np.empty((0, 6)) all_boxes np.array(all_boxes) keep cv2.dnn.NMSBoxes( all_boxes[:, :4].tolist(), all_boxes[:, 4].tolist(), score_thresholdconf_thres, nms_thresholdiou_thres ) return all_boxes[keep.flatten()]这段脚本里有几个值得留意的细节。overlap 参数设为 0.2意思是相邻切块有 20% 的宽度重叠这样落在切块边缘被截断一半的屋顶能在相邻切块里完整出现。填充值 114 与训练时 letterbox 保持一致我以前用 0 填充边界上的目标会多出一条黑色边框干扰判断。还原坐标时只需要加偏移量因为切块内部没有缩放坐标是天然对齐的。最后一步做全局 NMS 是为了消除同一个屋顶出现在两个切块里被重复检出的情况。切块的大小选择有两个方向。追求精度时用 512 或 640屋顶在切块内占比大模型能提取更多特征追求速度用 1024 或 1280切块数量少推理次数少但小屋顶在大切块里又变小了。我目前的习惯是高精度普查选 640快速巡查选 1280。这个方向没有绝对最优解建议在自己数据上做一次对比实验。除切图之外还有一个值得做的验证在验证集上分别统计大目标和小目标的 mAP。Ultralytics 的验证命令输出的 mAP 是整体指标会掩盖小目标漏检的问题。可以用脚本按目标面积分桶统计召回率——面积小于 32×32 的、32×32 到 96×96 的、大于 96×96 的各算一份。这个分桶统计虽然麻烦一点但能让你明确知道模型到底在哪一类目标上不行而不是被一个平均分蒙混过去。我做航拍屋顶识别的大半年里最大的教训就是这个任务的重点从来不在模型结构或训练技巧而在你对数据的态度——你愿不愿意把负样本补全、把划分做严谨、把切块策略调到位。YOLOv8 只是一个趁手的工具它不会替你做数据层面的功课。按照上面这套流程走一遍多数人的模型 mAP50 都能稳定到 0.85 以上小屋顶的召回率也会在一个可接受的范围。希望这个思路能帮你在自己的项目里少走一段弯路。本文还有配套的精品资源点击获取
延伸阅读

更多相关文章

2026/10/11 22:24:13

基于深度学习的红枣识别毕设:数据集、YOLO训练与数据库实现

简介:面向毕业设计场景的深度学习应用项目,围绕红枣识别算法展开,提供完整源码、数据库与说明文档。内容按论文章节组织,从红枣特征分类、识别流程与关键技术入手,逐步过渡到深度学习的原理和常用算法,再落…

2026/10/11 22:24:13

SQL四大分类详解:DDL、DML、DQL、DCL的边界与实战避坑

上周隔壁组出了个小事故:一个上线两年的老系统,运营想清理一张日志表的过期数据,结果直连数据库的同事把DELETE写成了DROP TABLE,回车一敲,整张表连结构带数据全没了。等发现的时候只能靠备份恢复,前后折腾…

2026/10/12 0:49:25

企业 Agent 提示词注入防御实战:双重护栏与对抗语义检测

在企业将多智能体(Multi-Agent)系统接入客服咨询、内部知识检索或自动化办公流后,安全攻防的对抗维度发生了一场根本性范式转移:传统的 SQL 注入或跨站脚本攻击(XSS)正在退居二线,而以自然语言为…

2026/10/12 0:44:24

YOLOv8+PyQt5行人过马路危险行为检测告警系统实战解析

简介:基于YOLOv8与PyQt5的行人过马路危险行为检测告警系统,面向计算机视觉、深度学习方向的在校生、研究者或企业开发者,主要解决过马路场景中行人低头玩手机、持机打电话等危险行为的实时识别,同时检测行人、斑马线、车辆等目标。…

2026/10/11 0:02:13

Python调用Gemini Structured Outputs实现工单路由门禁

客服工单最怕的不是模型“答错一句话”,而是它给出一段看起来合理的说明,程序却从中猜错优先级。通俗做法是:要求模型只交 JSON(JavaScript Object Notation,轻量数据格式),再让代码验证它。Gem…

2026/10/11 0:02:13

Spring Boot超市进销存系统毕设实战:从需求拆解到答辩通关

最近带的一个学生项目组里,有A同学跑来问我:选什么毕设题目最稳妥,既能让评审老师觉得工作量够,又不会在答辩时被问到语无伦次。我第一反应就是推荐基于Spring Boot的超市仓库管理系统——也就是超市进销存系统。这个题目乍一看平…

2026/10/11 0:02:13

Flutter StatefulWidget 生命周期核心解析

很多刚开始接触 Flutter 的朋友,在看完一堆“Hello World”和基础组件之后,大概率都会撞上同一堵墙:StatefulWidget 里那堆 initState、build、dispose 方法,到底什么时候被调用?为什么顺序是那样?在里面到…

2026/10/12 0:04:22

绝缘子缺陷检测数据集清洗与工业级训练实战指南

简介:本资源是面向电力AI研发人员、工业视觉工程师及智能巡检系统开发者的绝缘子缺陷检测专用YOLO格式数据集,解决无人机航拍场景下绝缘子破损、污闪、积雪等9类典型缺陷的精准识别与定位难题。数据集共2139张真实巡检图像(含训练/验证/测试集…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑