人脸对齐:使用 PyTorch 检测人脸特征点(附Wget下载方法)

2023-10-27

开发环境

作者:嘟粥yyds
时间:2023年8月12日
集成开发工具:Google Colab
集成开发环境:Python 3.10.6
第三方库:torch、torchvision、cv2、xml、os、math、matplotlib、PIL、time、imutils、random、tqdm、face_recognition

概要

有没有想过像 Instagram 之类的软件如何将惊人的滤镜应用到你的脸上呢?该软件可检测到人脸上的关键点,并投影一个遮罩。

本文将讲述如何使用 PyTorch 来构建具有类似功能的软件。

1 DLIB数据集下载

本文使用的是 DLib 官方数据集,其中包含 6666 张不同尺寸的图像。此外,labels_ibug_300W_train.xml(随数据集一起提供)包含每张人脸上 68 个标记点的坐标。
下载完成后自动将压缩数据集删除,若需要保留则去掉!rm -r 'ibug_300W_large_face_landmark_dataset.tar.gz'代码。

%%capture
if not os.path.exists('ibug_300W_large_face_landmark_dataset'):
    !wget http://dlib.net/files/data/ibug_300W_large_face_landmark_dataset.tar.gz
    !tar -xvzf 'ibug_300W_large_face_landmark_dataset.tar.gz'    
    !rm -r 'ibug_300W_large_face_landmark_dataset.tar.gz'

如果Windows系统未自带wget工具,则先去下载再执行上面的代码。
wget下载地址:GNU Wget 1.21.3 for Windows
下载完成后,将下载后的wget.exe文件放到C:\Windows\System32,并配置系统环境变量,一般都会自动配置,若未自动配置则手动配置一下。
确认安装成功:
cmd命令中输入wget,出现如下对话框,表示安装成功。
在这里插入图片描述

2 数据集可视化

file = open('ibug_300W_large_face_landmark_dataset/helen/trainset/10405424_1.pts')
# 剔除了前三行和最后一行。在该关键点标注文件中,前三行是一些注释信息,最后一行是文件末尾的标记
points = file.readlines()[3:-1]

landmarks = []

for point in points:
    # 获取点的x y坐标
    x,y = point.split(' ')
    landmarks.append([floor(float(x)), floor(float(y[:-1]))])  # 去除 y 坐标字符串末尾的换行符

landmarks = np.array(landmarks)

plt.figure(figsize=(6, 6))
plt.imshow(mpimg.imread('ibug_300W_large_face_landmark_dataset/helen/trainset/10405424_1.jpg'))
plt.scatter(landmarks[:,0], landmarks[:,1], s = 5, c = 'g')
plt.axis('off')
plt.show()

在这里插入图片描述
这是一张来自数据集的样本图像。可以发现,面部只占整个图像的很小一部分。如果我们将整个图像输入神经网络,它还将需要额外处理背景(不相关的信息),这样会使得模型难以学习。因此,我们需要裁剪图像并仅输入脸部给网络。

3 数据预处理

为了防止神经网络过拟合训练数据集,我们需要对数据集进行随机变换。我们将以下操作应用于训练和验证数据集:

  • 由于面部只占整个图像的一小部分,因此裁剪图像并仅使用面部进行训练。
  • 将裁剪的脸部调整为(224x224)图像的大小。
  • 随机更改脸部的亮度和饱和度。
  • 经过以上三个变换后,随机旋转面部。
  • 将图像和标记点转换为 PyTorch 张量,并在 [-1,1] 之间对其进行归一化。
