【PyTorch实战演练】使用CelebA数据集训练DCGAN(深度卷积生成对抗网络)并生成人脸(附完整代码)

文章目录

      • 0. 前言
      • 1. CelebA数据集
        • 1.1 核心特性与规模
        • 1.2 应用与用途
        • 1.3 获取方式
        • 1.4 数据预处理
      • 2. DCGAN的模型构建
        • 2.1 生成器模型
        • 2.2 判别器模型
      • 3. DCGAN的模型训练(重点)
        • 3.1 训练参数
        • 3.2 模型参数初始化
        • 3.3 训练过程
      • 4. 结果展示
        • 4.1 loss值变化过程
        • 4.2 生成图像演化过程
      • 5. 完整代码

0. 前言

按照国际惯例,首先声明:本文只是我自己学习的理解,虽然参考了他人的宝贵见解及成果,但是内容可能存在不准确的地方。如果发现文中错误,希望批评指正,共同进步。

本文基于PyTorch构建DCGAN(深度卷积生成对抗网络,Deep Convolutional Generative Adversarial Network),并使用CelebA数据集进行训练,最终使用训练好的生成网络生成人脸数据(图像),完整代码附在文章末尾。

本文的重点是基于PyTorch代码讲解DCGAN模型的构建及训练过程,对于GAN的理论部分将直接跳过,如果对于GAN的基础理论以及转置卷积不太了解,强烈建议先读下以下2篇相关内容:

  • 【硬核科普】一文读懂生成对抗网络GAN
  • 【PyTorch单点知识】深入理解与应用转置卷积ConvTranspose2d模块

本文的写作思路及部分代码借鉴了《PyTorch教程:21个项目玩转PyTorch实战》(北京大学出版社),这真是一本非常不错的书!以及参考了DCGAN的论文 UNSUPERVISED REPRESENTATION LEARNINGWITH DEEP CONVOLUTIONAL GENERATIVE ADVERSARIAL NETWORKS(下面说明以“原文”代表)。

最终DCGAN的生成器通过逐渐学习人脸特征,生成人脸的过程如下:
在这里插入图片描述

我怀着比较激动的心情在写此篇博客,因为2年前(2022)我就花费了较多时间(debug真的令人血压升高)写了一篇DCGAN相关的博客——基于Pytorch用GAN生成手写数字实例(附代码),但是结果不是特别理想:
在这里插入图片描述
在总结了相关经验后终于搞出了正文中的这个比较像样的结果。因为我是使用笔记本电脑(Nvidia RTX 4060 laptop)训练的网络模型,输出图像只能勉强调整到64×64大小。
虽然清晰度较差,但也能明显看出是一位金发美女。

1. CelebA数据集

CelebA 数据集(全称 Large-scale CelebFaces Attributes Dataset)是由香港中文大学多媒体实验室开发并公开提供的一个大规模名人面部属性数据集:
在这里插入图片描述

1.1 核心特性与规模
  • 包含图片数量: CelebA 数据集包含了超过 202,599 张名人面部图像,对应约 10,177 个不同的名人个体。
  • 多样性和复杂性: 数据集中的人脸图像具有丰富的姿态变化和复杂的背景环境,这为模型训练提供了极具挑战性的多样性和现实性,有助于提升模型在真实场景下的泛化能力。
  • 属性标注(本文不会用到): 每张图片均附带了详尽的属性标注,共计 40 个不同的面部属性。
  • 额外信息(本文不会用到): 除了属性标注外,CelebA 还提供了每张图片的人脸检测边界框(bounding box, bbox)、5个关键点(landmarks)的坐标位置,这对于进行精确的人脸对齐和定位等任务非常有用。
1.2 应用与用途

CelebA 数据集由于其规模大、标注详尽且具有多样性,被广泛应用于计算机视觉领域的各种研究和应用开发,主要包括:

  • 人脸识别与属性识别: 用于训练模型识别和分类人脸图像中的特定属性,如性别、发型、表情等。
  • 人脸合成与编辑(本文应用!): 作为训练数据,用于生成新的逼真人脸图像或修改现有图像的特定属性,如使用 GAN(如 DCGAN)进行人脸生成或风格迁移。
  • 人脸检测与对齐: 利用提供的边界框和关键点信息训练或评估人脸检测和关键点定位算法。
  • 深度学习模型验证与基准测试: 作为标准数据集,用于评估新提出的深度学习模型在人脸属性识别、生成或检测任务上的性能。
