ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

人像卡通化实战:从数据到推理的CycleGAN全流程指南

人像卡通化实战:从数据到推理的CycleGAN全流程指南 简介人像卡通化项目资源包基于 Python 和 PyTorch 实现面向需要将真实照片转换为卡通风格非真实感图像的开发者与研究者。项目针对照片与卡通画之间存在轮廓差异、成对数据难以绘制的痛点采用 unpaired image translation 技术而非 pix2pix 方案在保留人物身份信息和纹理细节的同时生成卡通化结果对眼睛、下巴等面部特征的处理更贴近卡通表现。资源包共 233 个文件压缩后约 217.78MB主要包含 206 张 PNG 图像、16 个 Python 脚本、预训练权重文件以及分割模型目录结构清晰便于按模块查阅。目前已有 804 人学习适合具备深度学习基础、想快速复现或扩展该应用的开发者。包内除预训练模型外还提供头部分割模型、人脸识别模型、ONNX 导出模型与卡通画开源数据可覆盖从训练、推理到部署的完整流程附带的说明文档、测试图片和结果演示图有助于对照理解代码与调试效果。1. 人像卡通化值得做吗从照片到卡通的落地全链路人像卡通化在 Python 生态里有大量开源实现但绝大多数卡在“能跑 demo、不能用于生产”这一层。这个项目把源码、数据集、模型三件套凑齐意味着你可以把一张真实照片转换成卡通风格的非真实感图像不是套滤镜而是让生成模型学习卡通画的线条和配色规律。它能解决的实际问题包括头像批量生成、二次元风格化、视频帧卡通化预处理、游戏 NPC 立绘草稿生成。适合的人群是已经跑通过目标检测或分类任务、想在 GAN 方向真刀真枪训练一次完整模型的开发者而不是停留在加载别人权重看输出的阶段。我按选型、数据、训练、推理、踩坑这条链路把完整路线拆开讲。2. 模型原理为什么非配对数据要用 CycleGAN 而不是直接堆滤镜2.1 非配对数据下的 CycleGAN 选型理由人像卡通化项目的数据集大概率是“一堆真实人像照片 一堆卡通风格图像”两边不配对。一张真实照片对应的标准卡通答案不存在也没有画师愿意给每张照片手工绘制对应的卡通样本。这个约束直接排除了 Pix2Pix 这类需要配对样本的监督方法也让很多刚接触的人产生困惑没有配对标签模型到底学什么CycleGAN 解决这个问题的思路是循环一致性。用生成器 G 把真实人像 x 转到卡通风格得到 fake 卡通 G(x)再用逆生成器 F 把 G(x) 转回真实风格得到重建照片 F(G(x))。如果 F(G(x)) 和 x 像素级接近说明 G 在风格转换过程中保留了人脸结构、五官位置、表情姿态等核心内容。另一个关键点是判别器 D_B 负责区分真实卡通图与生成的卡通图G 的目标是骗过 D_BF 的目标是骗过 D_A训练是一个双人博弈的迭代过程。有人问既然有对抗损失能不能只训练一个单向生成器可以但卡通风格是一个高自由度空间——同样的内容可以把线条画粗可以把颜色调暗可以夸张眼睛比例。没有循环一致性约束生成器很容易在优化过程中飘到任意卡通方向。训练结果看起来是卡通的但内容已经不是输入照片的那张脸。循环一致性就是这个框架里最关键的锚点。选择 CycleGAN 还有一个现实考量数据获取成本低。真实人像可以用公开人脸数据集卡通图可以收集动画截图两边不需要任何对应关系。如果你碰巧手里有几百对同一张脸的照片和手绘卡通图那就转用带配对约束的监督模型内容保真度会高一个档次。这个标题既然写明“数据集模型”基本可以判断走的是非配对训练路线。2.2 生成器结构下采样、九层残差块与上采样CycleGAN 的默认生成器是 ResNet 结构输入输出都是 RGB 三通道图像常见分辨率 256×256部分实现支持 512×512但显存敏感型选手要慎重。网络分三段下采样段先是卷积 InstanceNorm ReLU把 256×256×3 变成 256×256×64再连续两层 stride2 的卷积依次得到 128×128×128 和 64×64×256。这个阶段在压缩空间分辨率的同时把通道数做宽。残差段9 个残差块每个残差块内部有两次卷积保持 64×64×256 的分辨率不变。风格迁移的关键变换都发生在这里输入输出尺寸一致梯度可以从后向前顺畅回流。上采样段两层反卷积加一层普通卷积逐步恢复到 128×128×128、256×256×64、256×256×3通道数压回 RGB。残差块的 PyTorch 实现如下import torch.nn as nn class ResidualBlock(nn.Module): def __init__(self, in_channels256, kernel_size3): super(ResidualBlock, self).__init__() pad kernel_size // 2 self.conv_block nn.Sequential( nn.Conv2d(in_channels, in_channels, kernel_size, paddingpad), nn.InstanceNorm2d(in_channels), nn.ReLU(inplaceTrue), nn.Conv2d(in_channels, in_channels, kernel_size, paddingpad), nn.InstanceNorm2d(in_channels), ) def forward(self, x): return x self.conv_block(x)关键在最后的x conv_block(x)。如果卷积块学到的变换方向不对残差连接能保证输出不会偏离输入太远网络不会退化。InstanceNorm 替代 BatchNorm 是刻意选择——BatchNorm 依赖 batch 内的统计量训练和推理时 batch size 不一致容易出现行为漂移InstanceNorm 把统计范围限制在单张图内对风格迁移任务更稳。生成器通道数和输入分辨率直接相关。输入 512×512 时我会把残差块通道数从 256 提到 512否则感受野不够学到的纹理尺度偏小但显存压力会明显增加属于权衡取舍。2.3 判别器PatchGAN 为什么比全局判别器更适合卡通化人像卡通化的判别器用的是 PatchGAN不是传统 GAN 的全局二分类输出。实现如下class PatchDiscriminator(nn.Module): def __init__(self, in_channels3): super(PatchDiscriminator, self).__init__() # 4 层卷积逐步下采样最后输出 NxN patch 矩阵 self.model nn.Sequential( nn.Conv2d(in_channels, 64, 4, stride2, padding1), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(64, 128, 4, stride2, padding1), nn.InstanceNorm2d(128), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(128, 256, 4, stride2, padding1), nn.InstanceNorm2d(256), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(256, 512, 4, stride1, padding1), nn.InstanceNorm2d(512), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(512, 1, 4, stride1, padding1), ) def forward(self, x): return self.model(x)输入 256×256 图像输出 30×30 矩阵每个位置代表原图 70×70 感受野区域的真伪概率。PatchGAN 的哲学是与其只给整张图一个真/假分数不如对每个局部 patch 打分拼出一张真伪热力图。这个设计对卡通化任务的收益是实质性的。卡通风格的标志性特征——油亮的头发高光、干净的面部线条、大色块阴影——都是局部现象。全局判别器只看一个得分生成器容易在整体色调上作弊PatchGAN 逼着生成器在每一个局部纹理、笔触、边界上都过关。训练时你会发现判别器输出矩阵中某些区域反复给低分往往对应生成图片里脸部阴影或线条过渡最不自然的位置。2.4 损失函数配置对抗损失、循环损失和 identity loss 的取舍CycleGAN 训练总损失由三项加权相加对抗损失G 生成的卡通图骗过 D_BF 生成的假照片骗过 D_A。判别器用最小二乘形式训练更稳定生成图像质量比传统 log 损失更锐利。循环一致性损失重建图与原始图之间的 L1 距离。L1 比 L2 对边缘更友好不容易把人脸轮廓磨糊。identity loss可选把属于 B 域的卡通图送入 GA→B 的生成器输出应接近原图。这个约束强制模型不改动已经符合目标风格的图像实际作用是把颜色映射约束在合理范围。训练代码中损失计算的核心片段# 对抗损失G 要最小化 -log(D_B(G(x)))这里以判别器输出为真为优化目标 loss_GAN self.criterionGAN(self.netD_B(self.fake_B), True) # 循环一致性损失把生成图送进反向生成器重建图与原图算 L1 rec_x self.netG_A(self.fake_B) loss_cycle self.criterionCycle(rec_x, x) * lambda_A # identity lossG 处理一张已是卡通风格的图尽量别过度改造 identity_B self.netG_A(real_B) loss_identity self.criterionIdentity(identity_B, real_B) * lambda_idt权重配置常见默认值是lambda_A lambda_B 10.0identity loss 系数lambda_idt 0.5。多数项目直接沿用。如果生成图太写实、卡通感不足把对抗损失权重调高或把 lambda_idt 降到 0.1如果脸变卡通但五官位置漂移反过来加大循环损失权重。损失曲线要区分着看。对抗损失是来回震荡的不要盯着它判断死活要看循环损失和重建误差的下降趋势。循环损失稳定下降说明模型在有效学习内容保持训练后期如果循环损失突然反弹大概率是生成器跑太猛把判别器也带崩了这时候应该降低学习率而不是动损失权重。3. 数据集与预处理真实人像和卡通图如何变成模型能吃的样本3.1 数据集目录组织与数量要求人像卡通化项目的数据集分为两个域域 A 是真实人像照片域 B 是卡通风格图片。目录结构按 CycleGAN 的经典布局规划datasets/ ├─ cartoon_dataset/ │ ├─ trainA/ # 真实人像照片jpg/png建议 1500 张以上 │ ├─ trainB/ # 卡通风格图片jpg/png建议 1500 张以上 │ ├─ testA/ # 测试用真实人像50~200 张 │ └─ testB/ # 测试用卡通图50~200 张trainA 和 trainB 的命名不是随便取的。CycleGAN 的 dataloader 默认按文件夹名识别域代码里读 trainA 就是源域、trainB 就是目标域。把目录搞混是入门最常见的低级翻车训练跑起来后你会发现生成器在把卡通图往照片方向转方向反了。数据量方面我的经验是 trainA/trainB 各 1500~3000 张是一个够用区间。低于 1000 张循环一致性会压不住内容漂移尤其是五官位置容易歪掉。如果只有 800 张图先用随机裁剪和水平翻转扩充勉强能跑但要接受效果折扣。数据集规模训练效果预期单卡 2080Ti 耗时估算500 张/域能学到风格内容失真明显约 4 小时1500 张/域风格和内容保真度基本可用约 10~12 小时3000 张/域效果较稳可微调到接近生产可用约 20 小时以上以 256×256 输入、默认 200 epoch 为前提。实际训练 80~100 epoch 后 loss 往往已经平滑可以先中断看效果再决定是否继续。真实人像可以用公开人脸数据集卡通图可以收集动画截图或插画网站图片但注意版权只用于个人研究不要直接商业化。另一件要警惕的事是画风混入。如果 B 域既有日系漫画脸又有欧美写实卡通模型学到的会是一个「平均画风」输出观感差。同一批训练数据尽量保持画风一致。注意CycleGAN 的数据加载器严格区分 trainA 与 trainB目录名一旦写错训练方向就反了。启动训练前先打印一个 batch 确认 A 域是照片、B 域是卡通图这是最省时间的习惯。3.2 人脸检测与裁剪对齐采集到的原始照片尺寸、人脸位置各不相同。如果不做对齐模型要把大量容量花在避免人脸出现在不同位置上风格学习效率大打折扣。一般做法是先做人脸检测把检测框扩大一圈后裁剪再 resize 到 256×256。扩边比例建议 0.2~0.3 倍框宽高保证头发和耳朵一起裁进去。批量对齐的常用命令形态python align_faces.py --input_dir datasets/cartoon_dataset/raw_photos \ --output_dir datasets/cartoon_dataset/trainA \ --size 256 --margin 0.25align_faces.py 的核心逻辑分三步读取原始图 → 检测人脸框 → 按框扩大 margin 后裁剪 resize。margin 是相对人脸框宽高的比例0.25 表示上下左右各扩出 25% 的空白。太小会把额头切掉太大把背景大片带进来干扰风格学习0.2~0.3 是常规区间。卡通图同样要做对齐。如果卡通图里人脸特写、半身、全身混杂模型在 B 域中学到的特征极不稳定。对齐做干净训练收敛快得多。亲测同样数据集对齐前后训练到同样视觉效果耗时差 30% 以上这是血泪经验换来的。3.3 数据增强与像素归一化训练时常用的增强策略随机水平翻转人像没问题垂直翻转会导致脸部颠倒随机裁剪先裁 286×286 再 resize 回 256×256相当于有效数据量翻倍亮度/饱和度扰动模拟不同光照条件高斯模糊对含噪点照片有明显帮助CycleGAN 官方训练脚本自带 resize、random crop、flip 流水线通常不用重写。单卡显存 8GB 以下 crop size 用 25612GB 以上可尝试 286。GPU 资源不够时用 CPU 跑小数据集验证代码是另一种可靠路径一张 256×256 图大约 2 秒一个 step跑 5 epoch 确认数据流正确再切回 GPU。预处理里有个隐蔽坑像素归一化方式。CycleGAN 官方实现把像素映射到 [-1, 1]生成器最后一层 Tanh 激活与之匹配。如果你在预处理里只用了 [0, 1] 归一化但没改输出激活函数生成的图像对比度会明显诡异。拿到任何源码先读 dataloader 代码确认归一化方式不要凭感觉改。3.4 训练前可视化数据分布每批数据送入训练前值得写一个小脚本把 trainA 和 trainB 拼成网格看一眼import matplotlib.pyplot as plt from torchvision.utils import make_grid def preview_dataset(dataloader, save_pathpreview.png): batch next(iter(dataloader)) # A 域代表真实人像取前 16 张拼成 4x4 网格 grid make_grid(batch[A][:16], nrow4, normalizeTrue) plt.imsave(save_path, grid.numpy().transpose(1, 2, 0)) print(saved preview to, save_path)这里 batch[A] 假设 dataloader 返回的 key 叫 A不同实现可能叫 real_A打开源码看一眼 batch 结构改一下即可。这个步骤的价值在于留下训练前的分布快照训练到一半发现生成结果不对时翻出预览图和 checkpoint 对比能快速定位是数据问题还是训练问题。4. 训练与推理把源码跑通的最小命令和参数调整顺序4.1 训练参数默认值与调整顺序拿到源码后先通读 train.py 的参数定义。CycleGAN 系列的关键参数如下这个表适用于绝大多数衍生项目参数名常见默认值调整建议batch_size1风格迁移任务 batch1 反而稳定显存充裕也不建议一次开大lr0.0002前 100 epoch 固定后 100 epoch 线性衰减lambda_A / lambda_B10.0保持默认一般不动lambda_idt0.5风格不足时降到 0.1内容漂移时升到 1.0pool_size50判别器历史 buffer防止生成器被单一成图带偏crop_size256小显存用 256大显存可试 286netGresnet_9blocks数据几千张就用默认数据量小可换 resnet_6blocks训练启动命令的常见形态python train.py --dataroot datasets/cartoon_dataset \ --name cartoon_style \ --model cycle_gan \ --pool_size 50 \ --lambda_idt 0.5 \ --lambda_A 10 --lambda_B 10 \ --batch_size 1 \ --n_epochs 100 --n_epochs_decay 100 \ --save_epoch_freq 5参数含义--name cartoon_style是实验别名checkpoint 会存到checkpoints/cartoon_style/--n_epochs是固定学习率轮数--n_epochs_decay是线性衰减轮数合起来 200 轮--save_epoch_freq 5表示每 5 epoch 存一次权重跑崩了也有后悔药。调整优先级先保证 batch_size 和 crop_size 能在显存内稳定训练再动 lambda_idt。不要盲目把生成器残差块从 9 改到 13收益不明显训练时间和显存占用都翻倍。4.2 训练过程监控看哪条曲线才算数项目会在 checkpoints 目录下持续生成latest_net_G_A.pth等权重文件同时输出 loss 曲线。打开 TensorBoard 看曲线时有一个反直觉的点训练早期前 20 epoch生成器 loss 和判别器 loss 都是快速下降后反弹这不是发散是 GAN 训练的正常震荡。需要关注的信号只有两个循环一致性 loss 有没有稳定下降生成器 loss 有没有长期单调不动。如果 50 epoch 后 cycle loss 还在高位浮动多半不是时间问题先停掉检查数据。每 5 epoch 把生成的样例图存下来看效果比盯 loss 曲线直观得多。样例图重点看三处脸型轮廓有没有变形、头发线条是否乱、肤色分布是否自然。这三处最先暴露问题。提示对抗 loss 的形状像锯齿盯着它会焦虑。判断训练健康与否要看 cycle loss 的下降趋势和样例图的视觉质量而不是某个具体数值。4.3 推理脚本加载权重做单张转换训练完成后把权重文件用起来python test.py --dataroot datasets/cartoon_dataset --name cartoon_style --model cycle_gan --epoch 100test.py 读取 checkpoint 目录下的权重文件把 testA 文件夹里的输入照片转换到卡通风格输出到 results 目录。如果只想对单张照片做转换可以写一个更简洁的推理脚本import torch from torchvision import transforms from PIL import Image def load_generator(model_cls, checkpoint_path, device): model model_cls() state torch.load(checkpoint_path, map_locationdevice) # 项目不同state_dict 的 key 名也会不同打开 checkpoint 确认 model.load_state_dict(state[netG_A]) model.to(device).eval() return model def cartoonize_image(model, image_path, save_path, devicecuda): transform transforms.Compose([ transforms.Resize((256, 256)), transforms.ToTensor(), transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)), ]) img Image.open(image_path).convert(RGB) tensor transform(img).unsqueeze(0).to(device) with torch.no_grad(): out model.netG_A(tensor) # 生成器 G 负责 A-B # 反归一化生成器输出范围是 [-1,1]映射回 [0,255] arr ((out.squeeze(0).cpu() 1) * 127.5).clamp(0, 255).numpy().transpose(1, 2, 0) Image.fromarray(arr.astype(uint8)).save(save_path)这段代码的关键点Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))和训练保持一致把像素从 [0,1] 映射到 [-1,1]。生成器输出范围是 [-1,1]保存图片前必须做反归一化否则画面会整体偏灰。torch.no_grad()关掉梯度计算推理阶段不构建计算图省显存也提速。一个反复遇到的场景直接输入 4K 照片到推理脚本强行 resize 到 256×256 后五官比例会失真。正确做法是先做人脸检测裁剪把裁剪后的人脸区域送入生成器再把卡通人脸贴回原图这个流程在实战中不可省略。4.4 超参数调整的落地顺序生成效果不满意时按这个顺序调整不要乱动风格不足、写实感过强降低 lambda_idt 到 0.1或提高对抗损失在总损失中的占比内容不保真、脸型歪掉提高循环损失权重 lambda_A/lambda_B 到 15细节纹理脏乱把生成器从 resnet_9blocks 换成 resnet_6blocks降低模型容量反而能增强风格化属于小马拉大车的有效案例每次只改一个参数保留前后样例图做对比。多参数一起调的结果是出了问题不知道谁引起的排查成本翻倍。所有的调整先在 20 epoch 内观察趋势不要一上来就改完跑全量。5. 避坑手册人像卡通化从数据到模型的 5 个高频问题5.1 训练不收敛先怀疑数据再怀疑网络现象训练超过 50 epoch生成结果仍是灰蒙蒙的噪声图或整片色块loss 曲线乱跳。原因大部分情况是数据问题。trainA 和 trainB 目录搞反了、卡通图里有大量带水印或文字干扰的图片、归一化范围不一致输出激活函数和预处理不匹配。解决训练前先单独跑一次 dataloader 可视化代码确认加载的图片内容和预处理后的像素值范围。把 trainA/trainB 各取 20 张拼成图人工检查排除带水印、文字的样本。再核对 transform 里的 normalize 参数和生成器最后一层是否同为 Tanh。确认这些之后仍不收敛再把学习率降到 0.0001 试 10 个 epoch因为有时是原始 lr 太大GAN 这种博弈结构对学习率比常规分类模型敏感得多。5.2 生成图像肤色偏青或偏黄现象输入照片肤色正常生成卡通图后肤色整体带青色或灰黄色面部大面积色块不均匀。原因PatchGAN 的局部判别特性导致对抗损失只关注局部纹理对全局色调缺乏约束。生成器为了局部骗过判别器把颜色改动过大叠加循环一致性损失权重不够于是出现整体色偏。解决最常见是提高 lambda_A/lambda_B让循环一致性更强地约束颜色信息。也可以更彻底一些在数据增强里加入轻微的颜色扰动比如把饱和度随机偏移 ±0.1让模型对颜色变化不过度敏感。调完重新训练 20 epoch观察肤色趋势是否改善。如果只做推理不想重训可以在后处理阶段做一次颜色直方图匹配把输入照片的肤色分布映射到输出图上但这个操作属于补救效果不如训练阶段解决。5.3 五官位置漂移眼睛和鼻子对不上现象卡通脸风格对了但眼睛比原图大了一圈且位置偏移鼻子和嘴的距离也不对。原因循环一致性损失保障的是整体结构重建不是五官级对齐。PatchGAN 对局部纹理敏感但单 patch 的判别范围在 70 像素感受野不足以约束眼睛、鼻子这些中等语义区域的位置。模型把风格抽象放在较高优先级内容细节就被牺牲了。解决需要改损失函数。常见做法是在总损失里加一层 VGG perceptual loss用预训练 VGG 的特征图计算输入图和生成图之间的高层特征相似度权重取 0.5~1.0。实现方式是输入图和生成图同时过 VGG 的 conv1_2、conv2_2、conv3_3 层对中间特征算 L1 距离。VGG 特征携带更多语义位置信息能有效压制五官漂移。要注意的是加了 perceptual loss 后训练时间会变长 20% 左右且 VGG 预训练权重来自 ImageNet特征分布和人脸不完全匹配但不影响它作为约束项的作用。5.4 显存不足batch_size1 还是爆显存现象启动训练后报 CUDA out of memorybatch_size 已经是 1crop_size 是 256仍然报错。原因CycleGAN 一个 iteration 要跑四个子网络G_A、G_B、D_A、D_B外加循环一致性需要的两次重建实际显存占用相当于 8~10 张 256×256 图片的前向与反向传播总和。8GB 显存跑 resnet_9blocks 本来就很勉强训练中还叠加了 Adam 优化器的参数状态进一步加重压力。解决按顺序做三件事把 crop_size 从 256 降到 224 甚至 192显存占用按平方下降把生成器残差块从 9 减到 6确认没有其他程序占用显存启动训练前用nvidia-smi看实际剩余显存。如果还不行用 CPU 跑小数据集做 debug5 epoch 验证代码路径没问题再切回 GPU。另一种思路是把输入分辨率降到 192训练一个快速版模型推理时把人脸 crop 到 192效果略损失但完全可用。5.5 训练显示效果好推理输出却差一截现象训练时每 5 epoch 存的样例图看起来不错用保存的权重在推理脚本里跑单张图效果明显变差颜色和细节都对不上。原因训练和推理的预处理不一致。最典型的是训练用了 resize(286) random crop(256) flip推理时直接 resize 到 256两个流程的输入分布不同生成器在自己没见过的输入分布上表现打折。另一个隐蔽原因是模型里混用了 BatchNorm训练时 BN 用 batch 内统计量eval 模式下切换到全局统计量两者行为不一致导致输出漂移。解决推理脚本严格复制训练预处理的前三步不要想当然简化为直接 resize。如果源码里用了 BatchNorm推理前必须调用model.eval()让 BN 切到全局统计量模式。很多魔改版本的 CycleGAN 没有完全用 InstanceNorm这个问题排查起来很隐蔽一上推理就翻车的案例大部分出在这里。6. 进阶玩法把训练好的生成器接到摄像头实时卡通化模型训好后最直接的进阶玩法是把推理接到摄像头流上对着镜头直接看到卡通版自己。这事真正的门槛不在模型而在推理管线的工程组织。摄像头推理管线分三步读帧、人脸检测、生成器推理然后把卡通人脸贴回原帧import cv2 import torch def camera_cartoonize(model, devicecuda): cap cv2.VideoCapture(0) face_cascade cv2.CascadeClassifier(cv2.data.haarcascades haarcascade_frontalface_default.xml) while True: ret, frame cap.read() if not ret: break gray cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale(gray, 1.1, 5) for (x, y, w, h) in faces: # 扩大裁剪范围保留头发和耳朵 margin int(0.25 * w) x0, y0 max(0, x - margin), max(0, y - margin) x1, y1 min(frame.shape[1], x w margin), min(frame.shape[0], y h margin) face_roi frame[y0:y1, x0:x1] roi_resized cv2.resize(face_roi, (256, 256)) # BGR 转 RGB、归一化到 [-1,1] roi_tensor torch.from_numpy(roi_resized.transpose(2, 0, 1)).float() / 127.5 - 1.0 roi_tensor roi_tensor.unsqueeze(0).to(device) with torch.no_grad(): cartoon_roi model.netG_A(roi_tensor) # 反归一化后转回 BGR贴回原帧 cartoon_roi ((cartoon_roi.squeeze(0).cpu() 1) * 127.5).clamp(0, 255).byte().numpy().transpose(1, 2, 0) frame[y0:y1, x0:x1] cv2.resize(cartoon_roi, (x1 - x0, y1 - y0)) cv2.imshow(cartoon camera, frame) if cv2.waitKey(1) 0xFF ord(q): break cap.release() cv2.destroyAllWindows()性能瓶颈有三个。第一人脸检测和生成器推理都耗时Haar cascade 在 CPU 上约 5ms生成器在 GPU 上 15~30ms合计 30ms 以内能跑到 25~30 FPS主观感觉是流畅的。第二摄像头输入如果是 1080p整帧送进生成器极其浪费只送人脸裁剪区域推理后再贴回原图帧率提升会非常明显。第三画面里出现多张人脸时逐张推理会导致帧率下跌应该把多个人脸 crop 后拼成一个 batch一次前向完成全部推理torch.cat 就能做。验证模型泛化能力有个好习惯把训练集、验证集和完全没见过的照片分开跑一遍推理对比输出质量。如果没见过的那组效果明显差说明过拟合在训练数据分布上这时的解决方向是扩数据不是继续调参。换新风格时在已有模型基础上做一小段微调加载权重后把学习率降到 0.00002用新风格数据只跑 20~30 epoch比从头训练省 4~5 倍时间。我把这套流程沉淀成了自己的习惯每训一个新风格先固定跑 50 epoch 看样例再决定是继续训练还是调整方向不盲目追求完整 200 epoch。人像卡通化的价值不在一个特定模型而在这条从数据到模型的闭环流程你换掉数据集就能得到新风格模型接上摄像头就是实时卡通滤镜把它封装成 HTTP 接口还能对外提供服务。希望帮到你。本文还有配套的精品资源点击获取
返回列表