资讯动态

ArcGIS面要素标注转YOLOv8 TXT:坐标归一化与切片全流程

发布时间:2026/10/24 7:22:20 来源:尧图企业网站定制
ArcGIS 里一整个工程的面要素标注导出成 shp 之后丢进 YOLOv8 训练脚本里跑不通这个场景我碰到过不止一次。问题不在于标注质量而在于两套体系说的根本不是一种语言ArcGIS 面要素标注记录的是地图坐标下的多边形几何YOLOv8 的 TXT 标签要的是相对图像宽高归一化之后的纯数字序列。中间这段转换没人替你写官方文档也不会告诉你坑在哪。这篇内容就是把这段转换完整拆开从 ArcGIS 端面要素标注该守哪些规矩、坐标系怎么对齐、面要素转点的三种取法各自适合什么任务、大图切片之后坐标怎么偏移、一直到 TXT 落盘、画回原图做质检。读者对象是所有手里已经有一批 shp 面要素标注、但卡在格式转换这一步的人不管你是遥感桥梁检测、建筑物提取、还是别的什么目标只要源头是面要素、终点是 YOLOv8流程是通用的。代码全部给到可直接复现的程度参数计算过程也一步步写出来。1. 先想明白这一步转换到底难在哪1.1 制图语言和训练语言之间的断层在 ArcGIS 里画一个桥梁面要素你脑子里想的是这座桥的轮廓落在数据里可能是十几个顶点串起来的一个闭合环还可能是个带洞的多部件要素。这个几何对象挂在某个空间参考下坐标值动辄是六位数、七位数投影坐标系单位米或者是小数点后好几位地理坐标系单位度。而 YOLOv8 读的 TXT 里每一行都是一串 0 到 1 之间的浮点数多一位少一位都对不上。这个断层有三层。第一层是坐标单位米和度都不是像素而模型要的是像素。第二层是取值范围绝对坐标必须映射到图像内部的相对位置也就是除以图宽图高做归一化。第三层是语义一个面要素到底是转成一个外接框、一串多边形顶点、还是压缩成一个中心点取决于你跑的是检测、分割还是关键点任务选错了训练出来就是一堆没法解释的结果。很多人卡住不是因为不会写代码而是因为没意识到这三层是分开的。他们拿 shp 直接读出来坐标除以一个看起来像的数就写 TXT训练 loss 掉得很好看推理出来框全跑到图外面去了。这种情况十有八九是第二层没做对或者做对了但用错了参考宽度。1.2 YOLOv8 的 TXT 到底接受哪几种形态把话说清楚YOLOv8 系列任务对应的标签格式是分家的同一个labels/xxx.txt目录下内容形态由任务决定。任务类型每行结构数值含义归一化目标检测cls cx cy w h框中心点、宽高全部除以图宽图高实例分割cls x1 y1 x2 y2 ...多边形顶点序列全部归一化姿态/关键点cls cx cy w h x1 y1 v1 ...框 关键点 可见性坐标归一化v 为 0/1/2旋转框 OBBcls x1 y1 x2 y2 x3 y3 x4 y4四角点全部归一化标题里说的TXT 点格式落到实际场景通常是两类需求一类是把面要素的顶点序列直接导出成多边形点串喂给分割任务另一类是面要素太多太密、或者目标本身有位置无所谓精确边界比如桥梁中心位置定位、杆状物点位就只取一个代表点走关键点那一套。少数人还会把面要素外接框算出来当检测标签用这也算一种点的降维。我的建议是先定任务再定格式。桥梁这种长条形目标如果要做精细提取走分割格式最合理如果只是做哪里有桥的定位统计取质心点或者外接框都够用。格式选错了后面切图、增强、评估全得返工。注意YOLOv8 的姿态格式里每条标签只允许一套关键点结构且关键点必须属于同一个目标实例。把一堆散点硬塞进一行当关键点训练时会直接报维度不匹配。1.3 三条转换路线的取舍面要素转点格式我实际用过的有三条路。第一条是质心降维。用 shapely 的centroid或者representative_point取面内部的一个点再归一化成一个点坐标。优点是数据量小、标签干净、切片之后基本不会出现目标被切成两半的问题缺点是丢了几何信息模型学不到边界对面积、长宽比没概念。适合做目标计数、点位回归、粗定位。第二条是外接框降维。minx, miny, maxx, maxy直接转cx, cy, w, h。这条最常用兼容性最好所有检测模型都吃。缺点是长条形的桥梁斜着放的时候外接框会圈进一大片非目标区域标签质量打折。判断标准很简单如果你的目标长宽比普遍超过 5:1 且方向杂乱外接框会很难看。第三条是顶点序列全保留。把面的 exterior 环顶点按顺序导出走分割格式。信息最全但要求标注本身足够精细顶点数不能爆炸一般控制在 100 点以内而且切片时要对多边形做真实的裁剪求交不能简单按中心点判定。三条路线的代码骨架区别不大差别在几何处理和切片过滤策略上。下面我按全流程的方式展开最后会把三种写法的差异点标出来。2. 动手之前ArcGIS 端该守的规矩2.1 面要素标注的硬性规范转换脚本写得再漂亮源数据不规范也救不回来。这几条是我返工最多的点提前说清楚。单部件优先。ArcGIS 要素类允许一个要素由多个不相连的部分组成MultiPart。一座桥如果是主桥加引桥分开画的很容易变成多部件。转换时如果不做拆解取质心可能落到两个部分中间的水面上取外接框会把中间几百米全圈进去。规范做法是在 ArcGIS 里用多部件至单部件工具拆开或者在脚本里用explode处理。不允许自相交和退化环。手绘标注难免有回折形成自相交多边形centroid计算会抛出拓扑异常。导出前跑一次几何修复或者在读入后统一走buffer(0)做一次清洗。洞内环要明确处理。桥梁面一般没有洞但如果你标注的是环形立交或者带空洞的建筑内环会被exterior忽略掉。对分割任务来说这问题不大对面积统计类任务要单独考虑。要素不能在影像范围外。这条听起来废话但实际做过的都懂影像裁过边、标注是更早版本、或者工程坐标系和影像不匹配都会导致部分要素落在影像外。转换时必须做边界裁剪否则归一化出来是负数。提示在 ArcGIS 里先把要素类按面积字段排个序最小的一批挨个目视检查一遍。小面积要素往往是误标或者画错闭合导致的它们在切片后几乎全部会被过滤掉白占训练位。2.2 字段设计类别 ID 怎么传下去YOLO 的 TXT 第一列是类别索引从 0 开始且必须和data.yaml里的names顺序严格对应。ArcGIS 属性表里一般会有一个class或type字段存中文名直接用它做映射是最稳的。我习惯在导出前加一个整型字段叫cls_id用字段计算器批量填好。这样脚本读进来直接取整数字段不用在 Python 里再做一次字符串匹配。中文类别名在某些编码环境下读出来是乱码字符串匹配会静默失败最后所有标签都变成 class 0训练出来的模型只会认一个类排查起来极其痛苦。映射表建议单独存一份 CSV脚本读它人工核对它。类别一多硬编码在代码里的字典迟早会漏。原始类别名cls_id说明桥梁0主线目标高架1与桥梁形态接近注意标注一致性涵洞2面积普遍偏小切片时易丢背景—不写入标签作为负样本保留负样本这一行值得单独说。整幅影像里没有目标的位置最终会生成一个空的 TXT 文件0 字节。这是合法的YOLOv8 会把它当作这张图没有目标来处理。但如果切片策略不当空标签文件占比过高比如超过 60%训练效率会明显下降。这个比例在切完片之后统计一下心里要有数。2.3 坐标系整个流程最容易翻车的地方归一化需要的是图像像素坐标不是地图坐标。这两者之间隔着影像自己的仿射变换参数geotransform也就是六个数字左上角 x、像素宽、行旋转、左上角 y、列旋转、像素高通常为负。转换的两种典型情况情况一影像带完整空间参考且标注是在这个参考下画的。这是最理想的情况。用 rasterio 打开影像拿到transform然后用~transform * (x, y)反算像素行列号或者直接rasterio.transform.rowcol(transform, x, y)。这一步是精确的不需要任何人工对齐。情况二影像没有空间参考或者标注是在导出的 JPG 上手工画的。这时候标注坐标本身就是像素坐标或者某个比例尺下的坐标必须问清楚原始比例。我见过最坑的一次是标注在 ArcGIS 里对着 1:2000 的显示比例画的坐标值是地图单位而影像实际是 0.5 米分辨率的裁剪图两者差了 4000 倍。这种情况下唯一的解法是找两三个明显地物点做人工配准求出缩放和平移。判断自己处于哪种情况有个土办法随便取一个要素把它的坐标打印出来看看数值量级。六位数以上基本是投影坐标米个位数到三位数带小数可能是经纬度如果数值范围恰好和影像宽高对得上那就是像素坐标。这一步别省量级看一眼能省掉两天调试。注意地理坐标系经纬度下的面要素不能直接按度数做面积过滤因为经纬度的度在不同纬度对应的实际距离完全不同。要过滤面积先投影到等积坐标系或者用测地线面积计算。3. 核心算法拆解面到点的坐标变换3.1 归一化公式与参数计算过程不管走哪条路线最后一步都是归一化。公式本身简单但每个量的定义必须钉死。设影像宽W、高H某个像素坐标为(px, py)其中px是列号从左往右0 到 W-1py是行号从上往下0 到 H-1。归一化nx px / W ny py / H这里有个细节分母用W还是W-1。YOLO 官方实现用的是W直接除宽不是W-1。像素坐标本身按像素中心在 (col0.5)理解更严谨但实际标注工具和官方转换脚本都按整数像素除以宽高处理误差在千分之一像素级可以忽略。保持一致比追求精确更重要因为你后面质检回放用的也是同一套公式。外接框归一化cx (minx maxx) / 2 / W cy (miny maxy) / 2 / H w (maxx - minx) / W h (maxy - miny) / H主轴顺序是(x, y)对应(列, 行)也就是(经度方向, 纬度方向)。这个顺序写反了是新手最常见的错误症状是训练能跑、loss 能降、但框全是转置的。举个具体数字走一遍。假设影像 8192 × 6144 像素某个桥梁面的外接框像素范围是minx1024, miny2048, maxx2048, maxy2560cx (10242048)/2/8192 1536/8192 0.1875cy (20482560)/2/6144 2304/6144 0.375w (2048-1024)/8192 1024/8192 0.125h (2560-2048)/6144 512/6144 0.0833写进 TXT 就是0 0.187500 0.375000 0.125000 0.083333。保留 6 位小数是通行做法足够表达 8192 宽度下的亚像素精度1/8192 ≈ 0.0001226 位小数够。3.2 三种取点策略的具体几何处理质心策略。shapely 的polygon.centroid返回的是面积质心。对凸多边形没问题对 L 形、U 形的桥梁质心可能落在面外。要保证点一定在面内用polygon.representative_point()它保证返回面内的一个点但不保证在视觉中心。我一般两个都算比较一下距离偏差大的手工看一眼。对多部件要素务必先explode或者取max(geoms, keyarea)取最大部件否则质心会飘到中间。外接框策略。polygon.bounds给出(minx, miny, maxx, maxy)。这里注意是(xmin, ymin, xmax, ymax)的顺序别和(x1, y1, x2, y2)混。对倾斜的长条形目标外接框会显著大于实际目标可以加一步判断如果polygon.area / bbox_area 0.5说明目标方向和框轴差异大这时候要么保留检测任务能忍要么改用最小外接矩形minimum_rotated_rectangle走 OBB 分支。顶点序列策略。取polygon.exterior.coords去掉最后一个重复的闭合点。顶点数超过阈值比如 150时做简化用polygon.simplify(容差)容差取影像短边 / 500这个量级比较合适——1024 短边对应约 2 像素容差肉眼看不出来但能砍掉一半顶点。这里要提醒一句分割格式要求每行的点数是可变的不需要补齐。但有些自己写的校验脚本会假设固定长度导致报错。写校验时按len(line) 7 且 (len(line)-1) % 2 0判断。3.3 切片场景下的坐标偏移计算这是真正拉开工作量差距的部分。遥感影像动辄上万像素直接送进网络不现实必须切片。切完片之后标签坐标要从全图坐标换算到切片内坐标。设切片窗口左上角在全图中的像素坐标为(x0, y0)窗口宽高tw, th。切片内归一化nx (px - x0) / tw ny (py - y0) / th看起来就是减一下但过滤策略才是关键。检测任务的过滤我用的规则是目标框与切片窗口的交集面积占原框面积的比例 ≥ 0.4 才保留保留时用裁剪后的框。0.4 这个阈值是试出来的太低会引入大量只剩一条边的残缺目标模型学不到完整形态太高会丢掉位于切片边界的目标如果重叠设置得不够会漏检。分割任务的过滤必须做真实的多边形求交。用 shapely 的polygon.intersection(Window(x0, y0, x0tw, y0th).box())求交后可能得到 MultiPolygon取最大的一块。求交后再算交集面积占比同样按 0.4 左右判断。关键点任务的过滤最简单判断点是否落在窗口内即可x0 px x0tw。落在边界外直接丢。重叠参数怎么定给个计算过程。假设原图10000 × 8000切片1024 × 1024重叠256则步长stride 1024 - 256 768列数ncols ceil((10000 - 1024) / 768) 1 ceil(11.6875) 1 12 1 13行数nrows ceil((8000 - 1024) / 768) 1 ceil(9.083) 1 10 1 11总切片数13 × 11 143最后一片的起点是12 × 768 92169216 1024 10240 10000超出 240 像素。所以窗口生成时必须做min(x0, W - tw)的钳位否则读图会出错。钳位之后最后一片和倒数第二片的重叠会变大这没关系只是冗余多一点。提示切片尺寸选择上1024 是个比较稳妥的默认值。想训小目标就降到 640 并加大重叠到 320代价是切片数量翻好几倍磁盘和标注文件数量也跟着涨。4. 完整实操从 shp 到 TXT 跑通一遍4.1 环境与依赖pip install geopandas shapely rasterio numpy pillow matplotlib pandasgeopandas 负责读 shpshapely 做几何运算rasterio 拿影像的仿射变换和宽高。版本上 geopandas 0.14 以上、shapely 2.0 以上配合比较顺shapely 2.0 的几何运算比 1.x 快一个量级几万个面要素的转换时间能从十几分钟压到一分钟以内。4.2 读入、清洗与坐标统一import geopandas as gpd import rasterio from shapely.geometry import box import numpy as np SHP bridge_labels.shp IMG scene_01.tif OUT_DIR labels gdf gpd.read_file(SHP, encodingutf-8) # 1) 几何修复与多部件拆分 gdf gdf[gdf.geometry.notnull()].copy() gdf[geometry] gdf.geometry.buffer(0) gdf gdf.explode(index_partsFalse).reset_index(dropTrue) # 2) 影像参考信息 with rasterio.open(IMG) as src: W, H src.width, src.height T src.transform img_crs src.crs # 3) 坐标系对齐把标注投影到影像坐标系 if gdf.crs ! img_crs: gdf gdf.to_crs(img_crs) # 4) 面要素 - 像素坐标多边形 def to_pixel_polygon(geom, transform): from rasterio.features import geometry_mask from shapely.ops import transform as shp_transform return shp_transform(lambda x, y, zNone: rasterio.transform.rowcol(transform, x, y)[::-1], geom) gdf[pix_geom] gdf.geometry.apply(lambda g: to_pixel_polygon(g, T))这里有个容易忽略的点rowcol返回的是(row, col)而 shapely 的坐标是(x, y)所以必须[::-1]翻一下。翻漏了整批数据都会转置。另外buffer(0)对几乎所有自相交几何都能修复但它会改变极小面积图形的拓扑个别退化图形会变成空几何。加一步gdf gdf[~gdf[pix_geom].is_empty]兜底。4.3 切片生成与标签同步写盘import os from pathlib import Path from PIL import Image Path(OUT_DIR).mkdir(parentsTrue, exist_okTrue) Path(images).mkdir(parentsTrue, exist_okTrue) TILE 1024 OVERLAP 256 STRIDE TILE - OVERLAP MIN_RATIO 0.4 CLS_MAP {桥梁: 0, 高架: 1, 涵洞: 2} def gen_windows(W, H, tile, stride): xs list(range(0, max(W - tile, 0) 1, stride)) ys list(range(0, max(H - tile, 0) 1, stride)) xs sorted(set([min(x, max(W - tile, 0)) for x in xs] [max(W - tile, 0)])) ys sorted(set([min(y, max(H - tile, 0)) for y in ys] [max(H - tile, 0)])) return [(x, y, min(tile, W - x), min(tile, H - y)) for x in xs for y in ys] src_img Image.open(IMG).convert(RGB) total_boxes 0 empty_tiles 0 tile_count 0 for x0, y0, tw, th in gen_windows(W, H, TILE, STRIDE): tile_count 1 win box(x0, y0, x0 tw, y0 th) lines [] sub gdf[gdf[pix_geom].intersects(win)] for _, row in sub.iterrows(): cls_id CLS_MAP.get(str(row[type]).strip(), None) if cls_id is None: continue inter row[pix_geom].intersection(win) if inter.is_empty: continue ratio inter.area / row[pix_geom].area if ratio MIN_RATIO: continue minx, miny, maxx, maxy inter.bounds cx (minx maxx) / 2 - x0 cy (miny maxy) / 2 - y0 bw maxx - minx bh maxy - miny # 越界钳位 cx min(max(cx, 0), tw) cy min(max(cy, 0), th) bw min(bw, tw) bh min(bh, th) if bw 4 or bh 4: continue lines.append(f{cls_id} {cx/tw:.6f} {cy/th:.6f} {bw/tw:.6f} {bh/th:.6f}) total_boxes 1 name ftile_{x0}_{y0} src_img.crop((x0, y0, x0 tw, y0 th)).save(fimages/{name}.jpg, quality92) with open(f{OUT_DIR}/{name}.txt, w) as f: f.write(\n.join(lines) (\n if lines else )) if not lines: empty_tiles 1 print(f切片 {tile_count}有效框 {total_boxes}空标签片 {empty_tiles}空片占比 {empty_tiles/tile_count:.2%})跑完之后那几个统计数字必须看一眼。空片占比超过 60% 说明重叠没调好或者影像里有大片空白区域要么砍掉这些区域要么调整切片起点有效框数量少得离谱回头查坐标系是不是没统一。4.4 分割格式与关键点格式的写法差异同一套骨架把写行的那一段换掉就行。分割格式coords list(inter.exterior.coords)[:-1] # 去闭合点 if len(coords) 150: inter inter.simplify(max(tw, th) / 500) coords list(inter.exterior.coords)[:-1] pts [] for px, py in coords: lx min(max(px - x0, 0), tw) / tw ly min(max(py - y0, 0), th) / th pts.extend([f{lx:.6f}, f{ly:.6f}]) lines.append(f{cls_id} .join(pts))关键点格式需要外接框加点的可见性r row[pix_geom].representative_point() kx (r.x - x0) / tw ky (r.y - y0) / th if not (0 kx 1 and 0 ky 1): continue lines.append(f{cls_id} {cx/tw:.6f} {cy/th:.6f} {bw/tw:.6f} {bh/th:.6f} f{kx:.6f} {ky:.6f} 2)可见性取值0 表示未标注1 表示标注但不可见被遮挡2 表示可见。自己做数据一般全部填 2除非确实有被遮挡的桥梁需要区分。5. 转换完不算完把 TXT 画回图上验一遍5.1 可视化回放是最快的体检手段这一步千万别跳。我的习惯是每次转换完随机抽 20 张画回原图看比看任何统计数字都直观。import matplotlib.pyplot as plt import matplotlib.patches as patches def draw_tile(img_path, txt_path): img Image.open(img_path) w, h img.size fig, ax plt.subplots(1, 1, figsize(8, 8)) ax.imshow(img) if os.path.exists(txt_path): for line in open(txt_path): v line.split() if len(v) 5: continue cls, cx, cy, bw, bh int(v[0]), *map(float, v[1:5]) ax.add_patch(patches.Rectangle( ((cx - bw/2) * w, (cy - bh/2) * h), bw * w, bh * h, fillFalse, edgecolorred, linewidth1.5)) ax.text(cx*w, cy*h, str(cls), coloryellow, fontsize8) ax.set_axis_off() plt.show()看一眼就能判断的东西框是不是整体偏移了坐标系问题、框是不是全部转置了x/y 顺序问题、框是不是比实际目标大一圈面要素本身画大了或者用了外接框而目标很斜、有没有出现细长到不合理的框顶点数或求交出问题。5.2 数值级校验指标肉眼过得去还不够再跑几个数字。校验项判定方法异常含义坐标范围所有值落在 [0, 1]越界说明钳位没做或归一化分母错框宽高下限w*W 4且h*H 4过小的是噪声标注框面积占比单框面积 全图 50%超过说明有要素横跨整图未被裁类别分布统计各类 count严重偏斜要复查映射标签文件数等于切片数对不上说明写盘有跳漏平均框数/片与人工预估对比差一个量级基本都是坐标问题框面积占比这一项特别有用。我遇到过一次某类目标全部是整图框原因是 shp 里有一个覆盖全区的图幅边界要素被误当成目标类导出了。肉眼抽检 20 张里如果没抽到那张就发现不了统计一跑立刻暴露。6. 踩过的坑都在这里故障排查实录6.1 坐标整体偏移九成是参考系没对齐场景描述TXT 画回图像框的形状和大小都对就是整体平移了一个固定距离。这种情况最有可能是标注和影像的坐标原点不一致。排查顺序先比较影像左上角的地图坐标和标注要素的最小坐标量级是否接近。如果影像的transform左上角是(500000, 4000000)而标注坐标都在(0, 3000)这种小数值说明标注是相对某个裁剪框的局部坐标必须把裁剪时的偏移加回去。这个偏移量通常能在裁剪日志或者工程文件里找到。还有一种更隐蔽的影像本身是带旋转的少数航摄影像是这样transform里的 b 和 d 不为 0。这种必须用完整的仿射反变换不能只做减法和除法rowcol内部已经处理了如果你自己手写(x - x0) / res就会偏。6.2 空标签文件被误删有人觉得 0 字节的 TXT 是垃圾写成if not lines: continue直接不写文件。这是错的。YOLOv8 训练时图片和标签是配对查找的图片存在但标签缺失会被当作标签文件不存在处理某些版本会直接跳过这张图某些版本会报错。正确做法是写一个空文件明确表达这张图没有目标。反过来的错误是为了省事把没有目标的切片图片直接不生成。这也不是不行但前提是你确定负样本比例已经够高不再需要背景样本。遥感场景里负样本是很宝贵的纯背景切片能有效压制误检。6.3 类别 ID 全部变成 0症状训练日志里只有一个类别或者data.yaml加载时报警告。原因通常有两个。一是映射字典的键和 shp 属性值对不上——属性值里可能带空格、带全角字符、或者编码读成乱码。用gdf[type].value_counts()打印一下真实取值照着写映射。二是直接用enumerate(gdf[type].unique())自动生成 ID但unique()的顺序在不同数据、不同 pandas 版本下不确定第一次跑出来 0 是桥梁第二次可能变成 0 是涵洞。类别映射必须显式写死并落盘成文件。6.4 长条目标横跨多片导致大量残缺样本桥梁这种目标一座可能横跨两三张切片。按 0.4 阈值过滤后中间片保留完整形态两端的片保留残缺片段。如果重叠设得不够可能两端的片刚好被过滤掉中间那片又切不到全貌最终这座桥在任何一片里都不完整。解法是把重叠加大。经验公式OVERLAP ≥ 目标最大长度 × 0.5。如果桥最长 800 像素重叠就不能低于 400切片就得设成 1024 配 400 重叠步长 624切片数大约翻倍。6.5 常见问题速查表现象最可能原因处理办法框全部转置 90 度x/y 顺序写反检查[::-1]和(col, row)顺序框整体缩小到一角归一化分母用了切片尺寸但坐标是全图坐标先减偏移再除框整体放大超出图坐标减了偏移但除以的是原图宽分母统一用切片宽高所有框都是整图大小混入了图幅边界类要素按面积占比过滤部分目标消失切片时被交集过滤降低阈值或加大重叠txt 数量少于图片写了continue跳过空标签空标签也写空文件训练时报标签越界float 精度导致 1.000001写盘前clip(0, 1)分割标签死循环报错顶点数 3求交后判断len(coords) 3中文路径读不出图编码问题切片输出统一用 ASCII 命名训练 loss 不降类别 ID 全 0 或标签与图错配抽查 20 张可视化提示把转换脚本的中间产物像素多边形、切片窗口、过滤前后计数都落盘成 GeoJSON 或者 parquet出了问题能直接回到某一步核对比重新跑一遍快得多。6.6 几个不太起眼但真会咬人的细节图片命名不要带中文和空格。切片命名我用tile_{x0}_{y0}纯 ASCII 加下划线。带中文的路径在某些数据加载的 worker 进程里会静默失败表现为训练时 loss 正常但验证集指标完全不动排查方向会全跑偏。JPEG 质量别设太低。切片存 JPG 时质量给 92 以上。桥梁一般是细长结构压缩伪影会在边缘产生毛刺模型容易把这些毛刺当成纹理特征学进去。存储允许的话直接存 PNG遥感场景下用 PNG 更稳。归一化之后要 clip。理论上交集裁剪保证了坐标在 [0, tw] 内但浮点运算偶尔会给出1.0000001。YOLOv8 的标签加载会做校验越界直接抛异常。写盘前统一min(max(v, 0.0), 1.0)一行代码省掉一次半夜报错。训练配置里的 imgsz 要和切片尺寸对齐。切片是 1024训练imgsz设 640YOLOv8 会把图缩放到 640标签是归一化的所以自动跟着缩放逻辑上没问题。但缩放到 640 之后原本 8 像素宽的小桥只剩 5 像素检测难度陡增。要么切片直接切 640要么imgsz设成 1024。两者混着来是最容易出事的地方。数据集划分要按空间区块切不能随机切。这是遥感场景的硬要求。相邻切片重叠 256 像素如果随机划分同一座桥的重叠部分可能一半在训练集一半在验证集验证指标虚高。做法是按切片左上角坐标做分块比如(x0 // 2048 y0 // 2048) % 10 7划进训练集剩下进验证集保证空间上不重叠。一套流程跑下来从 ArcGIS 面要素到 YOLOv8 能直接读的 TXT快的话半天慢的话两三天取决于源数据的规范程度。我个人在实操中的体会是真正花时间的从来不是写转换代码而是回头去修源标注。所以如果重来一次我会在标注阶段就把规则钉死单部件、不自交、类别字段用整型、影像必须有且只有一个空间参考、面积过小的要素当场删掉。这五条守住了转换脚本基本跑一次就过。另一个小技巧是先拿三五张影像、几十个要素做一个小样本跑通全流程把可视化回放看仔细了再上全量。全量跑完发现坐标错重来一遍的成本是几小时甚至几天小样本验证的成本是十分钟。

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

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

免费获取报价 →
↑