import numpy as np
from math import radians, cos, sin
import torchvision.transforms as transforms
import torchvision.transforms.functional as TF
import imutils
import torch
from PIL import Image
import random
class Transforms():
    def __init__(self):
        pass
    
    def rotate(self, image, landmarks, angle):
        # 随机生成一个在 -angle 到 +angle 范围内的旋转角度
        angle = random.uniform(-angle, +angle)

        # 基于二维平面上的旋转变换的数学特性构建旋转矩阵
        transformation_matrix = torch.tensor([
            [+cos(radians(angle)), -sin(radians(angle))], 
            [+sin(radians(angle)), +cos(radians(angle))]
        ])

        # 对图像进行旋转:相比于 PIL 的图像旋转计算开销更小
        image = imutils.rotate(np.array(image), angle)

        # 将关键点坐标中心化:简化旋转变换的计算,同时确保关键点的变换和图像变换的对应关系
        landmarks = landmarks - 0.5
        # 将关键点坐标应用旋转矩阵
        new_landmarks = np.matmul(landmarks, transformation_matrix)
        # 恢复关键点坐标范围
        new_landmarks = new_landmarks + 0.5

        return Image.fromarray(image), new_landmarks

    def resize(self, image, landmarks, img_size):
        # 调整图像大小
        image = TF.resize(image, img_size)
        return image, landmarks

    def color_jitter(self, image, landmarks):
        # 定义颜色调整的参数:亮度、对比度、饱和度和色调
        color_jitter = transforms.ColorJitter(brightness=0.3, 
                            contrast=0.3,
                            saturation=0.3, 
                            hue=0.1)
        # 对图像进行颜色调整
        image = color_jitter(image)
        return image, landmarks

    def crop_face(self, image, landmarks, crops):
        # 获取裁剪参数
        left = int(crops['left'])
        top = int(crops['top'])
        width = int(crops['width'])
        height = int(crops['height'])

        # 对图像进行裁剪
        image = TF.crop(image, top, left, height, width)

        # 获取裁剪后的图像形状
        img_shape = np.array(image).shape
        # 对关键点坐标进行裁剪后的调整
        landmarks = torch.tensor(landmarks) - torch.tensor([[left, top]])
        # 归一化关键点坐标
        landmarks = landmarks / torch.tensor([img_shape[1], img_shape[0]])
        return image, landmarks

    def __call__(self, image, landmarks, crops):
        # 将图像从数组转换为 PIL 图像对象
        image = Image.fromarray(image)
        # 裁剪图像并调整关键点
        image, landmarks = self.crop_face(image, landmarks, crops)
        # 调整图像大小
        image, landmarks = self.resize(image, landmarks, (224, 224))
        # 对图像进行颜色调整
        image, landmarks = self.color_jitter(image, landmarks)
        # 对图像和关键点进行旋转变换
        image, landmarks = self.rotate(image, landmarks, angle=10)
        
        # 将图像从 PIL 图像对象转换为 Torch 张量
        image = TF.to_tensor(image)
        # 标准化图像像素值
        image = TF.normalize(image, [0.5], [0.5])
        return image, landmarks

4 数据集类

现在我们已经准备好转换,让我们编写数据集类labels_ibug_300W_train.xml 包含图像路径、标记点和人脸包围盒(用于裁剪脸部)坐标。我们会将这些值存储在列表中,以便在训练期间轻松访问它们。
本文在彩色图像上训练神经网络,训练集的BATCH_SIZE = 512,验证集的BATCH_SIZE = 64需要15G左右的显存,训练10个epoch需要2个小时左右。读者可根据自身的GPU情况自行更改这些超参数,若GPU显存过小或使用CPU训练,建议在灰色图像上训练。本文也给出灰色图像上训练神经网络的代码(已被注释)。

import cv2
import xml.etree.ElementTree as ET
from torch.utils.data import Dataset


class FaceLandmarksDataset(Dataset):
    def __init__(self, transform=None):
        # 解析 XML 文件
        tree = ET.parse('ibug_300W_large_face_landmark_dataset/labels_ibug_300W_train.xml')
        root = tree.getroot()
        
        # 初始化变量
        self.image_filenames = []
        self.landmarks = []
        self.crops = []
        self.transform = transform
        self.root_dir = 'ibug_300W_large_face_landmark_dataset'
        
        # 遍历 XML 数据:root[2] 表示 XML 中的第三个元素,即 <images> 部分,其中包含了每张图像的标注信息
        for filename in root[2]:
            self.image_filenames.append(os.path.join(self.root_dir, filename.attrib['file']))

            self.crops.append(filename[0].attrib)

            landmark = []
            for num in range(68):
                x_coordinate = int(filename[0][num].attrib['x'])
                y_coordinate = int(filename[0][num].attrib['y'])
                landmark.append([x_coordinate, y_coordinate])
            self.landmarks.append(landmark)

        self.landmarks = np.array(self.landmarks).astype('float32')     

        assert len(self.image_filenames) == len(self.landmarks)

    def __len__(self):
        return len(self.image_filenames)

    def __getitem__(self, index):
        # 读取图像以及关键点坐标
        image = cv2.imread(self.image_filenames[index])  # 以彩色模式读取图像
        # image = cv2.imread(self.image_filenames[index], 0) # 以灰色模式读取图像
        landmarks = self.landmarks[index]
        
        if self.transform:
            # 如果存在预处理变换,应用变换
            image, landmarks = self.transform(image, landmarks, self.crops[index])

        landmarks = landmarks - 0.5  # 进行中心化操作

        return image, landmarks

