代码解析—part 2 数据集加载MFS—CVPR2023—Implicit Identity Leakage: The Stumbling Block to Improving Deepfake

代码解析—part3 训练ADM—CVPR2023—Implicit Identity Leakage: The Stumbling Block to Improving Deepfake 论文代码训练模块 阅读详情

论文讲解请看:https://blog.youkuaiyun.com/JustWantToLearn/article/details/138758033
代码链接:https://github.com/megvii-research/CADDM
在这里,我们简要描述算法流程,着重分析模型搭建细节,以及为什么要这样搭建。
part 1:数据集准备,请看链接 https://blog.youkuaiyun.com/JustWantToLearn/article/details/138773005
part 2: 数据集加载,包含 Multi-scale Facial Swap(MFS) 模块 本文
part 3:训练过程,ADM模块 https://blog.youkuaiyun.com/JustWantToLearn/article/details/139116455

1、数据集加载

这里我在作者代码基础上做了一些修改,当landmark不存在时,跳过
最重要的函数:prepare_train_input

class DeepfakeDataset(Dataset):
    r"""DeepfakeDataset Dataset.

    The folder is expected to be organized as followed: root/cls/xxx.img_ext

    Labels are indices of sorted classes in the root directory.

    Args:
        mode: train or test.
        config: hypter parameters for processing images.
    """

    def __init__(self, mode: str, config: dict):
        super().__init__()

        self.config = config
        self.mode = mode
        self.root = self.config['dataset']['img_path']
        self.landmark_path = self.config['dataset']['ld_path']
        self.rng = np.random
        assert mode in ['train', 'test']
        self.do_train = True if mode == 'train' else False
        self.info_meta_dict = self.load_landmark_json(self.landmark_path)
        self.class_dict = self.collect_class()
        self.samples = self.collect_samples()

    def load_landmark_json(self, landmark_json) -> Dict:
        with open(landmark_json, 'r') as f:
            landmark_dict = json.load(f)
        return landmark_dict
        
    def __getitem__(self, index: int) -> Tuple:
        path, label_meta = self.samples[index] #获取样本
        ld = np.array(label_meta['landmark'])#样本landmark
        label = label_meta['labels']#样本标签
        source_path = label_meta['source_path']# #样本初始数据
        img = cv2.imread(path, cv2.IMREAD_COLOR)
        source_img = cv2.imread(source_path, cv2.IMREAD_COLOR)
        if self.mode == "train":
            img, label_dict = prepare_train_input(
                img, source_img, ld, label, self.config, self.do_train
            )。#调用 prepare_train_input 函数处理训练样本
            if isinstance(label_dict, str):
                return None, label_dict

            location_label = torch.Tensor(label_dict['location_label'])
            confidence_label = torch.Tensor(label_dict['confidence_label'])
            img = torch.Tensor(img.transpose(2, 0, 1))
            return img, (label, location_label, confidence_label)

        elif self.mode == 'test':
            img, label_dict = prepare_test_input(
                [img], ld, label, self.config
            )#调用 prepare_test_input 函数处理测试样本
            img = torch.Tensor(img[0].transpose(2, 0, 1))
            video_name = label_meta['video_name']
            return img, (label, video_name)

        else:
            raise ValueError("Unsupported mode of dataset!")

    def __len__(self):
        return len(self.samples)

1.1 收集样本 collect_samples

流程:构建每个文件的完整路径 path,并从路径中提取 info_key 和 video_name。
使用 info_key 从 info_meta_dict 获取记录信息 info_meta,包括 landmark、class_label 和 source_path。将路径和标记信息元组添加到 samples 列表中。
如果出现异常(如文件没有对应的标记信息),则增加 none_nums 计数器并打印。

    def collect_samples(self) -> List:
        samples = []
        none_nums =0
        directory = os.path.expanduser(self.root)
        for key in sorted(self.class_dict.keys()):
            d = os.path.join(directory, key)
            if not os.path.isdir(d):
                continue
            for r, _, filename in sorted(os.walk(d, followlinks=True)):
                for name in sorted(filename):
                    path = os.path.join(r, name)
                    info_key = path[:-4]
                    video_name = '/'.join(path.split('/')[:-1])
                    try:
                        info_meta = self.info_meta_dict[info_key]
                        landmark = info_meta['landmark']
                        class_label = int(info_meta['label'])
                        source_path = info_meta['source_path'] + path[-4:]
                        samples.append(
                            (path, {'labels': class_label, 'landmark': landmark,
                                    'source_path': source_path,
                                    'video_name': video_name})
                        )
                    except:
                        none_nums+=1
                        print(none_nums)
        return samples

1.2 收集类别 collect_class

    def collect_class(self) -> Dict:
        #使用 os.scandir 扫描根目录中的子目录,并获取子目录名称作为类别名称列表 classes
        classes = [d.name for d in os.scandir(self.root) if d.is_dir()]
        #对 classes 进行降序排序
        classes.sort(reverse=True)
        #返回一个字典 class_dict,键为类别名称,值为类别索引
        return {classes[i]: np.int32(i) for i in range(len(classes))}

1.3 prepare_train_input

将 targetRgb 和 sourceRgb 图像存储在 images 列表中。
包含multi_scale_facial_swap、label_assign

