Pytorch实现FCN图像语义分割网络

news2025/1/18 11:56:14

针对图像的语义分割网络,本节将介绍PyTorch中已经预训练好网络的使用方式,然后使用VOC2012数据集训练一个FCN语义分割网络。

一、使用预训练好的语义分割网络

PyTorch提供了已预训练好的图像语义分割网络,已经预训练好的可供使用的网络模型如下表所示:

网络类描述
segmentation.fcn_resnet50()具有Resnet-50结构的全卷积网络模型
segmentation.fcn_resnet101()具有Resnet-101结构的全卷积网络模型
segmentation.deeplabv3_resnet50()具有Resnet-50结构的DeepLabV3网络模型
segmentation.deeplabv3_resnet101()具有Resnet-101结构的DeepLabV3网络模型

下面以segmentation.fcn_resnet101()为例,介绍如何使用这些已经预训练好的网络结构进行图像的语义分割任务。

针对语义分割的分类器,需要输入图像使用了相同的预处理方式,即先将每张图像的像素值预处理到0 ~ 1之间,然后对图像进行标准化处理,使用的均值为[0.485,0.456,0.406],标准差为[0.229,0.224,0.225]。。数据集使用Pascal VOC 数据集,该数据集中存在20个类别和1个背景类,预训练好的模型在COCO train2017的子集上进行了预训练。这20个类别分为4个大类,分别为人、动物(鸟、猫、牛、马、羊)、交通工具(飞机、自行车、船、大巴、轿车、摩托车、火车)、室内物品(瓶子、椅子、餐桌、盆栽、沙发、显示器)等。

import torch
import torchvision
from torchvision import transforms
import numpy as np
import pandas as pd
import PIL
import PIL.Image as Image
import matplotlib.pyplot as plt
import os
os.environ["KMP_DUPLICATE_LIB_OK"]="TRUE"
model=torchvision.models.segmentation.fcn_resnet101(pretrained=True)
model.eval()#设置为验证模式
#下面从文件中读取一张图片进行预测
image=Image.open(r'C:\Users\zex\Downloads\VOCdevkit\VOC2012\JPEGImages\2012_001460.jpg')
image_trans=transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225]),
    ])
image_tensor=image_trans(image).unsqueeze(0)
output=model(image_tensor)['out']
#将输出转化为二维图像
outputarg=torch.argmax(output.squeeze(),dim=0).numpy()

上述程序对一整幅图像的预测结果,只需要使用网络输出的"out"对应的预测矩阵即可,该输出是一个三维矩阵,该三维矩阵可以使用torch.argmax()将其转化为二维矩阵,并且该二维矩阵中的每个取值均代表图像中对应位置像素点的预测类别。为了更直观地查看网络的图像分割结果,可以将像素值的每个预测类别分别编码为不同的颜色,然后将图像可视化,用于直观地观察图像的结果。
定义一个编码颜色的函数decode_segmaps(),程序如下所示: 

def decode_segmaps(image,label_colors,nc=21):
"""函数将输出的2D图像,会将不同的类编码为不同的颜色"""
    r=np.zeros_like(image).astype(np.uint8)
    g=np.zeros_like(image).astype(np.uint8)
    b=np.zeros_like(image).astype(np.uint8)
    for cla in range(0,nc):
        idx=image==cla
        r[idx]=label_colors[cla,0]
        g[idx]=label_colors[cla,1]
        b[idx]=label_colors[cla,2]
    rgbimage=np.stack([r,g,b],axis=2)
    return rgbimage

该函数通过参数label_colors来指定所有的颜色编码,然后对图像image中的不同像素点取值并定义一种颜色,nc参数指定数据的类别。下面对图像分割的结果进行可视化,程序如下所示:

