简体中文 English 日本語 Русский язык 한어

204

主题

129

回帖

4万

修为

站长

积分
101813

曼加塔第一期曼加塔第二期

LeoSasion 2025-10-3 11:49:54 | 显示全部楼层 | 阅读模式
【学习日记】Extract 切脸 - Deep 换脸 - BBS_Monster
Extractor.py 包含了本文的S3FD(边界框)和FANExtractor(关键点)
本文只讲S3FD


  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """
  4. S3FD人脸检测器实现

  5. 该模块实现了Single Shot Scale-invariant Face Detector (S3FD)人脸检测算法,
  6. 用于检测图像中的人脸并返回人脸边界框坐标。
  7. """

  8. import operator
  9. from pathlib import Path

  10. import cv2
  11. import numpy as np

  12. from core.leras import nn

  13. class S3FDExtractor(object):
  14.     """
  15.     S3FD人脸检测器提取器类

  16.     该类封装了S3FD人脸检测模型,提供了加载模型和从图像中提取人脸边界框的功能。
  17.     """

  18.     def __init__(self, place_model_on_cpu=False):
  19.         """
  20.         初始化S3FD人脸检测器

  21.         参数:
  22.             place_model_on_cpu (bool): 是否将模型放在CPU上运行,默认为False(使用GPU)
  23.         """
  24.         # 初始化神经网络环境,使用NHWC数据格式
  25.         nn.initialize(data_format="NHWC")
  26.         tf = nn.tf

  27.         # 模型路径设置
  28.         model_path = Path(__file__).parent / "S3FD.npy"
  29.         if not model_path.exists():
  30.             raise Exception("Unable to load S3FD.npy")

  31.         class L2Norm(nn.LayerBase):
  32.             """
  33.             L2归一化层

  34.             对输入特征图进行L2归一化处理,并应用可学习的缩放参数
  35.             """
  36.             def __init__(self, n_channels, **kwargs):
  37.                 self.n_channels = n_channels
  38.                 super().__init__(**kwargs)

  39.             def build_weights(self):
  40.                 # 创建可学习的缩放权重参数
  41.                 self.weight = tf.get_variable("weight", (1, 1, 1, self.n_channels),
  42.                                             dtype=nn.floatx, initializer=tf.initializers.ones)

  43.             def get_weights(self):
  44.                 return [self.weight]

  45.             def __call__(self, inputs):
  46.                 """
  47.                 执行L2归一化操作

  48.                 参数:
  49.                     inputs: 输入特征图

  50.                 返回:
  51.                     归一化并缩放后的特征图
  52.                 """
  53.                 x = inputs
  54.                 # L2归一化: x / sqrt(sum(x^2) + 1e-10) * weight
  55.                 x = x / (tf.sqrt(tf.reduce_sum(tf.pow(x, 2), axis=-1, keepdims=True)) + 1e-10) * self.weight
  56.                 return x

  57.         class S3FD(nn.ModelBase):
  58.             """
  59.             S3FD人脸检测模型类

  60.             实现了S3FD网络架构,包含特征提取和多尺度检测头
  61.             """
  62.             def __init__(self):
  63.                 super().__init__(name='S3FD')

  64.             def on_build(self):
  65.                 """
  66.                 构建网络层结构

  67.                 定义VGG风格的特征提取网络和多尺度检测头
  68.                 """
  69.                 # 图像预处理:均值减法
  70.                 self.minus = tf.constant([104, 117, 123], dtype=nn.floatx)

  71.                 # VGG风格的卷积层 - 第一阶段
  72.                 self.conv1_1 = nn.Conv2D(3, 64, kernel_size=3, strides=1, padding='SAME')
  73.                 self.conv1_2 = nn.Conv2D(64, 64, kernel_size=3, strides=1, padding='SAME')

  74.                 # 第二阶段
  75.                 self.conv2_1 = nn.Conv2D(64, 128, kernel_size=3, strides=1, padding='SAME')
  76.                 self.conv2_2 = nn.Conv2D(128, 128, kernel_size=3, strides=1, padding='SAME')

  77.                 # 第三阶段
  78.                 self.conv3_1 = nn.Conv2D(128, 256, kernel_size=3, strides=1, padding='SAME')
  79.                 self.conv3_2 = nn.Conv2D(256, 256, kernel_size=3, strides=1, padding='SAME')
  80.                 self.conv3_3 = nn.Conv2D(256, 256, kernel_size=3, strides=1, padding='SAME')

  81.                 # 第四阶段
  82.                 self.conv4_1 = nn.Conv2D(256, 512, kernel_size=3, strides=1, padding='SAME')
  83.                 self.conv4_2 = nn.Conv2D(512, 512, kernel_size=3, strides=1, padding='SAME')
  84.                 self.conv4_3 = nn.Conv2D(512, 512, kernel_size=3, strides=1, padding='SAME')

  85.                 # 第五阶段
  86.                 self.conv5_1 = nn.Conv2D(512, 512, kernel_size=3, strides=1, padding='SAME')
  87.                 self.conv5_2 = nn.Conv2D(512, 512, kernel_size=3, strides=1, padding='SAME')
  88.                 self.conv5_3 = nn.Conv2D(512, 512, kernel_size=3, strides=1, padding='SAME')

  89.                 # 第六阶段 (卷积替代全连接层)
  90.                 self.fc6 = nn.Conv2D(512, 1024, kernel_size=3, strides=1, padding=3)
  91.                 self.fc7 = nn.Conv2D(1024, 1024, kernel_size=1, strides=1, padding='SAME')

  92.                 # 第七阶段
  93.                 self.conv6_1 = nn.Conv2D(1024, 256, kernel_size=1, strides=1, padding='SAME')
  94.                 self.conv6_2 = nn.Conv2D(256, 512, kernel_size=3, strides=2, padding='SAME')

  95.                 # 第八阶段
  96.                 self.conv7_1 = nn.Conv2D(512, 128, kernel_size=1, strides=1, padding='SAME')
  97.                 self.conv7_2 = nn.Conv2D(128, 256, kernel_size=3, strides=2, padding='SAME')

  98.                 # L2归一化层用于不同尺度特征
  99.                 self.conv3_3_norm = L2Norm(256)
  100.                 self.conv4_3_norm = L2Norm(512)
  101.                 self.conv5_3_norm = L2Norm(512)

  102.                 # 多尺度检测头 - 用于检测不同大小的人脸
  103.                 # conv3_3层检测头 (小尺度人脸)
  104.                 self.conv3_3_norm_mbox_conf = nn.Conv2D(256, 4, kernel_size=3, strides=1, padding='SAME')
  105.                 self.conv3_3_norm_mbox_loc = nn.Conv2D(256, 4, kernel_size=3, strides=1, padding='SAME')

  106.                 # conv4_3层检测头
  107.                 self.conv4_3_norm_mbox_conf = nn.Conv2D(512, 2, kernel_size=3, strides=1, padding='SAME')
  108.                 self.conv4_3_norm_mbox_loc = nn.Conv2D(512, 4, kernel_size=3, strides=1, padding='SAME')

  109.                 # conv5_3层检测头
  110.                 self.conv5_3_norm_mbox_conf = nn.Conv2D(512, 2, kernel_size=3, strides=1, padding='SAME')
  111.                 self.conv5_3_norm_mbox_loc = nn.Conv2D(512, 4, kernel_size=3, strides=1, padding='SAME')

  112.                 # fc7层检测头
  113.                 self.fc7_mbox_conf = nn.Conv2D(1024, 2, kernel_size=3, strides=1, padding='SAME')
  114.                 self.fc7_mbox_loc = nn.Conv2D(1024, 4, kernel_size=3, strides=1, padding='SAME')

  115.                 # conv6_2层检测头
  116.                 self.conv6_2_mbox_conf = nn.Conv2D(512, 2, kernel_size=3, strides=1, padding='SAME')
  117.                 self.conv6_2_mbox_loc = nn.Conv2D(512, 4, kernel_size=3, strides=1, padding='SAME')

  118.                 # conv7_2层检测头 (大尺度人脸)
  119.                 self.conv7_2_mbox_conf = nn.Conv2D(256, 2, kernel_size=3, strides=1, padding='SAME')
  120.                 self.conv7_2_mbox_loc = nn.Conv2D(256, 4, kernel_size=3, strides=1, padding='SAME')

  121.             def forward(self, inp):
  122.                 """
  123.                 前向传播

  124.                 参数:
  125.                     inp: 输入数据,包含一个图像张量

  126.                 返回:
  127.                     各尺度的分类和回归结果列表
  128.                 """
  129.                 x, = inp
  130.                 # 图像预处理:减去均值
  131.                 x = x - self.minus

  132.                 # 第一阶段卷积和池化
  133.                 x = tf.nn.relu(self.conv1_1(x))
  134.                 x = tf.nn.relu(self.conv1_2(x))
  135.                 x = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], "VALID")

  136.                 # 第二阶段卷积和池化
  137.                 x = tf.nn.relu(self.conv2_1(x))
  138.                 x = tf.nn.relu(self.conv2_2(x))
  139.                 x = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], "VALID")

  140.                 # 第三阶段卷积和池化
  141.                 x = tf.nn.relu(self.conv3_1(x))
  142.                 x = tf.nn.relu(self.conv3_2(x))
  143.                 x = tf.nn.relu(self.conv3_3(x))
  144.                 f3_3 = x  # 保存特征图用于检测
  145.                 x = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], "VALID")

  146.                 # 第四阶段卷积和池化
  147.                 x = tf.nn.relu(self.conv4_1(x))
  148.                 x = tf.nn.relu(self.conv4_2(x))
  149.                 x = tf.nn.relu(self.conv4_3(x))
  150.                 f4_3 = x  # 保存特征图用于检测
  151.                 x = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], "VALID")

  152.                 # 第五阶段卷积和池化
  153.                 x = tf.nn.relu(self.conv5_1(x))
  154.                 x = tf.nn.relu(self.conv5_2(x))
  155.                 x = tf.nn.relu(self.conv5_3(x))
  156.                 f5_3 = x  # 保存特征图用于检测
  157.                 x = tf.nn.max_pool(x, [1, 2, 2, 1], [1, 2, 2, 1], "VALID")

  158.                 # 第六阶段卷积
  159.                 x = tf.nn.relu(self.fc6(x))
  160.                 x = tf.nn.relu(self.fc7(x))
  161.                 ffc7 = x  # 保存特征图用于检测

  162.                 # 第七阶段卷积
  163.                 x = tf.nn.relu(self.conv6_1(x))
  164.                 x = tf.nn.relu(self.conv6_2(x))
  165.                 f6_2 = x  # 保存特征图用于检测

  166.                 # 第八阶段卷积
  167.                 x = tf.nn.relu(self.conv7_1(x))
  168.                 x = tf.nn.relu(self.conv7_2(x))
  169.                 f7_2 = x  # 保存特征图用于检测

  170.                 # 特征归一化
  171.                 f3_3 = self.conv3_3_norm(f3_3)
  172.                 f4_3 = self.conv4_3_norm(f4_3)
  173.                 f5_3 = self.conv5_3_norm(f5_3)

  174.                 # 在各尺度特征图上进行检测
  175.                 # 分类分支(confidence)和回归分支(location)
  176.                 cls1 = self.conv3_3_norm_mbox_conf(f3_3)
  177.                 reg1 = self.conv3_3_norm_mbox_loc(f3_3)

  178.                 cls2 = tf.nn.softmax(self.conv4_3_norm_mbox_conf(f4_3))
  179.                 reg2 = self.conv4_3_norm_mbox_loc(f4_3)

  180.                 cls3 = tf.nn.softmax(self.conv5_3_norm_mbox_conf(f5_3))
  181.                 reg3 = self.conv5_3_norm_mbox_loc(f5_3)

  182.                 cls4 = tf.nn.softmax(self.fc7_mbox_conf(ffc7))
  183.                 reg4 = self.fc7_mbox_loc(ffc7)

  184.                 cls5 = tf.nn.softmax(self.conv6_2_mbox_conf(f6_2))
  185.                 reg5 = self.conv6_2_mbox_loc(f6_2)

  186.                 cls6 = tf.nn.softmax(self.conv7_2_mbox_conf(f7_2))
  187.                 reg6 = self.conv7_2_mbox_loc(f7_2)

  188.                 # 对conv3_3层的分类结果进行特殊处理:max-out背景标签
  189.                 # 这是S3FD算法的一个关键创新,用于提高小尺度人脸检测性能
  190.                 bmax = tf.maximum(tf.maximum(cls1[:, :, :, 0:1], cls1[:, :, :, 1:2]), cls1[:, :, :, 2:3])
  191.                 cls1 = tf.concat([bmax, cls1[:, :, :, 3:4]], axis=-1)
  192.                 cls1 = tf.nn.softmax(cls1)

  193.                 # 返回所有尺度的分类和回归结果
  194.                 return [cls1, reg1, cls2, reg2, cls3, reg3, cls4, reg4, cls5, reg5, cls6, reg6]

  195.         # 根据配置决定在CPU还是GPU上加载模型
  196.         e = None
  197.         if place_model_on_cpu:
  198.             e = tf.device("/CPU:0")

  199.         if e is not None: e.__enter__()
  200.         # 创建并加载模型
  201.         self.model = S3FD()
  202.         self.model.load_weights(model_path)
  203.         if e is not None: e.__exit__(None, None, None)

  204.         # 构建模型运行环境,输入为任意大小的RGB图像
  205.         self.model.build_for_run([(tf.float32, nn.get4Dshape(None, None, 3))])

  206.     def __enter__(self):
  207.         """
  208.         上下文管理器入口

  209.         返回:
  210.             S3FDExtractor实例自身
  211.         """
  212.         return self

  213.     def __exit__(self, exc_type=None, exc_value=None, traceback=None):
  214.         """
  215.         上下文管理器出口

  216.         参数:
  217.             exc_type: 异常类型
  218.             exc_value: 异常值
  219.             traceback: 堆栈跟踪

  220.         返回:
  221.             False - 将异常传递到外层
  222.         """
  223.         return False  # 传递异常到外层

  224.     def extract(self, input_image, is_bgr=True, is_remove_intersects=False):
  225.         """
  226.         从图像中提取人脸边界框

  227.         参数:
  228.             input_image: 输入图像数组
  229.             is_bgr: 输入图像是否为BGR格式,默认为True
  230.             is_remove_intersects: 是否移除相交的人脸框,默认为False

  231.         返回:
  232.             list: 人脸边界框列表,每个边界框为[l, t, r, b]格式(左、上、右、下坐标)
  233.         """

  234.         # 颜色空间转换:如果是BGR格式,转换为RGB
  235.         if is_bgr:
  236.             input_image = input_image[:, :, ::-1]
  237.             is_bgr = False

  238.         # 获取图像尺寸
  239.         (h, w, ch) = input_image.shape

  240.         # 计算缩放因子,保持图像纵横比
  241.         d = max(w, h)
  242.         scale_to = 640 if d >= 1280 else d / 2
  243.         scale_to = max(64, scale_to)  # 确保最小尺寸为64

  244.         # 计算输入缩放比例
  245.         input_scale = d / scale_to
  246.         # 缩放图像以提高检测效率
  247.         input_image = cv2.resize(input_image, (int(w/input_scale), int(h/input_scale)),
  248.                                 interpolation=cv2.INTER_LINEAR)

  249.         # 运行模型进行检测
  250.         olist = self.model.run([input_image[None, ...]])

  251.         # 处理检测结果
  252.         detected_faces = []
  253.         # 对模型输出进行后处理,获取人脸边界框
  254.         for ltrb in self.refine(olist):
  255.             # 将边界框坐标缩放到原始图像尺寸
  256.             l, t, r, b = [x * input_scale for x in ltrb]
  257.             bt = b - t
  258.             # 过滤过小的人脸(小于40像素的边界框)
  259.             if min(r - l, bt) < 40:  # 过滤任何边长小于40像素的人脸
  260.                 continue
  261.             # 稍微扩大底部边界,以更好地包含下巴区域
  262.             # 这是为了与2DFAN-4关键点检测器更好地配合
  263.             b += bt * 0.1
  264.             detected_faces.append([int(x) for x in (l, t, r, b)])

  265.         # 按面积大小对人脸框进行排序,面积大的排在前面
  266.         detected_faces = [[(l, t, r, b), (r - l) * (b - t)] for (l, t, r, b) in detected_faces]
  267.         detected_faces = sorted(detected_faces, key=operator.itemgetter(1), reverse=True)
  268.         detected_faces = [x[0] for x in detected_faces]

  269.         # 移除相交的人脸框(保留面积较大的)
  270.         if is_remove_intersects:
  271.             for i in range(len(detected_faces) - 1, 0, -1):
  272.                 l1, t1, r1, b1 = detected_faces[i]
  273.                 l0, t0, r0, b0 = detected_faces[i - 1]

  274.                 # 计算两个边界框的交集
  275.                 dx = min(r0, r1) - max(l0, l1)
  276.                 dy = min(b0, b1) - max(t0, t1)
  277.                 # 如果有交集,则移除当前人脸框
  278.                 if (dx >= 0) and (dy >= 0):
  279.                     detected_faces.pop(i)

  280.         return detected_faces

  281.     def refine(self, olist):
  282.         """
  283.         处理模型输出,生成边界框列表

  284.         参数:
  285.             olist: 模型输出的分类和回归结果列表

  286.         返回:
  287.             list: 处理后的边界框列表
  288.         """
  289.         bboxlist = []
  290.         # 遍历所有尺度的输出结果
  291.         for i, ((ocls,), (oreg,)) in enumerate(zip(olist[::2], olist[1::2])):
  292.             # 计算当前层的步长(每个特征图单元对应原图的像素数)
  293.             stride = 2 ** (i + 2)  # 4, 8, 16, 32, 64, 128
  294.             s_d2 = stride / 2
  295.             s_m4 = stride * 4

  296.             # 遍历所有置信度大于阈值的位置
  297.             for hindex, windex in zip(*np.where(ocls[..., 1] > 0.05)):
  298.                 # 获取置信度分数
  299.                 score = ocls[hindex, windex, 1]
  300.                 # 获取回归偏移量
  301.                 loc = oreg[hindex, windex, :]
  302.                 # 计算先验框的中心和大小
  303.                 priors = np.array([windex * stride + s_d2, hindex * stride + s_d2, s_m4, s_m4])
  304.                 priors_2p = priors[2:]
  305.                 # 根据回归偏移量调整边界框
  306.                 box = np.concatenate((priors[:2] + loc[:2] * 0.1 * priors_2p,
  307.                                       priors_2p * np.exp(loc[2:] * 0.2)))
  308.                 # 将中心点+大小格式转换为左上角+右下角格式
  309.                 box[:2] -= box[2:] / 2
  310.                 box[2:] += box[:2]

  311.                 # 添加边界框和置信度到列表
  312.                 bboxlist.append([*box, score])

  313.         # 转换为numpy数组
  314.         bboxlist = np.array(bboxlist)
  315.         # 如果没有检测到人脸,返回空边界框
  316.         if len(bboxlist) == 0:
  317.             bboxlist = np.zeros((1, 5))

  318.         # 应用非极大值抑制,过滤重叠边界框
  319.         bboxlist = bboxlist[self.refine_nms(bboxlist, 0.3), :]
  320.         # 过滤低置信度边界框,并转换为整数坐标
  321.         bboxlist = [x[:-1].astype(np.int) for x in bboxlist if x[-1] >= 0.5]
  322.         return bboxlist

  323.     def refine_nms(self, dets, thresh):
  324.         """
  325.         非极大值抑制(NMS)算法实现

  326.         参数:
  327.             dets: 检测边界框数组,格式为[x1, y1, x2, y2, score]
  328.             thresh: 重叠阈值,大于此值的重叠边界框将被抑制

  329.         返回:
  330.             list: 保留的边界框索引列表
  331.         """
  332.         keep = list()
  333.         # 处理空输入情况
  334.         if len(dets) == 0:
  335.             return keep

  336.         # 提取边界框坐标和置信度
  337.         x_1, y_1, x_2, y_2, scores = dets[:, 0], dets[:, 1], dets[:, 2], dets[:, 3], dets[:, 4]
  338.         # 计算每个边界框的面积
  339.         areas = (x_2 - x_1 + 1) * (y_2 - y_1 + 1)
  340.         # 按置信度降序排列
  341.         order = scores.argsort()[::-1]

  342.         keep = []
  343.         # 迭代执行非极大值抑制
  344.         while order.size > 0:
  345.             # 保留置信度最高的边界框
  346.             i = order[0]
  347.             keep.append(i)
  348.             # 计算当前边界框与其他边界框的交集
  349.             xx_1, yy_1 = np.maximum(x_1[i], x_1[order[1:]]), np.maximum(y_1[i], y_1[order[1:]])
  350.             xx_2, yy_2 = np.minimum(x_2[i], x_2[order[1:]]), np.minimum(y_2[i], y_2[order[1:]])

  351.             # 计算交集区域的宽度和高度
  352.             width, height = np.maximum(0.0, xx_2 - xx_1 + 1), np.maximum(0.0, yy_2 - yy_1 + 1)
  353.             # 计算交并比(IoU)
  354.             ovr = width * height / (areas[i] + areas[order[1:]] - width * height)

  355.             # 保留重叠度小于阈值的边界框
  356.             inds = np.where(ovr <= thresh)[0]
  357.             order = order[inds + 1]
  358.         return keep

复制代码




S3FDExtractor.py

17.93 KB, 下载次数: 0, 下载积分: 下品灵石 -1

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

简体中文
繁體中文
English(英语)
日本語(日语)
Deutsch(德语)
Русский язык(俄语)
بالعربية(阿拉伯语)
Türkçe(土耳其语)
Português(葡萄牙语)
ภาษาไทย(泰国语)
한어(朝鲜语/韩语)
Français(法语)
© 2001-2026 Monster社区 | ComfyUI论坛 · Powered by Discuz!
QQ网站导航Archiver手机版小黑屋粤ICP备2026002389号-1粤ICP备2026002389号-1 简体中文 English 日本語 Русский язык 한어