基于DCGAN的动漫头像生成系统(毕业设计)

代码相关在后半部分

前端实现

页面使用纯html、css、js制作。包括主页、网址导航、头像生成、精选图片、联系作者等功能。

主页实现

主页图片可以自动循环播放。

网址导航实现

网址导航点击相应模块可以跳转到相应网站,所有网站均免费无广告。节省用户寻找合适的网站消耗的大量时间,总共包括三个类别用动漫、小说漫画、图片。

图像生成实现、包括生成路线一与二。一是调用训练好的DCGAN模型进行自动生成。线路二是获取本地头像资源进行显示。

生成线路一展示

生成线路二展示

精选图片实现。本功能是图片展示模仿pinterest网站设计

联系作者实现

代码实现

train()函数代码

# coding:utf8
import os
import ipdb
import torch as t
import torchvision as tv
import tqdm  # 进度条库
from model import NetG, NetD
from torchnet.meter import AverageValueMeter  # 用于跟踪平均值


class Config(object):
    """model: 定义判别器和生成器的初始化参数"""
    nz = 100  # 判别器的输入,即噪声的维度 (1*1*nz)
    ngf = 64  # 生成器feature map数
    ndf = 64  # 判别器feature map数

    """加载数据"""
    # 数据集存放路径(文件夹里只有图片)
    data_path = './face/'
    # 多进程加载数据所用的进程数('win'中,该值设置为0)
    num_workers = 0
    # 数据集中的图片尺寸,图片大小都是固定的,为3*96*96
    image_size = 96
    # 小批量值
    batch_size = 256

    """训练所用到的超参数"""
    # 是否使用GPU
    gpu = True
    # 训练迭代次数
    max_epoch = 500
    # 生成器和判别器的学习率
    lr1 = 2e-4  # 生成器的学习率
    lr2 = 2e-4  # 判别器的学习率
    # 优化器参数: Adam优化器的beta1参数
    beta1 = 0.5
    # 每1个batch训练一次判别器
    d_every = 1
    # 每5个batch训练一次生成器
    g_every = 5

    """可视化生成器训练过程"""
    # 是否使用visdom可视化
    vis = True
    # visdom的env名字
    env = 'GAN'
    # 每间隔plot_every个batch,visdom画图一次
    plot_every = 20
    # 存在该文件则进入debug模式
    debug_file = '/游戏/Anime-Avatar-Generating_With-DCGAN-main/tmp/debuggan'

    """保存训练过程中,判别器生成的图片 """
    # 保存图片的路径
    save_path = 'visdom'
    # 每save_every个epoch保存一次模型权重和生成的图片,
    # 权重文件默认保存在checkpoints, 生成图片默认保存在save_path
    save_every = 20

    """预训练模型"""
    netd_path = None
    netg_path = None
    # netd_path = 'checkpoints/netd_299.pth'
    # netg_path = 'checkpoints/netg_299.pth'

    """使用训练好的生成器,生成图片"""
    # 从512张生成的图片中保存最好的64张
    gen_search_num = 512;
    gen_num = 64
    # 噪声均值和方差
    gen_mean = 0  # 生成噪声的均值
    gen_std = 1  # 生成噪声的标准差
    # result
    gen_img = 'result.png'


opt = Config()