label_colors=np.array([(0,0,0),(128,0,0),(0,128,0),(128,128,0),(0,0,128),(128,0,128),
                       (0,128,128),(128,128,128),(64,0,0),(192,0,0),(64,128,0),
                       (192,168,0),(64,0,128),(192,0,128),(64,128,128),(192,128,128),
                       (0,64,0),(128,64,0),(0,192,0),(128,192,0),(0,64,128)])
outputrgb=decode_segmaps(outputarg,label_colors)
plt.figure(figsize=(20,8))
plt.subplot(1,2,1)
plt.imshow(image)
plt.axis("off")
plt.subplot(1,2,2)
plt.imshow(outputrgb)
plt.axis("off")
plt.subplots_adjust(wspace=0.05)
plt.show()


上面的程序中label_colors参数定义了每种类别需要使用的颜色编码,图像分割结果如下所示:

 二、训练自己的语义分割网络

前面介绍的是使用预训练好的语义分割网络segmentation.fcn_resnet101(),对任意输入图像进行语义分割,该模型是以101层的ResNet网络为基础,全卷积语义分割模型。下面将基于VGG19网络,搭建、训练和测试自己的图像全卷积语义分割网络。
由于资源有限,将基于2012年VOC数据集对网络进行训练,主要使用该数据集的训练集和验证集,训练集用于训练网络,验证集防止网络过拟合。每个数据集约有1000张图片,并且图像之间的尺寸不完全相同,数据集共有21类需要学习的目标类别。

1.数据准备

针对VOC2012数据集,一共需要分割出的目标类别有21类,其中一类为背景。在标注好的图像中,每类对应的名称和颜色值如下:

classes=['background','aeroplane','bicycle','bird','boat','bottle','bus','car','cat','chair',
         'cow','diningtable','dog','horse','motorbike','person','potted plant','sheep','sofa',
         'train','tv/monitor']
colormap=[[0,0,0],[128,0,0],[0,128,0],[128,128,0],[0,0,128],
          [128,0,128],[0,128,128],[128,128,128],[64,0,0],[192,0,0],
          [64,128,0],[192,128,0],[64,0,128],[192,0,128],
          [64,128,128],[192,128,128],[0,64,0],[128,64,0],
          [0,192,0],[128,192,0],[0,64,128]]

数据预处理需要对每张图像进行如下几个操作:

(1)将原始图像和标记好的图像所对应的图片路径一一对应。

(2)将图像统一切分为固定的尺寸时,需保持原始图像和其对应的标记好的图像,在切分后每个像素也仍然是一一对应的,所以需要对原始图像和目标的标记图像从相同的位置进行切分。在切分之前还需要过滤掉尺寸小于给定切分尺寸的图像。

(3)对原始图像进行数据标准化。

(4)针对标记好的图像,每张图像均是RGB图像,将RGB值对应的类重新定义,把3D的RGB图像转化为一个二维数据,并且数组中每个位置的取值对应着图像在该像素点的类别。

为了完成上述的图像预处理操作,定义下面几个图像数据预处理的辅助函数。

#给一个标定好的图片,将像素值对应的物体类别找出来
def image2label(image,colormap):
    # 将标签转化为每个像素值为一类
    cm2lbl=np.zeros(256**3)
    for i,cm in enumerate(colormap):
        cm2lbl[(cm[0]*256+cm[1]*256+cm[2])]=i
    #对一张图像转换
    image=np.array(image,dtype='int64')
    ix=(image[:,:,0]*256+image[:,:,1]*256+image[:,:,2])
    image2=cm2lbl[ix]
    return image2

image2label函数可以将一张标记好的图像转化为类别标签图像。该函数完成的任务对应着上述操作(4)。 

#随机裁剪图像数据
def rand_crop(data,label,high,width):
    im_width,im_high=data.size
    #生成图像随机点的位置
    left=np.random.randint(0,im_width-width)
    top=np.random.randint(0,im_high-high)
    right=left+width
    bottom=top+high
    data=data.crop((left,top,right,bottom))
    label=label.crop((left,top,right,bottom))
    return data,label

