
简介面向Python机器学习爱好者与图像生成方向开发者这份资源围绕“爬虫采集写真图片—人脸识别定位—DCGAN自动生成”完整流程展开将数据获取、预处理与生成式模型训练串联成一个可参考的实战案例。压缩包共506个文件以442张jpg写真图、29张png图片为主要数据素材附带8个py脚本用于爬虫、人脸检测与DCGAN训练同时包含模型checkpoint、index/meta等TensorFlow模型文件以及txt、xml说明文件整体包体约77.58MB结构上兼顾了数据集、训练代码与模型参数。目前资源已被1736人学习适合希望从零搭建人脸生成项目、理解数据准备到生成效果的初学者。通过这套资料可以直观看到写真套图爬虫的采集逻辑、基于人脸识别的裁剪与对齐方式以及DCGAN迭代生成人脸的过程并可借助已有模型文件快速验证生成效果或继续调参训练。 这个项目不是那种纯练习题而是一条真正能跑通的数据闭环从公开图库采集人像图片用OpenCV把人脸检测并裁剪出来再丢给DCGAN训练出一个能自动生成人脸图像的网络。输入一个随机噪声向量模型就能吐出一张看起来还挺像样的全新人脸。别人听了可能觉得不就是把爬虫和深度学习拼在一起嘛对结果确实不复杂但中间串起来的过程才是真正涨经验的地方。这类组合非常适合作为进阶练手项目因为一个项目同时覆盖了网络数据采集、图像预处理、深度学习训练三块硬技能。如果你已经掌握基础Python语法、想把爬虫往实际方向进阶或者刚开始接触生成对抗网络、想亲手跑一次GAN训练这篇应该能给你一份比较完整的参考。我按自己实际操作顺序把几个环节的选型逻辑、代码、参数和踩坑记录全部写出来。1. 项目整体设计与技术选型1.1 一条完整的数据到生成流水线整个项目本质上是一条“数据原料采集 → 数据清洗 → 模型训练 → 内容生成”的流水线。先用爬虫从合法的公开图库或开放数据集页面抓取原始图片然后对每张图做人脸检测把脸部区域单独裁剪出来并统一缩放尺寸最后把这些经过对齐的人脸图片整理成训练集输入DCGAN进行训练。有人可能会问为什么不直接拿原始图片训练原因很直接DCGAN生成的图片分辨率通常在64x64或128x128如果训练图里有人脸、有全身、有背景模型会学得乱七八糟生成结果大概率是一堆模糊的“鬼影”。所以人脸检测这一步不是锦上添花而是决定模型最终效果的关键前置环节。数据质量决定模型上限这个原则在这个项目里体现得特别明显。1.2 技术栈选型及其取舍逻辑选型上我没有追求最新最猛的工具而是选了每个环节最“皮实”的方案爬虫使用 requests BeautifulSoup。目标如果是静态网页这两个库就够用了不需要一上来就上 Scrapy 那样的大框架先把请求、解析、存储这套逻辑跑顺再说。人脸检测使用 OpenCV 内置的 Haar Cascade。虽然检测精度和 MTCNN、RetinaFace 这些深度学习方案有差距但零额外模型加载成本CPU 上也能跑得很快对入门阶段来说性价比最高。后面如果想提升准确率可以无缝换成 MTCNN。生成模型使用 DCGAN。相比原始 GAN它把全连接层换成了卷积结构训练稳定性好很多在64x64分辨率下已经能生成轮廓清晰的人脸是最适合理解生成对抗网络运作逻辑的框架。这套组合还有一个好处是环境依赖简单基本只涉及 requests、beautifulsoup4、opencv-python、torch、torchvision 这几个库新手照着装也不容易出问题。提示如果机器没有独立显卡CPU 训练 DCGAN 跑 64x64 分辨率、几百张图片单轮时间也在可接受范围内只是训练轮数会受些影响。个人学习场景下CPU跑通整个流程完全可行。2. 数据采集爬虫部分的完整实现2.1 基础爬虫框架requests 搭配 BeautifulSoup爬虫部分是整条流水线的“原材料采购员”职责是稳定的抓取图片并保存到本地。我用的基础框架很简单requests 负责发 HTTP 请求BeautifulSoup 负责解析 HTML。核心代码大致是下面这个样子。import requests from bs4 import BeautifulSoup headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Referer: https://example.com/ } resp requests.get(https://example.com/gallery, headersheaders, timeout10) resp.raise_for_status() resp.encoding resp.apparent_encoding soup BeautifulSoup(resp.text, html.parser) img_tags soup.select(img.photo) for img in img_tags: src img.get(src) or img.get(data-src) # 有些站点懒加载真实地址在data-src里 if not src: continue if src.startswith(//): src https: src elif src.startswith(/): src https://example.com src print(src)这里有几个小地方值得注意。resp.encoding resp.apparent_encoding 是为了避免中文页面乱码很实用。用 img.get(src) or img.get(data-src) 是因为很多图集站对图片做了懒加载真实地址藏在>import time import random USER_AGENTS [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) ... Chrome/120.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ... Firefox/121.0, Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) ... Mobile Safari, ] def safe_get(url, retries3): for i in range(retries): try: headers {User-Agent: random.choice(USER_AGENTS)} resp requests.get(url, headersheaders, timeout10) if resp.status_code 200: return resp except requests.RequestException as e: print(f[retry {i1}] {e}) time.sleep(random.uniform(1, 3)) return None随机延迟是最笨但最有效的办法把请求间隔控制在 1 到 3 秒之间随机浮动能躲过绝大多数简单的频率检测。重试机制也很重要网络请求偶发超时太正常了不加重试的话整个采集过程很容易中途挂掉。高并发场景下才需要上代理池个人学习项目用上面的逻辑足够了。2.3 数据合规这条红线先说清楚数据采集的合规性必须放在最前面。做这个项目的默认前提是只能采集明确公开且允许抓取的图片资源必须遵守目标网站的 robots.txt 和服务条款采集的数据仅限于个人技术学习不能直接对外传播更不能用于商业用途。人脸数据还有一层额外的肖像权问题。如果爬取的是真实人物照片即便只是用于本地训练模型生成的图片若与真实人物相似公开传播也可能带来法律风险。所以我的建议是把爬虫练习目标和训练数据来源分开。爬虫部分可以用任何公开、无版权的图片站来练手而训练DCGAN使用的数据建议直接选用学术领域公开的人脸数据集比如 CelebA、FFHQ 的开放版本。这样既能保证模型效果又不用背着肖像权包袱。注意合规数据源的优先级永远高于模型效果。没有合适数据就换数据源不要冒着合规风险去抓取受保护的内容。3. 脸部识别与数据集制作3.1 人脸检测方案选型与参数调整采集到原始图片后下一步就是用脸部识别技术把人脸区域从图片中切出来。OpenCV 的 Haar Cascade 检测器在 CPU 上很快而且不需要额外下载模型代码也非常简洁。import cv2 face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) img cv2.imread(photo.jpg) gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(48, 48) ) for (x, y, w, h) in faces: face img[y:yh, x:xw] cv2.imwrite(ffaces/{x}_{y}.jpg, face)detectMultiScale 里几个参数值得多解释两句。scaleFactor 是每次检测时图片缩小的比例值越接近 1 检测越精细但耗时越长1.1 是精度和速度的平衡点。minNeighbors 表示每个候选区域至少要被多少个相邻窗口命中才算真的检测到人脸数值越大误检越少但可能漏掉侧面或小尺寸人脸5 左右比较合适。minSize 是检测目标的最小尺寸如果图片本身包含大量小脸把它设低一些能提高召回率不过误检也会变多。3.2 批量对齐、裁剪与数据存储检测过程跑完后得到的是大小不一的人脸图不能直接拿去训练需要统一处理成固定尺寸。一般 DCGAN 生成 64x64 图像我就把所有检测到的人脸 resize 到 64x64。这里必须强调一个实用细节直接 resize 会把脸拉伸变形最好先把人脸裁成正方形区域再做缩放避免比例失真影响训练效果。def extract_face(img, x, y, w, h, target_size64): margin 0.2 # 四周扩展20%减少刘海和下巴被切掉的情况 nx max(0, int(x - w * margin)) ny max(0, int(y - h * margin)) nw min(img.shape[1] - nx, int(w * (1 2 * margin))) nh min(img.shape[0] - ny, int(h * (1 2 * margin))) cropped img[ny:nynh, nx:nxnw] square cv2.resize(cropped, (target_size, target_size)) return squaremargin 扩展是我在实际操作里加进去的因为人脸检测框经常只包含眉毛到下巴的区域刘海和部分额头会被切掉留出 20% 的余量后裁剪出来的人脸更完整训练效果明显更好。处理完所有图片后保存时我建议直接存成 numpy 的 .npy 文件或者打包成 .h5这样训练时一次性加载到内存比训练过程中反复读图片快得多也避免了频繁 I/O 带来的卡顿。import numpy as np faces_data [] for f in sorted(all_files): img cv2.imread(f) img cv2.cvtColor(img, cv2.COLOR_BGR2RGB) img img / 255.0 * 2 - 1 # 归一化到[-1, 1] faces_data.append(img) np.save(faces_64.npy, np.array(faces_data)) print(完成共处理, len(faces_data), 张人脸)如果你发现检测到的人脸数量明显少于图片数量大概率是明暗对比、侧脸角度或遮挡问题导致漏检。可以考虑换用 MTCNN 或 RetinaFace 替代 Haar Cascade。它们的检测率更高代价是需要多安装一个库和模型文件处理速度会慢一些但对后续模型训练来说是值得的。4. DCGAN 人脸生成原理与实战4.1 先理解生成对抗网络的博弈逻辑DCGAN 是生成对抗网络GAN的卷积版本。它由两个网络组成生成器 G 和判别器 D。简单理解G 像一个造假画的画师给它一个随机噪声向量它尽量画出以假乱真的人脸D 像一个鉴定师负责判断一张图是真实照片还是 G 造出来的“假画”。两人不断博弈G 越来越擅长画脸D 也越来越擅长挑破绽最后收敛时G 生成的人脸已经能骗过训练集以外的新样本。这个思路听着抽象但实际跑起来其实挺直观。训练早期G 生成的是五颜六色的噪点图D 一眼就能甄别经过几十个 epoch 的对抗之后生成图开始出现人脸轮廓、五官雏形那种“模型真的在学会画脸”的成就感特别强。4.2 生成器和判别器的网络结构PyTorch 里实现 DCGAN 非常简洁。生成器输入一个 100 维的随机高斯噪声经过全连接层扩大尺寸后 reshape 成 4x4 的特征图再用四层转置卷积逐步上采样最终输出 64x64x3 的图片。import torch.nn as nn class Generator(nn.Module): def __init__(self, latent_dim100): super().__init__() self.fc nn.Sequential( nn.Linear(latent_dim, 512 * 4 * 4), nn.BatchNorm1d(512 * 4 * 4), nn.ReLU(inplaceTrue), ) self.deconv nn.Sequential( nn.ConvTranspose2d(512, 256, 4, 2, 1), # 4x4 - 8x8 nn.BatchNorm2d(256), nn.ReLU(inplaceTrue), nn.ConvTranspose2d(256, 128, 4, 2, 1), # 8x8 - 16x16 nn.BatchNorm2d(128), nn.ReLU(inplaceTrue), nn.ConvTranspose2d(128, 64, 4, 2, 1), # 16x16 - 32x32 nn.BatchNorm2d(64), nn.ReLU(inplaceTrue), nn.ConvTranspose2d(64, 3, 4, 2, 1), # 32x32 - 64x64 nn.Tanh(), ) def forward(self, z): out self.fc(z).view(-1, 512, 4, 4) return self.deconv(out)转置卷积的尺寸变化可以用公式算输出尺寸 (输入尺寸 - 1) * stride - 2 * padding kernel_size。以第一层为例输入 4stride2padding1kernel4输出就是 (4-1)*2 - 2 4 8正好翻倍。这个计算很重要每一层输出尺寸必须和注释里标的一致接错的话会直接报 shape 不匹配的错。判别器则是完全对称的结构用普通卷积逐步下采样最后输出一个概率值。class Discriminator(nn.Module): def __init__(self): super().__init__() self.conv nn.Sequential( nn.Conv2d(3, 64, 4, 2, 1), # 64x64 - 32x32 nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(64, 128, 4, 2, 1), # 32x32 - 16x16 nn.BatchNorm2d(128), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(128, 256, 4, 2, 1), # 16x16 - 8x8 nn.BatchNorm2d(256), nn.LeakyReLU(0.2, inplaceTrue), nn.Conv2d(256, 1, 4, 1, 0), # 8x8 - 1 nn.Sigmoid(), ) def forward(self, x): return self.conv(x).view(-1, 1)判别器里用 LeakyReLU 而不是普通 ReLU是因为负半轴的小斜率能避免神经元死亡这个对训练稳定性影响很大。生成器里用 ReLU 加 BatchNorm 则能加快收敛。4.3 训练循环细节与关键参数训练部分是整个项目最磨人的环节。损失函数用 BCEWithLogitsLoss 或者 BCE Loss优化器统一使用 Adam学习率建议设为 0.0002beta1 设为 0.5 而不是默认的 0.9。这个改动很关键因为Adam 的默认参数在 GAN 训练中容易震荡beta1 取 0.5 会让动量衰减更快训练更稳定。训练循环的核心逻辑是每次迭代先训练判别器 D用真实图片和生成图片各算一次损失再训练生成器 G目标是让生成图片被 D 判断为真实的概率尽量高。实际代码中常见做法是每更新一次 G 就更新一次 D但如果你发现 D 的损失降得太快而 G 跟不上可以反过来每个 epoch 让 G 多更新几次。输入图片的数值范围也要注意。数据准备阶段我把图片归一化到 [-1, 1]因为生成器最后用 Tanh 输出输出范围正好是 [-1, 1]两边的区间一致模型更容易拟合。criterion nn.BCELoss() opt_G torch.optim.Adam(G.parameters(), lr0.0002, betas(0.5, 0.999)) opt_D torch.optim.Adam(D.parameters(), lr0.0002, betas(0.5, 0.999)) for epoch in range(epochs): for real_img in dataloader: batch_size real_img.size(0) real_label torch.ones(batch_size, 1) fake_label torch.zeros(batch_size, 1) # 训练判别器 opt_D.zero_grad() pred_real D(real_img) loss_D_real criterion(pred_real, real_label) z torch.randn(batch_size, latent_dim) fake_img G(z) pred_fake D(fake_img.detach()) loss_D_fake criterion(pred_fake, fake_label) loss_D loss_D_real loss_D_fake loss_D.backward() opt_D.step() # 训练生成器 opt_G.zero_grad() pred_fake D(fake_img) loss_G criterion(pred_fake, real_label) loss_G.backward() opt_G.step()我一般 batch size 取 64训练 100 个 epoch 左右每 5 个 epoch 存一次生成样例图片。如果你发现生成的人脸非常模糊可能是分辨率低可以把数据提高到 128x128但代价是显存占用和训练时间都会显著上升。个人项目在 64x64 这个档位先跑通就够了。5. 训练过程中的常见问题与排查5.1 模式崩溃模式崩溃是所有 GAN 新手最容易遇到的现象训练一段时间后生成的图总是那么两三张脸甚至完全一样多样性消失了。原因是生成器找到了判别器目前的“盲区”只要生成某几种特定样式就能稳定骗过 D它就不再努力拓宽绘画范围。针对模式崩溃我实测有效的办法有几个。第一降低学习率别让 G 更新步子迈得太大。第二给判别器的真实标签做标签平滑把 1 改成 0.9让 D 不要对自己的判断过分自信。第三优先从数据上解决检查训练集中人脸角度、肤色、光照分布是否太单一数据多样性不足也会加剧模式崩溃。5.2 损失函数忽高忽低、图像模糊不清训练不收敛也是家常便饭。最常见的情况是 D 的 loss 一路掉到接近 0同时 G 的 loss 一直涨这说明判别器太强了生成器完全骗不过它。解决办法包括降低 D 的学习率、减少 D 的卷积层通道数或者在每个训练迭代中让 G 更新 2 次、D 只更新 1 次。还有一种情况是训练了好几十个 epoch生成图始终是一团模糊的色块轮廓都没有。这通常不是模型结构问题而是人脸对齐没做好。回到第 3 节检查一下裁剪出来的人脸是否包含太多背景、是否缩放到同一尺寸、是否做了归一化。我在项目里至少有一半的“模型翻车”最后都定位到数据预处理环节。下面这个表可以当成一个速查表用现象可能原因优先排查项生成图全是同一种脸模式崩溃降低学习率、标签平滑、检查数据多样性D的loss趋近0、G的loss飙高判别器过强降低D学习率、减少D容量、增加G更新次数图像模糊无轮廓数据对齐差/分辨率低检查人脸裁剪质量、尝试更高分辨率训练到一半显存溢出batch_size过大减小batch_size或降低图片分辨率5.3 保存训练中间态与效果评估方法保存模型也是需要从一开始就做好的习惯。我建议每个 epoch 都保存一次生成器生成的网格图并至少每 10 个 epoch 保存一次完整的模型 checkpt。这里有个比较实用的评价技巧不要只看 loss而要盯住生成图的实际效果。GAN 的 loss 本身并不直接反映生成质量有时候 loss 还在正常波动但你保存的样例图已经在明显变好了。if epoch % 5 0: with torch.no_grad(): sample G(fixed_noise).detach().cpu() torchvision.utils.save_image(sample, fsamples/epoch_{epoch}.png, nrow8) torch.save({G: G.state_dict(), D: D.state_dict()}, fcheckpoints/ckpt_{epoch}.pt)另外建议 fixed_noise 固定下来每次都用同一个随机向量做生成这样不同 epoch 之间的对比才直观。你还能看到同一个人脸“逐渐长出来”的过程特别治愈。如果条件允许给模型加一个 BatchNorm eval 的注意事项生成器做推理时要切换到 eval 模式否则 BatchNorm 在训练和推理状态下统计量的差异会影响输出。6. 写在最后一些实操体会这个项目我反复跑了多轮感受最深的其实不是某个算法细节而是“数据中间层”在整个链路里的分量。爬虫写起来半天就搞定人脸检测代码也不长真正花时间的是想清楚怎么处理数据人脸框要不要扩展、图片怎么对齐、训练样例什么时候该重新清洗一遍。任何一个环节的数据脏了最后都会在生成结果上以一种诡异的方式“还给你”。最后分享一个实战小技巧在你保存的 epoch 样例图里翻车最严重的那些图其实最值钱不要删。它们能直观地告诉你某一轮数据或训练参数到底出了什么问题。我第一次训练时发现每隔 3 个 epoch 就会蹦出一张极端扭曲的脸排查了半天才发现是某些人脸图片本身包含了大面积遮挡检测框又切进去一块背景模型被这些“坏样本”反复刺激才产生了周期性崩坏。如果把这条链路继续往下延伸下一步可以从 DCGAN 升级到 StyleGAN生成分辨率能做到 1024x1024效果基本看不出破绽也可以把数据采集端从图片换成视频流做实时人脸采集和生成再或者给爬虫端接入增量更新逻辑让训练集可以持续扩充。总之这个项目的天花板不在某一个环节的深度而在你把多个环节串起来之后能产生多少种变化。本文还有配套的精品资源点击获取