塔吊高空俯拍图像数据集清洗与YOLOv8目标检测训练实践 简介面向塔吊高空作业场景的视觉识别与安全监控数据集包含1000多张从高空向下俯拍的作业图像及配套标注。资源包采用zip格式共2110个文件其中1055个jpg图像与1055个xml标注文件一一对应整体大小为218.88MB解压后可直接按目录索引使用。xml标注文件记录了图像中的目标位置信息可用于目标检测、实例分割等监督学习模型的训练与验证免去手动标注的繁琐过程。全部图像均来自塔吊实际高空作业过程视角真实、场景集中能够为施工安全监测、作业行为分析、高空场景理解等研究提供贴合业务需求的数据基础。目前已有298人学习下载适合计算机视觉方向的学生、研究人员及智慧工地相关开发人员将其作为算法迭代与模型评估的参考数据集。1. 塔吊高空俯拍图像数据集与视觉模型前置条件智慧工地里塔吊顶部的俯拍摄像头拍到的画面和手持相机完全是两个世界。近垂直视角下地面人员、吊钩、吊物全部挤在同一个画面里远处的人可能只有十几个像素近处的吊臂占据半边屏幕再加上阳光、阴影和塔身振动直接在原图上跑通用检测模型漏检率会高到没法用。这份塔吊高空作业向下排的作业图像数据集就是 1000 多张从实际机位采集下来的原始 JPG文件名是毫秒级时间戳没有现成标注。它适合做安全帽检测、吊装活动识别、人员区域入侵判断的团队作为第一手训练素材。拿到这批图之后真正要处理的不是跑个模型这么简单而是得先解决数据清洗、标注策略、模型选型和部署适配这一整条链路。2. 数据清洗与图像预处理实战2.1 从文件名还原采集时间并做去重这批图像文件名长得像1652237739.0901837.jpg这是毫秒级 Unix 时间戳。很多采集端会在同一秒写入多帧或者在网络抖动时重复写文件直接拿来训练会导致相近样本占比畸高模型对时间分布产生误判。我一般会先写个脚本把时间戳解析出来按时间排序同时看有没有重复帧。import os import glob image_dir ./tower_crane_images images glob.glob(os.path.join(image_dir, *.jpg)) records [] for img in images: basename os.path.splitext(os.path.basename(img))[0] try: ts_ms float(basename) except ValueError: continue ts_sec ts_ms / 1000.0 records.append((ts_sec, img)) records.sort(keylambda x: x[0]) print(有效图像数量:, len(records)) # 去除时间戳完全一致的重复文件 seen set() unique [] for ts, img in records: if ts in seen: os.remove(img) # 或移到 recover 目录 continue seen.add(ts) unique.append(img) print(去重后数量:, len(unique))这段脚本先把毫秒时间戳转成秒方便后续按时间窗口切片。去重时直接用float精度比较因为同一张照片被重复保存时时间戳几乎不会巧合相同。如果担心误删可以改成移动到备份目录。时间戳信息本身也能用来做训练集/验证集切分——按时间顺序取前 80% 作为训练集后 20% 作为验证集避免同一天相似画面同时落在两个集合里。2.2 清晰度与曝光质量筛选高空摄像头受风振动和自动曝光影响画面经常出现两类问题对焦不实导致的整体模糊以及强逆光导致暗部死黑。模糊样本会造成标注困难让模型学到错误的纹理特征过暗样本则直接丢失目标信息。可以通过 Laplacian 算子方差评估清晰度再结合整体亮度做初筛。import cv2 import numpy as np def evaluate_image(path): image cv2.imread(path, cv2.IMREAD_COLOR) if image is None: return None, None, None, None gray cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) lap_var cv2.Laplacian(gray, cv2.CV_64F).var() brightness gray.mean() h, w image.shape[:2] return lap_var, brightness, h, w blurry [] under_exposed [] over_exposed [] for _, img_path in records: lap_var, brightness, h, w evaluate_image(img_path) if lap_var is None: continue if lap_var 50: # 清晰度阈值现场可按需调整 blurry.append(img_path) if brightness 32: # 低亮度阈值 under_exposed.append(img_path) if brightness 222: over_exposed.append(img_path) print(f模糊图: {len(blurry)}, 过暗图: {len(under_exposed)}, 过曝图: {len(over_exposed)})阈值 50 只是经验值不同摄像头和编码格式差异很大。我建议先随机抽 10 张图人工看一下 Laplacian 方差分布取明显模糊样本的方差作为分界线。过暗/过曝的判断也不能只看均值最好加上灰度直方图的 5% 分位数比如暗部像素占比超过 20% 才算过暗。筛选后的图像可以保留但要在标注阶段跳过而不是直接删除——调试时还可能用到。对于过暗但清晰度还行的图像可以用 CLAHE 做局部对比度增强让暗部区域露出更多细节后面训练和标注都用同一套预处理保持数据一致性。def clahe_preprocess(image): lab cv2.cvtColor(image, cv2.COLOR_BGR2LAB) l_plane, a_plane, b_plane cv2.split(lab) clahe cv2.createCLAHE(clipLimit2.0, tileGridSize(8, 8)) l_plane clahe.apply(l_plane) merged cv2.merge([l_plane, a_plane, b_plane]) return cv2.cvtColor(merged, cv2.COLOR_LAB2BGR)CLAHE 的clipLimit控制对比度增强幅度2.0比较适中调太大会让天空出现光晕。tileGridSize决定局部区域大小塔吊俯拍场景里人和吊物尺寸差异大8x8能在增强细节和控制噪声之间取得平衡。如果目标区域集中在画面中央可以改成16x16提升局部效果。表常见图像质量问题与处理策略问题类型判断指标处理策略运动模糊Laplacian 方差 50直接剔除不进入标注流程曝光不足灰度均值 32用 CLAHE 增强或加入训练集做亮度增强过曝灰度均值 222剔除过曝区域信息不可恢复分辨率不足长边 800 像素剔除小目标将完全丢失时间戳重复文件名时间戳相同保留一帧手动复核2.3 相似去重与场景均衡同一塔吊在静止状态拍摄的画面高度相似连续几百帧几乎都是同一片地面。直接全量训练会让模型在重复场景上过拟合换一个工地就失效。我会用感知哈希perceptual hash计算每张图像的指纹把汉明距离小于某个值的图像视为相似帧每段连续相似帧最多保留 3 张。import cv2 import numpy as np from PIL import Image import imagehash hash_list [] for img_path in unique: hash_value imagehash.phash(Image.open(img_path)) hash_list.append((hash_value, img_path)) selected [] for i, (hash_val, path) in enumerate(hash_list): if i 0: selected.append(path) continue last_hash hash_list[i-1][0] if hash_val - last_hash 8: # 汉明距离阈值 continue selected.append(path)这里用imagehash.phash生成的 64 位感知哈希差值越小表示图像越相似。阈值 8 意味着允许一定程度的视角抖动和光线变化。实际运行后1000 张图可能筛掉 300 多张高度相似的连续帧。这步做完剩下的样本场景多样性才够支撑一个可泛化的检测模型。3. 从原始图像到矢量化数据集标注与增强3.1 标签体系设计塔吊俯拍图像里最值得关注的几类目标地面施工人员person、塔吊吊钩hook、吊装物load、以及可能伸出的塔吊臂局部。不建议一开始就做安全帽佩戴状态识别因为垂直视角下安全帽在图像里只是一个圆点尺寸太小很难稳定区分颜色。我常用的是两级方案先检测 person再裁出人头区域做安全帽分类。这样标签简洁且对俯拍小目标友好。表塔吊俯拍数据集标签建议类别 ID类别名检测难点0person人小、密集、被吊索遮挡1hook背景复杂钢丝绳干扰2load形状不定阴影影响轮廓3background忽略塔吊臂本身不需要检测3.2 用自动标注生成初始框再人工修正1000 张图纯手工画框太慢现在的主流做法是用 Grounding DINO 或 SAM 生成预标注人工只修正错漏。这里以 SAM 计算 mask 再转 YOLO 格式为例说明如何把图像数据集转换成矢量化标注数据集。from segment_anything import sam_model_registry, SamPredictor import cv2 import numpy as np sam sam_model_registry[vit_b](checkpointsam_vit_b.pth) predictor SamPredictor(sam) for image_path in selected: image cv2.imread(image_path) image_rgb cv2.cvtColor(image, cv2.COLOR_BGR2RGB) predictor.set_image(image_rgb) masks, scores, _ predictor.predict( point_coordsNone, point_labelsNone, multimask_outputFalse ) # masks 为 (1, H, W) 的语义 mask需要转成每个物体的独立 mask这里只取了全屏最大的一个 mask实际生产环境要配合 Grounding DINO 的文本提示分别框出 person、hook 和 load。SAM 的核心贡献是省去人工逐像素勾边模型输出的 mask 边界光滑转成 YOLO 训练样本时可以直接从 mask 中提取最小外接矩形作为 bbox。def mask_to_polygon_and_bbox(mask, image_w, image_h, class_id): contours, _ cv2.findContours( mask.astype(np.uint8), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE ) boxes [] polygons [] for cnt in contours: area cv2.contourArea(cnt) if area 20: # 过滤噪点 continue x, y, w, h cv2.boundingRect(cnt) bx (x w / 2) / image_w by (y h / 2) / image_h bw w / image_w bh h / image_h boxes.append((class_id, bx, by, bw, bh)) # 矢量化将轮廓点归一化到 0~1保存为多边形坐标列表 cnt_norm cnt.reshape(-1, 2).astype(np.float32) cnt_norm[:, 0] / image_w cnt_norm[:, 1] / image_h polygons.append(cnt_norm.tolist()) return boxes, polygonsmask_to_polygon_and_bbox把 SAM 输出的二值 mask 转成两组数据一组是 YOLO 需要的归一化中心点/宽高格式另一组是归一化多边形坐标。这个多边形坐标就是矢量化的真正含义后续可以用在不同任务上比如做实例分割、几何测量或者区域网格化分析。注意cv2.findContours要求输入是二值图SAM 输出的 float mask 要先用astype(np.uint8)转换。自动标注结果通常有大量误标尤其是细长的钢丝绳和地面阴影。需要把预标注结果导入 LabelStudio 或 X-AnyLabeling 人工复核重点修正漏检和类别错误。经验上SAM 对小物体的召回率偏低遇到图像里只有 20 像素大小的工人时需要手动补框。3.3 适合高空俯拍的数据增强高空俯拍图像的特殊性在于同一目标从不同高度拍摄尺寸缩放幅度很大吊臂旋转导致图像任意角度变化太阳位置不同产生阴影方向不同。因此增强方案不能只用随机裁剪还要包含尺度缩放和任意角度旋转。import albumentations as A train_transform A.Compose( [ A.RandomRotate90(p0.5), A.Flip(p0.5), A.RandomScale(scale_limit(0.2, 1.0), p0.7), A.RandomBrightnessContrast( brightness_limit0.25, contrast_limit0.2, p0.6 ), A.RandomShadow(p0.3), A.HueSaturationValue(hue_shift_limit5, p0.2), ], bbox_paramsA.BboxParams( formatyolo, min_visibility0.3, ), )RandomScale的scale_limit写成了(0.2, 1.0)这是 Albumentations 1.4 的写法表示允许横向放大到 1.2 倍、纵向缩小到 0.2 倍实际含义取决于版本。更稳妥的做法是用A.Affine(scale(0.5, 1.5))控制尺度范围。min_visibility0.3表示增强后如果目标可见面积不足原来的 30%这个标注框会被丢弃避免训练时出现大量空框。RandomShadow模拟塔吊臂在地面投下的阴影能增强模型对光照变化的鲁棒性。4. 基于 YOLOv8 的塔吊俯拍目标检测训练4.1 训练目录结构与 YAML 配置清洗、标注、增强后把数据集组织成 YOLO 检测的标准目录结构。验证集不要从同一塔吊的连续画面里随机抽样而是按时间段或塔吊编号划分否则模型记住背景纹理就能得分虚高。crane_data/ ├── images/ │ ├── train/ │ │ ├── 1652237739.jpg │ │ └── ... │ └── val/ │ ├── 1652238800.jpg │ └── ... └── labels/ ├── train/ │ ├── 1652237739.txt │ └── ... └── val/ ├── 1652238800.txt └── ...对应的 YAML 文件path: ./crane_data train: images/train val: images/val nc: 3 names: 0: person 1: hook 2: load注意 YAML 里path指向项目根目录下面train和val是相对路径。如果训练时改用了相对路径但path写错YOLO 会报No labels found排查时首先检查这里的路径映射。4.2 训练命令与关键参数调整我用 YOLOv8 训练这类高空俯拍数据集时最常调的是imgsz。默认 640 对普通视觉任务够用但塔吊俯拍图里 person 通常只有 30x30 像素imgsz提到 1280 能显著提升小目标召回。yolo detect train \ modelyolov8s.pt \ datacrane.yaml \ imgsz1280 \ batch8 \ epochs120 \ workers4 \ device0 \ optimizerAdamW \ lr00.001 \ close_mosaic10 \ mosaic1.0表YOLOv8 训练参数经验值参数说明调整建议imgsz输入分辨率1280 或更高小目标明显增多batch批量大小根据显存设置A100 可以 16消费卡 3090 建议 8mosaic1.0马赛克增强保持开启但最后 10 epoch 关闭close_mosaic10后 10 epoch 关闭马赛克让模型稳定收敛否则训练损失震荡optimizerAdamW优化器小数据集用 AdamW 比 SGD 稳lr00.001初始学习率预训练模型建议 0.001~0.002mosaic1.0将四张图拼成一张相当于把塔吊俯拍图的全局空间混在一起能显著提升小目标的上下文信息。但为什么最后 10 个 epoch 要close_mosaic因为马赛克拼接产生的图像边界和真实场景不一致后期继续用会导致定位精度下降。我通常会尝试开启close_mosaic10然后观察最后一轮验证集 mAP 是否上升如果上升再逐步增大。4.3 模型尺寸选型与推理速度权衡YOLOv8 提供 n/s/m/l/x 五种尺寸塔吊高空俯拍任务不只是离线分析还可能部署到工地边缘设备。如果只有 RTX 3060 这类 GPU用yolov8s配 1280 分辨率差不多是上限换成 NVIDIA Jetson Orin则更适合yolov8n配 960 分辨率然后配合 TensorRT 加速。表不同模型尺寸在塔吊数据集上的对比维度模型参数量M适合场景感受野特点YOLOv8n3.2边缘设备实时监控对大目标更友好小目标需要高分辨率YOLOv8s11.2离线训练边缘推理平衡点推荐YOLOv8m25.9离线分析小目标精度更高但推理慢YOLOv8l43.7高精度离线需要更多显存适合夜间红外图塔吊上方摄像头往往是固定机位背景基本不变目标只集中在画面特定区域。这种情况下可以考虑注册背景模型用 ROI 区域分析减少误检。但训练时不要裁掉无关区域否则模型没见过塔吊臂部署时容易误报。4.4 训练结果评估与阈值调整训练完成后用验证集评估 PR 曲线和混淆矩阵。yolo detect val \ modelruns/detect/train/weights/best.pt \ datacrane.yaml \ imgsz1280评估输出里最关键的是all行的 mAP50 和 mAP50-95。mAP50 是 IoU 阈值 0.5 时的平均精度适合评估定位粗粒度场景mAP50-95 要求更高能反映框的紧致度。塔吊吊物的形状非常不规则mAP50 可能比 mAP50-95 高 0.3~0.5这是正常现象。如果两者接近反而说明模型对目标边缘没学出形状差异。如果验证集上的 recall 偏低通常是因为小目标漏检。此时优先增大imgsz其次减小aspect_ratio相关增强或关闭mixup。不要盲目增加模型层数小目标问题主要靠分辨率解决。5. 部署优化与高空视角推理排错把训练好的模型导出成 ONNX可以在边缘设备上用 ONNX Runtime 跑不依赖 PyTorch 环境。import onnxruntime as ort import cv2 import numpy as np session ort.InferenceSession( best.onnx, providers[CUDAExecutionProvider, CPUExecutionProvider] ) def preprocess(image, size1280): image cv2.resize(image, (size, size)) image cv2.cvtColor(image, cv2.COLOR_BGR2RGB) image image.astype(np.float32) / 255.0 # 归一化到与训练一致 image (image - np.array([0.485, 0.456, 0.406])) / np.array([0.229, 0.224, 0.225]) return np.transpose(image, (2, 0, 1))[None, :, :, :] img cv2.imread(test.jpg) blob preprocess(img) outputs session.run(None, {session.get_inputs()[0].name: blob})这段代码里需要特别注意的是输入图像分辨率必须和训练时一致ONNX 导出时默认固定动态尺寸。如果训练用 1280部署也要用 1280否则目标框坐标按原始图缩放会出现偏差。另外YOLOv8 导出的 ONNX 输出通常是一个 1x(4nc)xN 的张量N 是预测候选框数量需要后处理做 NMS。针对塔吊高空视角常见部署问题我有几点经验塔吊振动导致连续帧模糊。可以在摄像头端做 Laplacian 方差判断方差低于 40 的直接丢弃该帧不送入推理管线省算力又避免误检。逆光环境。在推理前对整帧做 CLAHE 再送入模型训练和推理的预处理保持一致否则模型看到的特征分布不一样。小目标漏检。把原图切成上下左右四块每块放大 1.5 倍后分别推理再把检测框映射回原图坐标做 NMS。这个 tiling 策略提升召回明显但推理耗时翻倍适合夜间巡检这类非实时场景。吊钩在强光下反光呈白色圆形容易和远处汽车玻璃混淆。解决方法是增加一个吊钩钢丝绳联合标签让模型学到吊钩必须伴随钢丝绳的结构特征。当置信度阈值设得太高时会漏检设得太低时会误报。我一般用统计方法取验证集全部预测框的最高置信度分布按分位数的 95% 作为初始阈值再根据现场报警频率上下调整。这样比人工拍脑袋设 0.5 更稳。最后把推理结果直接叠加到视频流里并用队列维护每个目标的最近 5 帧位置当目标连续 3 帧未更新时才认为其消失这样能有效抑制单帧闪烁误报警。本文还有配套的精品资源点击获取