# 创建数据集对象,并应用预处理变换
dataset = FaceLandmarksDataset(Transforms())

5 训练变换可视化

注意: landmarks = landmarks - 0.5 对标记点作零中心处理,因为神经网络更容易学习零中心数据。预处理后的数据集输出将类似于以下内容(标记点已绘制在图像上)。

image, landmarks = dataset[5]
landmarks = (landmarks + 0.5) * 224
plt.figure(figsize=(6, 6))
image_color = np.transpose(image.numpy(), (1, 2, 0))  # 将通道维度移到最后
image_color_normalized = (image_color + 1) / 2
image_rgb = image_color_normalized[..., ::-1]
plt.imshow(image_rgb)
plt.scatter(landmarks[:,0], landmarks[:,1], s=8);

在这里插入图片描述

划分数据集

# split the dataset into validation and test sets
len_valid_set = int(0.1*len(dataset))
len_train_set = len(dataset) - len_valid_set

print("The length of Train set is {}".format(len_train_set))
print("The length of Valid set is {}".format(len_valid_set))

train_dataset , valid_dataset,  = torch.utils.data.random_split(dataset , [len_train_set, len_valid_set])

# shuffle and batch the datasets
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=512, shuffle=True, num_workers=2)
valid_loader = torch.utils.data.DataLoader(valid_dataset, batch_size=64, shuffle=True, num_workers=2)

6 神经网络架构

我们将使用 ResNet18 作为基本框架。我们需要修改第一层和最后一层以适合本项目的目的。在第一层中,我们将使神经网络接受彩色图像的输入通道数为 3(若选择灰色图像训练则设为1)。同样,在最后一层中,模型的输出通道数应等于 68 68 68 x 2 = 136 2 = 136 2=136,以预测人脸 68 68 68 个标记点的 ( x , y ) (x, y) (x,y) 坐标。

import torch
import torch.nn as nn
import torchvision.models as models


class Network(nn.Module):
    def __init__(self,num_classes=136):
        super().__init__()
        self.model_name='resnet18'
        self.model=models.resnet18()
        self.model.conv1=nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)
        # self.model.conv1=nn.Conv2d(1, 64, kernel_size=7, stride=2, padding=3, bias=False)  # 灰色图像训练
        self.model.fc=nn.Linear(self.model.fc.in_features, num_classes)
        
    def forward(self, x):
        x=self.model(x)
        return x

7 训练神经网络

我们将使用预测和真实标记点之间的均方误差作为损失函数。请记住,学习率应保持较低以避免梯度爆炸。每当验证损失达到新的最小值时,将保存网络权重。训练至少 20 个 epochs 以获得最佳性能。(本文在GPU资源有限的情况下,仅训练了10个epoch)。

import torch
import torch.nn as nn
import torch.optim as optim
import time
from tqdm import tqdm


# 记录每个 epoch 的训练和验证损失
train_losses = []
valid_losses = []

# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
torch.autograd.set_detect_anomaly(True)

network = Network().to(device)

criterion = nn.MSELoss()
optimizer = optim.Adam(network.parameters(), lr=0.0001)

loss_min = np.inf
num_epochs = 10

