TensorFlow中WiderFace数据集随机裁剪及人脸Bounding Box留存方案咨询
在TensorFlow中为WiderFace实现带人脸框过滤的随机裁剪
我明白你遇到的痛点——用TFRecords和TfExampleDecoder时,图像静态形状是[None, None, 3],没法直接用静态形状做裁剪逻辑。别担心,我们可以用TensorFlow的运行时形状获取和张量操作来解决这个问题,下面是一步步的实现方案:
1. 先处理TFRecord解析的基础逻辑
首先,你需要从TFRecord中正确解析出图像和人脸边界框数据。假设你的TFExample里存储了图像二进制数据、人脸的xmin/ymin/xmax/ymax(通常是相对于图像尺寸的比例值,范围0-1),解析代码大概是这样:
import tensorflow as tf def parse_tfexample(example_proto): # 定义特征描述 feature_description = { 'image/encoded': tf.io.FixedLenFeature([], tf.string), 'image/object/bbox/xmin': tf.io.VarLenFeature(tf.float32), 'image/object/bbox/ymin': tf.io.VarLenFeature(tf.float32), 'image/object/bbox/xmax': tf.io.VarLenFeature(tf.float32), 'image/object/bbox/ymax': tf.io.VarLenFeature(tf.float32), } # 解析示例 parsed_features = tf.io.parse_single_example(example_proto, feature_description) # 解码图像 image = tf.image.decode_jpeg(parsed_features['image/encoded'], channels=3) # 将稀疏张量转为密集张量(处理多个人脸的情况) xmin = tf.sparse.to_dense(parsed_features['image/object/bbox/xmin']) ymin = tf.sparse.to_dense(parsed_features['image/object/bbox/ymin']) xmax = tf.sparse.to_dense(parsed_features['image/object/bbox/xmax']) ymax = tf.sparse.to_dense(parsed_features['image/object/bbox/ymax']) # 将比例坐标转为绝对坐标(关键:用运行时形状) image_shape = tf.shape(image) h, w = image_shape[0], image_shape[1] xmin = xmin * tf.cast(w, tf.float32) ymin = ymin * tf.cast(h, tf.float32) xmax = xmax * tf.cast(w, tf.float32) ymax = ymax * tf.cast(h, tf.float32) return image, tf.stack([xmin, ymin, xmax, ymax], axis=1)
2. 实现带人脸过滤的随机裁剪逻辑
核心思路是:
- 用
tf.shape()获取图像的真实运行时尺寸 - 随机生成裁剪区域的位置和大小
- 计算每个人脸的中心坐标,判断是否在裁剪区域内
- 过滤掉中心不在裁剪区的人脸,调整保留人脸的坐标到裁剪后的图像坐标系
- 处理极端情况(比如裁剪后无有效人脸,重新生成裁剪区域)
def random_crop_with_face_filter(image, bboxes, min_crop_ratio=0.5): image_shape = tf.shape(image) h, w = image_shape[0], image_shape[1] # 定义裁剪尺寸的范围:从min_crop_ratio到1.0的原图像尺寸 crop_h_min = tf.cast(tf.cast(h, tf.float32) * min_crop_ratio, tf.int32) crop_w_min = tf.cast(tf.cast(w, tf.float32) * min_crop_ratio, tf.int32) crop_h = tf.random.uniform([], minval=crop_h_min, maxval=h, dtype=tf.int32) crop_w = tf.random.uniform([], minval=crop_w_min, maxval=w, dtype=tf.int32) # 随机生成裁剪区域的左上角坐标,确保裁剪区域不超出图像边界 crop_y1 = tf.random.uniform([], minval=0, maxval=h - crop_h + 1, dtype=tf.int32) crop_x1 = tf.random.uniform([], minval=0, maxval=w - crop_w + 1, dtype=tf.int32) crop_y2 = crop_y1 + crop_h crop_x2 = crop_x1 + crop_w # 转换裁剪坐标为float,方便和bbox计算 crop_y1_float = tf.cast(crop_y1, tf.float32) crop_x1_float = tf.cast(crop_x1, tf.float32) crop_y2_float = tf.cast(crop_y2, tf.float32) crop_x2_float = tf.cast(crop_x2, tf.float32) # 计算每个人脸的中心坐标 bbox_centers_x = (bboxes[:, 0] + bboxes[:, 2]) / 2.0 bbox_centers_y = (bboxes[:, 1] + bboxes[:, 3]) / 2.0 # 判断中心是否在裁剪区域内 valid_mask = tf.logical_and( tf.logical_and(bbox_centers_x >= crop_x1_float, bbox_centers_x <= crop_x2_float), tf.logical_and(bbox_centers_y >= crop_y1_float, bbox_centers_y <= crop_y2_float) ) # 过滤有效bbox,并调整坐标到裁剪后的图像 valid_bboxes = tf.boolean_mask(bboxes, valid_mask) # 调整坐标:减去裁剪区域的左上角坐标,同时确保不小于0,不大于裁剪尺寸 valid_bboxes = tf.stack([ tf.maximum(valid_bboxes[:, 0] - crop_x1_float, 0.0), tf.maximum(valid_bboxes[:, 1] - crop_y1_float, 0.0), tf.minimum(valid_bboxes[:, 2] - crop_x1_float, tf.cast(crop_w, tf.float32)), tf.minimum(valid_bboxes[:, 3] - crop_y1_float, tf.cast(crop_h, tf.float32)) ], axis=1) # 处理裁剪后无有效人脸的情况:重新执行裁剪逻辑 def retry_crop(): return random_crop_with_face_filter(image, bboxes, min_crop_ratio) return tf.cond(tf.shape(valid_bboxes)[0] > 0, lambda: (tf.image.crop_to_bounding_box(image, crop_y1, crop_x1, crop_h, crop_w), valid_bboxes), retry_crop)
3. 整合到数据流水线中
把解析和裁剪逻辑组合起来,构建你的数据集流水线:
def create_dataset(tfrecord_path, batch_size=8, min_crop_ratio=0.5): dataset = tf.data.TFRecordDataset(tfrecord_path) dataset = dataset.map(parse_tfexample, num_parallel_calls=tf.data.AUTOTUNE) # 应用随机裁剪 dataset = dataset.map(lambda img, bboxes: random_crop_with_face_filter(img, bboxes, min_crop_ratio), num_parallel_calls=tf.data.AUTOTUNE) # 其他预处理(比如缩放、归一化) dataset = dataset.map(lambda img, bboxes: (tf.image.resize(img, (640, 640)), bboxes / tf.constant([640, 640, 640, 640], tf.float32))) # 批处理(注意:因为每个样本的bbox数量可能不同,需要用padded_batch) dataset = dataset.padded_batch(batch_size, padded_shapes=((640, 640, 3), (None, 4))) dataset = dataset.prefetch(tf.data.AUTOTUNE) return dataset
关键注意事项
- 运行时形状 vs 静态形状:全程用
tf.shape()获取图像的真实尺寸,不要依赖image.get_shape(),因为TFRecord解码后的图像静态形状是[None, None, 3],只有运行时才能拿到具体的H/W。 - bbox坐标转换:如果你的TFExample中存储的是比例坐标,一定要先转成绝对坐标再处理裁剪,否则计算会出错。
- 重试机制:用
tf.cond处理裁剪后无有效人脸的情况,避免训练时出现空bbox的样本。 - 可微分性:所有操作都用TensorFlow原生张量操作,避免使用
tf.py_function,确保整个流水线可用于训练(自动微分)。
内容的提问来源于stack exchange,提问作者Ploic1
相关产品推荐
相关产品推荐