rand_crop函数完成对原始图像数据和被标注的标签图像进行随机裁剪的任务,随机裁剪后的原图像和标签的每个像素一一对应。可通过参数high和width指定图像裁剪后的高和宽。

#单组图像的转换操作
def img_transforms(data,label,high,width,colormap):
    data,label=rand_crop(data,label,high,width)
    data_tfs=transforms.Compose([
        transforms.ToTensor(),
        transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
    ])
    data=data_tfs(data)
    label=torch.from_numpy(image2label(label,colormap))
    return data,label

img_transforms函数是对一组图像数据进行相关变换和预处理操作,包括数据的随机裁剪、将图像数据进行标准化、将标记图像数据进行二维标签化的操作,并且最后输出原始图像和类别标签的张量数据。

#定义读取数据路径的函数
def read_image_path(root=r"C:\Users\zex\Downloads\VOCdevkit\VOC2012\ImageSets\Segmentation\train.txt"):
    '''保存指定路径下的所有需要读取的图像文件路径'''
    image=np.loadtxt(root,dtype=str)
    n=len(image)
    data,label=[None]*n,[None]*n
    for i,fname in enumerate(image):
        data[i]="C:/Users/zex/Downloads/VOCdevkit/VOC2012/JPEGImages/%s.jpg"%(fname)
        label[i]='C:/Users/zex/Downloads/VOCdevkit/VOC2012/SegmentationClass/%s.png'%(fname)
    return data,label

read_image_path函数是从给定的文件路径中定义出对应的原始图像和标记好的目标图像的存储路径列表。原始图像路径输出为data,标记好的目标图像路径输出为label。
为了将数据定义为数据加载器Data.DataLoader()函数可以接受的数据格式,在定义好上述几个辅助函数后,则需要定义一个类操作,该类需要继承torch.utils.data.Dataset类,这样就可以将自己的数据定义为数据加载器操作Data.DataLoader()函数可以接受的数据格式。程序如下所示:

#定义一个MyDataset
class MyDataset(Data.Dataset):
    """用于读取图像,并进行相应的裁剪"""
    def __init__(self,data_root,high,width,imtransform,colormap):
        # data_root:数据所对应的文件名,high\width:图像剪裁后的尺寸
        # imtransform:预处理操作
        # colormap:颜色
        self.data_root=data_root
        self.high=high
        self.width=width
        self.imtransform=imtransform
        self.colormap=colormap
        data_list,label_list=read_image_path(root=data_root)
        self.data_list=self._filter(data_list)
        self.label_list=self._filter(label_list)
    def _filter(self,images):
        #过滤掉图片大小小于指定high\width的图片
        return [im for im in images if (Image.open(im).size[1]>high and Image.open(im).size[0]>width)]
    def __getitem__(self, idx):
        img=self.data_list[idx]
        label=self.label_list[idx]
        img=Image.open(img)
        label=Image.open(label).convert('RGB')
        img,label=self.imtransform(img,label,self.high,self.width,self.colormap)
        return img,label
    def __len__(self):
        return len(self.data_list)
    

在上面定义的类MyDataset包含了一个_filter方法,该方法用于过滤掉图像的尺寸小于固定切分尺寸的样本。在类中每张图像的读取通过Image.open()函数完成。
下面使用MyDataset()函数读取数据集的原始数据和对应的标签数据,然后使用Data.DataLoader()函数建立数据加载器,并且每个batch中包含8张图像,程序如下所示:

#读取数据
high,width=320,480
voc_train=MyDataset(r"C:\Users\zex\Downloads\VOCdevkit\VOC2012\ImageSets\Segmentation\train.txt",
                    high,width,img_transforms,colormap)
voc_val=MyDataset(r"C:\Users\zex\Downloads\VOCdevkit\VOC2012\ImageSets\Segmentation\val.txt",
                  high,width,img_transforms,colormap)
