Pytorch:图像风格快速迁移

2023-05-16

Pytorch: 图像风格快速迁移-残差网络,固定风格任意内容

Copyright: Jingmin Wei, Pattern Recognition and Intelligent System, School of Artificial and Intelligence, Huazhong University of Science and Technology

Pytorch教程专栏链接


文章目录

      • Pytorch: 图像风格快速迁移-残差网络,固定风格任意内容
    • @[toc]
        • Reference
        • 快速风格迁移网络准备
          • 定义残差块结构
          • 定义图像转换网络
        • 快速风格迁移数据准备
        • 快速风格迁移网络训练和数据可视化展示
        • CPU 上使用预训练好的 GPU 模型

本教程不商用,仅供学习和参考交流使用,如需转载,请联系本人。

Reference

Perceptual Losses for Real-Time Style Transfer and Super-Resolution

ResNet

和普通风格迁移不一样,普通图像风格迁移的输入图像是随机噪声,而快速风格迁移的输入是一张图像转换网络 f w fw fw 的输出。

快速风格迁移是通过输入图像 x x x 经过图像转换网络 f w fw fw ,得到网络的输出 y ^ \hat{y} y^ 。因此它可以实现任意内容的快速图像迁移。

参考 Perceptual Losses for Real-Time Style Transfer and Super-Resolution 一文,对图像转换网络的上采样操作进行相应调整。在建立的网络中,将会使用转置卷积操作进行特征映射的上采样。

import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt 
from PIL import Image
import time

import torch
import torch.nn as nn 
import torch.utils.data as Data 
import torch.nn.functional as F 
import torch.optim as optim
from torchvision import transforms
from torchvision.datasets import ImageFolder
from torchvision import models
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"

# 模型加载选择GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
# device = torch.device('cpu')
print(device)
print(torch.cuda.device_count())
print(torch.cuda.get_device_name(0))
cuda
1
GeForce MX250

快速风格迁移网络准备

通过 3 3 3 个卷积层对图像的特征映射进行降维操作,然后通过 5 5 5 个残差连接层,学习图像风格,并添加到内容图像上,最后通过 3 3 3 个转置卷积操作,对特征映射进行升维(类比语义分割网络) ,以重构风格迁移后的图像。

在转换网络的升维操作中,使用转置卷积来代替提文章中的上采样和卷积层的结合,因为输入的是标准化后的图像,像素值范围在 − 2.1 − 2.7 -2.1-2.7 2.12.7 之间,所以在网络最后的输出层中,不使用激活函数,网络的输出值大多会在 − 2.1 − 2.7 -2.1-2.7 2.12.7 之间,只有少部分不在该区间,故在实际训练网络时,会将输出裁剪到 − 2.1 − 2.7 -2.1-2.7 2.12.7 之间,即最后一层无需使用激活函数,其它层使用 ReLU 函数。在网络中,特征映射的数量逐渐从 3 3 3 增加到 128 128 128 ,并且每个残差连接层有 128 128 128 个特征映射,在转置卷积层特征映射的数量会从 128 128 128 减到 3 3 3 ,对应着图像的三个通道。

定义残差块结构

这部分如果不记得可以参考 ResNet教程。

聚焦于神经网络局部。设输入为 x 。假设我们希望学出的理想映射为 f(x),从而作为激活函数的输入。部分需要拟合出有关恒等映射的残差映射 f(x)−x 。残差映射在实际中往往更容易优化。以恒等映射作为我们希望学出的理想映射 f(x) 。我们只需将加权运算(如仿射)的权重和偏差参数学成 0 0 0 ,那么 f(x) 即为恒等映射。实际中,当理想映射 f(x) 极接近于恒等映射时,残差映射也易于捕捉恒等映射的细微波动。在残差块中,输入可通过跨层的数据线路更快地向前传播。

定义残差连接网络, 128 128 128 个特征映射,激活尺寸为 128 × 64 × 64 128\times64\times64 128×64×64

# ResidualBlock 残差块
class ResidualBlock(nn.Module):
    def __init__(self, channels):
        super(ResidualBlock, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(channels, channels, kernel_size = 3, stride = 1, padding = 1),
            nn.ReLU(),
            nn.Conv2d(channels, channels, kernel_size = 3, stride = 1, padding = 1)
        )
    def forward(self, x):
        return F.relu(self.conv(x) + x)
定义图像转换网络

分别是下采样模块, 5 5 5 个残差连接模块以及上采样模块