def prepare_train_input(targetRgb, sourceRgb, landmark, label, config, training=True):
    '''Prepare model input images.

    Arguments:
    targetRgb: original images or fake images.
    sourceRgb: source images.
    landmark: face landmark.
    label: deepfake labels. genuine: 0, fake: 1.
    config: deepfake config dict.
    training: return processed image with aug or not.
    '''
    #targetRgb: 原始图像或伪造图像
    #sourceRgb:源图像
    #landmark:81个人脸标记点
    rng = np.random
    images = [targetRgb, sourceRgb]
    #如果是训练模式且随机数大于等于 0.7,则对图像和标记进行 resize_aug 数据增强。
    if training and rng.rand() >= 0.7:
        images, landmark = resize_aug(images, landmark)
    # multi-scale facial swap.

    targetRgb, sourceRgb = images
    # if input image is genuine.
    mfs_result, bbox = targetRgb, np.zeros((1, 4))
    # if input image is fake image. generate new fake image with mfs.
    if label:#如果图片为假
    #随机选择混合类型 blending_type 为 'poisson' 或 'alpha'。
        blending_type = 'poisson' if rng.rand() >= 0.5 else 'alpha'

        if rng.rand() >= 0.2:
        #如果随机数大于等于 0.2,则执行全局人脸交换:
            # global facial swap.
            sliding_win = targetRgb.shape[:2]

            if rng.rand() > 0.5:
                # fake to source global facial swap.
                mfs_result, bbox = multi_scale_facial_swap(
                    targetRgb, sourceRgb, landmark, config,
                    sliding_win, blending_type, training
                )
            elif rng.rand() >= 0.5:
                # source to fake global facial swap.
                mfs_result, bbox = multi_scale_facial_swap(
                    sourceRgb, targetRgb, landmark, config,
                    sliding_win, blending_type, training
                )
            else:

                mfs_result, bbox = targetRgb, np.array([[0, 0, 224, 224]])
                cropMfs, landmark = get_align5p(
                    [mfs_result], landmark, rng, config, training
                )
                mfs_result = cropMfs[0]
        else:
            # parial facial swap.
          #如果随机数小于 0.2,则执行部分人脸交换,通过随机选择的滑动窗口sliding_win 进行交换。
            prior_bbox = config['sliding_win']['prior_bbox']
            sliding_win = prior_bbox[np.random.choice(len(prior_bbox))]
            mfs_result, bbox = multi_scale_facial_swap(
                sourceRgb, targetRgb, landmark, config,
                sliding_win, blending_type, training
            )
    else:
        # crop face with landmark.
        #如果输入图像是真实图像,则进行面部裁剪和对齐处理
        cropMfs, landmark = get_align5p(
            [mfs_result], landmark, rng, config, training
        )
        mfs_result = cropMfs[0]

    if mfs_result is None:
        return None, 'multi scale facial swap err.'

    if training:  # and rng.rand() >= 0.5:
    #如果是训练模式,进行水平翻转和噪声添加数据增强。
        mfs_result, bbox = image_h_mirror(mfs_result, bbox)
        mfs_result = add_noise(rng, mfs_result)
    #根据标签确定图像是否为真实图像
    genuine = True if not label else False
    #计算位置标签和置信度标签
    location_label, confidence_label = label_assign(
        bbox.astype('float32') / config['crop_face']['output_size'],
        config, genuine
    )
    #返回处理后的图像和标签信息,包括原始标签、位置标签和置信度标签。
    return mfs_result, {'label': label, 'location_label': location_label,
                        'confidence_label': confidence_label}

1.3.1 label_assign

为训练数据分配标签

def label_assign(bboxs, config, genuine=False):

    global Prior
    #根据配置获取先验框信息
    get_prior(config)
    #初始化标签张量 labels
    labels = torch.zeros(bboxs.shape[0],)
    defaults = Prior.forward().data  # information of priors
    #如果输入图像为真实图像,返回全零的定位标签和置信度标签
    if genuine:
        return np.zeros(defaults.shape), np.zeros(defaults.shape[0], )

    # anchor matching by iou.

    loc_t = torch.zeros(1, defaults.shape[0], 4)
    conf_t = torch.zeros(1, defaults.shape[0])
    #调用 match 函数,根据交并比(IOU)匹配先验框和输入边界框,分配定位和置信度标签
    match(
        0.5, torch.Tensor(bboxs), defaults,
        [0.1, 0.2], labels, loc_t, conf_t, 0)

    loc_t, conf_t = np.array(loc_t)[0, ...], np.array(conf_t)[0, ...]

    if loc_t.max() > 10**5:
        return None, 'prior bbox match err. bias is inf!'
    #返回定位标签和置信度标签
    return loc_t, conf_t

1.4 prepare_test_input函数

对输入图像和面部标志点进行对齐和裁剪

def prepare_test_input(img, ld, label, config):
    config = config['crop_face']
    #将输入图像根据面部标志点对齐,并裁剪出标准尺寸的面部图像
    img, ld = align_5p(
        img, ld=ld,
        face_width=config['face_width'], canvas_size=config['output_size'],
        scale=config['scale']
    )
    return img, {'label': label}

2、MFS 函数核心

多尺度人脸交换

  • 先进行全局或者局部人脸交换,接着生成 全局或局部混合掩码
  • 对伪造图像进行对齐和裁剪,并返回裁剪后的图像和边界框信息
  • global_facial_swap、partial_facial_swap、poisson_blending_func、alpha_blending_func、get_align5p、get_partial_bbox_gt
