恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
Python爬虫到DCGAN人脸生成:数据清洗决定模型上限的完整链路
首页
资讯中心
/
Python爬虫到DCGAN人脸生成:数据清洗决定模型上限的完整链路
Python爬虫到DCGAN人脸生成:数据清洗决定模型上限的完整链路
发布时间:2026/10/10 14:45:56
简介一份面向Python开发与机器学习初学者的综合实战资源聚焦写真美女套图爬虫采集、人脸识别定位与DCGAN人脸自动生成三大任务适合希望将爬虫、OpenCV图像处理、深度学习生成模型串联练习的读者。压缩包共506个文件约77.58MB核心包括442张jpg与29张png图像样本、8个py训练与采集脚本以及DCGAN模型checkpoint、index、meta等训练中间文件另附create_list.bat等辅助工具便于复现数据准备、模型训练与生成结果。该资源已有1739人学习浏览内容兼顾数据、代码与模型产物可直接对照学习爬虫抓取建库、人脸特征提取、以及基于DCGAN生成人脸图像的完整流程。适合具备基础Python语法、想以真实项目串联图像采集与生成技术的开发者作为练习参考。1. 从套图爬虫到 DCGAN 人脸生成这条流水线比想象中更吃数据而不是算力一提到“Python-写真美女套图爬虫 脸部识别 DCGAN 脸部自动生成”多数人第一反应是 GPU 要烧钱、模型要调参但实际跑通这条链路后你会发现真正的瓶颈在数据爬虫能不能稳定拿到干净的大图人脸检测能不能从套图里筛出足够多高质量的正脸样本决定了 DCGAN 最终是生成出“一张能看的脸”还是一个糊成一团的噪点。这篇笔记面向已经会用 Python 写脚本、想正经把采集→检测→生成三段流程串起来的开发者给你一套不需要分布式爬虫、单机也能落地的方案以及我在替换检测器、调 DCGAN 参数时踩过的具体坑。2. 套图爬虫怎么写requests xpath 的采集链路与合规边界2.1 请求与解析requests lxml 的 xpath 取数链路套图站的页面结构通常很规整一个专辑页列出几十张图片的地址图片地址要么直接写在img标签的src里要么写在某个>import requests from lxml import etree import time # 目标站点仅作技术演示上线前务必自查 robots.txt 与版权授权 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36, Referer: https://example-site.com/ } def fetch_album(url): resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() # 有些站点是 gbk有些是 utf-8先探测再解码 if resp.apparent_encoding and resp.apparent_encoding.lower() ! utf-8: resp.encoding resp.apparent_encoding html etree.HTML(resp.text) # 图片地址通常在 li a img 的>import os from concurrent.futures import ThreadPoolExecutor import hashlib import requests import time def download_image(url, save_dir, referer): # 用 URL 的 MD5 做文件名天然去重 fname hashlib.md5(url.encode()).hexdigest() .jpg fpath os.path.join(save_dir, fname) if os.path.exists(fpath): return False # 已存在跳过 headers { User-Agent: Mozilla/5.0, Referer: referer } try: resp requests.get(url, headersheaders, timeout15) if resp.status_code 200 and len(resp.content) 10_000: with open(fpath, wb) as f: f.write(resp.content) return True except requests.RequestException: return False finally: time.sleep(0.5) # 限速避免触发风控 def download_album(img_urls, save_dir, referer): os.makedirs(save_dir, exist_okTrue) with ThreadPoolExecutor(max_workers4) as pool: pool.map(lambda url: download_image(url, save_dir, referer), img_urls)这段代码把文件名直接做成 URL 的 MD5好处是同一个 URL 重复抓取时永远覆盖同一个文件不需要维护重复队列。len(resp.content) 10_000这个阈值是过滤掉错误页和占位图的关键——很多反爬机制会返回一个 1KB 左右的空白图下载下来对人脸检测毫无价值。这里单线程限速 0.5 秒四线程等效每秒约 8 张一万张套图大约二十分钟在可控范围内。关于合规边界我要多说一句爬虫只能处理允许匿名访问的公开内容套图如果涉及人物肖像、版权或平台明确禁止采集的内容这套代码只能用于本地技术验证。真正的生产流程里数据授权和来源审查比爬虫效率重要得多别踩版权红线。3. 人脸检测与清洗OpenCV 和 MTCNN 怎么选样本决定了 DCGAN 的天花板3.1 人脸检测选型OpenCV Haar Cascade 与 MTCNN 的取舍爬下来的套图不能直接丢给 DCGAN。写真图集里大量是全身照、侧脸、带墨镜和帽子的构图直接整图缩放训练生成器会学出一堆乱七八糟的背景纹理。所以要先用脸部识别把正脸区域裁出来再做缩放对齐。我的做法是先跑 OpenCV 的 Haar Cascade 做粗筛再对粗筛结果用 MTCNN 精修。Haar Cascade 的优势是快单张 1920×1080 的图在 CPU 上大约 30 毫秒但它对侧脸、模糊脸、暗光脸的召回率很差MTCNN 靠三个级联网络P-Net、R-Net、O-Net逐步精细定位召回率明显高但单张耗时是 Haar 的 10 倍以上。套图数据集如果只有几千张直接用 MTCNN 没问题但如果到了十万张量级Haar 先筛掉明显不是人脸的图能省下大量 GPU 时间。import cv2 import numpy as np face_cascade cv2.CascadeClassifier( cv2.data.haarcascades haarcascade_frontalface_default.xml ) def haar_detect(img_path): img cv2.imread(img_path) if img is None: return [] gray cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) faces face_cascade.detectMultiScale( gray, scaleFactor1.1, minNeighbors5, minSize(64, 64) ) # faces 是 [ [x, y, w, h], ... ] return img, facesscaleFactor1.1表示每次缩放 10%越小检测越慢但越细致minNeighbors5是每个候选矩形至少要有 5 个相邻检测才保留设低了会出现大量误检把背景纹理当成人脸。minSize(64, 64)对 DCGAN 很关键——最终训练图是 64×64如果检测框本身就小于 64×64上采样后全是马赛克这种样本没有训练价值。Haar 检测完我会把每个检测框向外扩 20% 再裁剪因为 Haar 的框往往紧贴脸部把额头和下巴裁掉一部分。扩边之后再缩放到 64×64人脸在画面里占比更自然。3.2 样本清洗与对齐人脸色块筛选的四个过滤条件MTCNN 检测完会返回关键点左眼、右眼、鼻尖、左嘴角、右嘴角我拿这些关键点做人脸对齐。DCGAN 对对齐非常敏感同样一套参数对齐过的数据集能学会清晰五官没对齐的全是鬼影。from mtcnn import MTCNN import cv2 import os detector MTCNN() def align_face(img, keypoints, target_size64): left_eye keypoints[left_eye] right_eye keypoints[right_eye] # 计算两眼连线与水平线的夹角 dy right_eye[1] - left_eye[1] dx right_eye[0] - left_eye[0] angle np.degrees(np.arctan2(dy, dx)) center ((left_eye[0] right_eye[0]) / 2, (left_eye[1] right_eye[1]) / 2) rot_mat cv2.getRotationMatrix2D(center, angle, 1.0) rotated cv2.warpAffine(img, rot_mat, (img.shape[1], img.shape[0])) # 再按检测框裁切 faces detector.detect_faces(rotated) if not faces: return None x, y, w, h faces[0][box] # 扩边 20% margin int(0.2 * w) x max(0, x - margin) y max(0, y - margin) w min(rotated.shape[1] - x, w 2 * margin) h min(rotated.shape[0] - y, h 2 * margin) crop rotated[y:yh, x:xw] return cv2.resize(crop, (target_size, target_size))人脸的色块筛选是很多人忽略的一步。写真图集里有大量黑白艺术照、重度滤镜图、背景与肤色接近的图这种样本放进 DCGAN 会让生成器学会“顺着色调画脸”而不是“顺着结构画脸”。我一般对裁剪后的图做三个判断全部通过才保留一是灰度方差要大于某个阈值过滤纯色背景脸二是 R/G 通道比值要在合理肤色范围过滤掉明显偏色的图三是图像熵要大于 5.5过滤掉模糊图。MTCNN 检测结果里如果confidence低于 0.95我也直接丢弃宁可让数据集小一点也不要脏样本。这些过滤条件在几千张的小数据集上看起来影响不大但到了十万张规模它们决定了 DCGAN 的训练是否会在 20 个 epoch 后开始完全崩坏。4. DCGAN 训练与生成从旷世到玄学参数的落地4.1 DCGAN 结构拆解生成器与判别器的核心层配置DCGAN 相比原始 GAN 的核心改动是把判别器和生成器里的全连接层换成了卷积和转置卷积并且大量使用 BatchNorm 和 LeakyReLU。我训练 64×64 人脸的常用配置是生成器输入 100 维高斯噪声经过一层全连接扩展成 128×8×8 的张量再通过四层转置卷积逐步上采样到 64×64×3。import torch import torch.nn as nn class Generator(nn.Module): def __init__(self, latent_dim100): super().__init__() self.fc nn.Sequential( nn.Linear(latent_dim, 128 * 8 * 8), nn.BatchNorm1d(128 * 8 * 8), nn.ReLU(True) ) self.deconv nn.Sequential( nn.ConvTranspose2d(128, 64, 4, 2, 1, biasFalse), nn.BatchNorm2d(64), nn.ReLU(True), nn.ConvTranspose2d(64, 32, 4, 2, 1, biasFalse), nn.BatchNorm2d(32), nn.ReLU(True), nn.ConvTranspose2d(32, 3, 4, 2, 1, biasFalse), nn.Tanh() ) def forward(self, z): x self.fc(z) x x.view(-1, 128, 8, 8) return self.deconv(x)每一层ConvTranspose2d的kernel_size4, stride2, padding1是 DCGAN 原文的标准配置它能让特征图尺寸精确翻倍8×8 → 16×16 → 32×32 → 64×64。最后一层用Tanh而不是ReLU因为 Tanh 输出范围是 -1 到 1和图片归一化到 [-1, 1] 匹配如果用 ReLU生成图像素的分布会被截断在正半轴训练早期会出现大片灰黑色块。判别器反过来输入 64×64×3 的图通过四层卷积下采样到 8×8再全连接输出一个实数表示真假概率。注意判别器里不能用 BatchNorm 跑得太激进容易导致训练不稳定我一般只在卷积层后面跟 BatchNorm输入层不接。4.2 训练脚本与参数表让 GAN 在 64×64 图上稳定收敛DCGAN 的“玄学参数”其实大部分都被论文定死了。latent_dim 用 100学习率固定 0.0002Adam 的 beta1 必须是 0.5 而不是默认的 0.9。这个细节几乎每个复现 DCGAN 翻车的人都会中招beta1 太大会让梯度动量过大判别器 loss 直接冲到负数生成器跟着崩。def train_dcgan(loader, gen, disc, epochs50, devicecuda): lr 0.0002 beta1 0.5 g_opt torch.optim.Adam(gen.parameters(), lrlr, betas(beta1, 0.999)) d_opt torch.optim.Adam(disc.parameters(), lrlr, betas(beta1, 0.999)) criterion nn.BCEWithLogitsLoss() fixed_z torch.randn(64, 100, devicedevice) # 固定的探针噪声 for epoch in range(epochs): for i, (real_imgs, _) in enumerate(loader): real_imgs real_imgs.to(device) batch_size real_imgs.size(0) real_labels torch.ones(batch_size, 1, devicedevice) fake_labels torch.zeros(batch_size, 1, devicedevice) # ---- 训练判别器 ---- z torch.randn(batch_size, 100, devicedevice) fake_imgs gen(z) d_loss_real criterion(disc(real_imgs), real_labels) d_loss_fake criterion(disc(fake_imgs.detach()), fake_labels) d_loss (d_loss_real d_loss_fake) / 2 d_opt.zero_grad() d_loss.backward() d_opt.step() # ---- 训练生成器 ---- z torch.randn(batch_size, 100, devicedevice) fake_imgs gen(z) g_loss criterion(disc(fake_imgs), real_labels) g_opt.zero_grad() g_loss.backward() g_opt.step() # 每个 epoch 保存探针图 with torch.no_grad(): sample gen(fixed_z).cpu() torchvision.utils.save_image(sample, fepoch_{epoch}.png, normalizeTrue, range(-1, 1))训练循环里有两个细节比调参更影响稳定性。第一个是判别器每轮先看真图再看假图两个 loss 各取一半做反向传播这是原始 GAN 的做法而不是 DCGAN 原文的变体实践下来在 64×64 人脸上收敛更平滑。第二个是生成器的 loss 用的是判别器对假图的输出和真实标签做 BCE——也就是让假图骗过判别器而不是让假图和真图的特征距离变近有人改成 MSE loss 后发现图像更模糊所以别乱换。参数表通常照着这个基准调就够用。参数推荐值说明latent_dim100高斯噪声维度过小容易模式崩塌lr0.0002生成器与判别器共用别单独调高beta10.5Adam 动量因子默认 0.9 会导致训练发散batch_size64单卡 8G 显存可以稳定跑图像尺寸64×64数据量和算力的平衡点训练轮数5080超过 100 轮后判别器通常会过强我一般会在训练到 20、40、60 轮时各停一次用 matplotlib 把探针图拼成一张大图看趋势。如果 20 轮时还是一团糊正常但如果 40 轮时依然完全没有人脸轮廓就去查数据集里是不是混入了大量非人脸样本而不是继续加轮数。5. 避坑与排查采集、清洗、训练三段最常见的翻车点5.1 爬虫阶段的翻车与限速策略现象一同一个 IP 爬半小时后所有请求都返回 403连首页都打不开。原因是请求频率过高站点做了基于 IP 的访问计数限流。解决方法是把并发线程降到 2并在每次请求后随机 sleep 0.3 到 1 秒。有人第一反应是建代理池但对套图站来说单机限速比代理池可靠得多代理池里大量匿名代理的延迟和连接失败率反而会让你的下载脚本充满异常分支。现象二下载下来的图片全是 1KB 左右的空白图。原因是图片 URL 需要带 Referer 才能访问漏了 Referer 时服务器会返回一张占位图。解决方式是把下载请求的headers[Referer]设为专辑页 URL并且把len(resp.content) 10000作为过滤条件。这也是我前面代码里把这两个防御写死的原因。现象三xpath 匹配不到任何节点但浏览器里明明能看到图片。原因是站点是前端 JS 渲染的图片列表requests 拿到的 HTML 里只有空壳 div。这时的常见做法是用 Selenium 或 Playwright 做渲染后再解析但更省事的替代方案是直接在浏览器开发者工具里搜m3u8或.jpg往往能在接口请求里找到后端返回的 JSON 图片地址直接请求那个 JSON 接口通常比渲染整个页面快得多。5.2 人脸检测与数据集构建的坑现象一Haar Cascade 把背景里的镂空花纹、车窗反光当成人脸框出来了。原因是minNeighbors太低默认 5 还是压不住某些纹理。解决方法是把minNeighbors提到 810同时把minSize提到 80×80因为 DCGAN 训练图是 64×64检测框小于 80×80 的样本即使裁剪出来上采样后也缺少有效五官信息。现象二MTCNN 检测结果很好但裁剪后的人脸很多是斜的、眼睛不在同一水平线。原因是直接按检测框裁剪忽略了关键点旋转。解决方法是先按左右眼连线角度做warpAffine旋转再裁剪。这一步不做DCGAN 训练出来的五官永远是歪的且生成器会把这种歪斜当成一种默认风格无法通过调参修正。现象三训练集里出现了大量重复图片。原因是爬虫下载阶段用 URL 的 MD5 去重但同一个图在站点上有多个 URL比如缩略图和原图地址不同下载到了同一张图的不同尺寸版本。解决方式是对裁剪后的人脸图再次做 perceptual hash感知哈希去重两张图的 pHash 汉明距离小于 5 就删掉一张。这个坑几乎不影响小数据集但数据集到了五万张以上重复样本会让 DCGAN 训练曲线看起来正常、生成结果却总是那几张面孔。5.3 DCGAN 训练阶段的稳定与崩溃问题现象一训练到第 10 个 epoch 左右判别器 loss 突然变成 0.000生成器 loss 飙升。原因是学习率偏大或者 beta1 用了默认 0.9判别器快速收敛到完美区分真假梯度直接消失。解决方法是把 lr 调回 0.0002、beta1 调成 0.5并且给判别器训练加 label smoothing真标签用 0.9 而不是 1.0。记着DCGAN 的判别器不是越强越好强到无敌的时候生成器就废了。现象二生成图像从第 5 个 epoch 开始就长一个样同一批探针噪声生成出来的脸基本没差异换了噪声还是类似五官。这是典型的 mode collapse原因通常不是超参而是数据集本身太单一——套图站同一个摄影师的图集光线、角度、构图高度相似DCGAN 学到的分布被压缩到少数几个模式。解决方法是把训练集扩展到至少两个不同摄影师的图集并加入随机水平翻转、±5 度旋转的数据增强。翻转对 DCGAN 尤其重要因为写真图集里人脸本就对称。现象三训练到 80 轮生成图依然带着明显网格纹理或者颜色条纹。原因是生成器最后一层ConvTranspose2d的步长设置为 2转置卷积会留下棋盘伪影。解决方法是把最后一层换成分数步长卷积nn.Upsample 普通卷积或者接受 64×64 的训练尺寸里伪影可以被 3×3 卷积掩盖。这是 DCGAN 的先天毛病不算 bug但如果你的目标是生成 128×128 甚至更高分辨率必须换用带残差结构的生成器。现象四训练显存占用一直在涨第 30 轮时直接 Out of Memory。原因是 DataLoader 的num_workers设得太大或者开了pin_memoryTrue却没把 batch_size 调小。解决方式是 batch_size 从 64 降到 32num_workers4并确认每张图加载后已经 resize 到 64×64 而不是原图尺寸。6. 用探针图与 FID 验证生成效果别让 DCGAN 成为黑匣子训练好 DCGAN 之后最忌讳的做法是看一眼最后几个 epoch 的探针图觉得“还行”就收工。探针图只能看出生成的人脸有没有五官、整体是否自然看不出多样性好不好、有没有真实感。我的习惯是同时做三层验证固定探针图对比、生成图多样性统计、FID 指标估算。第一层是固定探针法。训练脚本里固定了一个 64×100 的噪声矩阵每个 epoch 存一次生成图。把这些图按行拼成动画或长图你能清楚看到训练从噪声到人脸的演化过程。如果演化在第 20 轮后突然停滞但判别器 loss 还在下降说明生成器已经学不动了该提前停止而不是硬跑满 100 轮。第二层是多样性和真实感量化。生成 5000 张 64×64 人脸图先计算它们的像素标准差——如果标准差小于全训练集的 1/3说明生成器在“模仿均值脸”多样性不够。再把这 5000 张图重新喂给 MTCNN统计人脸检出率如果检出率低于 60%说明生成的人脸在检测器眼中不像人脸模型大概率还没收敛。这套方法不需要额外依赖比 FID 更容易先跑起来。第三层才是 FID。FID 需要用到 InceptionV3 在 ImageNet 上的预训练权重虽然它并不是专门为人脸设计的但作为分布距离的度量依然有效。我通常会算 FID 时把测试集限定在 5000 张随机采样的人脸上避免全量计算拖慢速度。import torchvision from torchvision import transforms from scipy.linalg import sqrtm import numpy as np def calculate_fid(real_loader, gen, device, num_samples5000): # 这里用简化的特征提取把图片先缩放到 299x299再走 InceptionV3 # 生产环境直接用 pytorch_fid 库的 FID 类更省事 inception torchvision.models.inception_v3( weightstorchvision.models.Inception_V3_Weights.IMAGENET1K_V1, transform_inputFalse ).to(device) inception.eval() # 省略中间特征层输出实际使用可换成 penultimate_layer ...FID 数值在 64×64 人脸上训练集内部一般能到 30 以下跨数据集对比没有绝对标准只看同一个数据集下不同训练方案的相对大小。我在实际项目中吃过这个亏第一次训完 FID 算出来 55觉得高得离谱后来发现是 FID 计算时把真实图和生成图都做了中心裁剪把本来就不大的 64×64 图裁成了 48×48特征提取质量全丢了。从那以后我养成了一个习惯——先随机抽 8 张真实图和 8 张生成图把它们拼成同一张对比图看一遍再算指标指标是给人做决策的但眼睛永远比指标先发现问题。这套流水线真正落地时爬虫、检测、生成三段每个环节都有各自的成熟工具但把它们串起来后大部分翻车点其实是数据质量而不是模型参数。希望你跑通后记住DCGAN 不是黑匣子它只是对输入数据的分布极其敏感当你对生成结果不满意先回看数据集再动学习率。希望帮到你。本文还有配套的精品资源点击获取