# 定义图像转换网络
class ImfwNet(nn.Module):
    def __init__(self):
        super(ImfwNet, self).__init__()
        # 下采样
        self.downsample = nn.Sequential(
            nn.ReflectionPad2d(padding = 4), # 使用边界反射填充
            nn.Conv2d(3, 32, kernel_size = 9, stride = 1),
            nn.InstanceNorm2d(32, affine = True), # 像素值上做归一化
            nn.ReLU(),
            nn.Conv2d(32, 64, kernel_size = 3, stride = 2),
            nn.InstanceNorm2d(64, affine = True),
            nn.ReLU(),
            nn.ReflectionPad2d(padding = 1),
            nn.Conv2d(64, 128, kernel_size = 3, stride = 2),
            nn.InstanceNorm2d(128, affine = True),
            nn.ReLU()
        )
        # 5个残差连接
        self.res_blocks = nn.Sequential(
            ResidualBlock(128),
            ResidualBlock(128),
            ResidualBlock(128),
            ResidualBlock(128),
            ResidualBlock(128),
        )
        # 上采样
        self.unsample = nn.Sequential(
            nn.ConvTranspose2d(128, 64, kernel_size = 3, stride = 2, padding = 1, output_padding = 1),
            nn.InstanceNorm2d(64, affine = True),
            nn.ReLU(),
            nn.ConvTranspose2d(64, 32, kernel_size = 3, stride = 2, padding = 1, output_padding = 1),
            nn.InstanceNorm2d(32, affine = True),
            nn.ReLU(),
            nn.ConvTranspose2d(32, 3, kernel_size = 9, stride = 1, padding = 4)
        )
    def forward(self, x):
        x = self.downsample(x) # 输入像素值在-2.1-2.7之间
        x = self.res_blocks(x)
        x = self.unsample(x) # 输出像素值在-2.1-2.7之间
        return x
myfwnet = ImfwNet().to(device)
from torchsummary import summary
summary(myfwnet, input_size=(3, 256, 256))
----------------------------------------------------------------
        Layer (type)               Output Shape         Param #
================================================================
   ReflectionPad2d-1          [-1, 3, 264, 264]               0
            Conv2d-2         [-1, 32, 256, 256]           7,808
    InstanceNorm2d-3         [-1, 32, 256, 256]              64
              ReLU-4         [-1, 32, 256, 256]               0
            Conv2d-5         [-1, 64, 127, 127]          18,496
    InstanceNorm2d-6         [-1, 64, 127, 127]             128
              ReLU-7         [-1, 64, 127, 127]               0
   ReflectionPad2d-8         [-1, 64, 129, 129]               0
            Conv2d-9          [-1, 128, 64, 64]          73,856
   InstanceNorm2d-10          [-1, 128, 64, 64]             256
             ReLU-11          [-1, 128, 64, 64]               0
           Conv2d-12          [-1, 128, 64, 64]         147,584
             ReLU-13          [-1, 128, 64, 64]               0
           Conv2d-14          [-1, 128, 64, 64]         147,584
    ResidualBlock-15          [-1, 128, 64, 64]               0
           Conv2d-16          [-1, 128, 64, 64]         147,584
             ReLU-17          [-1, 128, 64, 64]               0
           Conv2d-18          [-1, 128, 64, 64]         147,584
    ResidualBlock-19          [-1, 128, 64, 64]               0
           Conv2d-20          [-1, 128, 64, 64]         147,584
             ReLU-21          [-1, 128, 64, 64]               0
           Conv2d-22          [-1, 128, 64, 64]         147,584
    ResidualBlock-23          [-1, 128, 64, 64]               0
           Conv2d-24          [-1, 128, 64, 64]         147,584
             ReLU-25          [-1, 128, 64, 64]               0
           Conv2d-26          [-1, 128, 64, 64]         147,584
    ResidualBlock-27          [-1, 128, 64, 64]               0
           Conv2d-28          [-1, 128, 64, 64]         147,584
             ReLU-29          [-1, 128, 64, 64]               0
           Conv2d-30          [-1, 128, 64, 64]         147,584
    ResidualBlock-31          [-1, 128, 64, 64]               0
  ConvTranspose2d-32         [-1, 64, 128, 128]          73,792
   InstanceNorm2d-33         [-1, 64, 128, 128]             128
             ReLU-34         [-1, 64, 128, 128]               0
  ConvTranspose2d-35         [-1, 32, 256, 256]          18,464
   InstanceNorm2d-36         [-1, 32, 256, 256]              64
             ReLU-37         [-1, 32, 256, 256]               0
  ConvTranspose2d-38          [-1, 3, 256, 256]           7,779
