【数据集实战】从零构建LaneNet车道线检测数据集:基于Labelme与Tusimple格式的完整指南

张开发
2026/4/21 4:32:01 15 分钟阅读

分享文章

【数据集实战】从零构建LaneNet车道线检测数据集:基于Labelme与Tusimple格式的完整指南
1. 为什么需要自定义车道线数据集车道线检测是自动驾驶和高级驾驶辅助系统(ADAS)的核心技术之一。虽然公开数据集如Tusimple已经提供了大量标注样本但在实际项目中我们经常会遇到这些情况道路标志与训练数据差异大比如国内特有的虚实线组合、特殊天气条件雨雪雾、不同光照环境隧道/夜间等。这时候用自己采集的数据训练模型就显得尤为重要。我去年参与过一个园区无人车项目就遇到了典型问题园区内部道路的黄色虚线宽度和间距都与公共道路不同直接使用公开数据集训练的模型准确率不到60%。后来我们采集了2万张园区道路图像进行标注最终将检测准确率提升到了92%。这个经历让我深刻认识到定制化数据集的重要性。2. 数据采集的最佳实践2.1 拍摄设备选择用行车记录仪采集是最便捷的方案建议选择1080P以上分辨率、帧率30fps以上的设备。最近测试发现GoPro Hero10在动态场景下的防抖效果特别好夜间拍摄噪点控制也不错。如果预算有限小米70迈这类平价记录仪也能满足基本需求。重要提示拍摄时要保持车速稳定在40-60km/h这个速度区间获得的图像既有足够清晰度又能覆盖常见透视变形。太快的车速会导致运动模糊而低速时图像透视关系与真实驾驶场景差异较大。2.2 场景覆盖策略建议按这个比例规划采集路线晴天正常光照40%阴天/多云20%夜间15%雨天15%隧道/桥梁10%特别要注意采集一些极端案例比如强逆光下的车道线、部分磨损的标线、被落叶遮挡的路面等。这些场景虽然占比不高但对模型鲁棒性提升非常关键。3. 使用Labelme进行高效标注3.1 标注环境配置推荐使用Python 3.8环境conda create -n labelme python3.8 conda activate labelme pip install labelme5.1.1 pyqt5如果遇到PyQt5兼容性问题可以尝试pip uninstall pyqt5 pip install pyqt55.15.43.2 车道线标注技巧启动Labelme后建议按这个工作流操作使用Create Line工具标注车道线中心线命名规则建议left_solid, right_dashed等带线型信息对模糊或遮挡区域按住Shift键可以添加中间控制点按ESC键完成当前线段标注实测发现保持约每10cm一个标注点的密度对应1080P图像大约15-20像素间隔既能保证标注精度又不会过度增加工作量。对于弯曲车道需要在转弯处适当增加标注点密度。4. 批量处理标注结果4.1 文件结构整理建议采用这样的目录结构project/ ├── raw_images/ # 原始图像 ├── labelme_json/ # 标注生成的json文件 ├── masks/ # 转换后的标签 └── dataset/ # 最终数据集 ├── image/ ├── gt_image_binary/ └── gt_image_instance/使用这个Python脚本自动整理文件import os import shutil from tqdm import tqdm def organize_files(src_dir, dst_dir, ext.jpg): os.makedirs(dst_dir, exist_okTrue) for root, _, files in os.walk(src_dir): for file in tqdm(files): if file.endswith(ext): src_path os.path.join(root, file) dst_path os.path.join(dst_dir, file) shutil.copy(src_path, dst_path)4.2 JSON批量转换修改后的批量转换脚本增加了错误处理和进度显示import json import os import sys from labelme import utils from PIL import Image from tqdm import tqdm def process_single_json(json_path, output_dir): try: with open(json_path) as f: data json.load(f) img utils.img_b64_to_arr(data[imageData]) label_name_to_value {_background_: 0} for shape in data[shapes]: if shape[label] not in label_name_to_value: label_name_to_value[shape[label]] len(label_name_to_value) lbl utils.shapes_to_label(img.shape, data[shapes], label_name_to_value) base_name os.path.splitext(os.path.basename(json_path))[0] os.makedirs(output_dir, exist_okTrue) Image.fromarray(img).save(f{output_dir}/{base_name}_img.png) utils.lblsave(f{output_dir}/{base_name}_label.png, lbl) return True except Exception as e: print(fError processing {json_path}: {str(e)}) return False5. 转换为Tusimple格式5.1 格式规范详解Tusimple数据集需要三种关键文件原始图像1280×720 JPEG二值化标签车道线区域为255背景为0实例标签不同车道线用不同灰度值区分转换时需要特别注意图像必须resize到1280×720二值化标签的线宽要保持3-5像素实例标签中相邻车道线灰度值差至少为305.2 完整转换脚本这个增强版脚本增加了图像增强和自动分割功能import cv2 import numpy as np import os from sklearn.model_selection import train_test_split def convert_to_tusimple(src_dir, dst_dir, test_size0.2): # 创建输出目录 os.makedirs(f{dst_dir}/image, exist_okTrue) os.makedirs(f{dst_dir}/gt_image_binary, exist_okTrue) os.makedirs(f{dst_dir}/gt_image_instance, exist_okTrue) # 获取所有样本并分割 samples [f for f in os.listdir(src_dir) if f.endswith(_img.png)] train_samples, val_samples train_test_split(samples, test_sizetest_size) # 处理训练集 with open(f{dst_dir}/train.txt, w) as train_file: for sample in train_samples: process_sample(src_dir, dst_dir, sample, train_file) # 处理验证集 with open(f{dst_dir}/val.txt, w) as val_file: for sample in val_samples: process_sample(src_dir, dst_dir, sample, val_file) def process_sample(src_dir, dst_dir, sample_name, file_handler): base_name sample_name.replace(_img.png, ) # 读取并resize图像 img cv2.imread(f{src_dir}/{sample_name}) img cv2.resize(img, (1280, 720)) # 处理标签 label cv2.imread(f{src_dir}/{base_name}_label.png, 0) label cv2.resize(label, (1280, 720), interpolationcv2.INTER_NEAREST) # 生成二值化标签 binary np.zeros_like(label) binary[label 0] 255 # 生成实例标签 _, instance cv2.connectedComponents(label) instance (instance * (255/instance.max())).astype(np.uint8) # 保存文件 cv2.imwrite(f{dst_dir}/image/{base_name}.png, img) cv2.imwrite(f{dst_dir}/gt_image_binary/{base_name}.png, binary) cv2.imwrite(f{dst_dir}/gt_image_instance/{base_name}.png, instance) # 写入索引文件 file_handler.write( fimage/{base_name}.png fgt_image_binary/{base_name}.png fgt_image_instance/{base_name}.png\n )6. 数据增强与质量控制6.1 必须做的增强操作建议在转换后增加这些增强随机亮度调整±30%添加高斯噪声σ0.01模拟雨滴效果生成运动模糊这个增强脚本可以集成到流程中import albumentations as A transform A.Compose([ A.RandomBrightnessContrast(p0.5), A.GaussNoise(var_limit(0.001, 0.01), p0.3), A.RandomRain(p0.1), A.MotionBlur(blur_limit7, p0.2), ])6.2 质量检查清单完成数据集制作后务必检查所有图像尺寸是否为1280×720二值化标签是否完全覆盖车道线实例标签中不同车道线是否有足够区分度train.txt和val.txt中的路径是否正确确认没有图像和标签错位的情况可以运行这个检查脚本def validate_dataset(dataset_dir): # 检查目录结构 required_dirs [image, gt_image_binary, gt_image_instance] for d in required_dirs: if not os.path.exists(f{dataset_dir}/{d}): print(fMissing directory: {d}) return False # 检查样本数量是否匹配 img_count len(os.listdir(f{dataset_dir}/image)) binary_count len(os.listdir(f{dataset_dir}/gt_image_binary)) instance_count len(os.listdir(f{dataset_dir}/gt_image_instance)) if img_count ! binary_count or img_count ! instance_count: print(Sample counts do not match!) return False # 随机检查10个样本 samples random.sample(os.listdir(f{dataset_dir}/image), 10) for sample in samples: img cv2.imread(f{dataset_dir}/image/{sample}) binary cv2.imread(f{dataset_dir}/gt_image_binary/{sample}, 0) if img.shape[:2] ! (720, 1280): print(fInvalid size for {sample}) return False if np.max(binary) 1 and np.max(binary) ! 255: print(fInvalid binary values for {sample}) return False return True在最近的一个商业项目中我们发现有约5%的标注存在车道线断裂问题。通过添加这个质量检查环节成功将模型误检率降低了3个百分点。这提醒我们数据质量检查不是可选项而是必须认真执行的步骤。

更多文章