1.3 获取方式

CelebA 数据集可以从其官方网站直接下载,或者通过官方提供的百度网盘链接获取。由于数据集较大,下载可能需要一定时间。官方网址为:Large-scale CelebFaces Attributes (CelebA) Dataset

1.4 数据预处理

使用ImageFolderDataLoader进行数据预处理:

dataset = torchvision.datasets.ImageFolder(root='Img', transform=transforms.Compose([
    transforms.Resize(64),
    transforms.CenterCrop(64),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
]))
# print(len(dataset))  输出202599,总计20万个图像
dataloader = torch.utils.data.DataLoader(dataset, batch_size=128, shuffle=True, num_workers=4, drop_last = True)

如果对预处理方式不太熟悉可以看下往期博客对于这些模块的介绍:ImageFolder、DataLoader、transforms。

2. DCGAN的模型构建

2.1 生成器模型

生成器是一个转置卷积神经网络,由CBR×4+CT串联而成,具体代码如下:

CBR = ConvTranspose+BatchNorm+ReLu
CT = ConvTranspose+Tanh

class Gnet(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.ConvTranspose2d(in_channels=noise_size, out_channels=64 * 8, kernel_size=4, stride=1, padding=0,
                               bias=False),
            nn.BatchNorm2d(64 * 8),
            nn.ReLU(),
            # 输出特征图尺寸[64*8, 4, 4]

            nn.ConvTranspose2d(in_channels=64 * 8, out_channels=64 * 4, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 4),
            nn.ReLU(),
            # 输出特征图尺寸[64*4, 8, 8]

            nn.ConvTranspose2d(in_channels=64 * 4, out_channels=64 * 2, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 2),
            nn.ReLU(),
            # 输出特征图尺寸[64*2, 16, 16]

            nn.ConvTranspose2d(in_channels=64 * 2, out_channels=64, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),
            # 输出特征图尺寸[64, 32, 32]

            nn.ConvTranspose2d(in_channels=64, out_channels=3, kernel_size=4, stride=2, padding=1, bias=False),
            nn.Tanh()
            # 输出特征图尺寸[3, 64, 64]
        )

    def forward(self, x):
        return self.net(x)

这里有个细节需要说明下:生成器网络模型最终的激活函数为Tanh(),我原本认为这样并不好,因为Tanh()的取值范围为[-1, 1],这样会导致输出有负数。而负数是无法作为像素值绘图的(因为plt模块接受的像素范围为整数[0, 255]或浮点数[0, 1]),负数的像素值在绘图时会被舍弃。
但是当我尝试把最终的激活函数改为Sigmoid(),训练模型过程中奇怪的现象出现了:生成器和判别器的loss很快收敛为0。这是错误的现象,因为在前文GAN的理论中介绍过:当达到纳什均衡时,判别器的理论输出应该是0.5。判别器的loss必定是一个波动值,不会收敛到0。
对于仅更改最后的激活函数就造成这个现象的机理我还是没太想明白……

2.2 判别器模型

判别器是一个卷积神经元网络,由CL+CBL×3+CS模块构成,具体代码如下:

CL = Conv+LeakyReLu
CBL = Conv+BatchNorm+LeakyReLu
CS = Conv+Sigmoid