start_time = time.time()
for epoch in range(1, num_epochs + 1): 
    loss_train = 0
    loss_valid = 0
    running_loss = 0
    
    network.train()
    
    for step in tqdm(range(1, len(train_loader) + 1)):
        images, landmarks = next(iter(train_loader))
        
        images = images.to(device)
        landmarks = landmarks.view(landmarks.size(0), -1).to(device) 
        
        predictions = network(images)
        
        optimizer.zero_grad()
        loss_train_step = criterion(predictions, landmarks)
        loss_train_step.backward()
        optimizer.step()
        
        loss_train += loss_train_step.item()
        running_loss = loss_train / step
        
    network.eval() 
    with torch.no_grad():
        for step in range(1, len(valid_loader) + 1):
            images, landmarks = next(iter(valid_loader))
            
            images = images.to(device)
            landmarks = landmarks.view(landmarks.size(0), -1).to(device)
            
            predictions = network(images)
            loss_valid_step = criterion(predictions, landmarks)
            
            loss_valid += loss_valid_step.item()
            running_loss = loss_valid / step
    
    loss_train /= len(train_loader)
    loss_valid /= len(valid_loader)

    train_losses.append(loss_train)
    valid_losses.append(loss_valid)
    
    print('\n--------------------------------------------------')
    print('Epoch: {}  Train Loss: {:.4f}  Valid Loss: {:.4f}'.format(epoch, loss_train, loss_valid))
    print('--------------------------------------------------')
    
    if loss_valid < loss_min:
        loss_min = loss_valid
        torch.save(network.state_dict(), 'face_landmarks.pth') 
        print("\nMinimum Validation Loss of {:.4f} at epoch {}/{}".format(loss_min, epoch, num_epochs))
        print('Model Saved\n')
     
print('Training Complete')
print("Total Elapsed Time: {} s".format(time.time() - start_time))
100%|██████████| 12/12 [23:22<00:00, 116.91s/it]

--------------------------------------------------
Epoch: 1  Train Loss: 0.1304  Valid Loss: 0.0634
--------------------------------------------------

Minimum Validation Loss of 0.0634 at epoch 1/10
Model Saved

100%|██████████| 12/12 [10:42<00:00, 53.57s/it]

--------------------------------------------------
Epoch: 2  Train Loss: 0.0130  Valid Loss: 0.0139
--------------------------------------------------

Minimum Validation Loss of 0.0139 at epoch 2/10
Model Saved

100%|██████████| 12/12 [09:42<00:00, 48.53s/it]

--------------------------------------------------
Epoch: 3  Train Loss: 0.0084  Valid Loss: 0.0095
--------------------------------------------------

Minimum Validation Loss of 0.0095 at epoch 3/10
Model Saved

100%|██████████| 12/12 [09:49<00:00, 49.09s/it]

--------------------------------------------------
Epoch: 4  Train Loss: 0.0069  Valid Loss: 0.0073
--------------------------------------------------

Minimum Validation Loss of 0.0073 at epoch 4/10
Model Saved

100%|██████████| 12/12 [10:04<00:00, 50.35s/it]

--------------------------------------------------
Epoch: 5  Train Loss: 0.0063  Valid Loss: 0.0060
--------------------------------------------------

Minimum Validation Loss of 0.0060 at epoch 5/10
Model Saved

100%|██████████| 12/12 [09:27<00:00, 47.26s/it]

--------------------------------------------------
Epoch: 6  Train Loss: 0.0061  Valid Loss: 0.0058
--------------------------------------------------

Minimum Validation Loss of 0.0058 at epoch 6/10
Model Saved

100%|██████████| 12/12 [10:18<00:00, 51.53s/it]

--------------------------------------------------
Epoch: 7  Train Loss: 0.0058  Valid Loss: 0.0059
--------------------------------------------------
100%|██████████| 12/12 [09:48<00:00, 49.01s/it]

--------------------------------------------------
Epoch: 8  Train Loss: 0.0057  Valid Loss: 0.0054
--------------------------------------------------

Minimum Validation Loss of 0.0054 at epoch 8/10
Model Saved

100%|██████████| 12/12 [09:47<00:00, 48.92s/it]

--------------------------------------------------
Epoch: 9  Train Loss: 0.0055  Valid Loss: 0.0053
--------------------------------------------------