def multi_scale_facial_swap(
        srcRgb, targetRgb, landmark, config,
        sliding_win, blending_type='poisson', training=False):

    '''Multi-scale Facial Swap function.

    Argument.

    srcRgb: source image. type -> ndarray
    targetRgb: target image. type -> ndarray
    landmark: position of face landmarks.
    blending_type: image blending function. (poisson or alpha).
    '''

    assert min(sliding_win) > 0
    assert blending_type == 'poisson' or blending_type == 'alpha'

    bbox = [[0, 0, 224, 224]]
    #如果 srcRgb 和 targetRgb 的形状不同,调整 srcRgb 的大小与 targetRgb 相同
    if srcRgb.shape != targetRgb.shape:
        h, w = targetRgb.shape[:2]
        srcRgb = cv2.resize(srcRgb, (w, h))

    assert srcRgb.shape == targetRgb.shape

    swap_level = 'global' if sliding_win == targetRgb.shape[:2] else 'partial'

    # generate blending mask.

    # global facial swap. (the size of sliding window is equal to image shape)
    if swap_level == 'global':
    #全局人脸交换
        mask, blured, points = global_facial_swap(
            srcRgb, targetRgb, landmark, training)
    #部分人脸交换
    else:
        mask, blured, points = partial_facial_swap(
            srcRgb, targetRgb, landmark, sliding_win)
    #根据 blending_type 使用 poisson_blending_func 或 alpha_blending_func 进行图像混合。
    if blending_type == 'poisson':
        try:
            mfs_fake = poisson_blending_func(srcRgb, targetRgb, mask, points)
        except Exception:
            mfs_fake = alpha_blending_func(srcRgb, targetRgb, blured/255.)
    else:
        mfs_fake = alpha_blending_func(srcRgb, targetRgb, blured/255.)
    #调用 get_align5p 函数对齐并裁剪图像,返回裁剪后的图像和更新的标记点。
    images, landmark = get_align5p(
        [mfs_fake, srcRgb], landmark, np.random, config, training)

    cropMfs, cropSrc = images
    #如果是局部交换,通过 get_partial_bbox_gt 函数获取局部交换的边界框
    if swap_level == 'partial':
        partial_bbox = get_partial_bbox_gt(cropMfs, cropSrc, sliding_win)
        if partial_bbox is None:
            return None, 'partial swap err.'
        bbox.append(partial_bbox)
    #返回裁剪后的伪造图像 cropMfs 和边界框数组 bbox。
    return cropMfs, np.array(bbox)

2.1 全局人脸交换 global_facial_swap

  • 创建一个与源图像相同大小的掩码
  • 生成人脸标记点的凸包,并将凸包区域填充为白色
  • 在训练模式下,有一定概率对掩码进行弹性变换
  • 对掩码进行高斯模糊处理。
  • 返回生成的掩码、模糊掩码和凸包点。
def global_facial_swap(srcRgb, targetRgb, landmark, training=False):
    '''Global Facial Swap.

    Argument:

    srcRgb: background image. type -> ndarray.
    targetRgb: target image. type -> ndarray.
    landmark: position of face landmarks.
    '''
    #创建一个与 srcRgb 相同形状的全零掩码图像
    mask = np.zeros(srcRgb.shape, dtype=np.uint8)
    #使用 cv2.convexHull 函数生成标记点的凸包。
    points = cv2.convexHull(
        np.array(landmark).astype('float32')
    )
    #将凸包点扩展为多边形格式。
    corners = np.expand_dims(points, axis=0).astype(np.int32)
    #使用 cv2.fillPoly 函数将凸包区域填充为白色(值为 255)的多边形。
    cv2.fillPoly(mask, corners, (255,)*3)

    if training:
        rng = np.random
        #根据一定概率对掩码进行 接受掩码、随机的变换强度、形状参数等。
        if rng.rand() > 0.5:
            mask = elastic_transform(
                mask, random.randint(300, 500), mask.shape[0] * 0.08, 0
            )

    # gaussianblur.对掩码进行高斯模糊处理,使用 cv2.GaussianBlur 函数,模糊核大小为 (5, 5),标准差为 3。
    blured = cv2.GaussianBlur(mask, (5, 5), 3).astype('float32')
    #返回生成的掩码 mask,模糊掩码 blured 和凸包点 points。
    return mask, blured, points

2.2 部分人脸交换

生成全局交换掩码。
调用 generate_partial_swap_mask 函数生成部分交换掩码和滑动窗口边界。
对部分交换掩码进行高斯模糊处理。
返回部分交换掩码、模糊后的掩码和滑动窗口边界。
包含函数类:generate_partial_swap_mask

def partial_facial_swap(srcRgb, targetRgb, landmark, sliding_win):
    '''Partial Facial Swap.

    Argument:

    srcRgb: background image. type -> ndarray.
    targetRgb: target image. type -> ndarray.
    sliding_win: size of sliding window.
    '''
    #调用 global_facial_swap 函数生成全局交换掩码 global_swap_mask、模糊处理后的掩码 blured 和标记点 points
    global_swap_mask, blured, _ = global_facial_swap(srcRgb, targetRgb, landmark)
    #调用 generate_partial_swap_mask 函数生成部分交换掩码 partial_swap_mask 和滑动窗口的边界 points
    partial_swap_mask, points = generate_partial_swap_mask(
        targetRgb, srcRgb, global_swap_mask, landmark, sliding_win
    )
    #进行高斯模糊
    blured = cv2.GaussianBlur(partial_swap_mask, (5, 5), 3).astype('float32')
    #返回部分交换掩码 partial_swap_mask、模糊后的掩码 blured 和滑动窗口的边界 points
    return partial_swap_mask, blured, points

2.2.1 generate_partial_swap_mask

包含函数类:cut_face、cal_dssim、_sliding_bbox
切割输入图像和交换掩码。
计算图像之间的 DSSIM 以确定交换区域。
生成滑动窗口的边界和掩码。

def generate_partial_swap_mask(
        targetRgb, srcRgb, global_swap_mask,
        landmark, sliding_win):
    #提取滑动窗口的高度和宽度
    win_height, win_width = sliding_win
    # 调用 cut_face 函数,切割[targetRgb, srcRgb, global_swap_mask],返回切割后的图像 cut_images 和裁剪边界 crop_bbox
    cut_images, crop_bbox = cut_face(
        [targetRgb, srcRgb, global_swap_mask], landmark
    )

    cutSrc, cutTarget, cutSwapmask = cut_images
