简介一套基于深度学习算法的虚拟试衣镜Python实现面向计算机视觉、姿态估计与图像生成方向的开发者与学习者。程序整合人体姿态估计、人体分割、几何匹配和GAN四类模型仅依赖OpenCV库即可完成推理适合虚拟换装、电商试衣等场景的快速验证。压缩包包含24个文件其中20张为测试样图3个Python脚本分别承担主流程、人体解析和公共模块另附1份Markdown项目说明整体仅120KB便于对照图片与源码理解实现细节。这套实现中涉及自定义层CorrelationLayer需手动编码而onnxruntime等推理引擎并不直接支持自定义层反过来凸显了OpenCV方案的灵活性与可玩性。目前已有408人学习下载源码结构清晰从主程序到解析脚本均可直接阅读配合项目说明能帮助想深挖多模型组合与部署细节的读者少走弯路。1. 虚拟试衣镜不是滤镜这个 Python 项目到底在解决什么问题把一张平铺的服装图“穿”到一张人物照片上前后不过三秒背后却是深度学习算法里至少三个方向的协作人体解析、姿态估计与图像生成。虚拟试衣镜这类项目输入是“人物图 服装图”输出是换装后的自然人像它要解决的问题很具体——电商模特图拍摄成本高、直播试穿展示麻烦、线下门店试衣镜互动性弱。适合两类人一类是做视觉落地的工程师想把这个方向改造成自己的商品图批量生成工具另一类是拿它当课题的学生需要一套能跑通、能讲清原理的 python 源码工程。拿到这份源码和模型包先别急着跑它的推理管线、数据格式和参数约定有不少默认前提下面按“拆原理 → 跑 demo → 读代码 → 排坑”的顺序过一遍最后聊聊怎么把它变成能用的东西。2. 虚拟试衣镜的技术栈拆解分割、姿态估计与图像合成各管哪一段虚拟试衣镜的常规实现不是单一网络而是由三到四个模块串起来的推理管线。最常见做法是参考 VITON、VITON-HD、HR-VITON 这一系列工作的思路先对人物图做人体解析再做姿态估计接着把服装图变形对齐到人体上最后用生成器完成换装合成。理解这条管线才能看懂源码里每个文件存在的意义也才能在出问题时准确定位是哪一环掉了链子。2.1 先拆任务为什么换装不能直接“贴图”很多人第一次拿到虚拟试衣镜项目时第一反应是直接把服装图按位置贴到人身上不就行了实际做一次就会翻车。原因有三层。第一人物是有姿态的。站着、坐着、抬手、叉腰同一件衣服在不同姿态下形变完全不同矩形贴图只能做到平移缩放袖子、下摆、领口全部对不上。第二存在遮挡关系。手臂会压在衣服前面头发会垂到肩膀位置人脸、颈部、手部这些区域必须原样保留只有衣服覆盖的区域可以被替换这要求算法先知道“哪里是衣服、哪里不是”。第三服装图本身是平铺拍摄的光照、褶皱、边缘阴影和人物图完全不同直接贴上去会有明显的“贴纸感”这也是虚拟试衣镜必须用生成式模型而不是简单图像处理的原因。所以标准方案把任务拆成四条支线人体解析负责逐像素分类姿态估计负责关键点定位服装形变负责几何对齐图像合成负责光影与纹理融合。每条支线由一个独立模型承担彼此之间通过 mask 和特征图传递信息。后面所有参数调整都是在这四个环节上分别做文章。2.2 人体解析与姿态估计给算法一张“人体结构图”人体解析Human Parsing是对人物图的每个像素做分类。常见类别包括背景、头发、脸、左上衣、右上衣、左腿、右腿、左臂、右臂、裙子、腰带等不同方案类别数从 10 类到 20 类不等。这一阶段常用的深度学习方法有基于语义分割的 Schp、Graphonomy以及带注意力机制的解析网络。它们输出一张与输入同尺寸的分割图每个像素值对应该位置的人体部件类别。姿态估计Pose Estimation负责输出人体关键点坐标常用 OpenPose 或 HRNet关键点数量一般是 18 或 20 个覆盖头、颈、肩、肘、腕、髋、膝、踝。对虚拟试衣镜来说姿态关键点的核心作用有两个一是给服装形变提供对齐锚点比如肩点对应服装的肩缝位置二是判断人物姿态是否适合试穿侧身 90 度的图强行试穿通常效果很差。把两路结果合起来就能构造出“去衣人物表示”clothing-agnostic person representation把原图中的衣服区域挖掉保留脸、手、头发、背景和身体轮廓再把这份表示和形变后的服装图一起喂给生成器。这一步是整个项目最关键的地方因为生成器要学的不是“从零画人”而是“把新衣服补进一个已知的人”。2.3 服装形变TPS 薄板样条是怎么把衣服“穿”上去的服装图是平铺矩形人体是立体的因此必须有一个几何变换把服装 warp 到适合当前姿态的状态。早期方案用仿射变换效果很差——仿射只能做旋转、缩放、平移和切变对袖子这种需要局部弯曲的区域无能为力。现在绝大多数方案采用薄板样条Thin Plate SplineTPS做粗对齐再由可变形卷积或空间变换网络做精对齐。TPS 的基本思路是在服装图和人体姿态之间定义一组控制点比如肩膀两个点、腋下两个点、下摆几个点通过薄板样条插值得到整个平面的平滑变形场。控制点越多变形自由度越大但也越容易出现局部过度拉伸。实现层面TPS 通常由一个轻量的 warping 网络预测控制点位移再用 PyTorch 的 grid_sample 对服装图采样。grid_sample 有个容易忽略的参数 align_corners它决定采样网格是否对齐像素中心。在虚拟试衣镜源码里训练和推理阶段的 align_corners 必须完全一致否则会整体偏移几个像素领口袖口对不上。2.4 生成器与判别器VITON/HR-VITON 类方案的完整流程把前面几个模块串起来就是核心流程先对人物图做人体解析和姿态提取构造去衣人物表示再把服装图通过 TPS 变形到目标姿态最后把去衣人物表示和变形后的服装图在维度上拼接送入一个 U-Net 结构的深度卷积网络CNN生成器输出换装结果。生成器编码器提取人物和服装的语义特征解码器逐级恢复分辨率并生成 RGB 图像。训练阶段还有判别器形成 GAN 结构判别器判断生成的换装图是“真”是“假”逼生成器提升细节真实感。损失函数通常由三部分构成L1 重建损失保证像素接近感知损失用预训练 VGG 特征约束高层语义一致对抗损失提升纹理真实感。优化器一般配 Adamweight_decay 即 L2 正则项常见取 5e-4 到 1e-3这个参数在自建数据上微调时很关键。分辨率是复现时最需要关注的变量。VITON 原版工作在 256×192VITON-HD 提升到 1024×768HR-VITON 支持任意分辨率。分辨率直接决定显存占用和推理耗时后面跑 demo 时我会给出参数怎么设。3. 在本地跑通最小 demoPython 环境、权重放置与第一条推理命令拿到“源码 模型 项目说明”这套包之后最先做的不是读代码而是把环境对齐。虚拟试衣镜对 Python、PyTorch、CUDA 三者的版本组合非常敏感版本不匹配时往往不直接报错而是在推理时出现颜色偏差、张量维度对不上这类隐蔽问题。先把地基打牢后面才能少返工。3.1 Python 环境与 CUDA 版本对齐先解决“装不上”的问题常见做法是用 conda 单独建环境不要污染系统 Python。虚拟试衣镜这类项目大多基于 PyTorch选型时要注意源码是 PyTorch 1.x 还是 2.x对应的 torchvision 版本必须匹配。下面是一套兼容性很广的组合conda create -n viton python3.8 -y conda activate viton pip install torch1.13.1 torchvision0.14.1 --index-url https://download.pytorch.org/whl/cu117 pip install opencv-python4.8.1.78 numpy1.24.4 pillow tqdm scipy tensorboard几个参数要解释清楚。python3.8 是大多数虚拟试衣镜源码兼容性最好的版本3.10 以上容易出现 torchvision 的 C 扩展编译问题。torch 1.13.1 配 torchvision 0.14.1、CUDA 11.7是 30 系和 40 系显卡都能正常用的组合。opencv-python 固定到 4.8.1.78是因为新版 OpenCV 对部分后端做了调整可能影响图像读写行为。numpy 固定 1.24.4是为了避免 numpy 2.x 与旧版 PyTorch 在数据类型上出兼容问题。提示环境装好后先执行python -c import torch; print(torch.__version__)确认导入正常再继续下一步。如果 pip 下载超时把--index-url换成国内 PyPI 源即可版本号不要变。没有 NVIDIA 显卡的话把 cu117 换成 cpu 版本也能跑推理只是 512×768 的图可能要等十几秒。另外提醒一句装完 opencv 后import cv2报错多半是 numpy 版本冲突按上面固定版本重装一次就能解决。3.2 模型权重与数据准备项目包里 .pth 文件该怎么放这个项目包自带模型权重拿到手后先看 checkpoints 目录下的 .pth 文件是否齐全。按最常见的工程结构需要三类权重人体解析权重、姿态估计权重、换装生成器权重。有些实现把姿态和解析合在一个模型里但目录结构基本一致。建议项目根目录按下述结构组织项目根目录/ ├── checkpoints/ │ ├── parsing.pth │ ├── pose.pth │ └── generator.pth ├── data/ │ ├── person.jpg │ ├── cloth.jpg │ └── result.jpg ├── models/ ├── utils/ └── run.py测试图像有硬性要求。人物图最好是直立全身照分辨率不低于 512×768背景尽量简单衣服和背景颜色差异要大深色衣服配深色背景会让解析边界非常难分。服装图要求白底或透明底的平铺服装完整露出领口、袖口和下摆。测试图不符合这个分布后面换装效果会明显变差这不是模型的问题是输入分布和训练集差太多。我一般会用一段极简代码先验证权重能否加载避免进到主线才发现路径不对import torch ckpt torch.load(checkpoints/generator.pth, map_locationcpu) print(ckpt.keys()) if isinstance(ckpt, dict) and state_dict in ckpt: print(找到 state_dict共, len(ckpt[state_dict]), 个张量)这段代码只做一件事确认 .pth 里到底是裸的 state_dict 还是被包了一层。很多项目保存权重时习惯性套了{state_dict: ...}加载时没取对层就会报 missing keys 或 unexpected keys这是最典型的“权重加载失败”场景。3.3 跑通第一张换装图一条命令和它的参数清单大多数虚拟试衣镜项目包都会提供推理入口可能是 run.py 或 inference.py。最常见的调用方式如下python run.py \ --person data/person.jpg \ --cloth data/cloth.jpg \ --output data/result.jpg \ --load_size 512 \ --gpu_id 0这条命令把人物图和服装图送入由三个模型组成的推理管线最终把换装结果写到 result.jpg。核心参数见下表参数常见默认值作用注意事项--person无人物图路径全身照背景简单--cloth无服装图路径白底或透明底--outputresult.jpg输出路径所在目录必须存在--load_size512输入短边长度调到 768 效果更细显存和耗时约翻倍--gpu_id0GPU 编号无 GPU 时改用 --cpu跑之前先读项目包里的项目说明重点看两件事。第一权重加载路径是否写死很多源码里load_state_dict的路径是硬编码的和你的目录结构不对应时要在 run.py 里改第二输入归一化方式多数项目用 ImageNet 的 mean/std少数用 [-1,1] 归一化这个不一致会导致出图颜色整体偏差明显。如果项目说明里给出了环境版本要求以说明为准不要用我的推荐值硬套。第一张图哪怕效果一般只要跑通流程就说明环境没问题。之后的调参都建立在“管线通了”这个前提下否则出了问题你会分不清是环境还是算法。4. 核心代码走读从人体解析、TPS 形变到图像融合的三段实现跑通 demo 只是开始。要改出自己的效果必须把三段核心代码读明白人体解析、服装形变、图像合成。下面按虚拟试衣镜最常见的实现方式给出关键代码骨架你可以对照项目包里的源码逐段对应。4.1 人体解析与姿态关键点把“人”结构化人体解析模型的推理逻辑和普通语义分割几乎一样预处理、前向、argmax 取类别。需要特别小心的是 mask 的尺寸还原一定要用最近邻插值。import torch import cv2 import numpy as np # 以 Schp 风格解析模型为例输出类别数 NUM_CLASSES20 MEAN (0.485, 0.456, 0.406) STD (0.229, 0.224, 0.225) def infer_parsing(model, img_bgr): h, w img_bgr.shape[:2] # 按比例缩放到模型训练尺寸再用 padding 补边避免拉伸变形 scale 512.0 / max(h, w) resized cv2.resize(img_bgr, (int(w * scale), int(h * scale))) ph, pw resized.shape[:2] canvas np.zeros((512, 512, 3), dtypenp.float32) canvas[:ph, :pw] resized[:, :, ::-1] / 255.0 canvas (canvas - MEAN) / STD tensor torch.from_numpy(canvas.transpose(2, 0, 1)).unsqueeze(0).float() with torch.no_grad(): logits model(tensor) # [1, 20, 512, 512] mask torch.argmax(logits, dim1) # 每个像素取概率最大的类别 mask mask.squeeze(0).cpu().numpy().astype(np.uint8) # 还原到原图尺寸类别图不能用线性插值 mask cv2.resize(mask[:ph, :pw], (w, h), interpolationcv2.INTER_NEAREST) return mask逻辑说明预处理先按比例缩放再补边是为了避免直接拉伸导致人体比例失真argmax得到的是每个像素的类别 id 矩阵而不是概率图。最后一步 resize 必须用INTER_NEAREST如果用双线性3 和 5 会被插值成 4 这种既有错意义又模糊的类别边界全乱。参数上最需要注意的是 MEAN/STD 必须与训练时完全一致ImageNet 均值和自训练均值的差异会直接反映在边界质量上。姿态估计的代码结构类似输出从[1, 20, 512, 512]变成[1, N_KEYPOINTS, H, W]的热力图再对每张热力图求 argmax 得到坐标。关键点数量一般是 18 或 20源码里会定义一个 keypoint order 列表后续服装形变按索引取肩、肘、腕等点这个索引顺序千万不能改一旦和模型训练时不一致整条对齐线就偏了。4.2 TPS 服装形变把平铺服装图像到人体姿态上服装形变是整个项目里最“玄学”的部分效果好坏七分靠它。常见做法分两步先由轻量网络预测 TPS 控制点再对服装图做网格采样。控制点通常是 5×5 或 6×6 的均匀网格落在服装图的关键位置。import torch.nn.functional as F def tps_warp(cloth, theta, out_h256, out_w192): cloth: [B, 3, H, W] 平铺服装图 theta: [B, 2, num_ctrl] TPS 控制点位移num_ctrl 通常为 25 或 36 grid build_tps_grid(theta, out_h, out_w) # [B, out_h, out_w, 2] warped F.grid_sample( cloth, grid, modebilinear, padding_modeborder, align_cornersTrue, ) return warped参数说明theta 是网络预测出来的[B, 2, N]2 表示 x、y 两个方向的位移。build_tps_grid内部先构造基准网格再用薄板样条插值把控制点位移扩散到整个平面不同项目的实现细节差异很大但对外接口基本一致。grid_sample的 mode 用 bilinear 保留平滑纹理padding_mode用 border 而不是 zeros否则服装边缘会有一圈黑边。align_cornersTrue和 False 的差别只有半个像素量级但如果和训练时不一致袖口领口会整体错位非常隐蔽。这个阶段最容易踩的坑是控制点初始位置和服装图尺寸不匹配导致服装被裁掉一大块。建议每次调试都把 warp 结果可视化把变形后的服装叠加在人物图上确认领口对准脖子、袖口对准手臂再进入下一步合成。4.3 图像合成与后处理mask 融合和边缘羽化最后一步是把形变后的服装合入人物图。这里不能直接覆盖要先保护脸、手、头发区域再对服装边缘做羽化过渡。# 以 20 类解析为例5左上衣, 6右上衣这里按常见 id 示意 garment_mask np.isin(parsing, [5, 6]).astype(np.float32) # 对衣服区域做轻度膨胀防止原衣服边角从新衣服边缘漏出来 garment_mask cv2.dilate(garment_mask, np.ones((3, 3), np.uint8), iterations1) # 高斯羽化让边缘过渡自然ksize 根据输出分辨率调整 kernel cv2.getGaussianKernel(5, 1.5) feather_mask cv2.filter2D(garment_mask, -1, kernel, borderTypecv2.BORDER_CONSTANT) # 合成人物原图与变形服装按羽化权重加权 result person_bgr * (1 - feather_mask[..., None]) warped_bgr * feather_mask[..., None]逻辑说明garment_mask来自解析结果先膨胀是为了把换装区域向外扩一圈覆盖原衣服可能露出的边缘。feather_mask是羽化后的权重从边缘的 0 渐变到内部区域的 1合成时两个图按权重做逐像素加权。合成后可以对边缘再做一次轻微模糊但注意别波及衣服内部纹理。两个细节值得注意。第一脸、脖子、手这些区域的 mask 要单独从保护列表里取换装时完全保留原像素第二如果解析把头发误判成衣服羽化会把发丝也带进融合导致发梢糊掉这是下一章要展开的常见坑之一。5. 虚拟试衣镜避坑指南5 个高频问题的现象、原因与排查顺序环境通了、管线跑了之后真正花时间的是调效果。下面 5 个坑是这类项目里出现频率最高的每条都按“现象 → 原因 → 解决”三步写方便你直接对号入座。建议按 5.3 → 5.4 → 5.1 → 5.5 → 5.2 的顺序排查先确保输入结构正确再看几何对齐最后查颜色和资源占用。5.1 现象换上的衣服边缘发虚像贴上去的贴纸现象衣服与人脸、手臂交界处有一圈半透明过渡带纹理模糊整体合成感很强。原因主要有两个一是解析 mask 分辨率不够原图 1024 宽时对 512×512 的 mask 做双线性上采样边界类别被插值成灰色过渡带二是没做膨胀或羽化硬边界在合成结果上表现为锐利裁切线。解决mask 上采样固定用INTER_NEAREST对garment_mask先做 12 像素膨胀再羽化如果仍然发虚把--load_size从 512 提到 768让解析阶段保留更多边界细节。注意膨胀迭代次数不要超过 2否则衣服会向脸和手方向溢过去遮住锁骨和手腕。5.2 现象训练时显存爆掉batch size 调到 1 还是 OOM现象CUDA out of memory反复出现有时甚至把整个进程 kill 掉。原因生成器是 U-Net判别器还有辅助分支两个网络同时前向和反向512×768 分辨率下特征图占用很轻松突破 12GB而且 batch size 1 时 BatchNorm 统计量不稳定还会影响训练效果属于两难。解决先确认是不是真的在训练很多项目包的 run.py 默认开了训练模式推理时要把--mode test或对应开关打开训练用混合精度torch.cuda.amp显存能降到约一半还不行就把分辨率降到 256×192 这类原版小尺寸跑通再往上加。推理阶段不要加载判别器权重能省出一大块显存。5.3 现象人体解析把袖子当背景或把手当衣服现象换装结果里手臂区域出现了衣服纹理或者袖口位置露出了背景色。原因解析模型的训练集以正面直立模特为主测试图一旦出现手插兜、抬臂、侧身这类动作解析边界就会出错另一个常见原因是测试图分辨率太低手腕和袖口只有几十个像素类别置信度不够。解决测试图尽量选与训练集分布一致的正身直立姿势如果必须处理特殊姿势可以在送入解析前先做姿势筛查关键点角度超过阈值就提示不适合试穿有的项目包提供了姿态引导的解析后处理开关打开后能用关键点位置强制修正部分类别边界效果明显但会额外占几十毫秒需要自己权衡。5.4 现象服装形变后领口、袖口错位衣服歪向一边现象warp 后的服装领口和人物脖子对不上袖子偏离肩膀十几像素整体像“挂”上去的。原因多数是服装图预处理时被直接 resize 成正方形宽高比变了控制点语义位置全部偏移也可能 warp 网络训练分辨率和推理分辨率不一致控制点是归一化坐标分辨率变了对应像素位置就偏了。解决检查服装预处理是否保持宽高比用补边而不是拉伸把 warp 输出叠加在原图上可视化对领口、腋下、下摆三处逐一检查如果只是整体平移几个像素可以在后处理里给 warped 结果加一个全局平移修正这是最快见效的后悔药我在项目里经常用它救回 5 像素以内的错位。超过 10 像素就别硬修了回去查控制点网络输入。5.5 现象出图颜色整体偏灰或偏黄像坏了白平衡现象黑色衣服变成深灰白色衣服泛黄肤色也闷了一层。原因十有八九是归一化和反归一化不一致。训练时用 [-1,1] 归一化推出结果后忘了乘 0.5 加 0.5直接把输出当 [0,1] 乘 255 保存就会整体发灰反过来则会发黑。另一个常见原因是用 cv2.imwrite 保存时输入超出 [0,255] 范围不做裁剪。解决在源码里搜 mean、std、normalize 三个关键词确认训练和推理预处理完全一致输出保存前把数值统一np.clip(out, 0, 255)。注意 PyTorch 的save_image会自动裁剪但 cv2.imwrite 不会换用不同保存方式的人最容易栽在这。6. 把 demo 变成可用的试衣镜效果验证、提速与落地技巧6.1 效果验证指标之外一定要人工看图如果要把这个项目用在商品图或者门店场景验证不能只看一两个指标。我常用的做法是准备 100 组测试图覆盖正身、微侧身、浅色衣、深色衣、复杂背景各 20 组跑完后算 FID、SSIM、LPIPS 三个指标但更重要的是把这 100 张图按顺序翻一遍人工打分。FID 降了不代表纹理清楚这是拿显存换来的血泪经验。人工评估时重点看四类区域领口贴合度、袖口与手腕交界、下摆边缘、头发与肩膀过渡。6.2 三步提速轻量模型、半精度与批量推理先做模型轻量化。解析和姿态这两个前置模块是整条管线最耗时的部分常见做法是把 HRNet 主干换成 MobileNet 类轻量主干精度下降有限耗时能降一半以上。再做半精度推理model.half()加上输入张量转 fp16在 30 系和 40 系显卡上速度提升明显注意 BN 层在 fp16 下偶尔不稳定出图有噪点时对 BN 层保持 fp32。最后做批量推理门店场景多个用户同时试穿时把多组请求组合成一个 batch 送进网络比单张循环调用快得多这也是后端服务在 GPU 资源调度上最常见的优化手段。我最初做这套东西时只盯指标结果 FID 很好看放大一看全是糊的纹理后来才把人工看图变成固定流程。先通管线再校准输入分布最后才谈指标——这个顺序我踩过坑后才定下来希望帮到你。本文还有配套的精品资源点击获取