You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.21 07:06:04