PyTorch深度学习实战(30)——Deepfakes

news2024/10/7 18:23:04

PyTorch深度学习实战(30)——Deepfakes

    • 0. 前言
    • 1. Deepfakes 原理
    • 2. 数据集分析
    • 3. 使用 PyTorch 实现 Deepfakes
      • 3.1 random_warp.py
      • 3.2 Deepfakes.py
    • 小结
    • 系列链接

0. 前言

Deepfakes 是一种利用深度学习技术生成伪造视频和图像的技术。它通过将一个人的脸部特征或动作应用于另一个人的图像或视频中,以产生逼真的虚假内容。Deepfakes 技术在短时间内取得了显著的进展,并引起了广泛的关注和担忧,为了能够更好的采取相应的检测和防御措施,我们首先需要了解其基本原理。

1. Deepfakes 原理

我们已经学习了两种不同的图像到图像的转换任务,包括使用 UNet 执行语义分割和使用自编码器执行图像重建。 Deepfakes 是与以上任务具有相似理论基础的图像到图像转换的技术。
Deepfakes 是一种利用深度学习技术合成的虚假图像和视频的技术,Deepfakes 名称由 “deep learning” 和 “fake” 组成,通常用于制作虚假的音频和视频。例如,可以利用 Deepfakes 技术将一个人的脸替换到另一个人的身体上。
假设我们想要创建一个应用程序,获取给定的面部图像并以期望方式改变面部表情,在这种情况下,Deepfakes 是一种可行的技术,本节中,我们将介绍 Deepfakes 的基本原理,并使用小样本对抗学习技术生成具有感兴趣面部表情的逼真图像。
Deepfakes 任务中,我们使用数百张人物 A 的照片和数百张人物 B 的照片。目任务的目标是通过将 A 人物的面部表情应用到 B 人物脸上,或者将 B 人物的面部表情应用到 A 人物脸上,来重建人物 BA 的脸部特征。Deepfakes 图像生成过程的工作原理如下:

Deepfakes 原理
在上图中,我们通过编码器 (Encoder) 传递人物 A 和人物 B 的图像,得到与人物 A (Latent Face A) 和人物 B (Latent Face B) 对应的潜向量,将潜向量传递给它们相应的解码器 (Decoder ADecoder B) 以获取相应的原始图像 (Reconstructed Face AReconstructed Face B),编码器和解码器与自编码器非常相似。但在此任务中,我们只需要一个编码器,但需要两个解码器(每个解码器对应一个不同的人物)。
我们期望从编码器获得的潜向量表示图像中包含的面部表情信息,而解码器获取与人物相对应的图像。一旦编码器和两个解码器都经过训练后,在执行deepfakes图像生成时,我们按以下方式切换架构中的连接:

Deepfakes 原理
A 人物的潜在向量通过 B 人物的解码器时,重建的 B 人物脸部将具有 A 人物的特征,反之亦然,当 B 人物的潜在向量通过 A 人物的解码器时,重建的 A 人物脸部将具有 B 人物的表情。
另一个有助于生成逼真图像的技巧是对人脸图像进行扭曲,以这样的方式将其馈送到网络中:将扭曲后的人脸作为输入,预期的输出是原始图像。

2. 数据集分析

Pins Face Recognition 数据集包含通过 Pinterest 拍摄的图像,该数据集包括 100 多个不同人物的 10,000 多张图像,每个人物平均包含 100 张图片。
Pins Face Recognition 数据集的目标是推动人脸识别技术的发展,并为研究人员提供一个用于测试和比较不同算法性能的标准基准。该数据集中的图像包括不同的人物和背景,并且在姿势、光照和表情等方面具有一定的变化。可以通过 Kaggle 网站下载 Pins Face Recognition 数据集,并选择两个不同人物的照片作为训练数据集。

3. 使用 PyTorch 实现 Deepfakes

了解了 Deepfakes 的工作原理后,我们使用 PyTorch 实现使用自编码器生成将一个人物的表情转换到另一个人物面部的虚假图像。

3.1 random_warp.py

首先编写用于实现图像扭曲的实用脚本 random_warp.py

import numpy as np
import cv2

