智慧城市城管这块,平时打交道最多的就是从无人机拍回来的高分辨率影像里找问题房子:楼顶悄悄加盖了一层,院子里搭个彩钢棚,这些在城市低空遥感影像里其实特别扎眼,但靠人眼一张张看图,既慢又容易漏。我最近在做的这个“无人机拍摄城市管理建筑违规违章要素分割数据集”项目,核心就是用无人机影像配合YOLOv8的实例分割模型,把违建要素从背景里精细地“抠”出来,训练出一个能在城市管理场景里真正用的分割模型。
这个项目解决的不只是“看见违建”,而是“定位违建边界”和“量化违建面积”。用分割模型生成的不再是框住建筑的矩形,而是贴着楼顶加盖边缘走的闭合轮廓,这样后续叠加规划红线、计算违建占地面积、甚至生成执法文书附图,都有了可靠依据。不管你是刚接触遥感影像分割的学生,还是在城管、测绘、智慧城市公司里做算法落地的人,这篇内容里都有可以直接抄作业的思路和坑点。
1. 项目拆解:无人机遥感里的违建识别到底难在哪
1.1 从人工巡查到低空遥感,城管执法有了“天眼”
传统违建巡查主要靠执法队员跑现场、爬楼顶、翻台账,一个街道几十个小区,每栋楼的楼顶、平台、露台都要看一遍,工作量大,而且很多时候赶到现场,违建已经盖完了。无人机的价值在于“低成本高频次”:一架四旋翼无人机配合变焦云台相机,一个航次就能覆盖几百亩的区域,拿到厘米级的地面分辨率影响后,再通过算法自动把疑似违建挑出来,人只需要复核。
就我实际参与的项目来说,影像采集并不是随便飞一圈就完事的。城市低空遥感有自己的拍摄规范,航线要规划成“井”字形或“之”字形,旁向重叠率一般控制在70%左右,航向重叠率在80%左右,这样后期无论是做正射影像拼接还是直接分析单张影像,都有冗余。飞行高度基本保持在100米到150米,过高会导致地面采样间隔太大,楼顶上的水箱、梯屋、围栏都会糊成一片;过低又会让单个架次覆盖范围变小,数据量激增,处理成本和出图时间都会上来。
1.2 为什么选“要素分割”而不是普通目标检测
最开始我也想过直接用目标检测模型,一个框标一个违建,训练快、部署简单。但真做了几个版本后我发现,检测框在城市管理场景里根本不够用。
违建不是一个规整的矩形。楼顶加盖的门房、阳光房、彩钢棚,形状五花八门,有的贴着女儿墙呈长条形,有的沿楼梯间向外凸出,如果用矩形框去圈,边缘区域大片的合法屋顶会被框进来,面积计算自然失真。执法工作中最看重的就是违建占地多少平方米,你给一个偏大20%的矩形框,后面套核算成本、拆除材料量都会出问题。
所以这个项目选择了实例分割任务。模型输出的每个目标不仅仅有类别标签和置信度,还有一串多边形顶点坐标,精确到像素级。在数据集层面,对应的是要素分割数据集,每张无人机影像里的违建要素都要人工标注成多边形,而不是画个框了事。
至于为什么选YOLOv8,主要看中三点:一是分割精度在轻量级模型里属于第一梯队,二是训练和部署链路极其成熟,Ultralytics仓库一套代码走完训练、验证、导出全流程,对做业务项目的人来说省心;三是模型体积小,后期部署到现场的边缘计算盒子上也能跑得动,不用依赖强大的服务器。
1.3 整体技术流程:从航片到违建矢量的流水线
整个项目的技术路径可以分六步:无人机航拍采集影像、影像预处理与切片、违建要素人工标注、标注数据格式转换、YOLOv8分割模型训练、模型推理与业务系统对接。
第一步和第二步经常被忽略,但实际上决定了后面所有环节的上限。无人机采集到的原始影像动辄几千万像素,单张分辨率太高,直接扔进深度学习模型既不现实也没必要,通常要做一定程度的降采样和切片。我一般先把原始影像按照地面分辨率归一化,再切成1024x1024像素的瓦片,切出来的小图既保留了楼顶细节,又刚好符合YOLOv8训练时的输入尺寸范围。标注工作就基于这些瓦片进行,一张无人机原图切成十几张瓦片,每一张都是一个独立的训练样本。
后几步则是标准的深度学习流程:用标注工具生成多边形标签,转换成YOLO格式的txt文件,配置数据集描述文件,启动训练。训练完成后还要在独立的验证集上评估分割精度,最后把模型导出并接入业务系统,让无人机新拍摄的影像能自动出违建图斑。
2. 数据集:决定模型上限的“地基”
2.1 无人机拍摄与数据采集的关键参数
做违建分割,数据采集和普通航拍最大的区别在于“目标是建筑细节”而不是“整体景色”。楼顶违建往往在边缘、凸起、临时搭建物这些特征上和正常建筑构件纠缠在一起,因此对影像的地面分辨率、光照、阴影都有要求。
我实际使用的采集参数是:焦距24mm或35mm;飞行高度100米;云台俯角尽量在-60度到-90度之间,垂直向下拍摄时,建筑顶部线条最清晰,有利于标注人员准确画轮廓;快门速度不低于1/1000秒,避免无人机桨叶振动导致的模糊。
光照条件的选择经常被新人忽略。中午12点前后阳光直射,楼顶的阴影最少,但建筑立面反光强;上午9点到11点或者下午2点到4点,侧光会让楼顶围栏、楼梯间、加盖的搭建物产生微小阴影,这些阴影对模型反而有帮助,因为模型能通过边缘阴影判断违建和合法屋顶的“前后关系”。但阴影过于浓重也会造成误判,所以阴天拍摄反而是个不错的选择,光线漫反射均匀,轮廓清楚。
采集时还有一个细节:每一架次必须记录飞行高度、云台角度、经纬度坐标。这些元数据在后续GIS叠加分析时要用到,不然只拿到一张张图片,无法对应到具体地块,业务上等于白拍。
2.2 标注规范:违章要素到底怎么画
训练分割模型,最怕标注风格不统一。同一个楼顶加盖,甲方标注员可能把整个加建物连同楼梯间一圈都画进去,过两天换个人只画彩钢棚顶部分,模型学到的内容就乱了。
所以我在项目里定了三条标注规范。第一,所有违建轮廓必须沿着明显的物理边缘画,角点对齐建筑面板或墙体边界,不允许画“圆润”的模糊圈。第二,如果楼顶既有合法设备又有违建,只标注违建部分;如果有争议,当场截图记录,提交给业务负责人判断。第三,类别体系尽量精简,我最终只保留了三个核心类别:楼顶加盖、临时搭建、外扩改建。
楼顶加盖最常见,包括楼顶加建房间、搭建阳光房、钢结构加层等;临时搭建则包括彩钢棚、活动板房、简易棚架;外扩改建是建筑底层或阳台向外延伸的违章部分。类别过于细化会让模型在数据量不足时难以区分,反而拖低整体精度。三个类别既能覆盖日常城管执法的绝大多数场景,又能在标注时可以快速归类。
标注工具方面,我一开始用开源Labelme,后来换成了X-AnyLabeling,因为它支持自动分割提示工具,辅助标注效率更高。无论用什么工具,最后的导出格式都是JSON,每个目标记录一组多边形坐标点。
2.3 数据清洗、增强与类别均衡
采集回来的影像并不是都能直接进训练集。我做数据筛选时剔除三类图:一是大面积被树木或其它楼栋遮挡的图,目标区域根本看不清;二是严重过曝或欠曝的图,细节全部丢失;三是重复率过高的图,同一栋楼在相邻航片上重叠太多,如果不处理,训练集里会出现大量近似样本,导致过拟合。
清洗之后是数据增强。YOLOv8自带增强策略已经很强,但我在数据集层面额外做了一些人工增强,尤其是针对无人机影像的特殊性:随机旋转90度、水平翻转、亮度抖动、对比度抖动。城市航拍方向性很强,楼顶目标从不同方向看形状变化不大,旋转增强可以有效提高模型的方位泛化能力。
类别均衡也是实际操作中绕不开的问题。“临时搭建”的样本量经常是“楼顶加盖”的好几倍,因为彩钢棚到处都有,而楼顶加盖需要无人机恰好飞到楼的正上方才能拍到。遇到这种不平衡,我采取的办法不是简单删样本,而是对少数类样本做复制加多方式增强,并且在训练损失函数里增加了类别权重,让模型把注意力向样本少的类别倾斜。
2.4 标注格式转换:从JSON多边形到YOLO分割格式
YOLOv8分割模型要求标注文件为归一化的多边形坐标格式,也就是每行一个目标,第一个数字是类别编号,后面是归一化后的x1, y1, x2, y2...坐标序列。Labelme导出的JSON不能直接用,需要写脚本转换。
转换逻辑其实就三步:读取每个多边形的所有顶点像素坐标,用图像的宽和高分别去除得到归一化坐标,然后写入对应的文本文件,文件名必须与图像文件名一致。如果标注工具导出的是COCO格式,也可以用Ultralytics自带的数据转换接口处理。
基于我自己的经验,转换时最容易犯的错误是顶点坐标顺序混乱导致多边形变形。有些标注工具导出的坐标不保证是顺时针或逆时针,虽然YOLO训练时不太受影响,但在可视化检查时会误导你。所以我写了一个检查函数,对每个标注文件重新绘制标签到图像上,人工抽查几十张,确认轮廓没有发生错位。这个检查步骤只花半小时,却能避免模型在错误标签上跑几天。
3. YOLOv8分割模型训练实操
3.1 环境准备与依赖版本
模型训练前,环境配置是第一个坑。YOLOv8基于PyTorch,Ultralytics库本身对依赖做了很好的封装,但版本匹配仍然需要留意。我使用的环境是Python3.9、PyTorch 2.0.1、CUDA 11.8、Ultralytics 8.1.x。
conda create -n yolo python=3.9 -y conda activate yolo pip install torch==2.0.1 torchvision==0.15.1 --index-url https://download.pytorch.org/whl/cu118 pip install ultralytics安装完成后先跑一个验证程序,确认GPU可用并且CUDA版本正确。这一步不是走形式,我遇到过几次看起来安装成功却使用CPU训练的情况。用一个简单的YOLO检测模型做一次单批次推理,能快速暴露底层问题。
3.2 数据集目录结构与配置文件
Ultralytics支持自定义数据集,目录结构约定为这样:
dataset/ ├── images/ │ ├── train/ │ └── val/ ├── labels/ │ ├── train/ │ └── val/ └── dataset.yamlimages和labels两个目录下的文件名必须一一对应。我习惯按8:2比例划分训练集和验证集,划分的时候注意让同一栋楼的多个切片尽量进入同一个集合,避免数据泄漏导致评估指标虚高。
dataset.yaml配置文件内容如下:
path: /data/yolo/dataset train: images/train val: images/val names: 0: rooftop_addition 1: temporary_shed 2: outward_extension配置里最关键的是names字段的类别顺序,一旦开始训练,后续所有推理结果都和这个顺序绑定,中途修改会非常麻烦。所以在第一次训练前就把三个类别名称定死,不要今天叫add明天叫rooftop_add。
3.3 训练命令与关键参数选择
启动训练的方式很简单,但参数选择有讲究。我的训练脚本如下:
from ultralytics import YOLO model = YOLO("yolov8n-seg.pt") results = model.train( data="dataset.yaml", epochs=200, imgsz=1024, batch=8, lr0=0.001, optimizer="AdamW", patience=30, workers=4, val=True )imgsz我最终选择了1024。试过用640,速度是快了,但楼顶加盖的边缘细节丢失明显,尤其是一些从楼顶边缘凸出的小棚架,640尺寸下只有十几个像素宽,模型基本学不到有效特征。用1024后,边缘预测质量大幅提升。如果你的显卡显存有限,建议优先降低batch而不是降imgsz,因为分割任务对空间分辨率更敏感。
batch大小取决于GPU显存。我在单张24G显存卡上跑,batch能开到8;如果只有16G,就减到4或6,并配合梯度累积。epochs看起来是200,但因为设了patience=30,验证集指标连续30个epoch不提升就会提前停止,实际上通常100到150个epoch就收敛了。
optimizer选择AdamW而不是默认SGD,主要是AdamW在分割任务上收敛更平稳,特别是标注数据不够规整的情况下。学习率保持默认的0.001即可,过大的学习率会让Segmentation头部的损失震荡,导致mask预测出现大片噪点。
3.4 训练过程监控与结果输出
训练启动后,Ultralytics会在runs/segment/train/目录下实时记录各项损失和指标。我一般重点看三个地方:分割损失曲线是否持续下降、验证集mAP是否有波动、训练日志里是否有NaN异常。
训练结束后,项目目录下会生成best.pt和last.pt权重文件。best.pt是验证集上指标最高的模型,last.pt是最后一个epoch的模型。在实际部署中我几乎总是选best.pt,但如果best.pt出现得太早,说明模型在训练后期衰减甚至过拟合了,这时候需要回去检查学习率策略和数据增强设置,而不是直接部署。
此外,训练完一定要看一眼可视化预测图。Ultralytics会自动生成验证集的预测结果拼接图,我每次都用看图软件放大检查:楼顶加盖的边缘是否贴边、临时搭建的轮廓是否连续、有没有把楼体主体当成违建一起画进去。肉眼观察带来的信息量远大于单纯看mAP数字。
4. 评估、调优与常见问题排查
4.1 指标怎么看:mAP、IoU、precision/recall
很多入门同学对着训练的日志只看一个mAP50,其实这里面的信息量不够。YOLOv8分割模型报告两组核心指标:检测框相关的precision、recall,以及分割mask相关的mAP。
我在这类项目中更看重“mask mAP@50”,因为它考察的是生成的多边形区域和真实标注区域的重叠程度。如果模型框住目标了但轮廓偏差很大,这个指标会明显下降。在楼顶加盖这类目标上,mask mAP@50能做到0.85以上基本算可用,0.9以上算优秀。mAP@50:0.95则更严格,它对边缘细节要求更高,对违建分割项目来说属于“追求指标”,初期不用太强求。
precision和recall是一对矛盾指标。城管业务里,漏检(recall低)比误检(precision低)更麻烦,因为漏掉一个违建等于执法没有启动,而误检最多是让人复查一下。所以我调参时的倾向是稍微牺牲一些precision,换更高的recall,确保图上所有疑似违建都被找出来。
4.2 小目标与边缘漏检:图像切片的经验
无人机拍回来的原始影像分辨率远高于训练输入尺寸。训练时模型看到的是1024x1024的图,但实际推理时你不可能把一张6000x4000的航片整个塞进去,否则缩放到1024后,那些楼顶角落的小违建只有指甲盖大小,模型根本看不见。
我的做法是推理时沿用训练时的切片策略:把大图切分成1024x1024的瓦片,每张瓦片独立推理,再把所有瓦片的mask拼回完整图坐标系。切片设置需要考虑重叠率,我一般用20%的滑窗重叠,这样跨越切片边界的违建轮廓在拼接时不会断裂。
这里有一个容易踩的坑:在切片推理时,瓦片之间的违建目标会被重复检测。解决起来也简单,在拼接阶段对相邻瓦片重叠区域的mask做非极大值抑制或按置信度合并。如果懒得写复杂算法,最简单的办法是把重叠率调到10%,重复率会明显下降。
4.3 误检与干扰:来自实测的避坑记录
项目做到第二个星期,我发现模型频繁把楼顶的太阳能热水器判断成临时搭建。原因不难理解,太阳能热水器也是楼顶上的凸起物,形状和集热板构成的几何形态,在俯视影像里和彩钢棚确实有相似之处。
解决误检不能靠堆积数据。我在训练集里专门补充了一批没有违建的清洁屋顶影像,作为负样本参与训练。虽然YOLO格式的标注文件可以为空,但训练时仍然会用到这些背景图,模型从中学习“不能把普通屋顶乱分割”。补充之后,太阳能热水器误检率降了明显一截。
另一种干扰来自阴影。高楼层建筑在下午投下的阴影会横跨隔壁楼栋,阴影区域里如果有气窗、通风管道,模型容易把这些东西当作违建边缘。我的处理是在数据增强阶段增加了阴影模拟增强,把图像局部区域压暗,让模型学会区分“形状特征”和“光照阴影”。
4.4 常见错误对照表
| 问题表现 | 可能原因 | 排查与解决 |
|---|---|---|
| 训练时显存溢出 | imgsz过大、batch过大 | 降低batch,使用梯度累积,或改用yolov8s-seg以下规模的模型 |
| 验证集mAP波动剧烈 | 数据集划分泄漏或验证集样本太少 | 按楼栋分组划分数据,增大验证集比例 |
| 分割边缘参差不齐 | imgsz太小、标注不够细 | 提高输入分辨率,重新审查标注边缘 |
| 楼顶小目标全部漏检 | 未切片直接缩放推理 | 使用滑窗切片推理,提高推理输入分辨率 |
| 违建区域被预测成几块碎斑 | 标注时一个违建画成多个多边形 | 合并矢量标注,保证每个目标一个闭合多边形 |
| 模型对特定类型严重误检 | 该类型负样本太少 | 采集背景样本作为负样本加入训练集 |
5. 从实验到落地,再说几句心里话
训练出一个高mAP模型只是项目的一半。要让这套违建分割系统真正走进智慧城市城管的日常业务流程,还得做三件事:一是模型导出,把best.pt导出成TensorRT或ONNX格式,部署到现场的小型盒子或服务器上,推理速度要能跟上无人机数据的产出速度;二是跟业务系统搭桥,预测出来的mask要通过GIS工具转成矢量图层,叠加到地块管理和规划红线上,直接可视化成“违建图斑”;三是建立数据闭环,每次业务复核时新确认的违建样本要回流到训练集,持续做增量训练。
我在这个项目里最深的体会是:别把模型当成万能钥匙。就算分割精度做到很好,业务现场还是需要执法人员二次确认。模型的价值不是替代人,而是把人的精力从“逐栋看影像”解放到“快速判断模型给出的十几个图斑”上。一次航拍上千栋建筑,模型先过滤掉99%的正常屋顶,最终只需要人工盯着少数几处疑似区域,效率完全是数量级提升。
最后分享一个小技巧:项目交付时,除了模型权重和指标报告,一定要把标注规范文档、切片脚本、推理拼接脚本一起交付。很多项目模型做得准,但换个人接手后数据标准一乱,训练就崩了。规范化的数据集和工具链,比一个跑分很漂亮的权重重要得多。这也是这类智慧城市项目长期迭代真正能走稳的关键。