def train(**kwargs):
    for k_, v_ in kwargs.items():  # 覆盖配置,可以在调试中动态修改参数
        setattr(opt, k_, v_)

    device = t.device('cuda') if opt.gpu else t.device('cpu')
    print("device: ", device)
    if opt.vis:
        from visualize import Visualizer
        vis = Visualizer(opt.env)  # 初始化可视化工具

    # 设置数据转换和加载
    transforms = tv.transforms.Compose([
        tv.transforms.Resize(opt.image_size),
        tv.transforms.CenterCrop(opt.image_size),
        tv.transforms.ToTensor(),
        tv.transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))  # 接受两个mean(均值)和 std(标准差)
    ])

    dataset = tv.datasets.ImageFolder(opt.data_path, transform=transforms)
    print("dataset loader")
    dataloader = t.utils.data.DataLoader(dataset,
                                         batch_size=opt.batch_size,
                                         shuffle=True,
                                         num_workers=opt.num_workers,
                                         drop_last=True
                                         )

    # 网络
    print("net")
    netg, netd = NetG(opt), NetD(opt)
    map_location = lambda storage, loc: storage
    if opt.netd_path:
        netd.load_state_dict(t.load(opt.netd_path, map_location=map_location))  # pytorch提供标准提取模型函数
    if opt.netg_path:
        netg.load_state_dict(t.load(opt.netg_path, map_location=map_location))
    netd.to(device)
    netg.to(device)

    # 定义优化器和损失
    optimizer_g = t.optim.Adam(netg.parameters(), opt.lr1, betas=(opt.beta1, 0.999))
    optimizer_d = t.optim.Adam(netd.parameters(), opt.lr2, betas=(opt.beta1, 0.999))
    criterion = t.nn.BCELoss().to(device)  # 二元交叉熵损失函数,判断输入图像是真实的还是生成的
    # 真图片label为1,假图片label为0
    # noises为生成网络的输入
    true_labels = t.ones(opt.batch_size).to(device)  # 真实图片标签
    fake_labels = t.zeros(opt.batch_size).to(device)  # 伪造图片标签
    fix_noises = t.randn(opt.batch_size, opt.nz, 1, 1).to(device)  # 固定噪声,用于生成图片
    noises = t.randn(opt.batch_size, opt.nz, 1, 1).to(device)  # 噪声输入

    errord_meter = AverageValueMeter()  # 判别器误差
    errorg_meter = AverageValueMeter()  # 生成器误差

    epochs = range(opt.max_epoch)
    for epoch in iter(epochs):
        print("epoch:", epoch)
        fix_fake_imgs = None
        for ii, (img, _) in tqdm.tqdm(enumerate(dataloader)):
            real_img = img.to(device)

            if ii % opt.d_every == 0:
                # 训练判别器
                optimizer_d.zero_grad()  # 清除判别器梯度
                ## 尽可能的把真图片判别为正确
                output = netd(real_img)
                error_d_real = criterion(output, true_labels)
                error_d_real.backward()  # 反向传播真实图片的损失

                ## 尽可能把假图片判别为错误
                noises.data.copy_(t.randn(opt.batch_size, opt.nz, 1, 1))  # 生成新的随机噪声
                fake_img = netg(noises).detach()  # 根据噪声生成假图
                output = netd(fake_img)
                error_d_fake = criterion(output, fake_labels)  # 计算假图片产生的损失
                error_d_fake.backward()
                optimizer_d.step()  # 更新判别器的权重

                error_d = error_d_fake + error_d_real  # 总的判别器损失

                errord_meter.add(error_d.item())  # 更新判别器损失的计量器

            if ii % opt.g_every == 0:
                # 训练生成器
                optimizer_g.zero_grad()
                noises.data.copy_(t.randn(opt.batch_size, opt.nz, 1, 1))
                fake_img = netg(noises)
                output = netd(fake_img)
                error_g = criterion(output, true_labels)
                error_g.backward()
                optimizer_g.step()  # 更新生成器的权重
                errorg_meter.add(error_g.item())  # 更新生成器损失的计量器

            # if opt.vis and ii % opt.plot_every == opt.plot_every - 1:
        if opt.vis:
            ## 可视化
            # if os.path.exists(opt.debug_file):
            #     ipdb.set_trace()
            fix_fake_imgs = netg(fix_noises)
            vis.images(fix_fake_imgs.detach().cpu().numpy()[:64] * 0.5 + 0.5, win='fixfake')
            vis.images(real_img.data.cpu().numpy()[:64] * 0.5 + 0.5, win='real')
            vis.plot('errord', errord_meter.value()[0])  # 可视化判别器的平均损失
            vis.plot('errorg', errorg_meter.value()[0])  # 可视化生成器的平均损失

        if (epoch + 1) % opt.save_every == 0:
            # 保存模型、图片
            if fix_fake_imgs is not None:
                tv.utils.save_image(fix_fake_imgs.data[:64], '%s/%s.png' % (opt.save_path, epoch), normalize=True,
                                    range=(-1, 1))
            t.save(netd.state_dict(), 'checkpoints/netd_%s.pth' % epoch)
            t.save(netg.state_dict(), 'checkpoints/netg_%s.pth' % epoch)
            # 持续监控和记录判别器和生成器的损失值
            errord_meter.reset()
            errorg_meter.reset()


if __name__ == '__main__':
    # python -m visdom.server
    train()

生成器与判别器

# coding:utf8
from torch import nn


class NetG(nn.Module):
    """
    生成器定义
    """

    def __init__(self, opt):
        super(NetG, self).__init__()
        ngf = opt.ngf  # 生成器feature map数

        self.main = nn.Sequential(
            # 输入是一个nz维度的噪声,可以认为是一个1*1*nz的feature map
            nn.ConvTranspose2d(opt.nz, ngf * 8, 4, 1, 0, bias=False),# 逆卷积层,用于从随机噪声生成feature map
            nn.BatchNorm2d(ngf * 8), # 批量归一化,稳定训练
            nn.ReLU(True),# ReLU激活函数,增加非线性
            # 上一步的输出形状:(ngf*8) x 4 x 4

            nn.ConvTranspose2d(ngf * 8, ngf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 4),
            nn.ReLU(True),
            # 上一步的输出形状: (ngf*4) x 8 x 8

            nn.ConvTranspose2d(ngf * 4, ngf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf * 2),
            nn.ReLU(True),
            # 上一步的输出形状: (ngf*2) x 16 x 16

            nn.ConvTranspose2d(ngf * 2, ngf, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ngf),
            nn.ReLU(True),
            # 上一步的输出形状:(ngf) x 32 x 32

            nn.ConvTranspose2d(ngf, 3, 5, 3, 1, bias=False),
            nn.Tanh()  # 输出范围 -1~1 采用Tanh
            # 输出形状:3 x 96 x 96
        )

    def forward(self, input):
        return self.main(input)