random_transform_args = {
    'rotation_range': 10,
    'zoom_range': 0.05,
    'shift_range': 0.05,
    'random_flip': 0.4,
}

def get_training_data(images, batch_size):
    indices = np.random.randint(len(images), size=batch_size)
    for i, index in enumerate(indices):
        image = images[index]
        image = random_transform(image, **random_transform_args)
        warped_img, target_img = random_warp(image)

        if i == 0:
            warped_images = np.empty((batch_size,) + warped_img.shape, warped_img.dtype)
            target_images = np.empty((batch_size,) + target_img.shape, warped_img.dtype)

        warped_images[i] = warped_img
        target_images[i] = target_img

    return warped_images, target_images

def random_transform(image, rotation_range, zoom_range, shift_range, random_flip):
    h, w = image.shape[0:2]
    rotation = np.random.uniform(-rotation_range, rotation_range)
    scale = np.random.uniform(1 - zoom_range, 1 + zoom_range)
    tx = np.random.uniform(-shift_range, shift_range) * w
    ty = np.random.uniform(-shift_range, shift_range) * h
    mat = cv2.getRotationMatrix2D((w // 2, h // 2), rotation, scale)
    mat[:, 2] += (tx, ty)
    result = cv2.warpAffine(image, mat, (w, h), borderMode=cv2.BORDER_REPLICATE)
    if np.random.random() < random_flip:
        result = result[:, ::-1]
    return result

# get pair of random warped images from aligened face image
def random_warp(image):
    assert image.shape == (256, 256, 3)
    range_ = np.linspace(128 - 80, 128 + 80, 5)
    mapx = np.broadcast_to(range_, (5, 5))
    mapy = mapx.T

    mapx = mapx + np.random.normal(size=(5, 5), scale=5)
    mapy = mapy + np.random.normal(size=(5, 5), scale=5)

    interp_mapx = cv2.resize(mapx, (80, 80))[8:72, 8:72].astype('float32')
    interp_mapy = cv2.resize(mapy, (80, 80))[8:72, 8:72].astype('float32')

    warped_image = cv2.remap(image, interp_mapx, interp_mapy, cv2.INTER_LINEAR)

    src_points = np.stack([mapx.ravel(), mapy.ravel()], axis=-1)
    dst_points = np.mgrid[0:65:16, 0:65:16].T.reshape(-1, 2)
    mat = umeyama(src_points, dst_points, True)[0:2]

    target_image = cv2.warpAffine(image, mat, (64, 64))

    return warped_image, target_image

def umeyama(src, dst, estimate_scale):
    num = src.shape[0]
    dim = src.shape[1]

    # Compute mean of src and dst.
    src_mean = src.mean(axis=0)
    dst_mean = dst.mean(axis=0)

    # Subtract mean from src and dst.
    src_demean = src - src_mean
    dst_demean = dst - dst_mean

    A = np.dot(dst_demean.T, src_demean) / num

    d = np.ones((dim,), dtype=np.double)
    if np.linalg.det(A) < 0:
        d[dim - 1] = -1

    T = np.eye(dim + 1, dtype=np.double)

    U, S, V = np.linalg.svd(A)

    rank = np.linalg.matrix_rank(A)
    if rank == 0:
        return np.nan * T
    elif rank == dim - 1:
        if np.linalg.det(U) * np.linalg.det(V) > 0:
            T[:dim, :dim] = np.dot(U, V)
        else:
            s = d[dim - 1]
            d[dim - 1] = -1
            T[:dim, :dim] = np.dot(U, np.dot(np.diag(d), V))
            d[dim - 1] = s
    else:
        T[:dim, :dim] = np.dot(U, np.dot(np.diag(d), V.T))

    if estimate_scale:
        scale = 1.0 / src_demean.var(axis=0).sum() * np.dot(S, d)
    else:
        scale = 1.0

    T[:dim, dim] = dst_mean - scale * np.dot(T[:dim, :dim], src_mean.T)
    T[:dim, :dim] *= scale

    return T

3.2 Deepfakes.py

(1) 导入所需库:

from random_warp import get_training_data
import cv2
from glob import glob
import numpy as np
from torch.utils.data import DataLoader, Dataset
import random
import torch
import os
from torch import nn
from torch import optim
from matplotlib import pyplot as plt
device = 'cuda' if torch.cuda.is_available() else 'cpu'

(2) 从图像中提取人脸裁剪图像。

定义人脸级联分类器,人脸级联分类器是一种人脸检测方法,可以在图像中的面部周围绘制一个边界框:

face_cascade = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')

OpenCV 提供了 4 个级联分类器用于人脸检测,可以从 OpenCV 官方下载这些级联分类器文件:

  • haarcascade_frontalface_alt.xml (FA1)
  • haarcascade_frontalface_alt2.xml (FA2)
  • haarcascade_frontalface_alt_tree.xml (FAT)
  • haarcascade_frontalface_default.xml (FD)

可以使用不同的数据集评估这些级联分类器的性能,总的来说这些分类器具有相似的准确率。

定义用于从图像中裁剪人脸的函数 crop_face()

def crop_face(img):
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    faces = face_cascade.detectMultiScale(gray, 1.3, 5)
    if(len(faces)>0):
        for (x,y,w,h) in faces:
            img2 = img[y:(y+h),x:(x+w),:]
        img2 = cv2.resize(img2,(256,256))
        return img2, True
    else:
        return img, False

在以上函数中,我们通过人脸级联分类器传递灰度图像 gray 并裁剪包含人脸的矩形图像。接下来,返回一个重新调整大小的图像 img2。此外,为了考虑在图像中没有检测到人脸的情况,我们使用一个标志位记录图像中是否检测到人脸。

裁剪 personApersonB 的图像并将它们放在不同的文件夹中:

if not os.path.exists('cropped_faces_personA'):
    os.mkdir('cropped_faces_personA')
if not os.path.exists('cropped_faces_personB'):
    os.mkdir('cropped_faces_personB')
def crop_images(folder):
    images = glob(folder+'/*.jpg')
    for i in range(len(images)):
        img = cv2.imread(images[i], 1)
        img2, face_detected = crop_face(img)
        if(face_detected==False):
            continue
        else:
            cv2.imwrite('cropped_faces_'+folder+'/'+str(i)+'.jpg',img2)#cv2.cvtColor(img2, cv2.COLOR_RGB2BGR))
crop_images('personA')
crop_images('personB')

(3) 创建数据加载器并检查数据:

class ImageDataset(Dataset):
    def __init__(self, items_A, items_B):
        self.items_A = np.concatenate([cv2.cvtColor(cv2.imread(f,1), cv2.COLOR_BGR2RGB)[None] for f in items_A])/255.
        self.items_B = np.concatenate([cv2.cvtColor(cv2.imread(f,1), cv2.COLOR_BGR2RGB)[None] for f in items_B])/255.
        self.items_A += self.items_B.mean(axis=(0, 1, 2)) - self.items_A.mean(axis=(0, 1, 2))

    def __len__(self):
        return min(len(self.items_A), len(self.items_B))
    def __getitem__(self, ix):
        a, b = random.choice(self.items_A), random.choice(self.items_B)
        return a, b

    def collate_fn(self, batch):
        imsA, imsB = list(zip(*batch))
        imsA, targetA = get_training_data(imsA, len(imsA))
        imsB, targetB = get_training_data(imsB, len(imsB))
        imsA, imsB, targetA, targetB = [torch.Tensor(i).permute(0,3,1,2).to(device) for i in [imsA, imsB, targetA, targetB]]
        return imsA, imsB, targetA, targetB

a = ImageDataset(glob('cropped_faces_personA/*.jpg'), glob('cropped_faces_personB/*.jpg'))
x = DataLoader(a, batch_size=32, collate_fn=a.collate_fn)

数据加载器返回四个张量,imsAimsBtargetAtargetB。第一个张量 imsA 是第三个张量 targetA 的扭曲(变形)版本,第二个 imsB 是第四个张量 targetB 的扭曲(变形)版本。
我们使用两个图像文件夹,每个人脸图像使用一个文件夹 (a =ImageDataset(glob('cropped_faces_personA/*.jpg'), glob('cropped_faces_personB/*.jpg))),并且在 __iteritems__ 方法中,分别在两个文件夹中随机获取一张人脸图像。
collate_fn 方法中的关键函数是 get_training_data,用于通过扭曲(变形)人脸图像执行图像增强,将扭曲后的人脸图像作为自编码器的输入,并尝试预测正常人脸图像。使用扭曲图像,不仅增加了训练数据的数量,而且还可以作为神经网络的正则化器,强制神经网络根据扭曲的人脸图像学习关键的面部特征。

查看示例图像样本:

idx = 1
imgs = next(iter(x))[0]
for i in imgs:
    plt.subplot(2,4,idx)
    plt.imshow(i.permute(1,2,0).detach().cpu())
    idx += 1
    if idx > 8:
        break
plt.show()

扭曲人脸
可以看到,输入图像是扭曲人脸图像,而输出图像为正常人脸图像,且输入与输出图像一一对应。

(4) 构建模型并查看模型架构。

定义卷积 (_ConvLayer) 和反卷积 (_UpScale) 函数以及 Reshape 类:

def _ConvLayer(input_features, output_features):
    return nn.Sequential(
        nn.Conv2d(input_features, output_features, kernel_size=5, stride=2, padding=2),
        nn.LeakyReLU(0.1, inplace=True)
    )

def _UpScale(input_features, output_features):
    return nn.Sequential(
        nn.ConvTranspose2d(input_features, output_features, kernel_size=2, stride=2, padding=0),
        nn.LeakyReLU(0.1, inplace=True)
    )

class Reshape(nn.Module):
    def forward(self, input):
        output = input.view(-1, 1024, 4, 4) # channel * 4 * 4
        return output

定义 Autoencoder 模型类,它包含一个编码器和两个解码器 (decoder_Adecoder_B):

class Autoencoder(nn.Module):
    def __init__(self):
        super(Autoencoder, self).__init__()

        self.encoder = nn.Sequential(
            _ConvLayer(3, 128),
            _ConvLayer(128, 256),
            _ConvLayer(256, 512),
            _ConvLayer(512, 1024),
            nn.Flatten(),
            nn.Linear(1024 * 4 * 4, 1024),
            nn.Linear(1024, 1024 * 4 * 4),
            Reshape(),
            _UpScale(1024, 512),
        )

        self.decoder_A = nn.Sequential(
            _UpScale(512, 256),
            _UpScale(256, 128),
            _UpScale(128, 64),
            nn.Conv2d(64, 3, kernel_size=3, padding=1),
            nn.Sigmoid(),
        )

        self.decoder_B = nn.Sequential(
            _UpScale(512, 256),
            _UpScale(256, 128),
            _UpScale(128, 64),
            nn.Conv2d(64, 3, kernel_size=3, padding=1),
            nn.Sigmoid(),
        )

    def forward(self, x, select='A'):
        if select == 'A':
            out = self.encoder(x)
            out = self.decoder_A(out)
        else:
            out = self.encoder(x)
            out = self.decoder_B(out)
        return out

(5) 定义 train_batch 函数:

def train_batch(model, data, criterion, optimizers):
    optA, optB = optimizers
    optA.zero_grad()
    optB.zero_grad()
    imgA, imgB, targetA, targetB = data
    _imgA, _imgB = model(imgA, 'A'), model(imgB, 'B')

    lossA = criterion(_imgA, targetA)
    lossB = criterion(_imgB, targetB)
    
    lossA.backward()
    lossB.backward()

    optA.step()
    optB.step()

    return lossA.item(), lossB.item()

model(imgA, 'B') 使用来自 A 类的输入图像返回 B 类的图像,根据 imgA 预测 _imgA (其中 imgAtargetA 的扭曲版本)并使用 nn.L1Loss_imgAtargetA 进行比较,在训练期间预测新图像并定性地查看模型训练情况。

(6) 创建训练模型所需的所有组件:

model = Autoencoder().to(device)

dataset = ImageDataset(glob('cropped_faces_personA/*.jpg'), glob('cropped_faces_personB/*.jpg'))
dataloader = DataLoader(dataset, 32, collate_fn=dataset.collate_fn)

optimizers = optim.Adam([{'params': model.encoder.parameters()},
                          {'params': model.decoder_A.parameters()}],
                        lr=5e-5, betas=(0.5, 0.999)), \
             optim.Adam([{'params': model.encoder.parameters()},
                          {'params': model.decoder_B.parameters()}], 
                        lr=5e-5, betas=(0.5, 0.999))
             
criterion = nn.L1Loss()

(7) 训练模型:

n_epochs = 50000
train_loss_A = []
train_loss_B = []
if not os.path.exists('checkpoint'):
    os.mkdir('checkpoint')
for ex in range(n_epochs):
    N = len(dataloader)
    trn_loss_A = []
    trn_loss_B = []
    for bx,data in enumerate(dataloader):
        lossA, lossB = train_batch(model, data, criterion, optimizers)
        pos = (ex + (bx+1)/N)
        trn_loss_A.append(lossA)
        trn_loss_B.append(lossB)
    train_loss_A.append(np.average(trn_loss_A))
    train_loss_B.append(np.average(trn_loss_B))

    if (ex+1)%1000 == 0:
        state = {
                'state': model.state_dict(),
                'epoch': ex
            }
        torch.save(state, './checkpoint/autoencoder.pth')

    if (ex+1)%1000 == 0:
        bs = 5
        a,b,A,B = data
        _a = model(a[:bs], 'A')
        _b = model(a[:bs], 'B')
        x = torch.cat([A[:bs],_a,_b])
        idx = 1
        for im in x:
            plt.subplot(3, 5, idx)
            plt.imshow(im.permute(1,2,0).detach().cpu())
            idx += 1
        plt.show()
        _a = model(b[:bs], 'A')
        _b = model(b[:bs], 'B')
        x = torch.cat([B[:bs],_a,_b])
        idx = 1
        for im in x:
            plt.subplot(3, 5, idx)
            plt.imshow(im.permute(1,2,0).detach().cpu())
            idx += 1
        plt.show()

生成的重建图像如下所示:

重建图像
损失值的变化如下:

epochs = np.arange(n_epochs)+1
plt.plot(epochs, train_loss_A, 'bo', label='Training loss A')
plt.plot(epochs, train_loss_B, 'r-', label='Training loss B')
plt.title('Training and Test loss over increasing epochs')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.grid('off')
plt.show()

损失变化

如上图所示,可以通过调整自编码器使用两个解码器将表情从一张人脸交换到另一张人脸上。此外,随着训练的增加,重建的图像将变得更加逼真。

小结

Deepfakes 可以用于创造出艺术作品和娱乐内容,能够将一个人的表演或特征应用到不同的情境中,为电影、视频和游戏等领域带来创新和丰富多样的体验。也可以为电影制片人和视觉特效团队提供了更加高效和经济的方式来实现人物替换和数字化特效。相对于传统的化妆和后期制作技术,Deepfakes 可以更快速地生成逼真的效果。因此,Deepfakes 具备广泛创造性和应用潜力,但也应当研究相应的技术来避免隐私、欺骗和滥用等问题。

系列链接

PyTorch深度学习实战(1)——神经网络与模型训练过程详解
PyTorch深度学习实战(2)——PyTorch基础
PyTorch深度学习实战(3)——使用PyTorch构建神经网络
PyTorch深度学习实战(4)——常用激活函数和损失函数详解
PyTorch深度学习实战(5)——计算机视觉基础
PyTorch深度学习实战(6)——神经网络性能优化技术
PyTorch深度学习实战(7)——批大小对神经网络训练的影响
PyTorch深度学习实战(8)——批归一化
PyTorch深度学习实战(9)——学习率优化
PyTorch深度学习实战(10)——过拟合及其解决方法
PyTorch深度学习实战(11)——卷积神经网络
PyTorch深度学习实战(12)——数据增强
PyTorch深度学习实战(13)——可视化神经网络中间层输出
PyTorch深度学习实战(14)——类激活图
PyTorch深度学习实战(15)——迁移学习
PyTorch深度学习实战(16)——面部关键点检测
PyTorch深度学习实战(17)——多任务学习
PyTorch深度学习实战(18)——目标检测基础
PyTorch深度学习实战(19)——从零开始实现R-CNN目标检测
PyTorch深度学习实战(20)——从零开始实现Fast R-CNN目标检测
PyTorch深度学习实战(21)——从零开始实现Faster R-CNN目标检测
PyTorch深度学习实战(22)——从零开始实现YOLO目标检测
PyTorch深度学习实战(23)——使用U-Net架构进行图像分割
PyTorch深度学习实战(24)——从零开始实现Mask R-CNN实例分割
PyTorch深度学习实战(25)——自编码器(Autoencoder)
PyTorch深度学习实战(26)——卷积自编码器(Convolutional Autoencoder)
PyTorch深度学习实战(27)——变分自编码器(Variational Autoencoder, VAE)
PyTorch深度学习实战(28)——对抗攻击(Adversarial Attack)
PyTorch深度学习实战(29)——神经风格迁移

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

css 怎么绘制一个带圆角的渐变色的边框

1&#xff0c;可以写两个样式最外面的div设置一个渐变的背景色。里面的元素使用纯色。但是宽高要比外面元素的小。可以利用里面的元素设置padding这样挡住部分渐变色。漏出来的渐变色就像边框一样。 <div class"cover-wrapper"> <div class"item-cover…

Spark高级特性 (难)

Spark高级特性 (难) 闭包 /** 编写一个高阶函数&#xff0c;在这个函数要有一个变量&#xff0c;返回一个函数&#xff0c;通过这个变量完成一个计算* */Testdef test(): Unit { // val f: Int > Double closure() // val area f(5) // println(area)// 在这能否…

[易语言]易语言调用C++ DLL回调函数

易语言适合用于数据展示&#xff0c;数据的获取还是VC来的快、方便哈。 因此我一般使用VC编写DLL&#xff0c;使用易语言编写界面&#xff0c;同一个程序&#xff0c;DLL和EXE通讯最方便的就是使用接口回调了。 废话少说&#xff0c;进入主题。 1. VC编写DLL 为了DLL能够调…

精品量化公式——“风险指数”,适用于短线操作的交易系统,股票期货都适用!不漂移

不多说&#xff0c;直接上效果如图&#xff1a; ► 日线表现 代码评估 技术指标代码评估&#xff1a; 用于通过各种技术指标来分析股市走势。它使用了多个自定义变量&#xff08;VAR1, VAR2, VAR3, 等等&#xff09;&#xff0c;并且基于这些变量构建了复杂的条件和计算。以下…

PostgreSQL之SEMI-JOIN半连接

什么是Semi-Join半连接 Semi-Join半连接&#xff0c;当外表在内表中找到匹配的记录之后&#xff0c;Semi-Join会返回外表中的记录。但即使在内表中找到多条匹配的记录&#xff0c;外表也只会返回已经存在于外表中的记录。而对于子查询&#xff0c;外表的每个符合条件的元组都要…

爬虫入门学习(二)——response对象

大家好&#xff01;我是码银&#xff0c;代码的码&#xff0c;银子的银&#x1f970; 欢迎关注&#x1f970;&#xff1a; CSDN&#xff1a;码银 公众号&#xff1a;码银学编程 前言 在本篇文章&#xff0c;我们继续讨论request模块。从上一节&#xff08;爬虫学习(1)--reque…

【C++】异常机制

异常 一、传统的处理错误的方式二、C异常概念三、异常的使用1. 异常的抛出和捕获&#xff08;1&#xff09;异常的抛出和匹配原则&#xff08;2&#xff09;在函数调用链中异常栈展开匹配原则 2. 异常的重新抛出3. 异常安全4. 异常规范 四、自定义异常体系五、C 标准库的异常体…

编曲混音FL Studio21.2对电脑有什么配置要求

FL Studio 21是一款非常流行的音乐制作软件&#xff0c;它可以帮助音乐人和制作人创作出高质量的音乐作品。然而&#xff0c;为了保证软件的稳定性和流畅性&#xff0c;用户需要知道FL Studio 21对电脑的配置要求。本文将介绍FL Studio 21的配置要求&#xff0c;以帮助用户选择…

详解Java之Spring框架中事务管理的艺术

第1章&#xff1a;引言 大家好&#xff0c;我是小黑&#xff0c;咱们今天聊聊Spring框架中的事务管理。不管是开发小型应用还是大型企业级应用&#xff0c;事务管理都是个不可避免的话题。那么&#xff0c;为什么事务管理这么重要呢&#xff1f;假设在银行系统中转账时&#x…

Java实现海南旅游景点推荐系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用户端2.2 管理员端 三、系统展示四、核心代码4.1 随机景点推荐4.2 景点评价4.3 协同推荐算法4.4 网站登录4.5 查询景点美食 五、免责说明 一、摘要 1.1 项目介绍 基于VueSpringBootMySQL的海南旅游推荐系统&#xff…

远程开发之vscode端口转发

远程开发之vscode端口转发 涉及的软件forwarded port 通过端口转发&#xff0c;实现在本地电脑上访问远程服务器上的内网的服务。 涉及的软件 vscode、ssh forwarded port 在ports界面中的port字段&#xff0c;填需要转发的IP:PORT&#xff0c;即可转发远程服务器中的内网端…

SSH镜像、systemctl镜像、nginx镜像、tomcat镜像

目录 一、SSH镜像 二、systemctl镜像 三、nginx镜像 四、tomcat镜像 五、mysql镜像 一、SSH镜像 1、开启ip转发功能 vim /etc/sysctl.conf net.ipv4.ip_forward 1sysctl -psystemctl restart docker 2、 cd /opt/sshd/vim Dockerfile 3、生成镜像 4、启动容器并修改ro…

AirServer2024官方最新版免费下载

AirServer官方版是一款使用方便的投屏软件&#xff0c;在教室&#xff0c;会议室以及游戏中极为方便。AirServer官方版支持IOS、Android、Windows、mac、Chromebook等多种设备&#xff0c;使用AirServer不需要其他的设备即可完成投屏操作&#xff0c;相比其他投屏软件&#xff…

星图地球——Landsat5_C2_TOA_T1数据集

简介 Landsat 5是美国陆地卫星系列&#xff08;Landsat&#xff09;的第五颗卫星&#xff0c;于1984年3月1日发射&#xff0c;2011年11月停止工作。16天可覆盖全球范围一次。Landsat5_C2_TOA数据集是由Collection2 level1数据通过MTL文件计算得到的TOA反射率产品。数据集的空间…

Spring Boot 中实现文件上传、下载、删除功能

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

VUE好看的个人简历模板

文章目录 1.设计来源1.1 首页界面1.2 关于我界面1.3 我的资历界面1.4 项目经验界面1.5 我的技能界面1.6 联系我界面 2.效果和源码2.1 动态效果2.2 源码目录结构 源码下载 作者&#xff1a;xcLeigh 文章地址&#xff1a;https://blog.csdn.net/weixin_43151418/article/details/…

【python】12.字符串和正则表达式

使用正则表达式 正则表达式相关知识 在编写处理字符串的程序或网页时&#xff0c;经常会有查找符合某些复杂规则的字符串的需要&#xff0c;正则表达式就是用于描述这些规则的工具&#xff0c;换句话说正则表达式是一种工具&#xff0c;它定义了字符串的匹配模式&#xff08;…

网站万词霸屏推广系统源码:实现关键词推广,轻松提高关键词排名,带完整的安装部署教程

现如今&#xff0c;互联网的快速发展&#xff0c;网站推广成为企业网络营销的重要手段。而关键词排名作为网站推广的关键因素&#xff0c;一直备受关注。罗峰给大家分享一款网站万词霸屏推广系统源码&#xff0c;该系统可实现关键词推广&#xff0c;有效提高关键词排名&#xf…

Github 2024-01-15开源项目周报Top14

根据Github Trendings的统计&#xff0c;本周(2024-01-15统计)共有14个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目6TypeScript项目3Jupyter Notebook项目3Java项目2Kotlin项目1C#项目1C项目1 Microsoft PowerToys:…

实现STM32烧写程序-(2)Flash Loader 发送指令解析

简介 实现STM32烧写程序-(1)获取Bootloader版本信息&#xff0c; 看了数据手册之后可以了解到指令的发送, 但实现之前可以使用现成的工具进行测试和查看他的收发情况; Usart Bootloader 指令列表 Usart Bootloader 指令列表 应答ACK | NACK ACK(0x79) 表示 正常答复, NACK(0x…