简介飞机型号识别数据集04是一份面向目标检测与细粒度图像分类研究者的可见光遥感数据集采集自俄罗斯机场覆盖苏霍伊、米格、安东诺夫、伊尔、雅克、图波列夫等47种军民机型适合军机识别、飞机检测等算法训练与验证。资源包共2001个文件含1000张1024×768的RGB图像、1000个labelimg标注的xml标签及1个说明txt压缩包约250.43MB图像与标签一一对应可直接接入YOLO、Faster R-CNN等主流检测框架。目前已有218人学习下载。该批次与02、03、05等批次采集地点和机型种类均不相同可组合使用以扩充数据多样性帮助研究者快速搭建实验基线、验证模型泛化能力并排查标注与训练问题。1. 从一批俄罗斯机场的军机照片说起这个数据集到底能干什么如果你正在做飞机型号识别或者军机识别相关的目标检测项目大概率会遇到一个很现实的问题公开的 COCO、VOC 里飞机只有一个笼统的airplane类根本分不出苏-27 和米格-29。想自己从零标注光是查机型图鉴就够喝一壶。这个飞机型号识别数据集04解决的就是这件事——1000 张采集自俄罗斯机场的可见光 RGB 图像1024×768 分辨率47 种军民飞机细分类别覆盖苏霍伊、米格、安东诺夫、伊尔、雅克、图波列夫等系列标签用 labelimg 标注成标准 xml 格式。它适合做细粒度分类、目标检测微调、小样本迁移学习的人也适合想验证自己检测框架在细分类场景下到底行不行的人。下面我按实际拆包、转格式、训练、排错的顺序把这份资源怎么落地讲清楚。2. 拆开压缩包先看什么目录结构、类别分布与标签格式核对拿到数据集别急着写 dataloader先把目录结构和标签质量摸一遍。这一步花二十分钟能省后面几小时的 debug。2.1 目录组织与文件命名规律从项目正文给出的文件名看图片命名是RUS-04-XXXX.jpg这种格式RUS代表采集地俄罗斯04是批次号后面四位是序号。常见做法是图片和 xml 放在同一级目录或者images/与annotations/分开。我一般先跑一段脚本统计文件配对情况确认没有孤儿图片或孤儿标签。import os from pathlib import Path img_dir Path(RUS-04/images) xml_dir Path(RUS-04/annotations) imgs {p.stem for p in img_dir.glob(*.jpg)} xmls {p.stem for p in xml_dir.glob(*.xml)} print(图片数:, len(imgs)) print(标签数:, len(xmls)) print(有图无标签:, sorted(imgs - xmls)[:10]) print(有标签无图:, sorted(xmls - imgs)[:10])这段脚本做的是集合差运算。imgs - xmls就是有图没标签的反过来是有标签没图的。参数上注意glob的大小写有些批次图片后缀可能是.JPG那就得改成glob(*.[jJ][pP][gG])或者统一转小写。如果差集不为空先别训练要么补标要么剔除否则 dataloader 会在某个 epoch 突然报KeyError。2.2 解析 xml 统计 47 类的真实分布xml 是 Pascal VOC 格式核心字段是filename、size、object下的name和bndbox。47 类听着美好但实际分布大概率是长尾的——苏-27、米格-29 这种常见机型样本多安-124 或者某些雅克改型可能只有个位数。先统计再决定要不要做类别合并或重采样。import xml.etree.ElementTree as ET from collections import Counter counter Counter() box_sizes [] for xml_path in xml_dir.glob(*.xml): tree ET.parse(xml_path) root tree.getroot() for obj in root.findall(object): name obj.find(name).text.strip() counter[name] 1 bbox obj.find(bndbox) w float(bbox.find(xmax).text) - float(bbox.find(xmin).text) h float(bbox.find(ymax).text) - float(bbox.find(ymin).text) box_sizes.append((w, h)) print(类别总数:, len(counter)) for name, cnt in counter.most_common(): print(f{name}: {cnt}) import numpy as np ws np.array([b[0] for b in box_sizes]) hs np.array([b[1] for b in box_sizes]) print(宽 min/median/max:, ws.min(), np.median(ws), ws.max()) print(高 min/median/max:, hs.min(), np.median(hs), hs.max())逻辑说明遍历所有 xml用Counter累计每个name出现次数同时把每个 bbox 的宽高收集起来。参数上xmax - xmin得到的是像素宽1024×768 的图里如果中位数宽只有几十像素那说明大量目标偏小后面训练要考虑小目标检测的策略比如提高输入分辨率或者用 FPN 多尺度。如果发现某个类别只有 1 到 2 个样本直接训练基本学不动常见做法是合并到父类或者先做数据增强扩充。2.3 标签格式核对VOC xml 转 YOLO txt 的边界处理现在主流检测框架里 YOLO 系用 txt每行class_id cx cy w h且全部归一化到 0 到 1。转格式本身不难坑在边界裁剪和类别映射。import xml.etree.ElementTree as ET from pathlib import Path classes sorted(counter.keys()) # 沿用上一步统计结果 cls2id {c: i for i, c in enumerate(classes)} out_dir Path(RUS-04/labels) out_dir.mkdir(exist_okTrue) for xml_path in xml_dir.glob(*.xml): tree ET.parse(xml_path) root tree.getroot() size root.find(size) W int(size.find(width).text) H int(size.find(height).text) lines [] for obj in root.findall(object): name obj.find(name).text.strip() bbox obj.find(bndbox) xmin max(0, float(bbox.find(xmin).text)) ymin max(0, float(bbox.find(ymin).text)) xmax min(W, float(bbox.find(xmax).text)) ymax min(H, float(bbox.find(ymax).text)) if xmax xmin or ymax ymin: continue # 跳过退化框 cx (xmin xmax) / 2 / W cy (ymin ymax) / 2 / H bw (xmax - xmin) / W bh (ymax - ymin) / H lines.append(f{cls2id[name]} {cx:.6f} {cy:.6f} {bw:.6f} {bh:.6f}) (out_dir / (xml_path.stem .txt)).write_text(\n.join(lines))关键点有三个一是max(0, ...)和min(W, ...)做边界裁剪标注时手抖画出图外的框很常见不裁的话归一化后会出现负数或大于 1 的值训练时 loss 直接 NaN二是退化框判断xmax xmin说明框宽为零或负必须跳过三是类别映射用sorted保证每次运行 id 一致否则训练和推理的类别对不上这个坑我踩过模型输出全是乱的。归一化保留六位小数足够YOLO 官方也是这个精度。3. 用这份数据训练检测模型从配置到跑通第一个 epoch数据摸清楚了接下来落到训练。这里以 YOLO 系为例因为它的数据配置最直观换成 MMDetection 或 Detectron2 思路一样只是配置文件写法不同。3.1 数据集划分与 data.yaml 配置1000 张图不算多按 8:1:1 划分训练、验证、测试比较稳妥。划分时要注意同一架飞机的多张照片尽量别跨集否则验证集精度会虚高。常见做法是按文件名前缀或者拍摄时段分组划分但这份数据文件名只有序号那就随机划分并固定随机种子。import random from pathlib import Path import shutil random.seed(42) stems sorted(p.stem for p in Path(RUS-04/images).glob(*.jpg)) random.shuffle(stems) n len(stems) train stems[:int(n*0.8)] val stems[int(n*0.8):int(n*0.9)] test stems[int(n*0.9):] for split, items in [(train, train), (val, val), (test, test)]: for sub in [images, labels]: (Path(dataset) / split / sub).mkdir(parentsTrue, exist_okTrue) for s in items: shutil.copy(fRUS-04/images/{s}.jpg, fdataset/{split}/images/{s}.jpg) shutil.copy(fRUS-04/labels/{s}.txt, fdataset/{split}/labels/{s}.txt)random.seed(42)保证每次划分一致方便复现。shutil.copy是物理复制如果磁盘紧张可以用软链接。划分完检查一下每个 split 的类别分布如果验证集里某类一个样本都没有那这个类在验证指标上就是空的评估时要注意。data.yaml 写法path: ./dataset train: train/images val: val/images test: test/images nc: 47 names: [An-124, An-24, Il-76, ...] # 按 cls2id 顺序填全nc必须和 names 长度一致names 顺序必须和转格式时的cls2id完全一致。我见过有人手写 names 时漏了一个类训练不报错但推理时类别全错位这种属于血泪经验。3.2 训练参数怎么设小目标与长尾类的取舍1024×768 的图如果直接 resize 到 640 训练小目标会缩得更小。建议imgsz设 1024 或者至少 896batch 根据显存调8G 显存跑 1024 大概 batch4 到 8。学习率用默认的 0.01 配 SGD或者 AdamW 配 1e-3。长尾类的问题可以在 loss 里加类别权重或者对稀有类做复制增强。yolo detect train \ datadata.yaml \ modelyolov8s.pt \ imgsz1024 \ epochs100 \ batch8 \ lr00.01 \ lrf0.01 \ warmup_epochs3 \ cos_lrTrue \ close_mosaic10 \ patience20 \ device0参数说明imgsz1024保留原始分辨率信息对小目标友好close_mosaic10表示最后 10 个 epoch 关闭 mosaic 增强让模型在真实分布上收敛patience20是早停验证指标 20 轮不升就停防止过拟合cos_lrTrue余弦退火比阶梯下降更平滑。如果显存不够把imgsz降到 768但小目标召回会掉这个取舍要自己跑对比实验。3.3 训练过程看什么指标mAP50 与混淆矩阵跑起来之后别只盯着 loss。检测任务看mAP50和mAP50-95前者是 IoU 0.5 下的平均精度后者是 0.5 到 0.95 多阈值平均后者更能反映框的定位质量。如果 mAP50 高但 mAP50-95 低说明分类对了但框不够准可能是标注框松紧不一致。混淆矩阵能看出哪些类互相混比如苏-27 和苏-30 外形接近混是正常的如果米格-29 混到苏-27 那就要查标注。from ultralytics import YOLO model YOLO(runs/detect/train/weights/best.pt) metrics model.val(datadata.yaml, imgsz1024) print(mAP50:, metrics.box.map50) print(mAP50-95:, metrics.box.map) print(每类AP:, metrics.box.ap50)metrics.box.ap50是数组顺序对应 names。找出 AP 最低的几个类单独看它们的验证图大概率是样本太少或者标注有问题。常见做法是把低 AP 类的样本抽出来重新检查标注或者对这些类做针对性增强。4. 避坑与排查标注、格式、训练里最容易翻车的几件事这一章全是实际踩过的坑按现象、原因、解决三段写遇到对应情况直接对号入座。4.1 现象训练 loss 变 NaN几轮后中断原因xml 里有框坐标超出图像边界归一化后出现负值或大于 1 的值或者存在宽高为零的退化框。YOLO 在计算 CIoU loss 时对非法框没有兜底直接产生 NaN。解决回到 2.3 的转换脚本确认边界裁剪和退化框跳过都生效。转换完再跑一遍校验检查所有 txt 里的数值是否都在 0 到 1 之间。bad [] for txt in Path(dataset).rglob(*.txt): for line in txt.read_text().splitlines(): parts line.split() vals [float(v) for v in parts[1:]] if any(v 0 or v 1 for v in vals): bad.append((txt, line)) print(非法行数:, len(bad))4.2 现象验证集 mAP 很高但拿新图推理全是错类原因类别 id 映射不一致。转格式时用sorted得到一套顺序训练时 data.yaml 里 names 手写又用了另一套顺序模型学到的 id 和推理时解析的 id 对不上。解决把cls2id字典存成 jsondata.yaml 的 names 直接从 json 生成杜绝手写。推理后处理也用同一份 json 反查类别名。4.3 现象某些类别 AP 始终为 0原因该类别样本数极少或者验证集里根本没有这个类。1000 张图分 47 类平均每类 20 张左右长尾类可能只有 1 到 2 张划分后验证集里就没了。解决先统计每类在 train/val/test 的分布对样本数少于 5 的类要么合并到相近父类要么在划分时保证每个 split 都有该类的样本。如果坚持保留用过采样或 copy-paste 增强把样本数补到 10 以上再训。4.4 现象训练速度极慢GPU 利用率低原因imgsz1024加上 dataloader 的workers设太小CPU 预处理跟不上 GPU。或者图片没做缓存每个 epoch 都从磁盘读原图解码。解决把workers设成 CPU 核数的 0.7 倍左右比如 8 核设 6。开启cacheTrue把图片缓存到内存1000 张 1024×768 的图大概占 2 到 3G 内存一般机器扛得住。如果还慢检查是不是在做在线增强时用了太重的变换。4.5 现象同一张图里多个目标只检测出一个原因NMS 的 IoU 阈值设太高两个相邻飞机的框被互相抑制。或者标注时两个目标框重叠严重模型学到的特征纠缠。解决推理时把 NMS 的 IoU 阈值从默认 0.7 降到 0.5 到 0.6 试试。如果是标注重叠问题回看原图确认是不是真的两个独立目标必要时重新标。另外 1024 分辨率下小目标密集时可以开agnostic_nms或者调max_det。5. 进阶玩法用这份数据做迁移与细粒度验证的几个技巧数据跑通之后如果想再压榨一点价值可以试试下面几个方向。第一个是跨批次迁移这份 04 批采集自俄罗斯机场02、03、05 批采集地不同机型分布也有差异。拿 04 批预训练在另一批上微调能验证模型对采集域变化的鲁棒性。具体做法是冻结 backbone 前几层只训 head 和后面几层学习率调小到 1e-4。yolo detect train \ datadata_05.yaml \ modelruns/detect/train/weights/best.pt \ imgsz1024 \ epochs50 \ batch8 \ lr00.0001 \ freeze10 \ device0freeze10表示冻结前 10 层适合小数据微调防止过拟合。如果目标域差异大可以只冻结前 5 层。第二个技巧是细粒度验证。47 类里苏霍伊系列内部差异很小可以单独抽出一个二分类任务比如苏-27 vs 苏-30看模型在极相似类上的表现。做法是把这两类的框裁出来训练一个分类器用混淆矩阵看误判率。如果误判率高说明检测模型提取的特征对细粒度不够可以考虑换更大的 backbone 或者加注意力模块。第三个是标注质量抽检。随机抽 50 张图把模型预测框和原始 xml 框画在一起对比看有没有漏标、错标。我一般用下面这段脚本生成对比图肉眼过一遍。import cv2 import xml.etree.ElementTree as ET from pathlib import Path import random random.seed(0) samples random.sample(list(Path(RUS-04/images).glob(*.jpg)), 50) for img_path in samples: img cv2.imread(str(img_path)) xml_path Path(RUS-04/annotations) / (img_path.stem .xml) tree ET.parse(xml_path) for obj in tree.getroot().findall(object): b obj.find(bndbox) x1, y1 int(b.find(xmin).text), int(b.find(ymin).text) x2, y2 int(b.find(xmax).text), int(b.find(ymax).text) cv2.rectangle(img, (x1, y1), (x2, y2), (0, 255, 0), 2) cv2.putText(img, obj.find(name).text, (x1, y1-5), cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2) cv2.imwrite(fcheck/{img_path.name}, img)跑完打开check/目录翻一遍重点看框是不是把机翼、尾翼都包进去了有没有把地面车辆误标成飞机。抽检发现的问题如果超过 5%建议全量复查否则模型学到的就是错的。从那以后我每次拿到新数据集都强制先跑一遍配对检查、类别统计、边界校验这三步再开始转格式和训练。这套流程帮我省下了至少三次通宵 debug。希望这份拆解能帮到你把这份飞机型号识别数据集真正用起来。本文还有配套的精品资源点击获取
企业数字化 ERP 产品动态
相关推荐
COMSOL变压器电磁-热耦合仿真建模全流程解析 1. 变压器热仿真项目概述变压器作为电力系统中的核心设备,其热管理一直是工程师关注的重点。这次我们要用COMSOL Multiphysics搭建一个完整的变压器电磁-热耦合仿真模型,重点分析铁芯和绕组在交变磁场作用下的涡流损耗分布,以及由此产生的温度… · 2026/9/23 12:27:02
斯坦索姆地图手写实现:3个坑点帮你拿下面试 斯坦索姆地图手写实现:3个坑点帮你拿下面试 官方文档翻了三遍还是云里雾里?别慌,这正是斯坦索姆地图面试的高频陷阱。很多新手在这里栽跟头,不是代码写不对,而是没抓住考点核心。我见过太多候选人,简历上写着精通数据结构,一上手画地图就懵圈,时间全… · 2026/9/23 12:26:56
端侧大模型实战:MiniCPM5-2B高智能密度与Agent落地全解析 我们先把话说在前头:端侧大模型这件事,过去几年的讨论基本都停在"能不能跑"的层面,真正把它当生产力工具用起来的人其实并不多。原因很简单——能跑和能用之间,隔着一条巨大的鸿沟:推理速度、内存占用、上下… · 2026/9/23 12:26:49
Pandas缺失值处理完全指南:dropna与fillna实战详解 1. 为什么缺失值处理是数据分析的第一个分水岭不管是处理爬虫抓来的原始数据、业务导出的Excel报表,还是接数仓里其他人跑出来的表,几乎没有人能避开那一行行刺眼的NaN、None或者空白。我见过不少初学者拿到数据后第一件事就是data.dropna()一把梭&#… · 2026/9/23 13:03:20
opaicn原理详解 3个核心技巧搞定opacn报错,高频面试题秒懂 打开控制台满屏红色报错,StackTrace 长得像天书,连第一行错误在哪都找不到?这种崩溃感,很多刚接触全栈开发的建筑工人朋友都经历过。别慌,这不仅是技术问题,更是高频面试题里的重灾区。… · 2026/9/23 13:03:07
勍怎么读:从生僻字到实战项目的破局指南 勍怎么读:从生僻字到实战项目的破局指南 学会语法却不知怎么搭项目,这是无数开发者卡脖子最狠的地方。你背下了Python的 def ,记住了Java的 class… · 2026/9/23 13:03:01
3个坑解决微信密友版性能问题附完整示例 3个坑解决微信密友版性能问题附完整示例 官方文档翻了三遍还是觉得云里雾里?别慌,微信密友版这种涉及隐私与实时性平衡的复杂机制,光看文字描述确实容易抓不住重点。很多开发者卡在“消息加密”和“好友列表隔离”这两个点上,导致面试时答非所问。今天这… · 2026/9/23 13:03:01
3招搞定手机怎么下载微信面试难题实战项目解析 3招搞定手机怎么下载微信面试难题实战项目解析 面试被问“手机怎么下载微信”背后的原理,90%的人答不上来。别笑,这看似弱智的问题,实则是考察你对移动应用分发机制、安全校验及网络协议理解的试金石。我带过不少校招新人,他们背了八股文,却连一个A… · 2026/9/23 0:00:03
你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 你有新短消息请注意查收:3个新手避坑指南搞定消息系统选型 面试被问“高并发下如何保证消息不丢失”,你张口就是“用Redis”,结果面试官追问“如果Redis宕机了怎么办”,你瞬间卡壳。这种场景太常见了,很多新手在背八股文时,只记住了技术名词… · 2026/9/23 0:00:29