class Dnet(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=64, kernel_size=4, stride=2, padding=1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(in_channels=64, out_channels=64 * 2, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 2),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(in_channels=64 * 2, out_channels=64 * 4, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 4),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(in_channels=64 * 4, out_channels=64 * 8, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 8),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(in_channels=64 * 8, out_channels=1, kernel_size=4, stride=1, padding=0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.net(x)

3. DCGAN的模型训练(重点)

3.1 训练参数
  • 损失函数:选用BCE(Binary Cross-Entropy,二元交叉熵);
loss = nn.BCELoss()
  • 优化器:生成器和判别器均选用Adams;
opt_G = torch.optim.Adam(G_NET.parameters(), lr=0.0002, betas=(0.5, 0.999))  # 在Adam优化器中,beta1 和 beta2 是两个关键的超参数,分别用于控制梯度一阶动量和二阶动量的衰减速度,进而影响优化器对模型参数的更新方式。
opt_D = torch.optim.Adam(D_NET.parameters(), lr=0.0002, betas=(0.5, 0.999))
  • 迭代次数:epoch设定为20,iteration=202599/128,取整后为1582。
3.2 模型参数初始化

这是非常关键的一步!DCGAN的论文建议需要把权重初始化为均值为0标准差为0.02

原文如下:在这里插入图片描述

代码实现方式为:

def weights_init(m):
    classname = m.__class__.__name__
    if classname.find('Conv') != -1:
        print("init:%s"%classname)
        nn.init.normal_(m.weight.data, 0.0, 0.02)
    elif classname.find('BatchNorm') != -1:
        print("init:%s"%classname)
        nn.init.normal_(m.weight.data, 1.0, 0.02)
        nn.init.constant_(m.bias.data, 0)

这里的.apply()函数是PyTorch中的nn.Module类提供的一个方法,用于递归地将指定函数应用到模块及其所有子模块的所有参数或缓冲区上,确保模型在训练开始前具有合理的初始权重分布,有助于模型的收敛和性能表现。

3.3 训练过程

首先需要设定真标签和假标签:

true_label = torch.ones(128).to(device)  #128为batch_size
false_label = torch.zeros(128).to(device)

DCGAN的训练过程即是生成器和判别器的交替训练过程:

  • 判别器训练:

    1. 从真实数据集中抽取一批真实样本。
    2. 生成器G生成一批假样本,使用同样的批次大小,输入为采样的随机噪声。
    3. 将真实样本和生成的假样本混合,组成一个批次的训练数据,并附上对应的标签(真实样本为“真”,生成样本为“假”)。损失值为: l o s s D = B C E ( D ( x d a t a ) , true-label ) + B C E ( D ( G ( z ) ) , false-label ) loss_D = BCE(D(x_{data}), \text{true-label})+ BCE(D(G(z)), \text{false-label}) lossD=BCE(D(xdata),true-label)+BCE(D(G(z)),false-label) x d a t a x_{data} xdata为真实样本, z z z为符合某种分布的随机向量。
    4. 利用这些数据和标签训练判别器D,目标是最大限度地提高D正确区分真实数据和假数据的能力。
  • 生成器训练:

    1. 冻结判别器D的参数,使其在本阶段不更新。
    2. 从噪声分布中再次采样一批新的随机向量作为G的输入。
    3. 生成器G根据这些输入生成假样本,并将它们传递给判别器D。
    4. 此时,G试图欺骗D,即让D认为这些生成的样本是真实的。为此,G的训练目标是最大化D对这些假样本判断为“真”的概率。通常,这等同于最小化D给出的“假”概率,即D的输出值。训练过程中可能使用负的D的输出(或相应的损失函数)作为G的损失来更新G的权重。损失值为: l o s s G = B C E ( D ( G ( z ) ) , true-label ) loss_G = BCE(D(G(z)), \text{true-label}) lossG=BCE(D(G(z)),true-label)

注意:判别器的训练是每隔k步才进行一次的,因为相比于生成器,判别器更容易训练。

4. 结果展示

4.1 loss值变化过程

在这里插入图片描述
其中红色为生成器的loss,绿色为判别器的loss,横坐标为迭代次数iteration。

4.2 生成图像演化过程

我们可以把0~19个epoch的训练权重结果都保存下来:
在这里插入图片描述

随机选取一个batch的4个图像观察生成器从epoch0到epoch20生成图像的演化过程:
在这里插入图片描述

5. 完整代码

  • 训练部分:

文件名:DCGAN_main_revise1,后面验证部分会用到。

import torch
import torchvision
import torch.nn as nn
from torchvision import transforms
import random
from tqdm import tqdm
import matplotlib.pyplot as plt

random.seed(666)   #设定随机种子
torch.manual_seed(666)

dataset = torchvision.datasets.ImageFolder(root='Img', transform=transforms.Compose([
    transforms.Resize(64),
    transforms.CenterCrop(64),
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))
]))
# print(len(dataset))  输出202599,总计20万个图像
dataloader = torch.utils.data.DataLoader(dataset, batch_size=128, shuffle=True, num_workers=4, drop_last = True)


noise_size = 100