#创建数据加载器每个batch使用4张图像
train_loader=Data.DataLoader(voc_train,batch_size=8,shuffle=True,pin_memory=True)
val_loader=Data.DataLoader(voc_val,batch_size=8,shuffle=True,pin_memory=True)


#检查一个训练集的batch的样本的维度是否正确
for step,(b_x,b_y) in enumerate(train_loader):
    if step>0:
        break
print("b_x.shape",b_x.shape)
print('b_y.shape',b_y.shape)

从一个batch的图像尺寸输出中可以看出,训练数据中的b_x包含8张320×480的RGB图像,而b_y则包含8张320×480的类别标签数据。下面可以将一个batch的图像和其标签进行可视化,以检查数据是否预处理正确,在可视化之前需要定义两个预处理函数,即inv_normalize_image()和label2image()。

#将标准化后的图像转化为0-1之间
def inv_normalize_image(data):
    rgb_mean=np.array([0.485,0.456,0.406])
    rgb_std=np.array([0.229,0.224,0.225])
    data=data.astype('float32')*rgb_std+rgb_mean
    return data.clip(0,1)
#从预测的标签转化为图像的操作
def label2image(prelabel,colormap):
    #预测到的标签转化为图像,针对一个标签图
    h,w=prelabel.shape
    prelabel=prelabel.reshape(h*w,-1)
    image=np.zeros((h*w,3),dtype='int32')
    for i in range(len(colormap)):
        index=np.where(prelabel==i)
        image[index,:]=colormap[i]
    return image.reshape(h,w,3)

在上面的两个函数中,inv_normalize_image函数用于将标准化后的原始图像进行逆标准化操作,可方便对图像数据进行可视化;而label2image函数则是将二维的类别标签数据转化为三维的图像分割后的数据,不同的类别转化为特定的RGB值。下面针对一个batch的图像进行可视化操作,程序如下所示:

#可视化一个batch的图像
b_x_numpy=b_x.data.numpy()
b_x_numpy=b_x_numpy.transpose(0,2,3,1)
b_y_numpy=b_y.data.numpy()
plt.figure(figsize=(32,12))
for i in range(8):
    plt.subplot(2,8,i+1)
    plt.imshow(inv_normalize_image(b_x_numpy[i]))
    plt.axis('off')
    plt.subplot(2,8,i+9)
    plt.imshow(label2image(b_y_numpy[i],colormap))
    plt.axis("off")
plt.subplots_adjust(wspace=0.5,hspace=0.5)
plt.show()

 2.搭建网络

搭建全卷积语义分割时,基础网络是预训练的VGG19网络,而且不需要使用全连接层,该网络可以直接从torchvision库中导入。下面导人VGG19网络,并对其网络结构进行简单的分析,程序如下:

model_vgg19=vgg19(pretrained=True)
base_model=model_vgg19.features

下面搭建基于FCN-8s的语义分割网络,通过将网络中间的输出联合起来进行转置卷积,从而获得更多有用的语义分割信息,所以可以得到更好的语义分割结果。其操作方式可以使用下图展示。

 上图展示了不同的FCN语义分割操作方法,其中FCN-32s就是将最后的卷积或池化结果通过转置卷积,直接将特征映射的尺寸扩大32倍进行输出,而FCN-16s则是联合前面一次的结果将特征映射进行16倍的放大输出,而FCN-8s是联合前面两次的结果,通过转置卷积将特征映射的尺寸进行8倍的放大输出。在FCN-8s中将进行以下的操作步骤:
(1)将最后一层的特征映射P5(在VGG19中是第5个最大值池化层)通过转置卷积扩大2倍,得到新的特征映射T5,并和pool4的特征映射P4相加可得到T5+P4。
(2)将T5+P4通过转置卷积扩大2倍得到T4,然后与pool3的特征映射P3相加得到T4+P3。
(3)通过转置卷积,将特征映射T4+P3的尺寸扩大8倍,得到和输入形状一样大的结果。
下面搭建语义分割网络FCN-8s,用于图像的语义分割,程序如下所示:

#定义FCN分割网络
class FCN8s(nn.Module):
    def __init__(self,num_classes):
        super(FCN8s, self).__init__()
        #num_classes:训练数据的类别
        self.num_classes=num_classes
        model_vgg19=vgg19(pretrained=True)
        #不使用VGG19网络中后面的AdaptiveAvgPool2d和Linear层
        self.base_model=model_vgg19.features
        #定义几个需要的层操作,并且使用转置卷积将特征映射进行升维度
        self.relu=nn.ReLU(inplace=True)
        self.deconv1=nn.ConvTranspose2d(512,512,kernel_size=3,stride=2,padding=1,dilation=1,output_padding=1)
        self.bn1=nn.BatchNorm2d(512)
        self.deconv2=nn.ConvTranspose2d(512,256,3,2,1,1,1)
        self.bn2=nn.BatchNorm2d(256)
        self.deconv3=nn.ConvTranspose2d(256,128,3,2,1,1,1)
        self.bn3=nn.BatchNorm2d(128)
        self.deconv4=nn.ConvTranspose2d(128,64,3,2,1,1,1)
        self.bn4=nn.BatchNorm2d(64)
        self.deconv5=nn.ConvTranspose2d(64,32,3,2,1,1,1)
        self.bn5=nn.BatchNorm2d(32)
        self.classifier=nn.Conv2d(32,num_classes,kernel_size=1)
        #VGG19中MaxPool2D层
        self.layers={
            '4':'maxpool_1',
            '9':'maxpool_2',
            '18':'maxpool_3',
            '27':'maxpool_4',
            '36':'maxpool_5'
        }
    def forward(self,x):
        output={}
        for name,layer in self.base_model._modules.items():
            # 从第一层开始获取图像特征
            x=layer(x)
            #如果是layers参数指定的特征,那就保存到output中
            if name in self.layers:
                output[self.layers[name]]=x
        x5=output['maxpool_5']
        x4=output['maxpool_4']
        x3=output['maxpool_3']
        #对特征进行相关的卷积操作
        score=self.relu(self.deconv1(x5))
        score=self.bn1(score+x4)
        score=self.relu(self.deconv2(score))
        score=self.bn2(score+x3)
        score=self.bn3(self.relu(self.deconv3(score)))
        score=self.bn4(self.relu(self.deconv4(score)))
        score=self.bn5(self.relu(self.deconv5(score)))
        score=self.classifier(score)
        return score
    

上述的语义分割网络类FCN-8s是基于VGG19建立的,且在网络的前向传播中,分别保存网络在最大值池化层的输出,方便后面对相应层输出的使用,该类使用时需要输入一个参数num_classes,用于表示网络需要分类的数量。

3、网络训练和测试

使用训练集对网络FCN-8s进行训练,使用验证集监督网络的训练过程,定义train_model()函数,该函数按照指定的优化方法,使用相关数据对网络模型训练一定的次数,并输出训练过程中最优的网络模型。函数的程序如下所示:

def train_model(model,criterion,optimizer,traindataloader,valdataloader,num_epochs=25):
    since=time.time()
    best_model_wts=copy.deepcopy(model.state_dict())
    best_loss=1e10
    train_loss_all=[]
    train_acc_all=[]
    val_loss_all=[]
    val_acc_all=[]
    since=time.time()
    for epoch in range(num_epochs):
        print("Epoch {} / {}".format(epoch,num_epochs-1))
        print('-'*10)
        train_loss=0.0
        train_num=0
        val_loss=0.0
        val_num=0
        #每个epoch包括训练和验证阶段
        model.train()#设置模型为训练模式
        for step,(b_x,b_y) in enumerate(traindataloader):
            optimizer.zero_grad()
            b_x=b_x.float().to(device)
            b_y=b_y.long().to(device)
            out=model(b_x)
            out=F.log_softmax(out,dim=1)
            pre_lab=torch.argmax(out,1)#预测的标签
            loss=criterion(out,b_y)
            loss.backward()
            optimizer.step()
            train_loss += loss.item() * len(b_y)
            train_num += len(b_y)
        #计算一个epoch在训练集上的损失和精度
        train_loss_all.append(train_loss / train_num)
        print('{} Train loss : {:.4f}'.format(epoch,train_loss_all[-1]))
        #计算一个epoch训练后在验证集上的损失
        model.eval()#设置模式为评估模式
        for step,(b_x,b_y) in enumerate(valdataloader):
            b_x=b_x.float().to(device)
            b_y=b_y.long().to(device)
            out=model(b_x)
            out=F.log_softmax(out,dim=1)
            pre_lab=torch.argmax(out,1)
            loss=criterion(out,b_y)
            val_loss+=loss.item() * len(b_y)
            val_num+=len(b_y)
        val_loss_all.append(val_loss /val_num)
        print('{} Val loss : {:.4f}'.format(epoch,val_loss_all[-1]))
        #保存最好的网络参数
        if val_loss_all[-1]<best_loss:
            best_loss=val_loss_all[-1]
            best_model_wts=copy.deepcopy(model.state_dict())
        #每个epoch的花费时间
        time_use=time.time()-since
        print("Train and val complete in {:.0f}m {:.0f}s".format(time_use // 60,time_use % 60))
    train_process=pd.DataFrame(
        data={'epoch':range(num_epochs),
              'train_loss_all':train_loss_all,
              'val_loss_all':val_loss_all,
              })
    model.load_state_dict(best_model_wts)
    return model,train_process

下面定义优化方法和损失函数,并调用函数对网络进行训练,在网络训练结束后,通过折线图将网络在训练过程中的损失函数变化情况进行可视化,程序如下所示。

criterion=nn.NLLLoss()
optimizer=optim.Adam(fcn8s.parameters(),lr=0.0003,weight_decay=1e-4)
#对模型进行迭代训练,对所有数据训练epoch轮
fcn8s,train_process=train_model(fcn8s,criterion,optimizer,train_loader,val_loader,num_epochs=30)
#保存训练好的网络fcn8s
torch.save(fcn8s,'fcn8s.pkl')
#可视化训练过程
plt.figure(figsize=(10,6))
plt.plot(train_process.epoch,train_process.train_loss_all,'ro-',label='Train loss')
plt.plot(train_process.epoch.train_process.val_loss_all,'bs-',label="Val loss")
plt.legend()
plt.xlabel('epoch')
plt.ylabel('Loss')
plt.show()

下面使用训练好的网络,从验证集中获取一个batch的图像,对其进行语义分割,将得到的结果和人工标注的结果进行对比,可使用下面的程序进行可视化,得到如下图所示的结果。 

 

 上图第一行所示为原始的RGB图像,第二行所示为人工标注的语义分割图像,第三行所示为网络对图像的分割结果。从对比图中可以看出网络虽然可以分割出一些目标,但是在精度上并不是很高,还有很大的提升空间。这与我们使用的基础网络深度不够、使用的训练数据较少有关。

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

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

相关文章

Java 对象的创建过程面试总结

Java对象创建的过程 Java对象创建的过程主要分为五个步骤&#xff0c;下面我将详细介绍这五个步骤。 Step1:类加载检查 虚拟机遇到一条new指令时&#xff0c;首先会去检查这个指令的参数是否能在class文件中的常量池中定位到这个类的符号引用&#xff0c;并且会检查这个符号…

unplugin-vue-components 源码原理分析

unplugin-vue-components 是一款按需自动导入Vue组件的库。支持 Vue2 和 Vue3&#xff0c;同时支持组件和指令。使用此插件库后&#xff0c;不再需要手动导入组件&#xff0c;插件会自动识别按需导入组件以及对应样式&#xff0c;我们只需要像全局组件那样使用即可。 当然上面…

深入谈谈内存压缩那些事!

1. 技术背景 说到压缩这个词&#xff0c;我们并不陌生&#xff0c;应该都能想到是降低占用空间&#xff0c;使同样的空间可以存放更多的东西&#xff0c;类似于我们平时常用的文件压缩,内存压缩同样也是为了节省内存。 尽管当前android手机6GB&#xff0c;8GB甚至12GB的机器都…

两个月15斤以上的健康减脂减重法,与饥饿、运动等无关的自我实验的验证方法(第六篇完结,无收费内容)...

阅读本文前请先看前五篇内容&#xff0c;本文有部分修改&#xff0c;但是涉及前五篇的内容会大量隐藏&#xff0c;只保留关联修改部分&#xff0c;链接如下&#xff1a; 两个月15斤以上的健康减脂减重法&#xff0c;与饥饿、运动等无关的自我实验的验证方法&#xff08;第五篇&…

OpenCascade安装编译

重新编译OpenCascade&#xff0c;在漫长的等待过程中&#xff0c;记录一下编译的流程 下载安装 OpenCascade官网中提供了直接安装的二进制版本&#xff0c;如果只是简单的使用需求可以直接下载安装&#xff0c;二进制版本使用VC 2017 64 bit编译 官网地址 源码编译 源码编…

Docker容器:docker基础

目录 一、docker容器简介 1、什么是容器 2、容器的优点 3、什么是docker容器 4、docker的logo及设计宗旨 5、docker与虚拟机对比 6、docker容器2个重要技术 7、docker三大核心概念 二、docker的安装及管理 1、安装docker 2、配置docker加速器 3、docker镜像相关基础…

你为什么从上一家公司离职?程序员这样回答最机智

想必每一位跳槽的程序员伙伴&#xff0c;在面试时都会被问到这个问题吧&#xff1a; “为什么从上一家公司离职&#xff1f;” 可能不少人跳槽的原因都是钱少事多离家远&#xff0c;加班干到十二点&#xff0c;同事之间还内卷&#xff0c;但是这些原因在面试的时候都能说吗&a…

软件测试培训

软件测试培训 软件测试培训是一门针对软件测试人才培养的技术&#xff0c;培训内容涉及到&#xff1a;软件测试基础、自动化测试、性能测试、安全测试、接口测试、云计算测试等。就业方向&#xff1a;软件开发工程师&#xff0c;软件测试工程师&#xff0c; web前端工程师&…

Docker概念|容器|镜像|命令详细(创建,删除,修改,添加)

Docker概念|容器|镜像|命令详细&#xff08;创建&#xff0c;删除&#xff0c;修改&#xff0c;添加&#xff09; 一&#xff0c;Docker简介二&#xff0c;Docker与虚拟机的区别三&#xff0c;容器核心技术四&#xff0c;Docker核心概念五 docker的安装5.1关闭防火墙,关闭文件防…

2022年 团体程序设计天梯赛——题解集

Hello各位童学大家好&#xff01;&#x1f60a;&#x1f60a;&#xff0c;茫茫题海你我相遇即是缘分呐&#xff0c;或许日复一日的刷题已经让你感到疲惫甚至厌倦了&#xff0c;但是我们真的真的已经达到了我们自身极限了吗&#xff1f;少一点自我感动&#xff0c;没有结果前别太…

大悦城中粮物业:用二维码搭建物业系统,“多快好省”提高管理效率

中粮物业&#xff08;大悦服务&#xff09;隶属于大悦城控股集团股份有限公司&#xff0c;成立于1993年6月&#xff0c;是中国物业管理协会会员单位、广东省物业管理行业协会常务理事单位。 作为连续多年蝉联“中国物业服务百强企业”的头部物业公司&#xff0c;中粮物业自主研…

OMRON Sysmac Studio如何将PDO参数复制到其他轴

Sysmac Studio如何将PDO参数复制到其他轴 实验时间&#xff1a;2023/4/19 实验设备&#xff1a;Sysmac Studio(ver 1.53)、EveryThing搜索工具 实验目的&#xff1a;将轴分配的PDO参数复制到其他轴 一. 实验概述 ​ 当PLC当很多相同的伺服轴时&#xff0c;轴的PDO映射是完全…

ESP32在ESP-IDF框架下使用LVGL(v8.3)

开发环境 VSCodeESP-IDF插件 说明&#xff1a;IDF版本为4.4.4&#xff0c;最新版的5.0.1弃用了些东西&#xff0c;而lvgl_esp32_drivers对5以上的版本未适配&#xff0c;所以不建议使用5以上的版本。 安装&#xff1a;安装教程&#xff0c;建议整体看完在进行安装&#xff0c;以…

解决vue-print-nb打印时多出一页空白,vue-print-nb打印有空白页

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 打印table表格&#xff0c;需要用到vue-print-nb插件&#xff0c;使用该插件可以一键弹窗打印页面 效果如图 问题描述 提示&#xff1a;这里描述项目中遇到的问题&#xff1a; 查了一下&#xff…

斩获“双金”!玻色量子在中国移动第七届创客马拉松大赛脱颖而出

​4月7日&#xff0c;中国移动第七届创客马拉松大赛总决赛在厦门圆满落幕。此次大赛以“能力无界 智算同行”为主题&#xff0c;经过近4000个创新项目的层层选拔&#xff0c;玻色量子凭借“相干量子计算设备”项目脱颖而出&#xff0c;成功摘取“双金”&#xff1a;总决赛全球通…

Qt5.12实战之菜单栏工具栏与状态栏使用

演示效果: 1.widget工程创建 2.ui设计 双击打开下图所示的.ui文件 菜单设计: 添加子菜单 修改QMenu及QAction对象名称为可识别名 具体修改操作如下: QMenu和QAction类似 在QAction列表中修改QAction对象 具体修改方法如下: 为QAction也就是菜单项目添加点击事件处理: 选择信号…

公网远程访问连接Minecraft我的世界服务器 - MCSM控制面板

文章目录 概述1.MCSManager 安装2.内网穿透2.1 安装cpolar内网穿透 3. 访问公网地址4.固定公网地址4.1 保留一个二级子域名4.2 配置固定二级域名4.3 访问固定公网地址 5. 设置节点公网地址6. 固定节点公网地址6.1 保留一个固定tcp地址6.2 配置固定TCP地址 概述 MCSManager 是一…

After Effects 2022(AE 2022)forMac/win图文安装教程

After Effects简称“AE”是Adobe公司推出的一款图形视频处理软件&#xff0c;适用于从事设计和视频特技的机构&#xff0c;包括电视台、动画制作公司、个人后期制作工作室以及多媒体工作室。属于层类型后期软件。可以帮助您高效且精确地创建无数种引人注目的动态图形和震撼人心…

NPDP认证|B端产品经理是如何做竞品调研的?

做竞品的目的主要是为了对比和测试。 对方比我好的地方我规避&#xff0c;发挥我自己的产品优势&#xff0c;对方差的地方我要切入&#xff0c;把他打败。 通过竞品分析后&#xff0c;企业可以确定我们进入市场的机会&#xff0c;也为后期进行产品测试提供有效的分析依据。 那么…

9.4 数组的指针和指向数组的指针变量-1

9.4 数组的指针和指向数组的指针变量-1 一.指向数组元素的指针变量的定义和赋值二.通过指针引用数组元素1.情况12.情况23.情况34.情况45.情况5注意事项&#xff1a; 情况6&#xff1a;*p 优先级相同&#xff0c;并且都是从右到左的结合性&#xff0c;所以 *(p)情况7&#xff1a…