Minimum Validation Loss of 0.0053 at epoch 9/10
Model Saved

100%|██████████| 12/12 [09:51<00:00, 49.27s/it]

--------------------------------------------------
Epoch: 10  Train Loss: 0.0054  Valid Loss: 0.0052
--------------------------------------------------

Minimum Validation Loss of 0.0052 at epoch 10/10
Model Saved

Training Complete
Total Elapsed Time: 7878.068931341171 s
# 可视化损失曲线
plt.figure(figsize=(10, 5))
plt.plot(range(1, num_epochs + 1), train_losses, label='Train Loss')
plt.plot(range(1, num_epochs + 1), valid_losses, label='Valid Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.title('Training and Validation Loss')
plt.legend()
plt.grid()
plt.show()

在这里插入图片描述

8 测试集上预测

OpenCV Harr 级联分类器用于检测图像中的脸部。使用 Haar 级联进行对象检测是一种基于机器学习的方法,其中使用一组输入数据来训练级联函数。OpenCV 包含许多针对面部、眼睛、行人等已经训练好的分类器。这里我们使用的是脸部分类器,你需要为其下载预训练的分类器 XML 文件并将其保存到工作目录中。该代码在不支持GPU的代码上也能正常运行,不过效果不如下面的仅能在支持GPU的环境下正常运行的代码。

import time
import cv2
import os
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import imutils

import torch
import torch.nn as nn
from torchvision import models
import torchvision.transforms.functional as TF

#######################################################################
image_path = 'Sample1.jpg'
weights_path = 'face_landmarks.pth'
frontal_face_cascade_path = 'haarcascade_frontalface_default.xml'
#######################################################################

class Network(nn.Module):
    def __init__(self, num_classes=136):
        super().__init__()
        self.model_name = 'resnet18'
        self.model = models.resnet18(pretrained=False)
        self.model.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)  # 1:单通道训练    3:三通道训练
        self.model.fc = nn.Linear(self.model.fc.in_features, num_classes)
        
    def forward(self, x):
        x = self.model(x)
        return x

#######################################################################
face_cascade = cv2.CascadeClassifier(frontal_face_cascade_path)
if face_cascade.empty():
    raise Exception("Failed to load face cascade classifier")


best_network = Network()
best_network.load_state_dict(torch.load(weights_path, map_location=torch.device('cpu'))) 
best_network.eval()

image = cv2.imread(image_path)
color_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # 三通道训练
# color_iamge = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 单通道训练
height, width, _ = image.shape

faces = face_cascade.detectMultiScale(color_image, 1.1, 4)

all_landmarks = []
for (x, y, w, h) in faces:
    face_region = color_image[y:y+h, x:x+w]
    face_region = TF.resize(Image.fromarray(face_region), size=(224, 224))
    face_region = TF.to_tensor(face_region)
    face_region = TF.normalize(face_region, [0.5], [0.5])

    with torch.no_grad():
        landmarks = best_network(face_region.unsqueeze(0)) 

    landmarks = (landmarks.view(68, 2).detach().numpy() + 0.5) * np.array([[w, h]]) + np.array([[x, y]])
    all_landmarks.append(landmarks)

# 绘制人脸和关键点
plt.figure(figsize=(7, 7))
plt.imshow(color_image)
for landmarks in all_landmarks:
    plt.scatter(landmarks[:, 0], landmarks[:, 1], c='c', s=5)

plt.axis('off')
plt.show()

12个Epoch后的测试效果

下面这个代码仅能在支持GPU的环境下正常运行,同时需要确保安装了 face_recognition 依赖库。

!pip install -i https://pypi.tuna.tsinghua.edu.cn/simple face_recognition  
import cv2
import numpy as np
import matplotlib.pyplot as plt
from PIL import Image
import imutils
import torch
import torch.nn as nn
from torchvision import models
import torchvision.transforms.functional as TF
import face_recognition  # 导入 face_recognition 库

#######################################################################
image_path = 'sample.jpg'
weights_path = 'face_landmarks.pth'
#######################################################################