class Gnet(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.ConvTranspose2d(in_channels=noise_size, out_channels=64 * 8, kernel_size=4, stride=1, padding=0,
                               bias=False),
            nn.BatchNorm2d(64 * 8),
            nn.ReLU(),

            nn.ConvTranspose2d(in_channels=64 * 8, out_channels=64 * 4, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 4),
            nn.ReLU(),

            nn.ConvTranspose2d(in_channels=64 * 4, out_channels=64 * 2, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 2),
            nn.ReLU(),

            nn.ConvTranspose2d(in_channels=64 * 2, out_channels=64, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64),
            nn.ReLU(),

            nn.ConvTranspose2d(in_channels=64, out_channels=3, kernel_size=4, stride=2, padding=1, bias=False),
            nn.Tanh()
        )

    def forward(self, x):
        return self.net(x)

class Dnet(nn.Module):
    def __init__(self):
        super().__init__()
        self.net = nn.Sequential(
            nn.Conv2d(in_channels=3, out_channels=64, kernel_size=4, stride=2, padding=1, bias=False),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(in_channels=64, out_channels=64 * 2, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 2),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(in_channels=64 * 2, out_channels=64 * 4, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 4),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(in_channels=64 * 4, out_channels=64 * 8, kernel_size=4, stride=2, padding=1, bias=False),
            nn.BatchNorm2d(64 * 8),
            nn.LeakyReLU(0.2, inplace=True),

            nn.Conv2d(in_channels=64 * 8, out_channels=1, kernel_size=4, stride=1, padding=0, bias=False),
            nn.Sigmoid()
        )

    def forward(self, x):
        return self.net(x)

loss = nn.BCELoss()
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
G_NET = Gnet().to(device)
D_NET = Dnet().to(device)

opt_G = torch.optim.Adam(G_NET.parameters(), lr=0.0002, betas=(0.5, 0.999))  # 在Adam优化器中,beta1 和 beta2 是两个关键的超参数,分别用于控制梯度一阶动量和二阶动量的衰减速度,进而影响优化器对模型参数的更新方式。
opt_D = torch.optim.Adam(D_NET.parameters(), lr=0.0002, betas=(0.5, 0.999))

true_label = torch.ones(128).to(device)
false_label = torch.zeros(128).to(device)

def weights_init(m):
    classname = m.__class__.__name__
    if classname.find('Conv') != -1:
        print("init:%s"%classname)
        nn.init.normal_(m.weight.data, 0.0, 0.02)
    elif classname.find('BatchNorm') != -1:
        print("init:%s"%classname)
        nn.init.normal_(m.weight.data, 1.0, 0.02)
        nn.init.constant_(m.bias.data, 0)


if __name__ == '__main__':
    G_NET.apply(weights_init)  # 进行权重初始化
    D_NET.apply(weights_init)

    epoch = 20

    for e in tqdm(range(epoch)):
        for i, (image, label) in enumerate(dataloader):  # image的维度为torch.Size([128, 3, 64, 64])
            G_NET.zero_grad()
            noise = torch.randn(128, noise_size, 1, 1, device=device)  # 产生正态分布的噪声
            fake = G_NET(noise) # 使用生成器产生假数据
            output = D_NET(fake).view(-1)
            loss_G = loss(output,true_label)
            loss_G.backward()
            loss_G_mean = output.mean().item()
            opt_G.step()

            if i%2 == 0:
                D_NET.zero_grad() #梯度清0
                real_image = image.to(device)
                output_real = D_NET(real_image).view(-1)
                loss_D_real = loss(output_real, true_label)    # 计算真实数据的损失
                output_fake = D_NET(fake.detach()).view(-1) # 使用判别器,此时将它们标记为假数据
                loss_D_fake = loss(output_fake, false_label)
                loss_D = loss_D_real + loss_D_fake
                loss_D_mean = loss_D.mean().item()
                loss_D.backward()
                opt_D.step()


            # 输出训练状态
            if i % 50 == 0:
                print("epoch:%i/[%i] iter:%i/[1550]"%(e,epoch,i),'|',"loss_D:%f"%loss_D_mean,'|',"loss_G:%f"%loss_G_mean)
                iter = e*1550+i
                plt.scatter(iter,loss_G_mean, c='r',s = 5)
                plt.scatter(iter,loss_D_mean, c = 'g',s = 5)

        torch.save(obj=G_NET.state_dict(),f='weight/G_Net_parameter_%i_epoch.pth'%e)
        torch.save(obj=D_NET.state_dict(), f='weight/D_Net_parameter_%i_epoch.pth' % e)

    plt.show()
  • 验证部分:
from DCGAN_main_revise1 import Gnet
import torch
import matplotlib.pyplot as plt
import random

random.seed(888)   #设定随机种子
torch.manual_seed(888)

Gnet_test = Gnet()
noise = torch.randn(128, 100, 1, 1)



for i in range(20):
    Gnet_test.load_state_dict(torch.load('weight/G_Net_parameter_%i_epoch.pth'%i))
    output = Gnet_test(noise)
    output = output.detach()

    concate1 = torch.concat((output[0].permute(1,2,0),output[2].permute(1,2,0)),dim=1)
    concate2 = torch.concat((output[7].permute(1, 2, 0), output[6].permute(1, 2, 0)), dim=1)
    concate3 = torch.concat((concate1, concate2), dim=0)
    plt.imshow(concate3)
    plt.savefig('output_images/composed_%i'%i)

以上,终于写完了~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/606407.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

从零开始!学习绘制3D表情的详细指南

在2020 年的苹果全球开发者大会(WWDC),苹果发布了新的 macOS 11(又名 Big Sur)。其中在UI视觉方面macOS Big Sur 系统最大的变化就是图标上, Big Sur更新了很多新设计风格的 3D应用图标,3D设计的确可以提升UI整体的视觉氛围,并且现…

Linux——socket编程之tcp通信

前言 前面我们学习socket的udp通信,了解到了socket的概念与udp的实现方法,今天我们来学习一下面向连接的tcp通信。 一、tcp套接字创建 UDP和TCP都是通过套接字(socket)来实现通信的,因此TCP也得使用socket()接口创建…

企业架构领域的天花板——TOGAF证书

TOGAF证书是一项重要的企业架构认证,为专业人士提供了广泛的知识和技能,帮助他们在企业架构领域取得突破。 一、TOGAF证书的重要 提升专业知识和技能:TOGAF证书提供了广泛的企业架构知识和最佳实践,使专业人士能够更好地理解和应…

ESD静电问题 | 电容谐振频率点及选择

静电枪放出的静电属于共模干扰 针对静电整改加电容的时候选择频率在17.5MHz~350MHz区间的电容 【转自微信公众号:韬略科技EMC】

漫画对话 ai翻译

復讐の教科書ーー81 81-1 いい加減吐け!!冴木!! 快说吧!!冴木!! お前が一連の事件の犯人なんだろ!? 你就是连续事件的犯人吧!? だか…

elementUI表格table文字不换行

在对应不需要换行的列加上属性::show-overflow-tooltip"true" 即可

leetcode 1235