================================================================
Total params: 1,676,675
Trainable params: 1,676,675
Non-trainable params: 0
----------------------------------------------------------------
Input size (MB): 0.75
Forward/backward pass size (MB): 246.85
Params size (MB): 6.40
Estimated Total Size (MB): 253.99
----------------------------------------------------------------
# 输出网络结构
from torchviz import make_dot

x = torch.randn(1, 3, 256, 256).requires_grad_(True)
y = myfwnet(x.to(device))
myResNet_vis = make_dot(y, params=dict(list(myfwnet.named_parameters()) + [('x', x)]))
myResNet_vis


在这里插入图片描述

快速风格迁移数据准备

下载地址:https://cocodataset.org/#home

使用 COCO2014 的验证集作为模型输入。

# 定义图像预处理
data_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(256), # 图像尺寸为256*256
    transforms.ToTensor(), # 转为0-1的张量
    transforms.Normalize(mean = [0.485, 0.456, 0.406],
                         std = [0.229, 0.224, 0.225]) 
                         # 像素值转为-2.1-2.7
])
# 从文件夹中读取数据
dataset = ImageFolder('./data/COCO', transform = data_transform)
# 每个batch使用4张图像
data_loader = Data.DataLoader(dataset, batch_size = 4, shuffle = True,
                              num_workers = 8, pin_memory = True)
dataset
Dataset ImageFolder
    Number of datapoints: 40504
    Root location: ./data/COCO
    StandardTransform
Transform: Compose(
               Resize(size=256, interpolation=bilinear)
               CenterCrop(size=(256, 256))
               ToTensor()
               Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
           )

说明:参数 pin_memory 表示创建 DataLoader 时,生成的 Tensor 数据最开始是属于内存中的锁页内存(显卡中的显存全部是锁页内存),这样将内存的 Tensor 转移到 GPU 的显存就会更快一些,并且针对高性能的 GPU 运算速度会更快。

接下来读取预训练的 VGG16 网络,只需要其中的 features 包含的层,将其设置到 GPU 设备上。计算时只需要使用 VGG 网络提取特定层的特征映射,不需要对其中参数进行训练,设置为 eval 即可

# 读取预训练的VGG16网络
vgg16 = models.vgg16(pretrained = True)
# 不需要分类器,只需要卷积层和池化层
vgg = vgg16.features.to(device).eval()

定义一个方法,能读取风格图像,且转为 VGG 网络可使用的四维张量的格式。

# 定义一个读取风格图像函数,并将图像进行必要的转化
def load_image(img_path, shape = None):
    image = Image.open(img_path)
    size = image.size
    if shape is not None:
        size = shape # 如果指定了图像尺寸就转为指定的尺寸
    # 使用transforms将图像转为张量,并标准化
    in_transform = transforms.Compose([
        transforms.Resize(size),
        transforms.ToTensor(), # 转为0-1的张量
        transforms.Normalize(mean = [0.485, 0.456, 0.406],
                            std = [0.229, 0.224, 0.225])
    ])
    # 使用图像的RGB通道,并添加batch维度
    image = in_transform(image)[:3, :, :].unsqueeze(dim = 0)
    return image
# 定义一个将标准化后的图像转化为便于利用matplotlib可视化的函数
def im_convert(tensor):
    '''
    将[1, c, h, w]维度的张量转为[h, w, c]的数组
    因为张量进行了表转化,所以要进行标准化逆变换
    '''
    tensor = tensor.cpu()
    image = tensor.data.numpy().squeeze() # 去除batch维度的数据
    image = image.transpose(1, 2, 0) # 置换数组维度[c, h, w]->[h, w, c]
    # 进行标准化的逆操作
    image = image * np.array((0.229, 0.224, 0.225)) + np.array((0.485, 0.456, 0.406))
    image = image.clip(0, 1) # 将图像的取值剪切到0-1之间
    return image

读取风格图像并可视化

# 读取风格图像
style = load_image('./data/COCO/COCO/COCO_val2014_000000000139.jpg', shape = (256, 256)).to(device)
# 可视化图像
plt.figure()
plt.imshow(im_convert(style))
plt.axis('off')
plt.show()


在这里插入图片描述

快速风格迁移网络训练和数据可视化展示

与普通风格迁移一样,首先要计算输入张量的 Gram 矩阵:

# 定义计算格拉姆矩阵
def gram_matrix(tensor):
    '''
    计算表示图像风格特征的Gram矩阵,它最终能够在保证内容的情况下,
    进行风格的传输。tensor:是一张图像前向计算后的一层特征映射
    '''
    # 获得tensor的batch_size, channel, height, width
    b, c, h, w = tensor.size()
    # 改变矩阵的维度为(深度, 高*宽)
    tensor = tensor.view(b, c, h * w)
    tensor_t = tensor.transpose(1, 2)
    # 计算gram matrix,针对多张图像进行计算
    gram = tensor.bmm(tensor_t) / (c * h * w)
    return gram

注意的是,因输入的数据使用一个 batch 的特征映射,所以在张量乘以其转置时,需要计算每张图像的 Gram 矩阵,故使用 tensor.bmm 方法完成相关的矩阵乘法计算

定义 get-features 获取图像数据在指定网络指定层上的特征映射:

# 定义一个用于获取图像在网络上指定层的输出的方法
def get_features(image, model, layers = None):
    '''
    将一张图像image在一个网络model中进行前向传播计算,
    并获取指定层layers中的特征输出
    '''
    # 将映射层名称与论文中的名称相对应
    if layers is None:
        layers = {'3': 'relu1_2',
                  '8': 'relu2_2',
                  '15': 'relu3_3', # 内容图层表示
                  '22': 'relu4_3'} # 经过ReLU激活后的输出
    features = {} # 获得的每层特征保存到字典中
    x = image # 需要获取特征的图像
    # model._modules是一个字典,保存着网络model每层的信息
    for name, layer in model._modules.items():
        # 从第一层开始获取图像的特征
        x = layer(x)
        # 如果是layers参数指定的特征,就保存到features中
        if name in layers:
            features[layers[name]] = x
    return features

其中 relu3_3 层输出的特征映射用于度量图像内容的相似性。

下面计算风格图像的 4 4 4 个指定多层上的 Gram 矩阵,并用字典来保存

# 计算风格图像的风格表示
style_layer = {'3': 'relu1_2',
               '8': 'relu2_2',
               '15': 'relu3_3',
               '22': 'relu4_3'}
content_layer = {'15': 'relu3_3'}
# 内容表示的图层,均使用经过relu激活后的输出
style_features = get_features(style, vgg, layers = style_layer)
# 为我们的风格表示计算每层的格拉姆矩阵,使用字典保存
style_grams = {layer: gram_matrix(style_features[layer]) for layer in style_features}

接下来开始对网络进行训练。在训练过程中定义了三种损失,分别为风格损失、内容损失和全变分(Total Variation)损失,它们的权重为 1 0 5 , 1 , 1 0 − 5 10^5,1,10^{-5} 105,1,105 ,优化器为 Adam,学习率为 0.0003 0.0003 0.0003 。针对 4 4 4 万多张图像数据,每 4 4 4 张图像为一个 batch,训练 4 4 4 个 epoch,即约有 40000 40000 40000 次迭代。