class Network(nn.Module):
    def __init__(self, num_classes=136):
        super().__init__()
        self.model_name = 'resnet18'
        self.model = models.resnet18(pretrained=False)
        self.model.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False)  # 1:单通道训练   3:三通道训练
        self.model.fc = nn.Linear(self.model.fc.in_features, num_classes)
        
    def forward(self, x):
        x = self.model(x)
        return x

best_network = Network()
best_network.load_state_dict(torch.load(weights_path, map_location=torch.device('cuda'))) 
best_network.eval()

# 读取图像并进行预处理
image = cv2.imread(image_path)
color_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)  # 三通道训练时
# color_image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)  # 单通道训练时
display_image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)

# 使用 face_recognition 库进行人脸检测
face_locations = face_recognition.face_locations(color_image)

# 初始化关键点列表
all_landmarks = []

# 对于每个检测到的人脸位置
for face_location in face_locations:
    top, right, bottom, left = face_location
    face_region = color_image[top:bottom, left:right]
    
    # 进行预处理
    face_region = Image.fromarray(face_region)
    face_region = TF.resize(face_region, size=(224, 224))
    face_region = TF.to_tensor(face_region)
    face_region = TF.normalize(face_region, [0.5], [0.5])

    with torch.no_grad():
        landmarks = best_network(face_region.unsqueeze(0)) 

    landmarks = (landmarks.view(68, 2).detach().numpy() + 0.5) * np.array([[right - left, bottom - top]]) + np.array([[left, top]])
    all_landmarks.append(landmarks)

# 绘制人脸和关键点
plt.figure(figsize=(7, 7))
plt.imshow(display_image)
for landmarks in all_landmarks:
    plt.scatter(landmarks[:, 0], landmarks[:, 1], c='c', s=5)

plt.axis('off')
plt.show()

12个Epoch后的测试效果图

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

人脸对齐:使用 PyTorch 检测人脸特征点(附Wget下载方法) 的相关文章