#提取裁剪边界,计算cutSrc, cutTarget图像之间的 DSSIM(结构相似性差异)
    x0, y0, x1, x2 = crop_bbox
    dssim = cal_dssim(cutSrc, cutTarget)
    #将交换掩码值小于 1e-4 的区域的 DSSIM 值设为 0
    dssim[cutSwapmask < 1e-4] = 0
    #计算 DSSIM 图像尺寸相对于固定大小 224 的比例
    h_ratio, w_ratio = np.array(dssim.shape[:2]) / 224
    h, w = int(win_height * h_ratio), int(win_width * w_ratio)
    #根据比例调整滑动窗口的高度和宽度
    bbox = _sliding_bbox(dssim, h, w)
    #创建一个与背景图像相同大小的全零掩码 bbox_mask
    bbox_mask = np.zeros(srcRgb.shape)
    #在掩码上标记滑动窗口区域,值设为 255
    bbox_mask[y0+bbox[0]:y0+bbox[2], x0+bbox[1]:x0+bbox[3], :] = 255
    #返回滑动窗口掩码 bbox_mask 和滑动窗口边界 bbox
    return bbox_mask, bbox

2.2.1.1 cut face

根据面部特征点的位置裁剪图像。

  • 首先计算特征点的最小和最大边界
  • 根据这些边界扩展裁剪区域,并将结果应用于输入的每个图像
  • 扩展的比例由参数 alpha 控制,裁剪后的图像保存在 cut_images 列表中,同时返回初始裁剪边界 crop_bbox