# 网络训练,定义三种损失的权重
style_weight = 1e5
content_weight = 1
tv_weight = 1e-5
# 定义优化器
optimizer = optim.Adam(myfwnet.parameters(), lr = 1e-3)
myfwnet.train()
since = time.time()
for epoch in range(4):
    print('Epoch: {}'.format(epoch + 1))
    content_loss_all = []
    style_loss_all = []
    tv_loss_all = []
    all_loss = []
    for step, batch in enumerate(data_loader):
        optimizer.zero_grad()

        # 计算使用图像转换网络后,内容图像得到的输出
        content_images = batch[0].to(device)
        transformed_images = myfwnet(content_images)
        transformed_images = transformed_images.clamp(-2.1, 2.7)

        # 使用VGG16计算原图像对应的content_layer特征
        content_features = get_features(content_images, vgg, layers = content_layer)

        # 使用VGG16计算\hat{y}图像对应的全部特征
        transformed_features = get_features(transformed_images, vgg)

        # 内容损失
        # 使用F.mse_loss函数计算预测(transformed_images)和标签(content_images)之间的损失
        content_loss = F.mse_loss(transformed_features['relu3_3'], content_features['relu3_3'])
        content_loss = content_weight * content_loss

        # 全变分损失
        # total variation图像水平和垂直平移一个像素,与原图相减
        # 然后计算绝对值的和即为tv_loss
        y = transformed_images # \hat{y}
        tv_loss = torch.sum(torch.abs(y[:, :, :, :-1] - y[:, :, :, 1:])) + torch.sum(torch.abs(y[:, :, :-1, :] - y[:, :, 1:, :]))
        tv_loss = tv_weight * tv_loss

        # 风格损失
        style_loss = 0
        transformed_grams = {layer: gram_matrix(transformed_features[layer]) for layer in transformed_features}
        for layer in style_grams:
            transformed_gram = transformed_grams[layer]
            # 是针对一个batch图像的Gram
            style_gram = style_grams[layer]
            # 是针对一张图像的,所以要扩充style_gram
            # 并计算计算预测(transformed_gram)和标签(style_gram)之间的损失
            style_loss += F.mse_loss(transformed_gram,
                                style_gram.expand_as(transformed_gram))
        style_loss = style_weight * style_loss

        # 3个损失加起来,梯度下降
        loss = style_loss + content_loss + tv_loss
        loss.backward(retain_graph = True)
        optimizer.step()

        # 统计各个损失的变化情况
        content_loss_all.append(content_loss.item())
        style_loss_all.append(style_loss.item())
        tv_loss_all.append(tv_loss.item())
        all_loss.append(loss.item())
        if step % 5000 == 0:
            print('step: {}; content loss: {:.3f}; style loss: {:.3f}; tv loss: {:.3f}, loss: {:.3f}'.format(step, content_loss.item(), style_loss.item(), tv_loss.item(), loss.item()))
            time_use = time.time() - since
            print('Train complete in {:.0f}m {:.0f}s'.format(time_use // 60, time_use % 60))
            # 可视化一张图像
            plt.figure()
            im = transformed_images[1, ...] # 省略号表示后面的内容不写了
            plt.axis('off')
            plt.imshow(im_convert(im))
            plt.show()
Epoch: 1
step: 0; content loss: 21.736; style loss: 679.825; tv loss: 17.357, loss: 718.918
Train complete in 0m 10s

在这里插入图片描述

step: 5000; content loss: 11.223; style loss: 4.921; tv loss: 1.068, loss: 17.212
Train complete in 32m 21s

在这里插入图片描述

step: 10000; content loss: 10.715; style loss: 3.768; tv loss: 1.101, loss: 15.584
Train complete in 64m 34s

在这里插入图片描述

Epoch: 2
step: 0; content loss: 12.664; style loss: 3.324; tv loss: 1.182, loss: 17.170
Train complete in 65m 40s

在这里插入图片描述

step: 5000; content loss: 5.582; style loss: 3.621; tv loss: 1.234, loss: 10.438
Train complete in 97m 55s

在这里插入图片描述

step: 10000; content loss: 5.797; style loss: 3.302; tv loss: 1.209, loss: 10.308
Train complete in 130m 11s

在这里插入图片描述

Epoch: 3
step: 0; content loss: 4.639; style loss: 3.312; tv loss: 1.250, loss: 9.201
Train complete in 131m 16s

在这里插入图片描述

step: 5000; content loss: 4.507; style loss: 3.565; tv loss: 1.291, loss: 9.364
Train complete in 163m 32s

在这里插入图片描述

step: 10000; content loss: 4.570; style loss: 3.609; tv loss: 1.098, loss: 9.276
Train complete in 195m 48s

在这里插入图片描述

Epoch: 4
step: 0; content loss: 4.425; style loss: 2.844; tv loss: 1.239, loss: 8.509
Train complete in 196m 46s

在这里插入图片描述

step: 5000; content loss: 6.227; style loss: 4.176; tv loss: 1.231, loss: 11.633
Train complete in 229m 2s

在这里插入图片描述

step: 10000; content loss: 4.537; style loss: 3.191; tv loss: 1.178, loss: 8.906
Train complete in 261m 19s

在这里插入图片描述

# 保存训练好的网络myfwnet
torch.save(myfwnet.state_dict(), './model/imfwnet_dict.pkl')

为了测试训练得到的风格迁移网络 fwnet,下面随机获取数据集中的一个 batch 的图像,进行图像风格迁移:

myfwnet.eval()
for step, batch in enumerate(data_loader):
    content_images = batch[0].to(device)
    if step > 0:
        break
plt.figure(figsize = (16, 4))
for ii in range(4):
    im = content_images[ii, ...]
    plt.subplot(1, 4, ii + 1)
    plt.axis('off')
    plt.imshow(im_convert(im))
plt.show()
transformed_images = myfwnet(content_images)
transformed_images = transformed_images.clamp(-2.1, 2.7)
plt.figure(figsize = (16, 4))
for ii in range(4):
    im = im_convert(transformed_images[ii, ...])
    plt.subplot(1, 4, ii + 1)
    plt.axis('off')
    plt.imshow(im)
plt.show()


在这里插入图片描述
在这里插入图片描述

CPU 上使用预训练好的 GPU 模型

# 读取内容图像
content = load_image('./data/COCO/COCO/COCO_val2014_000000000192.jpg', shape = (256, 256))
# 导入训练好的GPU网络
device = torch.device('cpu')
newfwnet = ImfwNet()
newfwnet.load_state_dict(torch.load('./model/imfwnet_dict.pkl', map_location = device)) # GPU模型映射到基于CPU计算的网络
transform_content = newfwnet(content)
# 可视化图像
plt.figure()
plt.subplot(1, 2, 1)
plt.imshow(im_convert(content))
plt.axis('off')
plt.subplot(1, 2, 2)
plt.imshow(im_convert(transform_content))
plt.axis('off')
plt.show()


在这里插入图片描述

一般而言,普通风格迁移花费时间长(会花费数个小时),但风格迁移效果好。

快速风格迁移非常迅速(网络已训练好,是个 offline 的过程),但效果相对没那么理想。

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

Pytorch:图像风格快速迁移 的相关文章

  • 11.滑动窗口的最大值——重要结构双端队列

    滑动窗口最大 xff08 小 xff09 值 1 滑动窗口最大值结构 窗口概念 xff1a 一开始窗口左边界L 有边界R都停留在数组左侧 xff0c 窗口L和R都只能往数组右边移动 xff0c 并且左边界L永远不能超过有边界R 任何时刻都能
  • 12.单调栈——解决接雨水和柱状图中的最大矩形等问题

    单调栈 1 单调栈实现结构 单调栈解决的问题 xff1a 给你一个数组 想要用尽可能低的代价知道数组中每一个元素的左边元素比它大的或者右边元素比他大的信息是什么 如果用暴力方法 xff0c 左边遍历一次右边遍历一次 xff0c 时间复杂度为
  • 12.快速排序

    1荷兰国旗问题 问题1 xff1a 给定一个数组arr和一个数num xff0c 将小于等于num的数放在数组的左边大于num的数放在数组的右边 xff08 不要求有序 xff09 要求额外空间复杂度为O 1 时间复杂度为O N 遍历数组元
  • 死锁预防、死锁避免、死锁检测

    死锁 1 死锁的概念 1 1死锁的定义 多个进程并发执行 xff0c 由于竞争资源而造成的一种僵局 xff08 互相等待 xff09 xff0c 若无外力作用 xff0c 这些进程都将无法推进 xff0c 这就是死锁现象 例如 xff1a
  • 内存分配方式

    内存分配方式 1 基本概念 内存管理的基本概念 虽然计算机硬件发展 xff0c 内存容量在不断变大 xff0c 但是也不可能将所有用户进程和系统所需要的程序和数据放入内存中 xff0c 因此操作系统必须要对内存空间进行合理划分和有效动态分配
  • 虚拟内存和LRU页面置换算法

    虚拟内存 1 虚拟内存的基本概念 传统存储管理方式的特征 传统的内存管理策略都是为了同时将多个进程保存进内存中 xff0c 它们具有以下的共同特征 xff1a 一次性 作业必须一次性全部装入内存后 xff0c 才能开始运行 xff08 静态
  • 0.0C++和C的区别

    C 43 43 和C的区别 C 43 43 如今是一个同时支持面向过程 面向对象 函数形式 泛型形式 元编程形式的语言 我们该如何理解C 43 43 这门语言呢 xff1f Effective C 43 43 书中给出了一个简单的方法 xf
  • 15.9为什么要将成员变量设置为private

    为什么要将成员变量声明为private 为什么要将成员变量封装为private xff0c 主要有以下四个原因 xff1a 好处1 xff1a 如果成员变量不是public xff0c 那么客户唯一能访问成员变量的唯一方式就是通过成员函数
  • 2.7.C++中static关键字的5种基本用法

    static关键字 static关键字主要应用于以下几种情况 xff1a 情况1 xff1a static静态函数 定义静态函数 xff1a 在函数返回类型前加上static关键字 xff0c 函数即被定义为静态函数 静态函数只能在本源文件
  • 进程调度算法

    进程调度 在多道程序系统中 xff0c 进程数量往往多于处理机的个数 xff0c 因此进程竞争使用处理机的情况在所难免 处理机调度是对处理机进行分配 xff0c 即从就绪队列中按照一定的算法选择一个进程并将处理机分配给它运行 xff0c 以
  • git clone 出现fatal: unable to access ‘https://github 类错误解决方法

    git clone 遇到问题 xff1a fatal unable to access https github comxxxxxxxxxxx Failed to connect to xxxxxxxxxxxxx 问题 将命令行里的http
  • 进程通信的方式

    进程通信 1 进程通信的概念 进程是一个独立的资源分配单元 xff0c 不同进程 xff08 主要是指不同的用户进程 xff09 之间的资源是独立的 xff0c 没有关联的 xff0c 不能在一个进程中直接访问另一个进程的资源 但是 xff
  • 网络通信的过程

    网络通信的过程 封装 上层协议时如何使用下层协议提供的服务的呢 xff1f 其实这是通过封装实现的 应用程序是在发送到物理网络上之前 xff0c 将沿着协议栈从上往下依次传递 每层协议都将在上层数据的基础上加上自己的头部信息 xff08 有
  • TCP三次握手、四次挥手

    TCP通信流程 TCP和UDP TCP和UDP区别如下 xff1a UDP xff1a 用户数据报文协议 xff0c 面向无连接 xff0c 可以单播 xff0c 多播 xff0c 广播 xff0c 面向数据报 xff0c 不可靠 TCP
  • Qt的多线程编程

    Qt线程 基本概念 并发 当有多个线程在操作时 xff0c 如果系统只有一个CPU xff0c 则它根本不可能真正同时进行一个以上的线程 xff0c 它只能把CPU运行时间划分成若干个时间段 xff0c 再将时间段分配给各个线程执行 xff
  • CMake编译C++文件

    这篇文章介绍如何使用cmake工具编译一个最简单的helloworld cpp文件 首先创建一个空的文件夹 mkdir cmake test 在该文件夹下 xff0c 我们新建一个helloworld cpp文件 span class to
  • 智能小车建图导航-在rviz中导航(运行)

    笔记来源 xff1a 机器人开发与实践 xff08 古月 xff09 或者直接运行这个脚本文件 xff1a xff08 如果你没有在 bracsh文件中加入source xff0c 建议加入或者在脚本文件的上面中添加source xff0c
  • 004-S500无人机-相关的器件参数以及计算

    这篇博客主要是记录S500无人机的相关器件的参数 xff0c 参数的来源来源于holybro官网 xff1a https shop holybro com 我这里进行参数的归纳以及计算 一 电机 xff08 2216 880kv xff09
  • TX2 学习记录(开启板载/USB摄像头)

    刚拿到手一个TX2 xff0c 简单地学习一下这块板子 xff0c 因为是学长留下来的板子 xff0c 所以刷机的步骤我就省略了 xff0c 各位小伙伴可以参考其他大佬的博客进行刷机 xff0c 再来就记录一下一些操作指令吧 打开USB摄像
  • ubuntu16.04中进行ROS通信编程

    ROS通信学习 基础知识学习字段ROS通信小例子一 创建一个工作区二 创建一个ROS工程包三 创建通信的发 收节点四 测试程序的正确性 图像ROS通信小例子视频ROS通信小例子多机ROS通信 基础知识学习 x1f31f 话题与服务的区别 话

随机推荐

  • 2021电赛F题智能送药小车方案分析(openMV数字识别,红线循迹,STM32HAL库freeRTOS,串级PID快速学习,小车自动返回)

    2021全国大学生电子设计竞赛F题智能送药小车 前提 xff1a 本篇文章重在分享自己的心得与感悟 xff0c 我们把最重要的部分 xff0c 摄像头循迹 xff0c 摄像头数字识别问题都解决了 xff0c 有两种方案一种是openARTm
  • CARLA常见错误解决方案以及常见的问题解决方案

    记录Linux环境 Windows环境下常见的运行自动驾驶仿真器CARLA出现的错误 问题1 问题1比较基础 xff0c 创建虚拟环境以及删除虚拟环境 conda create span class token operator span
  • cmd找不到conda以及通过cmd启用Anaconda中的Python环境(base)

    问题 xff1a 在cmd中输入python无法进入或启用python ipython conda jupyter notebook 一 解决方法 xff1a 在系统环境中添加Anaconda路径 lt 1 gt 1 打开高级系统设置 xf
  • c语言实现strcat函数

    char strcat char strDestination const char strSource 一 函数介绍 作用 xff1a 连接字符串的函数 xff0c 函数返回指针 xff0c 两个参数都是指针 xff0c 第一个参数所指向
  • C/C++的static关键字作用(转载)

    一 限制符号的作用域只在本程序文件 若变量或函数 xff08 统称符号 xff09 使用static修饰 xff0c 则只能在本程序文件内使用 xff0c 其他程序文件不能调用 xff08 非static的可以通过extern 关键字声明该
  • crc校验

    参考链接 xff1a https www cnblogs com esestt archive 2007 08 09 848856 html 一 CRC校验原理 1 CRC校验全称为循环冗余校验 xff08 Cyclic Redundanc
  • ubuntu安装eclipse教程

    在安装eclipse之前 xff0c 要先安装JDK xff0c 一 安装JDK 1 从官网上下载JDK 链接 xff1a https www oracle com java technologies downloads 选择的jdk文件一
  • UDP通信入门篇

    UDP通信属于网络通信中的一种方式 xff0c 需要用套接字来进行通信 初接触UDP通信时 xff0c 不知道需要链接静态库 pragma comment lib ws2 32 lib xff0c 导致自己在前期浪费了很多时间去排查问题 除
  • window11配置深度学习环境

    Anaconda 43 PyCharm 43 CUDA 43 CUDNN 43 PyTorch 1 Anaconda安装 下载路径 xff1a https www anaconda com 安装方式 xff1a 以管理员身份安装 中间选项
  • python配置opencv环境后,读取图片,报错:can‘t open/read file: check file path/integrity

    运行出错代码 xff1a import cv2 import numpy as np image 61 cv2 imread 39 C Pictures 桌面背景图片切换 wallhaven 6oq1k7 jpg 39 cv2 IMREAD
  • 断言

    代码中放置一些假设 xff0c 通过判断假设是否为真 xff0c 进而判断程序是否正确 断言就是用来测试程序中的假设是否正确的 xff0c 若果假设被违反 xff0c 那么就中断程序的执行 断言assert是定义在assert h中的 宏
  • STM32输出SPWM波,HAL库,cubeMX配置,滤波后输出1KHz正弦波

    SPWM波 对于功率方向 输出SPWM波是必须要掌握的 工程 stm32生成spwm代码Keil工程链接资源 引用spwm波定义 PWM波形就是指占空比可变的波形 SPWM波形是指脉冲宽度按正弦规律变化且和正弦波等效的PWM波形 两者的区别
  • C语言链表写法,练习链表

    C语言链表写法 xff0c 练习链表 建立要一个文件 xff1a LinkList h 内容 xff1a span class token macro property span class token directive keyword
  • 树莓派摄像头 C++ OpenCV YoloV3 实现实时目标检测

    树莓派摄像头 C 43 43 OpenCV YoloV3 实现实时目标检测 本文将实现树莓派摄像头 C 43 43 OpenCV YoloV3 实现实时目标检测 xff0c 我们会先实现树莓派对视频文件的逐帧检测来验证算法流程 xff0c
  • RTK定位原理

    一 卫星测距原理说明 天上的卫星发送数据被便携式RTK终端接收到 xff0c 卫星和终端之间的距离D 61 C T C为光速 xff0c T为卫星发送的信号到达便携式RTK终端的时间 xff0c 通过时间乘以距离可以获得卫星和便携式终端的实
  • 网络协议与网络编程(单电脑信息传输)

    C C 43 43 网络编程 单电脑信息传输 Copyright Jingmin Wei Pattern Recognition and Intelligent System School of Artificial and Intelli
  • Pytorch:全连接神经网络-MLP回归

    Pytorch 全连接神经网络 解决 Boston 房价回归问题 Copyright Jingmin Wei Pattern Recognition and Intelligent System School of Artificial a
  • Pytorch:卷积神经网络-空洞卷积

    Pytorch 空洞卷积神经网络 Copyright Jingmin Wei Pattern Recognition and Intelligent System School of Artificial and Intelligence
  • Pytorch:目标检测网络-人体关键点检测

    Pytorch 目标检测网络 人体关键点检测 Copyright Jingmin Wei Pattern Recognition and Intelligent System School of Artificial and Intelli
  • Pytorch:图像风格快速迁移

    Pytorch 图像风格快速迁移 残差网络 xff0c 固定风格任意内容 Copyright Jingmin Wei Pattern Recognition and Intelligent System School of Artifici