class NetD(nn.Module):
    """
    判别器定义
    """

    def __init__(self, opt):
        super(NetD, self).__init__()
        ndf = opt.ndf
        self.main = nn.Sequential(
            # 输入 3 x 96 x 96
            nn.Conv2d(3, ndf, 5, 3, 1, bias=False),# 卷积层,从图像中提取特征
            nn.LeakyReLU(0.2, inplace=True), # LeakyReLU激活函数,允许小的负值,增加非线性
            # 输出 (ndf) x 32 x 32

            nn.Conv2d(ndf, ndf * 2, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 2),
            nn.LeakyReLU(0.2, inplace=True),
            # 输出 (ndf*2) x 16 x 16

            nn.Conv2d(ndf * 2, ndf * 4, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 4),
            nn.LeakyReLU(0.2, inplace=True),
            # 输出 (ndf*4) x 8 x 8

            nn.Conv2d(ndf * 4, ndf * 8, 4, 2, 1, bias=False),
            nn.BatchNorm2d(ndf * 8),
            nn.LeakyReLU(0.2, inplace=True),
            # 输出 (ndf*8) x 4 x 4

            nn.Conv2d(ndf * 8, 1, 4, 1, 0, bias=False),
            nn.Sigmoid()  # 输出一个概率
        )

    def forward(self, input):
        return self.main(input).view(-1)

generate()函数

# coding:utf8
import os
import torch as t
import torchvision as tv
from model import NetG, NetD



class Config(object):

    """model: 定义判别器和生成器的初始化参数"""
    nz = 100        # 判别器的输入,即噪声的维度 (1*1*nz)
    ngf = 64        # 生成器feature map数
    ndf = 64        # 判别器feature map数
    
    """加载数据"""
    # 数据集存放路径(文件夹里只有图片)
    data_path = './faces'
    # 多进程加载数据所用的进程数('win'中,该值设置为0)  
    num_workers = 0     
    # 数据集中的图片尺寸,图片大小都是固定的,为3*96*96
    image_size = 96     
    # 小批量值
    batch_size = 256
    
    """训练所用到的超参数"""
    # 是否使用GPU
    gpu = True      
    # 训练迭代次数
    max_epoch = 300
    # 生成器和判别器的学习率
    lr1 = 2e-4      # 生成器的学习率
    lr2 = 2e-4      # 判别器的学习率
    # 优化器参数: Adam优化器的beta1参数
    beta1 = 0.5
    # 每1个batch训练一次判别器 
    d_every = 1
    # 每5个batch训练一次生成器
    g_every = 5
    
    """可视化生成器训练过程"""
    # 是否使用visdom可视化
    vis = True
    # visdom的env名字
    env = 'GAN'
    # 每间隔plot_every个batch,visdom画图一次
    plot_every = 20
    # 存在该文件则进入debug模式
    debug_file = '/tmp/debuggan'
    
    """保存训练过程中,判别器生成的图片 """
    # 保存图片的路径
    save_path = 'visdom'
    # 每save_every个epoch保存一次模型权重和生成的图片,
    # 权重文件默认保存在checkpoints, 生成图片默认保存在save_path
    save_every = 50

    """预训练模型"""
    # netd_path = None 
    # netg_path = None
    # 379不错
    netd_path = 'checkpoints/netd_399.pth'
    netg_path = 'checkpoints/netg_399.pth'


    """使用训练好的生成器,生成图片"""
    # 从512张生成的图片中保存最好的64张
    gen_search_num = 512;gen_num = 64
    # 噪声均值和方差
    gen_mean = 0
    gen_std = 1  
    # result
    # gen_img = './static/img-generate/result.png'
    gen_img= './static/img-generate/'

opt = Config()

