简介:这份带标注的红外热成像建筑缺陷识别数据集,面向建筑质量检测、计算机视觉目标检测方向的研究者与工程师,配合YOLOv12训练流程使用,据标注信息训练可达到约91.5%的识别率,可用于墙体裂缝、渗漏、保温层脱落等典型缺陷场景的自动识别。压缩包内共1401个文件,包含700张JPG红外图像、700个对应的TXT标注文件(记录缺陷类别与边界框坐标),以及1个YAML配置文件(定义类别名称与路径参数),总体积仅34.53MB,轻量易部署。目前已有53人学习下载。数据集与YOLO系列标注格式直接兼容,下载后即可开始训练,省去繁琐的标注整理环节;同时多场景样本有助于提升模型泛化能力,适合用于算法研发、毕业论文实验以及工程化落地前的模型验证。无论是初学目标检测的新手,还是需要快速验证算法的研究者,都能从这套规范的标注数据中直接受益。
1. 带标注的红外热成像建筑缺陷识别数据集:能复现91.5%还是只能跑demo,先看这几件事
做建筑外墙检测的人都有个共同痛点:可见光照片拍不出裂缝背后的渗水和空鼓,必须上红外热成像。热像仪把温差变成图像,缺陷一目了然,但回来就得让算法接住,而算法得靠带标注的数据喂。红外建筑缺陷数据集本来就少,能直接跑YOLOv12的更稀罕。看到“带标注的红外热成像建筑缺陷识别数据集,支持YOLOv12,91.5%识别率,700张图”这个项目,多数人第一个念头是:能不能直接用,训练坑多不多。
这700张带检测框的热像图,配好YOLOv12训练配置,等于把采集、标注、训练、评估串成完整链路。适合两类人:刚接触目标检测、想快速用红外图出模型的工程技术人员;已经在做可见光缺陷检测、想迁移到热成像场景的算法工程师。后面按数据格式、训练流程、踩坑和提点技巧的顺序讲透。
2. 红外热成像建筑缺陷数据集:数据构成、标注规范与YOLOv12适配方式
2.1 红外热成像为什么是建筑缺陷识别的最优选型
先说原理。建筑缺陷在热像图上不是靠“形状”暴露的,而是靠“温度”暴露的。裂缝和空鼓区域的热传导路径被切断,表面温度与周边完好区域出现温差;渗水区域因为水分比热容大,升温降温都比干燥墙体慢,在热像图上表现为一块块边界模糊的低温或高温区域。红外热成像把这种温差变成可视化图像,缺陷识别任务就从“找纹理”变成了“找温度异常区”,这对检测模型来说是本质不同的任务。
这也是为什么同样是YOLO,可见光模型直接拿去跑红外图会掉点。可见光图有丰富的颜色和边缘信息,红外图是单通道灰度,对比度低、纹理少、噪声大。模型学到的特征不一样,必须用红外数据本身去训练,或者至少做充分的域适应。标题里这个数据集压在91.5%识别率,就是建立在“缺陷在热像图上的温度表现是稳定可学”这个前提上,这一点决定了后续所有训练策略的取向。
所以拿到这个项目,不要一上来就套用你之前的RGB训练配方。先把图当作红外图来对待:灰度、单通道、可能有伪彩映射,这些细节在训练参数上的影响,第4章会专门讲。这里先记住一个结论:红外热成像建筑缺陷识别不是换了个数据集那么简单,它换了一个成像模态,训练策略必须跟着换。
实际采集的时候还有一个很容易忽略的条件:温差建立。热像仪拍建筑缺陷,讲究的是在日出后或者日落后等外墙温度场快速变化的窗口期去拍,这时候裂缝、空鼓与背景的温差最大,缺陷在热像图上最清晰。如果图源是在中午拍的,整个墙面温度均匀,缺陷和背景几乎没有温差,标注员看原图都费劲,标出来的框质量就可想而知。拿到数据集先看有没有采集时间信息,或者直接从图里感受对比度,对比度差的图源后面再怎么调参都补不回来。
2.2 标注体系:缺陷类别、标注格式与工具链
这个数据集的标注对象通常是几类常见建筑缺陷:开裂、渗水/潮湿、空鼓、保温层缺陷、锈蚀,有的还会把“疑似缺陷”单独分一类。类别数量一般不会太多,目标检测在5到8个类这个量级上最稳,类别太多且样本不平衡时,小样本类别的AP会掉得很明显。拿到数据后第一件事就是把类别清单列出来,确认每类对应的标签编号,这一步不做,后面训练百分百翻车。
下表是按热像图表现归纳的常见类别,标注时可以参考这个逻辑来确定框的贴合方式:
| 类别 | 热像图上的典型表现 | 标注难点 |
|---|---|---|
| 开裂 | 线状温度突跳,通常比周围低 | 长宽比极端,框要贴合细长区域 |
| 渗水/潮湿 | 大面积低温区域,边界模糊 | 容易把框画大,把完好墙面圈进来 |
| 空鼓 | 温差斑块,形状不规则 | 边界不清晰,标注靠经验判断 |
| 保温层缺陷 | 大范围温差过渡带 | 和目标边界不清,容易漏标 |
| 锈蚀 | 局部高温点 | 尺寸小,易漏标 |
标注格式上,既然标题点名支持YOLOv12,最稳妥的约定就是YOLO格式的txt标注:每个txt文件对应一张同名图片,每一行是“类别编号 x_center y_center width height”,后面四个值都除以图片宽高做了归一化。这个格式是所有YOLO系列模型的通用语言,YOLOv12也不例外。如果你手里的原始标注是CVAT导出的COCO JSON,或者LabelImg导出的XML,就得先转换,转换脚本在第3章里给。
这里顺带提一句数据标注工具的选择。做这类数据集的标注,常见做法是用CVAT或LabelImg,这两者在目标检测标注工具里属于最主流的。CVAT适合团队协作,能在线标,导出格式多;LabelImg轻量,单机标注方便。不管用哪个,导出后统一转成YOLO格式这一步都少不了,而且建议转完后随机抽几张图,把框画出来做人工核验,别信“导出即正确”。红外图中“边界模糊”的缺陷特别考验标注员耐心,框稍微画大一点,模型学到的边界就是错的,这个隐患会一路传导到识别率里。
2.3 YOLOv12适配数据集的目录组织与data.yaml
YOLOv12在数据加载上沿用了Ultralytics系的路子,数据集的目录结构要求固定:images下放图,labels下放同名txt,再加一个data.yaml描述路径和类别名。700张图的常见划分是8比2,也就是约560张训练、140张验证,下面这个结构可以直接套用:
building_defect_dataset/ ├── data.yaml ├── images/ │ ├── train/ # 约560张红外热像图 │ └── val/ # 约140张红外热像图 └── labels/ ├── train/ # 与images/train同名的txt标注 └── val/ # 与images/val同名的txt标注目录只要符合这个约定,训练脚本不用改路径逻辑。data.yaml的内容也很标准,关键是把names列表和你的类别编号严格对应:
# building_defect_dataset/data.yaml path: /path/to/building_defect_dataset # 数据集根目录的绝对路径 train: images/train val: images/val names: 0: crack 1: moisture 2: delamination 3: insulation_defect 4: corrosion这里的参数要盯死两点。第一,path建议写绝对路径,相对路径在换机器跑的时候经常因为工作目录不同而找不到图。第二,names的索引顺序必须和txt标注里的类别编号一致,比如0对应crack,那txt里类别编号0就只能是裂缝,不能是别的。见过不少人在这上面出错,最常见的是把类别编号从1开始写,结果模型把背景当成了第0类,这个在第4章会展开讲。
YAML写完后,建议顺手跑一遍完整性校验,统计训练集里每个类各有多少个框,缺标注的图片有多少张:
python -c " from pathlib import Path from collections import Counter cnt = Counter() for txt in Path('labels/train').glob('*.txt'): for line in txt.read_text().splitlines(): cnt[int(line.split()[0])] += 1 print(dict(sorted(cnt.items()))) "输出结果应该和data.yaml里的names列表一一对应。如果某个类别一次都没出现,就要回数据源核对该类是不是没标或标漏了。这一步在数据集交付时也可能帮你判断交付方是否真的做过质量检查。
3. 用YOLOv12在本地跑通训练:环境、转换脚本与训练参数
3.1 环境准备:把ultralytics装到能跑YOLOv12的状态
YOLOv12的训练入口在Ultralytics生态里,所以要先把ultralytics这个库装到位。官方建议用GPU训练,至少一张显存8G以上的NVIDIA显卡,显存不够可以用imgsz降到480或batch降到8来换。安装命令很简单:
pip install -U ultralytics装完确认环境,YOLOv12的权重命名是yolov12n.pt、yolov12s.pt这套,用下面的命令确认当前环境是否支持:
yolo help | grep -i yolov12 python -c "from ultralytics import YOLO; print(YOLO('yolov12s.yaml'))"同时确认CUDA和PyTorch是否就位,这一步能省掉后面跑训练时才发现环境问题的返工时间:
nvidia-smi python -c "import torch; print(torch.cuda.is_available(), torch.__version__)"如果第一条命令没有输出,说明当前ultralytics版本不带YOLOv12,需要升级,或者从YOLOv12官方仓库按README把模型文件放进你的工程目录后重新导入。这一步看着简单,实际是很多人卡最久的地方,因为pip默认装到的旧版本并不包含YOLOv12的注意力模块定义。
环境就绪后,把预训练权重下载到本地,建议用yolov12s.pt起步。s规模在700张图的数据集上是性价比最高的选择,因为数据量小,用n或s能更快收敛,直接用l甚至x反而更容易过拟合,后面章节会解释原因。如果你的显卡确实跑不动YOLOv12的注意力模块,退一步用yolov8s.pt做初始化也完全能跑,效果差距通常小于5个百分点,这个数据集的算法兼容性比大多数人想象的好。
3.2 把COCO/VOC标注转成YOLO格式:转换脚本与参数说明
如果你的标注已经是YOLO txt,这节可以跳过。但据我所知,这类红外数据集原始交付格式五花八门,CVAT导出COCO JSON的占比很高,所以这里给出一个把COCO JSON转YOLO txt的脚本,直接按你的路径改就能用:
# convert_coco_to_yolo.py import json, os from pathlib import Path def convert(coco_json: str, img_dir: str, out_dir: str): with open(coco_json, 'r', encoding='utf-8') as f: coco = json.load(f) imgs = {im['id']: im for im in coco['images']} # COCO类别的id不一定是连续的,这里统一映射成从0开始的连续编号 cats = {cat['id']: idx for idx, cat in enumerate(coco['categories'])} anns_by_img = {} for ann in coco['annotations']: anns_by_img.setdefault(ann['image_id'], []).append(ann) os.makedirs(out_dir, exist_ok=True) for img_id, anns in anns_by_img.items(): im = imgs[img_id] w, h = im['width'], im['height'] lines = [] for ann in anns: cls_id = cats[ann['category_id']] x, y, bw, bh = ann['bbox'] # COCO bbox是[x, y, width, height] cx = (x + bw / 2) / w cy = (y + bh / 2) / h nw = bw / w nh = bh / h lines.append(f'{cls_id} {cx:.6f} {cy:.6f} {nw:.6f} {nh:.6f}') if lines: txt = Path(out_dir) / (Path(im['file_name']).stem + '.txt') txt.write_text('\n'.join(lines), encoding='utf-8') if __name__ == '__main__': convert( coco_json='defect_annotations.json', # CVAT导出的COCO标注 img_dir='images/', out_dir='labels/train/', )这个脚本有两点要注意。一是COCO的bbox是左上角坐标加宽高,而YOLO格式是中心点坐标加宽高,换算公式就是脚本里那四行,别把原始坐标直接抄进去。二是COCO的类别id经常不是从0开始也不连续,脚本里的cats字典做了重映射,这一步能避免后面类别编号错位的坑。
如果原始标注是VOC XML,转换逻辑相同,只是读取方式要从json换成xml.etree,核心还是“左上角宽高”到“中心点宽高”的换算。转完之后不要急着训练,先把txt里每个类别的框数量统计一次,与data.yaml的names对照,确认没有空类和编号错位。
3.3 训练参数:epochs、imgsz、batch与迁移学习的取舍
调参之前先想清楚700张图意味着什么。700张是典型的小数据集,训练时模型很容易把训练集的温度和纹理分布背下来,验证集一换就掉点。针对这个量级,我的经验是:加大epoch但配合早停,batch适中,imgsz保持640或以上,mosaic保持开启但注意它在末段自动关闭的机制。
下面是直接可用的训练命令:
yolo detect train \ data=/path/to/building_defect_dataset/data.yaml \ model=yolov12s.pt \ epochs=300 \ imgsz=640 \ batch=16 \ patience=50 \ cache=True \ device=0 \ project=runs/building_defect \ name=exp_yolov12s逐个参数说含义。epochs设成300是因为小数据集每轮看到的样本少,需要更多轮次来充分收敛,实际用到多少看patience早停。patience=50表示验证集mAP连续50个epoch不涨就自动停,这是防止过拟合的最后一道闸。batch=16在8G显存下跑s模型问题不大,如果你的卡只有6G,batch降到8、imgsz降到480。cache=True把训练图像缓存进显存或内存,700张图完全放得下,训练速度能快一截。
显存和模型规模的对应关系,按我的经验可以参考这张表来定起点:
| 显存 | 模型 | imgsz | batch |
|---|---|---|---|
| 6G | yolov12n | 480 | 8 |
| 8G | yolov12s | 640 | 16 |
| 12G | yolov12s | 640 | 32 |
| 24G | yolov12m | 640 | 32 |
这里专门说一句mosaic。Ultralytics默认会在训练中开启mosaic拼图增强,把4张图拼成1张喂进网络。mosaic对小目标多的场景收益很大,但也会改变图像分布,所以ultralytics训练的最后10个epoch会自动关闭mosaic。这一点不用改,默认就行,别手动全局关闭,除非你发现目标框大量贴在图像边缘导致标签错乱。
迁移学习的权重选择也有讲究。标题写“支持YOLOv12”,那就从yolov12s.pt开始。如果显卡或框架跑不动YOLOv12的注意力模块,退一步用yolov8s.pt做初始化也完全能跑,效果差距通常小于5个百分点,这个数据集的算法兼容性比大多数人想象的好。
3.4 验证结果:mAP、Precision、Recall怎么解读,91.5%在哪一层
训练结束后,ultralytics会在runs/building_defect/exp_yolov12s/目录下生成weights/best.pt和last.pt,以及val_batch0_pred.jpg这类可视化图。best.pt是验证集上mAP表现最好的权重,训练和推理都用它。验证命令是:
yolo detect val \ data=/path/to/building_defect_dataset/data.yaml \ model=runs/building_defect/exp_yolov12s/weights/best.pt \ imgsz=640 \ batch=16跑完会打印一张指标表,关键是mAP50和mAP50-95两列。标题里的91.5%识别率,如果对应的是mAP50,那属于不错但正常的水平;如果对应的是mAP50-95,那说明模型泛化能力非常强。复现的时候先盯这两列,别盯着Loss曲线看,Loss在训练集上降得再漂亮都说明不了泛化。
这里插一句血泪经验:我在类似项目里吃过亏,训练完只看训练集loss,以为模型稳了,一上验证集立刻现原形。小数据集上验证集mAP才是唯一的裁判,训练集Loss只是过程指标。所以每次训练完,先打开results.png看mAP50曲线的走势,如果曲线在某个epoch突然掉头向下,多半是早停没触发或数据增强关闭导致过拟合提前引爆。
4. 训练避坑与常见问题排查:红外图预处理、标签错位与小目标漏检
4.1 16位热像图被当8位读:对比度丢失与成像噪声放大
现象:训练出来的模型在验证集上mAP不高,可视化预测框大量漏检,尤其渗水和保温层缺陷这种大面积、低对比度的类别,框得七零八落。
原因:不少红外相机直接输出16位PNG或TIFF,温度数据以uint16存储。ultralytics加载图片走的是OpenCV的imread默认路径,16位图会被截断到8位,或因为像素值范围过大而整体发黑发灰,原本清晰可见的温差区域被压缩成一片灰。
解决:先把所有训练图全部转换并增强成8位,不要指望训练时自动处理。推荐用百分比截断加CLAHE的方式:
# preprocess_thermal.py import cv2 import numpy as np from pathlib import Path src_dir = Path('raw_images') out_dir = Path('images/train') out_dir.mkdir(parents=True, exist_ok=True) for p in src_dir.glob('*.*'): img = cv2.imread(str(p), cv2.IMREAD_UNCHANGED) # 按原深度读入 if img.dtype == np.uint16: # 按1%和99%分位截断,去掉传感器极端值,保留温差主体 lo, hi = np.percentile(img, (1, 99)) img_f = np.clip((img.astype(np.float32) - lo) / max(hi - lo, 1), 0, 1) img_8u = (img_f * 255).astype('uint8') else: img_8u = img # CLAHE对热像图对比度提升非常明显,clipLimit不宜太大,2.0左右够用 clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8)) img_enhanced = clahe.apply(img_8u) cv2.imwrite(str(out_dir / p.name), img_enhanced)参数说明:percentile的(1, 99)是截断区间,如果图像过曝或过暗,可以放宽到(0.5, 99.5)。clipLimit是CLAHE的对比度限制,太大容易把热噪声一起放大,2.0是经验的稳定值;tileGridSize是分块尺寸,8x8是默认值,对建筑热像图这种大面积均匀区域比较合适。这一步做完再进YOLO,效果通常立竿见影。
4.2 背景被当成类别0:编号错位的经典翻车现场
现象:训练能跑完,loss也正常,但验证时预测出的框全部偏移,或者某一类永远检测不出来,打开results.png一看混淆矩阵,背景类占了一大块。
原因:这是目标检测数据准备的经典错误。数据集的类别编号从1开始写,比如txt里写“1 0.5 0.5 0.2 0.2”,但data.yaml里names列表第0个索引对应的是crack,结果模型把“1”当成了背景的负样本,或者把本来就该是背景的图像区域当成目标。
解决:训练前强制检查所有txt的类别编号最大值,必须小于names列表长度,并且编号从0开始连续。用一条命令就能查:
python -c " from pathlib import Path max_id = 0 for txt in Path('labels').rglob('*.txt'): for line in txt.read_text().splitlines(): cid = int(line.split()[0]) if cid > max_id: max_id = cid print('最大类别编号:', max_id, '(names长度应为', max_id + 1, ')') "如果打印结果里最大编号是5而names只有5项,那说明类别从1或2开始编了,把txt里的类别号统一减到从0开始。这个操作宁可多花十秒确认,也别带着错位的标签跑几十个epoch,白烧卡的时间最不值钱。
4.3 小目标漏检:裂缝和锈蚀这类缺陷为什么推不动
现象:整体mAP50看着有85%以上,但拆到每个类别,裂缝的AP很低,预测框经常只有一半盖住裂缝,或者压根没框出来。红外图里裂缝只有几个像素宽,几百像素长,长宽比极端。
原因:YOLO系列的特征金字塔在高层特征图上对小目标不友好,加上红外图像噪声大、边缘模糊,裂缝这种极端长宽比目标的中心点回归很容易漂移。700张图里如果裂缝样本只有几十条,那模型基本学不稳。
解决:针对小目标,优先增大imgsz到960试试,代价是显存翻倍,batch可能得降到8。其次是考虑对小目标区域做滑窗切图,把一张1024x1024的热像图切成四张512x512的图,标注坐标跟着平移映射,再做训练。切图能把小目标在输入里的像素占比拉大,mAP提升很明显。
具体做法并不复杂:切图时记录每个子图的偏移量,把原标注框的坐标减去偏移量,滤掉完全落在子图外的框,保留与子图有交集的框并把越界部分裁掉。切图建议写成独立脚本放在训练流程的预处理环节里。切出来的图数量会翻几倍,正好缓解700张图偏少的问题,但要注意切图后同一缺陷可能出现在多张子图里,验证集统计会偏高,所以切图只对训练集做,验证集保持原图。
4.4 700张小数据集过拟合判断:Loss背出来了,泛化没跟上
现象:训练集mAP50冲到96%,验证集只有80%,差距越拉越大,或者验证集mAP50曲线在某个epoch掉头向下。这就是过拟合的典型信号。
原因:700张图对YOLOv12这种带注意力机制的大模型来说偏少,模型容量太大,训练集特征被快速记忆,泛化能力跟不上。
解决:三个方向同时抓。第一,用早停,patience设30到50,别让验证集mAP恶化后再跑几十轮。第二,加大数据增强,除了mosaic,把fliplr、scale等增强参数调强一点,ultralytics里可以在训练命令上加augment=True并配合自定义的增强配置。第三,改用更小的模型,或者从冻结权重开始:先用yolov12n.pt冻结backbone训练30个epoch,再解冻全模型继续训练,这样既保留预训练特征,又不会让小数据集把随机初始化的检测头带偏。
另外,验证集中单独拿出固定的一部分做评估是必要的。700张图划分时常见做法是8比2,但如果各类别样本数极不均衡,建议按类别比例做分层抽样,保证验证集里每一类都有足够的样本。这样评估结果才不是黑匣子里的数字游戏。
4.5 训练中断、显存溢出与标签非法值的排查顺序
现象:跑到一半报CUDA out of memory;或者训练一开始就报“Label shape not compatible”之类的错误;还有的图加载时报“image corrupted”。
原因:显存溢出通常是batch和imgsz的乘积超过了显存上限;标签非法值通常是归一化坐标里有负数或大于1的数,多源于标注框越界或转换脚本没做裁剪;图像损坏大多是原始红外图带EXIF信息或特殊编码格式,读取时出错。
解决:按照顺序排查。先显存,把batch降到8、imgsz降到480,如果还炸就是数据加载本身有问题,检查cache=True生成的缓存是否损坏,删掉datasets目录下的缓存文件重来。再查标签,用一条命令找出所有坐标不在[0,1]区间的txt:
python -c " from pathlib import Path for txt in Path('labels').rglob('*.txt'): for line in txt.read_text().splitlines(): vals = list(map(float, line.split())) if len(vals) != 5 or any(v < 0 or v > 1 for v in vals[1:]): print(txt, line) "这行命令把所有非法行打印出来。有非法值就是在转换脚本里没做clip或没做全,重新跑一遍转换脚本,并保证对cx、cy、w、h都做0到1的裁剪。最后是损坏图,直接用OpenCV逐张读取,读不出来的单独挑出来检查是格式损坏还是文件名带中文导致路径解析失败。这几步做完,绝大多数翻车都能定位到具体环节,不用全盘重来。
5. 把91.5%复现到你的机器上:核对清单与一条小目标进阶路线
5.1 复现前的核对清单
91.5%这个数字本身不可怕,可怕的是你不知道它是什么口径。复现之前先确认三件事:数据划分是否一致,各类别样本占比是否一致,评价指标是mAP50还是mAP50-95。这三个口径任何一处对不上,复现出来的数字就无从比较。
我的习惯是先用best.pt做一次完整推理,把预测结果画到原图上,人眼过一遍。画出框的位置对不对、类别贴不贴、有没有把屋面和墙体背景误报成缺陷,这比看任何指标都直观。指标可以刷,但漏检和误报的分布骗不了人。
5.2 一条最值得试的进阶路线
如果复现的精度不理想,先对验证集做一次带TTA的推理,确认上限在哪,命令是在val里打开augment:
yolo detect val \ model=runs/building_defect/exp_yolov12s/weights/best.pt \ data=/path/to/building_defect_dataset/data.yaml \ imgsz=640 \ augment=Trueaugment=True会在推理时做多尺度融合和翻转推理,mAP通常能提升1到3个点,代价是推理耗时翻倍,适合最后压榨精度时用。如果TTA提升超过3个点,说明模型本身对尺度变化敏感,回头应该优先做多尺度训练而不是继续加数据。
如果非得给一条优先级最高的建议,我会说:做滑窗切图,把700张变成单类别目标占比更高的几千张子图,同时保持验证集不切。这条路对裂缝、锈蚀这类小目标缺陷的提升,通常比换任何模型都明显。这也是我做这类红外项目收尾时最常用的一招。
写到这里,刚入手的项目能跑通,瓶颈也看得见了。最细的坑其实就一句话:小数据集上,处理好数据胜过调三个月参。希望帮到你。
本文还有配套的精品资源,点击获取