def cut_face(images, landmark, alpha=1.2):
#images传入的 [targetRgb, srcRgb, global_swap_mask]
    cut_images = list()
    #计算面部特征点在 x 轴和 y 轴上的最小和最大值,得到裁剪边界
    xmin, xmax = landmark[:, 0].min(), landmark[:, 0].max()
    ymin, ymax = landmark[:, 1].min(), landmark[:, 1].max()
    crop_bbox = (int(xmin), int(ymin), int(xmax), int(ymax))

    for img in images:
    #计算扩展后的裁剪宽度 w 和高度 h
        x0, y0, x1, y1 = crop_bbox
        w = int((x1 - x0) * alpha)
        h = int((y1 - y0) * alpha)
        x0 = int(x0 - (x1 - x0)*(alpha - 1)//2)
        y0 = int(y0 - (y1 - y0)*(alpha - 1)//2)
        cut_images.append(img[
            max(y0, 0): min(y0 + h, img.shape[0]),
            max(x0, 0):min(x0 + w, img.shape[1])
        ])
    #返回裁剪后的图像列表 cut_images 和初始裁剪边界 crop_bbox
    return cut_images, crop_bbox
2.2.1.2 计算DISSM

计算cutSrc, cutTarget图像之间的 DSSIM

def cal_dssim(img1, img2):
    '''Get dssim between the image1 and image2.

    Argument:

    img1: input image -> ndarray.
    img2: input image ->ndarray.

    return dssim: ndarray shape like img1 and img2.
    '''

    C1 = (0.01 * 255)**2
    C2 = (0.03 * 255)**2
    # 将输入图像 img1 和 img2 转换为 float64 类型,以确保计算精度
    img1 = img1.astype(np.float64)
    img2 = img2.astype(np.float64)
    #创建高斯滤波器 kernel,用于平滑图像,window 是二维高斯滤波器,由 kernel 的外积计算得到。
    kernel = cv2.getGaussianKernel(11, 1.5)
    window = np.outer(kernel, kernel.transpose())
    #使用高斯滤波器对图像进行卷积,计算 img1 和 img2 的局部均值 mu1 和 mu2
    mu1 = cv2.filter2D(img1, -1, window)
    mu2 = cv2.filter2D(img2, -1, window)
    #计算图像的平方和积
    mu1_sq = mu1**2
    mu2_sq = mu2**2
    mu1_mu2 = mu1 * mu2
    #计算 img1 和 img2 的局部方差和协方差
    sigma1_sq = cv2.filter2D(img1**2, -1, window) - mu1_sq
    sigma2_sq = cv2.filter2D(img2**2, -1, window) - mu2_sq
    sigma12 = cv2.filter2D(img1 * img2, -1, window) - mu1_mu2
    #使用均值、方差和协方差计算 SSIM 映射 ssim_map
    ssim_map = ((2 * mu1_mu2 + C1) * (2 * sigma12 + C2)) \
        / ((mu1_sq + mu2_sq + C1) * (sigma1_sq + sigma2_sq + C2))
    #计算 DSSIM 映射 dssim,其值为 (1 - ssim_map) / 2,表示反相的 SSIM
    dssim = abs((np.ones(ssim_map.shape) - ssim_map) / 2)
    #如果 dssim 是二维的(灰度图),则将其扩展为三维的(彩色图),使其与输入图像的形状一致
    if len(dssim.shape) == 2:
        dssim = np.stack((dssim, )*3, -1)
    return dssim
2.2.1.3 滑动窗口

通过滑动窗口在给定的 DSSIM 掩码中找到 DSSIM 值最大的区域,并返回该区域的边界框

def _sliding_bbox(mask, h, w):
    '''sliding window.
    select a window size -> [h, w], sliding and find max dssim area.

    Argument:

    mask: dssim mask.
    h: sliding window hight.
    w: sliding window width.
    '''
    #step:滑动窗口的步长,设为掩码高度和宽度的五分之一
    #max_area:记录当前找到的最大 DSSIM 值区域的 DSSIM 总和,初始化为 0
    #res:记录 DSSIM 最大区域的边界框坐标,初始化为 [0, 0, 0, 0]。
    step = [mask.shape[0]//5, mask.shape[1]//5]
    max_area = 0
    res = [0] * 4
    #外层循环:i 从 0 开始,每次增加 step[0],直到 mask 的高度
    #内层循环:j 从 0 开始,每次增加 step[1],直到 mask 的宽度。
    for i in range(0, mask.shape[0], step[0]):
        for j in range(0, mask.shape[1], step[1]):
            if i + h <= mask.shape[0] and j + w <= mask.shape[1]:
            #计算窗口 [i:i+h, j:j+w] 内的 DSSIM 值总和
                area = np.sum(mask[i:i+h, j:j+w])
                if area > max_area:
                    max_area = area
                    res = [i, j, i+h, j+w]
    #返回找到的 DSSIM 最大区域的边界框坐标 res
    return res

2.3 混合方法:alpha_blending_func和poisson_blending_func

alpha混合: I F ′ = I F ∗ M + I S ∗ ( 1 − M ) I^{'}_F = I_F*M+I_S*(1-M) IF′​=IF​∗M+IS​∗(1−M) , I F ′ I^{'}_F IF′​
Poisson 混合,使用无缝克隆算法将targetRgb融合到srcRgb

def alpha_blending_func(srcRgb, targetRgb, mask):
    '''Alpha blending function

    Argument:

    srcRgb: background image. type -> ndarray.
    targetRgb: target image. type -> ndarray.
    mask: blending mask. type -> ndarray.
    '''
    return (mask * targetRgb + (1 - mask) * srcRgb).astype(np.uint8)


def poisson_blending_func(srcRgb, targetRgb, mask, points):
    '''Poisson blending function

    Argument:

    srcRgb: background image. type -> ndarray.
    targetRgb: target image. type -> ndarray.
    mask: blending mask. type -> ndarray.
    points: blending position. type -> ndarray.
    '''

    points = np.array(points)
    points = points.reshape((-1, 2))
    center_x = (max(points[:, 0]) + min(points[:, 0])) / 2
    center_y = (max(points[:, 1]) + min(points[:, 1])) / 2
    center = (int(center_y), int(center_x))

    return cv2.seamlessClone(targetRgb, srcRgb, mask, center, cv2.NORMAL_CLONE)


2.4 get_align5p

对齐图像

def get_align5p(images, ld, rng, config, training=False):
    #images是[mfs_fake, srcRgb]
    config = config['crop_face']

    images, landmark = align_5p(
        images, ld=ld,
        face_width=config['face_width'], canvas_size=config['output_size'],
        scale=(rng.randn()*0.1+0.9 if training else config['scale']),
        translation=([
            rand_range(rng, -25, 25), rand_range(rng, -25, 25)
        ] if training else [0, 0]),
        rotation=(30*rand_range(rng, -1, 1)**3 if training else 0),
        sa=(rand_range(rng, .97, 1.03) if training and rng.rand() > 0.8 else 1),
        sb=(rand_range(rng, .97, 1.03) if training and rng.rand() > 0.8 else 1),
    )

    return images, landmark

2.4.1 align_5p

根据面部标志点对图像进行对齐和裁剪

def align_5p(
        images, ld, face_width, canvas_size,
        translation=[0, 0], rotation=0,
        scale=1, sa=1, sb=1
):
    '''crop face with landmark.

    images: input images. -> ndarray.
    ld: face landmark of input images. -> ndarray; shape -> (5, 2)
    face_width: face width ratio of the cropped images. -> float
    canvas_size: shape of the cropped face.
    return list of cropped images. -> list(ndarray)
    '''
    #从标志点数组 ld 中提取面部关键点
    nose_tip = ld[30]
    left_eye = np.mean(ld[36:42], axis=0).astype('int')
    right_eye = np.mean(ld[42:48], axis=0).astype('int')
    left_mouth, right_mouth = ld[48], ld[54]

    lm = np.array([left_eye, right_eye, nose_tip, left_mouth, right_mouth])
    #MEAN_FACE 是预定义的平均面部标志点
    mf = MEAN_FACE * scale
    #将平均面部标志点按比例缩放,并根据 face_width 和 canvas_size 调整
    mf = get_mean_face(mf, face_width, canvas_size)

    M1 = np.eye(3)
    #调用 get_align_transform 函数计算从 lm 到 mf 的仿射变换矩阵,并将结果填充到 M1 的前两行
    M1[:2] = get_align_transform(lm, mf)

    M2 = np.eye(3)
    #使用 cv2.getRotationMatrix2D 函数生成旋转矩阵(中心点为图像中心,旋转角度为 rotation),并将结果填充到 M2 的前两行
    M2[:2] = cv2.getRotationMatrix2D((canvas_size/2, canvas_size/2), rotation, 1)

    def stretch(va, vb, s):
        m = (va+vb)*0.5
        d = (va-vb)*0.5
        va[:] = m+d*s
        vb[:] = m-d*s

    mf = mf[[0, 1, 3, 4]].astype(np.float32)
    mf2 = mf.copy()
    stretch(mf2[0], mf2[1], sa)
    stretch(mf2[2], mf2[3], 1.0/sa)
    stretch(mf2[0], mf2[2], sb)
    stretch(mf2[1], mf2[3], 1.0/sb)

    mf2 += np.array(translation)

    M3 = cv2.getPerspectiveTransform(mf, mf2)

    M = M3.dot(M2).dot(M1)

    dshape = (canvas_size, canvas_size)
    images = [cv2.warpPerspective(img, M, dshape) for img in images]

    # warp landmark.
    ld = np.array(ld)
    ld = ld.dot(M[:, :2].T) + M[:, 2].T

    return images, ld[:, :2]
2.4.1.1 get_mean_face
def get_mean_face(mf, face_width, canvas_size):
#0.34967 是一个常量,用于归一化面部宽度
    ratio = face_width / (canvas_size * 0.34967)
    left_eye_pupil_y = mf[0][1]
    ratioy = (left_eye_pupil_y * ratio + 0.5) * (1 + 1.42)
    mf[:, 0] = (mf[:, 0] * ratio + 0.5) * canvas_size
    mf[:, 1] = (mf[:, 1] * ratio + 0.5) * canvas_size / ratioy
    #调整后的平均面部标志点数组 mf
    return mf
2.4.1.2 get_align_transform
def get_align_transform(lm, mf):
#计算平均面部标志点 mf 的 x 和 y 坐标的平均值 mx 和 my。
#计算输入面部标志点 lm 的 x 和 y 坐标的平均值 dmx 和 dmy。
    mx = mf[:, 0].mean()
    my = mf[:, 1].mean()
    dmx = lm[:, 0].mean()
    dmy = lm[:, 1].mean()
    #计算 mf 和 lm 中每个点相对于中心点的偏移量。
    ux = mf[:, 0] - mx
    uy = mf[:, 1] - my
    dux = lm[:, 0] - dmx
    duy = lm[:, 1] - dmy
    #旋转和平移所需的三个常量 c1、c2 和 c3
    c1 = (ux * dux + uy * duy).sum()
    c2 = (ux * duy - uy * dux).sum()
    c3 = (dux**2 + duy**2).sum()
    #仿射变换矩阵的旋转参数 a 和 b
    a = c1 / c3
    b = c2 / c3
    kx, ky = 1, 1
    #构造仿射变换矩阵 transform
    transform = np.zeros((2, 3))
    transform[0][0] = kx * a
    transform[0][1] = kx * b
    transform[0][2] = mx - kx * a * dmx - kx * b * dmy
    transform[1][0] = -ky * b
    transform[1][1] = ky * a
    transform[1][2] = my - ky * a * dmy + ky * b * dmx
    return transform

2.5 get_partial_bbox_gt

  • 计算裁剪后的目标图像和源图像之间的 DSSIM 值。
  • 如果 DSSIM 值大于某个小阈值的像素数超过一个预定值,则在 DSSIM 图上使用滑动窗口查找最大差异区域,并返回该区域的边界框。
  • 如果未找到合适的区域,则返回 None。
def get_partial_bbox_gt(cutMfs, cutSrc, sliding_win):
    #计算裁剪后的目标图像和源图像之间的结构差异度量 (DSSIM)
    dssim = cal_dssim(cutMfs, cutSrc)
    if len(dssim[dssim > 1e-3]) > 40**2*3:
        #调用 _sliding_bbox 函数,在 DSSIM 图上使用滑动窗口查找最大差异区域,并返回该区域的边界框
        return _sliding_bbox(dssim, sliding_win[0], sliding_win[1])
    return None
常用的集成TTL门电路 本篇主要分析了常用的逻辑门电路与非门,详细分析了电源原理。值得收藏。 阅读详情

相关推荐

Carsim与AVL CRUISE联合仿真:从零搭建整车模型的实战指南

本文详细介绍了如何从零开始搭建Carsim与AVL CRUISE联合仿真整车模型,涵盖软件环境配置、接口设置、模型连接及调试技巧。通过联合仿真,可充分发挥Carsim在车辆动力学和AVL CRUISE在动力系统仿真方面的优势,为电动车和混合动力汽车的研发提供精准的整车模型分析。

weixin_29067143的博客 938

C1FlexGrid的属性操作

1.  列自由拖拽 我们可以拖动教育的列放到最后一行,如图 C1控件默认支持   2.  前几列锁定   Frozen 的数值 设为2,则前两列锁定,拉动横向滚动条,前2列是不动的。用于定义性的属性数据的显示,比如工卡,姓名或物料代码,物料名称等数据。   3.  相同行合并的统计功能 总的C1控件设置   分列合并设置

1362

手把手教你用QEMU虚拟化华为路由器固件(附完整网络配置指南)

本文提供了一份详尽的QEMU虚拟化华为路由器固件的实战指南。从固件解包、网络桥接配置到QEMU虚拟机启动,手把手教你搭建完整的嵌入式设备沙箱环境,特别适合安全研究、逆向分析和学习嵌入式系统工作原理。

tgb3456789的博客 937

代码解析—part1 数据准备—CVPR2023—Implicit Identity Leakage: The Stumbling Block to Improving Deepfake

代码理解

JustWantToLearn的博客 924

comsol 多物理场仿真流程-以开关柜为例

1、主要方法 在SolidWorks中对于开关柜的内部与外部结构进行建模,生成能够导入软件计算的3D模型。将SolidWorks中的模型导入comsol软件中,设置参数进行多物理场耦合仿真。 1、具体步骤 删除高压开关柜中对其内部电磁环境无较大影响的结构,如低压测量柜、断路器内部结构等。 导入模型:将厂家提供的3D模型转化为solidworks结构模型。 修复模型:修复已转化模型中转化错误的部分、修复已转化模型中非实体部分、修复已转化模型中重叠实体部分 化简模型:去除过于...

健忘16的博客 7020

ANPC三电平拓扑:两种调制方式解析

ANPC结构是基于“I”型NPC结构(下面讲NPC如不特殊说明,均指的是“I”型结构NPC)发展出来的一种改进型。我们在讲NPC结构的时候会谈到他的一个缺点就是损耗分布不均匀,会导致温升不均衡,进而限制模块的功率输出能力。而ANPC结构由于多了两个开关管以及相应的两个中性点换流回路(如下图),可以更加灵活任意的配置开关管可以使损耗没有那么的不均衡,从而能一定程度上提高模块的输出功率。ANPC拓扑结构图以及增加的环流回路。

IOTBOT的博客 9482

通过 AIGC 生成视频何时才能告别“抽卡”!

如果视频的主体内容已经符合要求,只存在错误字幕或局部画面瑕疵时,重新生成不仅增加成本,还可能改变原本满意的部分。针对这类问题,WAND-EraseVibe+ 提供了视频擦除与画面重建能力。根据腾讯云披露的技术方案,该工具采用基于隐空间 Transformer 的生成式修复架构,先将视频画面编码为隐空间特征,在特征层推断并重建需要修复的区域,再解码为视频画面。白模前置、垂直场景调优和局部修复分别作用于不同环节,目的都是减少为了获得可用内容而付出的重复生成成本。

Enweitech Software Works 134

PCB布局规范及其注意事项(纯干货)

这样做的好处是信号1,3层不会有串扰,4和5作为电源有紧密的耦合,3个信号层都有GND平面可以参考,且不会互相产生串扰,唯一的缺点是布线的层面只有3个信号层(信号03的是最适合来传输高速信号,因为该层的二面都有屏蔽,防止干扰传出,也防止干扰传入)33、影响阻抗匹配的参数(ER介电常数(1GHZ以下默认为4.2,以上的话问板厂),H介质厚度(就是内层压合的PP片厚度),W走线的线宽,T走线的厚度(铜厚/减小线的厚度可以增大阻抗))我这里使用的是CITS25来计算阻抗。

qq_42379138的博客 8843

星耀影都AIGC微短剧创作培训营第二期开启报名

据悉,本期导师汇集了多位行业资深专家,既有深耕一线的爆款微短剧操盘手,也有AIGC领域的前沿探索者,包括:AI漫剧《风水天师》(上线12小时播放量破亿)总制片人,江西时刻互动CTO/合伙人庞恩辽;现象级AI漫剧《持械入宋》(全网播放量超40亿)总策划,“老十一”团队创始人张汉生;本次活动由北京市广播电视局指导,怀柔区人民政府、抖音集团主办,怀柔区委宣传部、怀柔区文化产业发展促进中心、剪映、红果短剧承办,北京电影学院、火山引擎、即梦、《网络视听》杂志社、怀柔区杨宋镇人民政府协办。

2508_92108265的博客 233

工控安全政策系列导读:国际标准IEC 62443标准系列

IEC 62443由ANSI/ISA提出,被ISO/IEC采纳,所以该标准会以不同的名字出现,包括:ANSI/ISA-99,ISA-99,ISA 62443,ISO/IEC 62443,ISO 62443,IEC 62443。其中IEC 62443-2-1已等同采纳为国标,于2016年10月发布,标准编号为GB/T 33007。 IEC 62443是在国...

weixin_34132768的博客 2622

[ 人力资源面试篇 ] HR 面试题分析详解大集合,看完直怼面试官(二)

那么在这个系列的文章中,我们聊到 HR 面谈什么?并且总结了五十个常见的人力资源面试题,在这里分享给大家。

qq_51577576的博客 1380

爱查宝 AIGC 检测效果实测:对标知网维普朱雀

本文深度评测一款AI文本检测工具,从核心引擎机制、多场景实测到与知网、维普等系统的对比,全面分析其在识别纯AI生成、人工改写及混合内容中的表现。结果显示,该工具基于困惑度与爆发度的双维度模型,具备高精度与强适应性,尤其在细粒度报告呈现、可视化解读和长文稳定性方面优势显著。尽管存在对极规范化文本的误判风险,但通过人工复核可有效规避。结论强调:技术应服务于人,助力学术诚信与创作真实,而非取代判断。

深夜代码酒神 284

基于YOLOv5的空间目标智能监测系统:航天器与太空碎片识别技术实现

做完这个项目,你会发现自己完整走过了“数据采集→标注→模型训练→功能集成→界面开发”的毕设全流程,这不仅是技术能力的证明,更是对“技术守护宇宙安全”这一理念的实践。答辩时,你可以重点讲这几点:系统解决了航天监测的哪些痛点、YOLOv5在太空目标检测中的性能优势、系统未来能怎么优化(比如对接航天监测网络、实现实时轨道预测)。未来,这个系统还能往“更智能”的方向发展:比如结合轨道计算预测碰撞风险、开发移动端APP让天文爱好者参与太空垃圾上报、在航天任务中实现全自动避障决策……想象空间很大。

m0_64661770的博客 109

AIGC 应用工程师证书怎么考?报考条件、考试形式、级别说明(2026 版)

AIGC 应用工程师证书是面向人工智能生成技术应用方向的职业技术证书,分初级、中级、高级三个级别。

tipdm0526的博客 171

Camera2Demo

在Camera2 的基础上进行封装,使得Camera2一堆初始化逻辑与Activity分离,降低耦合度,完成预览拍照并保存的操作

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

【AIGC实战】Python+SDXL打造“国潮IP”,自动化生成“舞狮椰子冻”硬核商业视觉

今天分享一个完整的AIGC实战项目:利用 Python 配合 Stable Diffusion XL (SDXL) 模型,打造“英歌舞舞狮IP”,并将其完美融入到硬核国潮椰子冻的设计中。AI绘画引擎: Stable Diffusion XL (SDXL),利用其强大的语义理解能力,精准控制“Q版舞狮”与“精致椰子冻”的质感构图。print(f"[生成失败]: {flavor}, 错误代码: {response.status_code}")print(f"[成功保存]: {save_path}")

2601_95871228的博客 189

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

电商商品图用哪个 AIGC 算力平台?先守住商品细节,再谈一天能出多少张

给电商团队选 AIGC 算力平台,商品图和概念插画的区别,在于有些地方可以生成,有些地方不能改。背景换成棚拍、户外或节日布景都可以,但包装文字、商品结构和颜色不能跟着模型自由发挥。

a123456534的博客 99

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

6 款电商 AI 图片生成软件实测|国内 + 跨境卖家工具清单

电商 AI 作图工具已经从 "能不能用" 进入了 "好不好用" 的阶段。对于绝大多数卖家而言,现在的 AI 工具已经可以覆盖 60%-80% 的基础商品图需求,显著降低拍摄和设计成本。选择工具的核心逻辑不是 "哪款最强",而是 "哪款最适配你的品类、平台规模和团队能力"。垂直电商工具胜在工作流效率,通用生图工具强在创意上限,模板类工具赢在上手速度。建议根据自身业务阶段,搭配 1-2 款主力工具 + 1 款辅助工具,形成适合自己的视觉生产流程。

Mardi_Ladin的博客 352

视频隐私脱敏工程化:CSRT/KCF追踪算法在本地推理中的实现与边界

在短视频内容合规审查中,动态遮挡(Dynamic Masking)是高频且高耗时的环节。传统手动关键帧打码不仅效率低下,且在目标快速移动或遮挡时极易出现遮罩漂移。本文从计算机视觉工程角度,解析基于CSRT与KCF算法的目标追踪方案在视频隐私处理中的技术路径、性能权衡及落地实践。

Mysssone的博客 195

我用 GPUWorks 在 M1 Pro 上搭了个发票识别原型

公司里经常要看电子发票。我想先做个小原型:把 PDF 发给一个自己部署的图文模型,让它提取发票类型、日期、交易双方和金额,再把结果交给同事核对。这个原型不用先接财务系统,先看看从部署到调用要走多少步,模型返回的内容能不能帮上忙。

鸽芷咕的博客 2万+

医疗 AI 有两种,别混着谈:一个改流程,一个改范式

InfoXMed 是面向医生、医学生和医学科研人员的 AI 医学工具平台,提供文献检索、全文翻译、AI 解读、指南查询和题库练习等功能,辅助临床学习、科研汇报与医学备考。原文是日本医学会杂志(JMA Journal)的一篇综述(Review Article):基本事实:这一点必须先说清:这篇论文提出的是一个研究纲领。它文中的"预测"指的是科学设想,不是可以拿来用的临床预测能力。文中甚至自己承认了一个硬缺口(见第三节)。本文只谈它的术语辨析与科学方法论,不转述任何可作为临床依据的预测或处置主张。这篇论文的全部

2601_96948740的博客 233

您您这可以把问小白的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭

第三层是格式编译层。这个决策背后的判断很清晰:一个批量导出工具的价值,不在于“试三次”,而在于你真正用它处理了几十份技术文档、访谈记录或课程讲义之后,它是否值得留在你的工作流里。问小白的会话列表通过分页接口加载,AI导出鸭 在页面运行时建立旁路监听,捕获每一次分页请求返回的完整对话清单,包括会话 ID、标题、时间戳和隐藏的元数据字段。让AI导出回归优雅——这句话说的是:导出不该是体力活,而应该是一次完整的、可预期的文件交付。注意:这里的“批量”指的是左侧列表里的多个独立会话,不是一条会话内的多轮对话。

laoyunsi的博客 232

河道漂浮物目标检测数据集

目标检测,yolo,河道漂浮物目标检测数据集

DL_data_set的博客 191

AI Agent之核心框架

A: 显式 Thought 有三类价值:可解释性(便于人类审计)、可调试性(定位哪一步选错工具)、学习信号(可做监督微调或评估中间步骤质量)。ReAct 把推理与 可执行行动 绑定,每一步行动后都有 真实 Observation 反馈,从而用工具结果约束生成,降低闭卷幻觉。做完不等于结束——还要 评估做得好不好,把 教训 记下来,下次 带着教训重试,反思作为 记忆,影响后续尝试的策略。工作流程:Action → Evaluation → Reflection → Retry。A: 普通 CoT 只在。

剑雨巍巍的博客 190

AI 应用开发工程师技术实践:大模型接入、通用组件封装与性能优化

本文提出AI应用从PoC到生产的核心挑战:模型调用的持续可用、可控与可优化。通过构建统一LLM网关实现多供应商路由与故障转移,封装对话、工具调用与RAG组件以解耦业务逻辑,结合前缀缓存、批处理与流式背压治理等性能优化手段,形成“接通→接稳→接快”的工程路径,最终以工程化能力决定AI应用的生产可用性。

2301_79189962的博客 541

大模型 Embedding 与向量相似度详解

Embedding 解决的问题:计算机不懂文字,但懂数字│▼文本 → 向量(1024维浮点数数组)│▼相似度解决的问题:怎么判断两个向量(文本)有多接近│▼余弦相似度:看向量方向的夹角,越接近0°越相似│▼最终效果:语义搜索——理解意思,而非字面匹配一句话总结:Embedding 把文字变成数字,余弦相似度比较数字的方向,两者结合实现了"理解语义"的搜索。

T3165919332的博客 79

AI前沿日报 · 2026-11-04 | 祛魅时刻:Astra“纯读权重“神话的崩塌

①Astra"裸权重通吃"被spaCy创始人Honnibal精准祛魅——仅1/3实验真裸读,数学能力提升依赖Python+50次迭代;②Simon Willison用Qwen 3.8-27B端侧复现权重空间编程;③Nvidia开源Auto-Ontology自动知识图谱;④$800开源全域抓取机器人Stringman;⑤OpenAI悄然重开Codex训练开关引信任危机;⑥DeepMind发布Gemini 4 Argon机器人基础模型;⑦美国科技巨头隐性AI债务达3.2万亿美元,6个月增90%。

johnsong2009的博客 552
上一篇: 面试真题——self-attention 不要只记公式了!!
下一篇: 代码解析—part3 训练ADM—CVPR2023—Implicit Identity Leakage: The Stumbling Block to Improving Deepfake
DeepLearning01
博客等级 码龄7年 242粉丝 18原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值