随机推荐

  • 跳出for循环

    跳出for循环有三种方式 1 continue 跳出当次循环 可继续进行下一个循环 function ceshi for var i 0 i lt 6 i if i 3 continue console log i ceshi 效果图 2
  • 如何设计管理员和用户登录界面C语言,管理员登录设计(第7节)

    本文实现管理员登录效果 当用户名和密码正确时 弹出登录成功提示 否则弹出用户名或密码错误提示 今天有以下三大学习任务 学习任务一 了解命名空间的定义 定义 namespace 空间名 类 引用 using 空间名 学习任务二 实现管理员登陆
  • 【micropython】SPI触摸屏开发

    背景 最近买了几块ESP32模块 看了下mircopython支持还不错 所以买了个SPI触摸屏试试水 记录一下使用过程 硬件相关 SPI触摸屏 使用2 4寸屏幕 常见淘宝均可买到 驱动为ILI9341 具体参数如下图 引脚描述 ESP32
  • C++中【字符串】与【整型】和【浮点型】转换攻略

    异想之旅 本人原创博客完全手敲 绝对非搬运 全网不可能有重复 本人无团队 仅为技术爱好者进行分享 所有内容不牵扯广告 本人所有文章仅在CSDN 掘金和个人博客 一定是异想之旅域名 发布 除此之外全部是盗文 C 算法刷题等过程中经常会遇到字符
  • 化工安全生产管理平台:融合重大危险源监控预警、可燃有毒气体检测报警、企业安全风险分区于一体

    产品概述 化工企业安全生产信息化管理平台 以实现化工企业安全生产数字化 信息化 智能化管理为目标 建设融合重大危险源监控预警管理 可燃有毒气体检测报警管理 企业安全风险分区管理 生产人员在岗在位管理和企业生产全流程管理于一体的安全生产信息化
  • 测试从业1到3年经验,常见软件测试工程师面试题总结

    前言 软件测试工程师 和开发工程师相比起来 虽然前期可能不会太深 但是涉及的面还是比较广的 前期面试实习生或者一年左右的岗位 问的也主要是一些基础性的问题比较多 涉及的知识主要有MySQL数据库的使用 Linux操作系统的使用 软件测试框架
  • SpringMVC文件的上传下载&JRebel的使用

    目录 前言 一 JRebel的使用 1 IDea内安装插件 2 激活 3 离线使用 使用JRebel的优势 二 文件上传与下载 1 导入pom依赖 2 配置文件上传解析器 3 数据表 4 配置文件 5 前端jsp页面 6 controlle
  • 【多目标跟踪MOT学习笔记】字节跳动ByteTrack论文研究(一):BYTE策略

    文章目录 前言 一 是什么ByteTrack 二 BYTE 1 BYTE method 概览 2 First Association 关联1 3 Second Association 关联2 4 Post Processing 后处理 4
  • 涅槃重生,BitKeep如何闯出千万用户新起点

    在全球 BitKeep钱包现在已经有超过千万用户在使用 当我得知这个数据的时候 有些惊讶 也有点意料之中 关注BitKeep这几年 真心看得出这家公司的发展之迅速 还记得2018年他们推出第一个版本时 小而美 简洁顺手 他们大胆且略显理想主
  • [非线性控制理论]4_反馈线性化_反步法

    非线性控制理论 1 Lyapunov直接方法 非线性控制理论 2 不变性原理 非线性控制理论 3 基础反馈稳定控制器设计 非线性控制理论 4 反馈线性化 反步法 非线性控制理论 5 自适应控制器 Adaptive controller 非线
  • 【操作系统 · 线程】介绍、分类、多线程

    线程 介绍 分类 多线程 一 介绍 1 进程与线程 2 多线程 3 线程的功能 二 线程分类 1 用户级线程 2 内核级线程 3 其他方案 三 多核 多线程 一 介绍 进程中有两个重要概念 资源所有权 执行 因这一区别 许多操作系统中出现了
  • ueditor1.5 新版ueditor设置字体大小文件所在位置

    ueditor src plugins font js文件 将想添加的字体大小设置即可 fontsize 10 11 12 14 16 18 20 24 36 72
  • 【HBZ分享】java之二叉堆排序实战代码

    废话不多说 直接上代码 注释都在代码中 利用大顶堆排序 最终数组从小到大 复制即用无需导包 package 大顶堆 import java lang reflect Array import java util Arrays public
  • 30天自制操作系统学习-第1天

    第一天 1 批处理文件 直接由DOS命令行解释执行的文件 通常后缀名为 bat或 cmd 2 镜像文件 img后缀的文件 可用于制作操作系统文件 一个3 5英寸的软盘容量为1440k 3 nask 作者自己制作的汇编代码编译器 代码与nas
  • Adobe Creative Suite 5.5 简体中文版

    http www itopdog cn graphic image web design software adobe creative suite html Adobe Creative Suite 5 5 简介 Adobe Creati
  • xml和javaBean实现互转

    最近遇到了调用接口需要xml报文传递 用传统最简单的方式就是我们string手动拼接报文传递 这样调用问题解决了 返回的数据问题没有得到解决 因为返回的也是xml报文 需要对其做一些转换操作 既然转换 直接在传递时通过简单的方式将报文序列化
  • Python 日志管理模块logging

    参考 link https www cnblogs com nancyzhu p 8551506 html 日志 日志是跟踪软件运行时所发生的事件的一种方法 软件开发者在代码中调用日志函数 表明发生了特定的事件 事件由描述性消息描述 该描述
  • ORA-12505: TNS: 监听程序当前无法识别连接描述符中所给出的SID等错误解决方法

    程序连接orarle报ORA 12505错误 一 异常 ORA 12505 TNS listener does not currently know of SID given in connect descriptor The Connec
  • VTK_python入门

    tk基本介绍 VTK 库的框架结构 vtkObject gt vtkSource gt vtkFilter gt vtkMapper gt vtkActor gt vtkRenderer 对象 介绍 vtkObject vtk库的基类 vt
  • 人脸对齐:使用 PyTorch 检测人脸特征点(附Wget下载方法)

    开发环境 作者 嘟粥yyds 时间 2023年8月12日 集成开发工具 Google Colab 集成开发环境 Python 3 10 6 第三方库 torch torchvision cv2 xml os math matplotlib