@t.no_grad()
def generate(**kwargs):
    """
    随机生成动漫头像,并根据netd的分数选择较好的
    """
    # 将参数kwargs中的参数赋值给opt对象,这里假设opt是一个命名空间对象
    for k_, v_ in kwargs.items():
        setattr(opt, k_, v_)
    
    device=t.device('cuda') if opt.gpu else t.device('cpu')
    print("device: ",device)
    # 创建生成器和判别器的实例,并设置为评估模式
    netg, netd = NetG(opt).eval(), NetD(opt).eval()
    # 生成随机噪声向量
    noises = t.randn(opt.gen_search_num, opt.nz, 1, 1).normal_(opt.gen_mean, opt.gen_std)
    noises = noises.to(device)
    # 加载判别器和生成器的参数,并将模型移动到对应的设备上
    map_location = lambda storage, loc: storage
    netd.load_state_dict(t.load(opt.netd_path, map_location=map_location))
    netg.load_state_dict(t.load(opt.netg_path, map_location=map_location))
    netd.to(device)
    netg.to(device)

    # 生成图片,并计算图片在判别器的分数
    fake_img = netg(noises)
    scores = netd(fake_img).detach()

    # 挑选最好的某几张
    print("fake img")
    indexs = scores.topk(opt.gen_num)[1]
    print("indexs: ",indexs)
    result = []
    for ii in indexs:
        result.append(fake_img.data[ii])
    # 保存图片
    print("result")
    # tv.utils.save_image(t.stack(result), opt.gen_img, normalize=True, value_range=(-1, 1))
    if not os.path.exists(opt.gen_img):
        os.makedirs(opt.gen_img)
    for i, img in enumerate(result, start=1):
        # 构造图片保存路径,如./static/img-generate/1.png, ./static/img-generate/2.png, ..., ./static/img-generate/64.png
        img_path = os.path.join(opt.gen_img, f'{i}.png')
        # 保存单张图片
        tv.utils.save_image(img, img_path, normalize=True, range=(-1, 1))
if __name__ == '__main__':
    generate()

可视化函数

# coding:utf8
from itertools import chain
import visdom
import torch
import time
import torchvision as tv
import numpy as np


class Visualizer():
    """
    封装了visdom的基本操作,但是你仍然可以通过`self.vis.function`
    调用原生的visdom接口
    """

    def __init__(self, env='default', **kwargs):
        import visdom
        self.vis = visdom.Visdom(env=env, use_incoming_socket=False,**kwargs)

        # 画的第几个数,相当于横座标
        # 保存(’loss',23) 即loss的第23个点
        self.index = {}
        self.log_text = ''

    def reinit(self, env='default', **kwargs):
        """
        修改visdom的配置
        """
        self.vis = visdom.Visdom(env=env,use_incoming_socket=False, **kwargs)
        return self

    def plot_many(self, d):
        """
        一次plot多个
        @params d: dict (name,value) i.e. ('loss',0.11)
        """
        for k, v in d.items():
            self.plot(k, v)

    def img_many(self, d):
        for k, v in d.items():
            self.img(k, v)

    def plot(self, name, y):
        """
        self.plot('loss',1.00)
        """
        x = self.index.get(name, 0)
        self.vis.line(Y=np.array([y]), X=np.array([x]),
                      win=(name),
                      opts=dict(title=name),
                      update=None if x == 0 else 'append'
                      )
        self.index[name] = x + 1

    def img(self, name, img_):
        """
        self.img('input_img',t.Tensor(64,64))
        """

        if len(img_.size()) < 3:
            img_ = img_.cpu().unsqueeze(0)
        self.vis.image(img_.cpu(),
                       win=(name),
                       opts=dict(title=name)
                       )

    def img_grid_many(self, d):
        for k, v in d.items():
            self.img_grid(k, v)

    def img_grid(self, name, input_3d):
        """
        一个batch的图片转成一个网格图,i.e. input(36,64,64)
        会变成 6*6 的网格图,每个格子大小64*64
        """
        self.img(name, tv.utils.make_grid(
            input_3d.cpu()[0].unsqueeze(1).clamp(max=1, min=0)))

    def log(self, info, win='log_text'):
        """
        self.log({'loss':1,'lr':0.0001})
        """

        self.log_text += ('[{time}] {info} <br>'.format(
            time=time.strftime('%m%d_%H%M%S'),
            info=info))
        self.vis.text(self.log_text, win=win)

    def __getattr__(self, name):
        return getattr(self.vis, name)

数据集准备

数据集来自:二次元头像生成(DCGAN/WGAN)数据集下载,人工删除部分错误图像._二次元头像数据集-CSDN博客文章浏览阅读826次。该数据集有5万多张96*96的二次元头像,从网上下载后人工删除部分只有身子\模糊\单色等主观判断不行的图像后的数据集.大小260MB左右.百度网盘地址:https://pan.baidu.com/s/1QpwhGHS5NWcw6mdXzg37oA提取码:l4mr..._二次元头像数据集https://blog.csdn.net/docoter_c/article/details/116644882

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值