PASCAL VOC 2012 数据集实战:从下载到语义分割数据增强
1. PASCAL VOC 2012数据集简介PASCAL VOC 2012是计算机视觉领域最经典的数据集之一主要用于目标检测、语义分割等任务。这个数据集包含了20个常见物体类别比如人、动物鸟、猫、狗等、交通工具汽车、飞机、自行车等以及日常用品椅子、沙发、电视等。每张图片都带有精细的标注信息包括物体边界框和像素级分割标签。我第一次接触这个数据集是在做一个语义分割项目时当时最大的感受就是虽然官方提供的标注图片数量有限只有2913张但数据质量非常高。每张图片都经过专业标注团队的仔细标注标注一致性很好。不过对于深度学习模型训练来说这个数据量确实有点捉襟见肘这也是为什么我们需要使用SBD数据集进行增强。数据集中的图片都是真实场景拍摄的包含了各种光照条件、遮挡情况和拍摄角度非常贴近实际应用场景。这也是为什么这么多年过去了PASCAL VOC仍然是评估模型性能的重要基准之一。2. 数据集下载与准备2.1 官方数据集下载PASCAL VOC 2012官方数据集可以通过牛津大学的服务器下载。我推荐直接下载打包好的tar文件这样比较方便。下载命令如下wget http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar下载完成后解压文件tar -xvf VOCtrainval_11-May-2012.tar解压后会得到一个VOCdevkit目录里面包含了所有数据。我建议把这个目录放在你的项目目录下方便后续处理。2.2 SBD增强数据集下载为了增加训练数据量我们需要下载Semantic Boundaries Dataset (SBD)wget http://www.eecs.berkeley.edu/Research/Projects/CS/vision/grouping/semantic_contours/benchmark.tgz解压SBD数据集tar -zxvf benchmark.tgzSBD数据集提供了额外的8498张训练图片和2857张验证图片这些图片都有像素级的标注。不过要注意的是SBD的标注格式是MAT文件我们需要先转换成PNG格式才能使用。3. 数据集合并与增强3.1 数据集结构分析在合并数据集之前我们先来看看两个数据集的结构VOC2012官方数据集JPEGImages/存放所有原始图片SegmentationClass/存放分割标签PNG格式ImageSets/Segmentation/包含train.txt, val.txt等文件列出了用于训练和验证的图片名称SBD数据集img/存放原始图片JPG格式cls/存放类别标签MAT格式inst/存放实例标签MAT格式3.2 数据合并策略要构建包含10582张图片的增强训练集trainaug我们需要合并VOC2012和SBD的数据。具体步骤如下首先处理VOC2012的原始数据训练集1464张train.txt验证集1449张val.txt然后处理SBD的数据训练集8498张验证集2857张合并时要注意去除重复的图片。经过分析SBD训练集中有1133张与VOC训练集重复SBD训练集中有545张与VOC验证集重复SBD验证集中有1张与VOC训练集重复SBD验证集中有558张与VOC验证集重复最终我们可以得到训练集VOC训练集(1464) VOC验证集(1449) SBD独有的训练图片(6820) SBD独有的验证图片(2298) 12031张从中去掉VOC验证集(1449)剩下的10582张就是trainaug3.3 实际操作步骤首先将SBD的MAT标签转换为PNG格式。我写了一个Python脚本import scipy.io import numpy as np from PIL import Image import os def mat2png(mat_path, png_path): mat scipy.io.loadmat(mat_path) seg mat[GTcls][Segmentation][0][0] seg seg.astype(np.uint8) Image.fromarray(seg).save(png_path) input_dir benchmark_RELEASE/dataset/cls output_dir benchmark_RELEASE/dataset/cls_png os.makedirs(output_dir, exist_okTrue) for mat_file in os.listdir(input_dir): if mat_file.endswith(.mat): mat_path os.path.join(input_dir, mat_file) png_path os.path.join(output_dir, mat_file.replace(.mat, .png)) mat2png(mat_path, png_path)合并文件名列表。我们需要生成一个包含所有训练图片名的trainval.txt文件voc_train set(open(VOCdevkit/VOC2012/ImageSets/Segmentation/train.txt).read().splitlines()) voc_val set(open(VOCdevkit/VOC2012/ImageSets/Segmentation/val.txt).read().splitlines()) sbd_train set([f.split(.)[0] for f in os.listdir(benchmark_RELEASE/dataset/img) if f.endswith(.jpg)]) # 获取SBD独有的图片 sbd_unique sbd_train - voc_train - voc_val # 合并所有训练图片名 trainaug list(voc_train) list(sbd_unique) with open(trainaug.txt, w) as f: f.write(\n.join(trainaug))4. 数据预处理与增强4.1 数据格式统一由于VOC和SBD的标签格式不同我们需要统一处理VOC的标签已经是PNG格式每个像素值对应类别IDSBD的标签我们从MAT转换成了PNG但可能需要调整类别ID使其与VOC一致我建议检查一下两个数据集的类别映射关系确保它们使用相同的ID体系。4.2 数据增强技巧除了合并数据集我们还可以应用一些数据增强技术来进一步提高模型性能随机缩放0.5-2.0倍随机水平翻转随机旋转-10°到10°颜色抖动亮度、对比度、饱和度调整随机裁剪裁剪到固定尺寸如512x512在TensorFlow中可以这样实现def augment_image(image, label): # 随机左右翻转 if tf.random.uniform(()) 0.5: image tf.image.flip_left_right(image) label tf.image.flip_left_right(label) # 随机缩放 scale tf.random.uniform([], 0.5, 2.0) h tf.shape(image)[0] w tf.shape(image)[1] new_h tf.cast(h * scale, tf.int32) new_w tf.cast(w * scale, tf.int32) image tf.image.resize(image, [new_h, new_w]) label tf.image.resize(label, [new_h, new_w], methodnearest) # 随机裁剪 image, label random_crop(image, label, crop_size[512, 512]) return image, label def random_crop(image, label, crop_size): combined tf.concat([image, label], axis-1) combined_crop tf.image.random_crop(combined, size[crop_size[0], crop_size[1], 4]) return combined_crop[:, :, :3], combined_crop[:, :, 3:]5. 生成TFRecord文件为了高效训练我们可以将数据转换为TFRecord格式。这是TensorFlow推荐的数据格式特别适合处理大量数据。5.1 创建TFRecord文件def create_tf_example(image_path, label_path): image tf.io.read_file(image_path) label tf.io.read_file(label_path) feature { image: tf.train.Feature(bytes_listtf.train.BytesList(value[image.numpy()])), label: tf.train.Feature(bytes_listtf.train.BytesList(value[label.numpy()])), height: tf.train.Feature(int64_listtf.train.Int64List(value[tf.shape(image)[0].numpy()])), width: tf.train.Feature(int64_listtf.train.Int64List(value[tf.shape(image)[1].numpy()])), } return tf.train.Example(featurestf.train.Features(featurefeature)) def write_tfrecord(output_file, image_paths, label_paths): with tf.io.TFRecordWriter(output_file) as writer: for img_path, lbl_path in zip(image_paths, label_paths): tf_example create_tf_example(img_path, lbl_path) writer.write(tf_example.SerializeToString()) # 准备路径列表 image_paths [os.path.join(VOCdevkit/VOC2012/JPEGImages, f{name}.jpg) for name in trainaug] label_paths [os.path.join(VOCdevkit/VOC2012/SegmentationClass, f{name}.png) for name in trainaug] # 写入TFRecord write_tfrecord(voc2012_trainaug.tfrecord, image_paths, label_paths)5.2 读取TFRecord文件训练时我们可以这样读取TFRecord数据def parse_tfrecord(example): feature_description { image: tf.io.FixedLenFeature([], tf.string), label: tf.io.FixedLenFeature([], tf.string), height: tf.io.FixedLenFeature([], tf.int64), width: tf.io.FixedLenFeature([], tf.int64), } example tf.io.parse_single_example(example, feature_description) image tf.image.decode_jpeg(example[image], channels3) label tf.image.decode_png(example[label], channels1) # 统一调整大小 image tf.image.resize(image, [512, 512]) label tf.image.resize(label, [512, 512], methodnearest) return image, label def get_dataset(tfrecord_file, batch_size8): dataset tf.data.TFRecordDataset(tfrecord_file) dataset dataset.map(parse_tfrecord, num_parallel_callstf.data.AUTOTUNE) dataset dataset.map(augment_image, num_parallel_callstf.data.AUTOTUNE) dataset dataset.batch(batch_size).prefetch(tf.data.AUTOTUNE) return dataset train_dataset get_dataset(voc2012_trainaug.tfrecord)6. 实际应用中的注意事项在使用PASCAL VOC 2012数据集进行语义分割时有几个坑我踩过这里分享给大家类别不平衡问题数据集中不同类别的样本数量差异很大。比如person类很多而potted plant类很少。建议使用加权交叉熵损失函数给少数类别更高的权重。标注不一致问题VOC和SBD的标注风格略有不同。VOC的边界更清晰而SBD的边界有时比较模糊。训练时可能会发现模型在VOC数据上表现更好。小物体分割问题数据集中有很多小物体如bottle、bird等。这些物体在分割时容易被忽略。可以尝试使用多尺度训练或注意力机制来改善。数据泄露问题在合并VOC和SBD时一定要确保验证集是干净的不能包含任何训练数据。我曾经不小心把一些验证集图片混入了训练集导致验证指标虚高。内存不足问题如果一次性加载所有10582张图片可能会耗尽内存。建议使用生成器或TFRecord格式按需加载数据。最后虽然PASCAL VOC 2012是一个相对较小的数据集但它仍然是测试模型能力的良好基准。在实际项目中我通常会先在VOC上快速验证想法然后再在更大的数据集如COCO上进行训练。这种循序渐进的方法可以节省大量时间和计算资源。