leetcode 1235 代码 class Solution { public:int jobScheduling(vector<int>& startTime, vector<int>& endTime, vector<int>& profit) {int n startTime.size();vector<vector<int>> jobs(n);for(int i0; i<n; i){jobs[i] …

CMakeLists.txt语法规则:数学运算 math

一. 简介 前面几篇文章学习了 CMakeLists.txt语法中的一些常用变量&#xff0c;常用命令&#xff0c;双引号的作用。条件判断语句&#xff0c;循环语句等等。 本文简单学习一下 CMakeLists.txt语法中数学运算 match。 二. CMakeLists.txt语法规则&#xff1a;数学运算 math 在…

【动态规划】子数组、子串系列I|最大子数组和|环形子数组的最大和|乘积最大子数组|乘积为正数的最长子数组长度

一、最大子数组和 最大子数组和 算法原理&#xff1a; &#x1f4a1;细节&#xff1a; 1.返回值为dp表每个位置的最大值&#xff0c;而不是只看最后一个位置&#xff0c;因为可能最后一个位置都不选 2.可以直接在填dp表的时候就进行返回值的比较 3.如果初始化选择多开一个位…

Apifox 教程:如何实现跨语言调用(Java、PHP、Python、Go 等)

在一些特定场景下&#xff0c;比如需要在 Apifox 中对文件进行读写、加密、转换格式或者进行其它业务的操作时&#xff0c;仅使用 Apifox 内置的 JS 类库可能无法满足业务需求&#xff0c;这时&#xff0c;就可以借助「外部程序」作为解决方案。 外部程序是保存在「外部程序目…

Tower for Mac:Git管理的新境界

Tower for Mac&#xff0c;让您的Git管理进入新境界&#xff01;这款专为Mac用户打造的Git客户端&#xff0c;凭借其出色的性能和丰富的功能&#xff0c;成为众多开发者的首选工具。 Tower不仅支持常规的Git操作&#xff0c;如提交、推送和拉取&#xff0c;还提供了许多高级功能…

AR人脸美妆SDK解决方案,让妆容更加贴合个人风格

美妆行业正迎来前所未有的变革&#xff0c;为满足企业对高效、精准、创新的美妆技术需求&#xff0c;美摄科技倾力打造了一款企业级AR人脸美妆SDK解决方案&#xff0c;为企业打开美妆领域的新世界大门。 革命性的人脸美妆技术 美摄科技的AR人脸美妆SDK解决方案&#xff0c;不…

攻略:ChatGPT3.5~4.0(中文版)国内无限制免费版(附网址)【2024年5月最新更新】

一、什么是ChatGPT&#xff1f; 1、ChatGPT的全名是Chat Generative Pre-trained Transformer&#xff0c;其中"chat"表示聊天。"GPT"则是由三部分组成&#xff1a;生成式&#xff08;generative&#xff09;意味着具有创造力&#xff1b;预训练&#xff0…

计算机毕业设计 | vue+springboot图书借阅 书籍管理系统(附源码)

1. 开发目的 实现图书的智能化、信息化和简单化&#xff1b;实现图书信息的增加、删除、修改、查找、借阅、还书、收藏的显示操作及实时数据库的提交和更改和对普通用户的增、删、改、查&#xff1b;提高图书管理员工作信息报送及反馈的工作效率&#xff0c;减轻管理员的劳动负…

基于Spring Boot框架实现大学生选课管理系统

文章目录 源代码下载地址项目介绍项目功能界面预览 项目备注源代码下载地址 源代码下载地址 点击这里下载源码 项目介绍 项目功能 教务处管理 开课、开班审批&#xff0c;排课处理&#xff0c;班级操作&#xff0c;选课时间段管理** 使用了sql解决了开课开班的时间段的冲突…

Python的Web框架Flask+Vue生成漂亮的词云图

生成效果图 输入待生成词云图的文本&#xff0c;点击生成词云即可&#xff0c;在词云图生成之后&#xff0c;可以点击下载图片保存词云图。 运行步骤 分别用前端和后端编译器&#xff0c;打开backend和frontend文件夹。前端运行 npm install &#xff0c;安装相应的包。后端…

再也不用担心 AI 图片脸崩手崩了

如果你经常用 Stable Diffusion 画人物&#xff0c;相信你一定画出过脸崩的图片。这也是目前文生图 AI 工具普遍存在的问题。连 Midjourney V6 也不例外&#xff01;当它画一个人的时候表现还好&#xff0c;当画面里的人一多&#xff0c;局面就难以控制了。 看&#xff0c;这就…

远动通讯屏的作用

远动通讯屏的作用 远动通讯屏有时有称为调度数据网柜&#xff0c;远动通讯屏具体干啥作用&#xff1f;远动通讯屏是以计算机为基础的生产过程与调度自动化系统&#xff0c;可以对现场的运行设备进行监视和控制、以实现数据采集、设备测量、参数调节以及各类信号报警等各项功能。…

segment anythin 新标注工具 paddleocr训练自己的数据

快递单ocr检测 1.总结2.需求3.方案4.面单定位4.1反转图片扩充数据集4.2新的标注方式4.3json2yolo4.4yolov5推理 5.paddleocr5.1 数据标注5.2 文本检测训练5.3 文本识别训练检测结果 1.总结 按照惯例&#xff0c;先吐槽一下。反正也没人看我比比歪歪。做事全部藏着掖着&#xf…

致力于双碳减排服务——安科瑞推出碳电表

1. 概述 全球首个“碳关税”——欧盟碳边境调节机制于2023年10月启动试运行。自此&#xff0c;首批纳入欧盟碳边境调节机制的6个行业相关产品在出口至欧盟国家时需提供碳排放数据&#xff0c;这会倒逼国内制造业企业加快开展产品碳足迹核查的步伐。以钢铁行业为例&#xff0c;…
最新文章