pyTorch入门(四)——导出Minist模型,C++ OpenCV DNN进行识别

news2024/11/24 7:31:05

学更好的别人,

做更好的自己。

——《微卡智享》

e214a2586127e1b41a1c29db8e3f9c19.jpeg

本文长度为2548,预计阅读8分钟

前言

前三章介绍了pyTorch训练的相关,我们也保存模型成功了,今天这篇就是使用C++ OpenCV的DNN模块进行手写图片的推理。

5211dde6c655301afad658ae000adb46.png

实现效果

d3be349de5811e45e58938ad1e4ec878.png

018f68be7cb451ce71d7a8a51d9d700f.png

导出的推理模型使用的是Minist中训练预测率为99%的ResNet模型,从上面两张图来看,大部分数字识别是没问题的,但是两张图中数字7都识别为数字1了。这个暂时不是本篇要解决的问题,我们先看看怎么实现的导出模型和推理。

微卡智享

导出模型

5d8dfe45784573f1b00d0d65a5a6d67f.png

由于不想再重新写一篇网络模型了,所以将原来train.py中的加载训练集和测试集,网络模型等都改为trainmodel.py里面了。然后新建了一个traintoonnx.py的文件,用于导出ONNX的模型文件。接下来直接放源码,说重点

train.py

import torch
import time
import torch.optim as optim
import matplotlib.pyplot as plt
from pylab import mpl
import trainModel as tm


##训练轮数
epoch_times = 10


##设置初始预测率,用于判断高于当前预测率的保存模型
toppredicted = 0.0


##设置学习率
learnrate = 0.01 
##设置动量值,如果上一次的momentnum与本次梯度方向是相同的,梯度下降幅度会拉大,起到加速迭代的作用
momentnum = 0.5


##生成图用的数组
##预测值
predict_list = []
##训练轮次值
epoch_list = []
##loss值
loss_list = []


model = tm.Net(tm.train_name)
##加入判断是CPU训练还是GPU训练
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
model.to(device)


##优化器 
optimizer = optim.SGD(model.parameters(), lr= learnrate, momentum= momentnum)
# optimizer = optim.NAdam(model.parameters(), lr= learnrate)


##训练函数
def train(epoch):
    running_loss = 0.0
    current_train = 0.0
    model.train()
    for batch_idx, data in enumerate(tm.train_dataloader, 0):
        inputs, target = data
        ##加入CPU和GPU选择
        inputs, target = inputs.to(device), target.to(device)


        optimizer.zero_grad()


        #前馈,反向传播,更新
        outputs = model(inputs)
        loss = model.criterion(outputs, target)
        loss.backward()
        optimizer.step()


        running_loss += loss.item()
        ##计算每300次打印一次学习效果
        if batch_idx % 300 == 299:
            current_train = current_train + 0.3
            current_epoch = epoch + 1 + current_train
            epoch_list.append(current_epoch)
            current_loss = running_loss / 300
            loss_list.append(current_loss)


            print('[%d, %5d] loss: %.3f' % (current_epoch, batch_idx + 1, current_loss))
            running_loss = 0.0




def test():
    correct = 0 
    total = 0
    model.eval()
    ##with这里标记是不再计算梯度
    with torch.no_grad():
        for data in tm.test_dataloader:
            inputs, labels = data
            ##加入CPU和GPU选择
            inputs, labels = inputs.to(device), labels.to(device)




            outputs = model(inputs)
            ##预测返回的是两列,第一列是下标就是0-9的值,第二列为预测值,下面的dim=1就是找维度1(第二列)最大值输出
            _, predicted = torch.max(outputs.data, dim=1)


            total += labels.size(0)
            correct += (predicted == labels).sum().item()


    currentpredicted = (100 * correct / total)
    ##用global声明toppredicted,用于在函数内部修改在函数外部声明的全局变量,否则报错
    global toppredicted
    ##当预测率大于原来的保存模型
    if currentpredicted > toppredicted:
        toppredicted = currentpredicted
        torch.save(model.state_dict(), tm.savemodel_name)
        print(tm.savemodel_name+" saved, currentpredicted:%d %%" % currentpredicted)


    predict_list.append(currentpredicted)    
    print('Accuracy on test set: %d %%' % currentpredicted)        


##开始训练
timestart = time.time()
for epoch in range(epoch_times):
    train(epoch)
    test()
timeend = time.time() - timestart
print("use time: {:.0f}m {:.0f}s".format(timeend // 60, timeend % 60))






##设置画布显示中文字体
mpl.rcParams["font.sans-serif"] = ["SimHei"]
##设置正常显示符号
mpl.rcParams["axes.unicode_minus"] = False


##创建画布
fig, (axloss, axpredict) = plt.subplots(nrows=1, ncols=2, figsize=(8,6))


#loss画布
axloss.plot(epoch_list, loss_list, label = 'loss', color='r')
##设置刻度
axloss.set_xticks(range(epoch_times)[::1])
axloss.set_xticklabels(range(epoch_times)[::1])


axloss.set_xlabel('训练轮数')
axloss.set_ylabel('数值')
axloss.set_title(tm.train_name+' 损失值')
#添加图例
axloss.legend(loc = 0)


#predict画布
axpredict.plot(range(epoch_times), predict_list, label = 'predict', color='g')
##设置刻度
axpredict.set_xticks(range(epoch_times)[::1])
axpredict.set_xticklabels(range(epoch_times)[::1])
# axpredict.set_yticks(range(100)[::5])
# axpredict.set_yticklabels(range(100)[::5])


axpredict.set_xlabel('训练轮数')
axpredict.set_ylabel('预测值')
axpredict.set_title(tm.train_name+' 预测值')
#添加图例
axpredict.legend(loc = 0)


#显示图像
plt.show()

trainmodel.py

import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
from ModelLinearNet import LinearNet
from ModelConv2d import Conv2dNet
from ModelGoogleNet import GoogleNet
from ModelResNet import ResNet




batch_size = 64
##设置本次要训练用的模型
train_name = 'ResNet'
print("train_name:" + train_name)
##设置模型保存名称
savemodel_name = train_name + ".pt"
print("savemodel_name:" + savemodel_name)




transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=(0.1307,), std=(0.3081,))
]) ##Normalize 里面两个值0.1307是均值mean, 0.3081是标准差std,计算好的直接用了


##训练数据集位置,如果不存在直接下载
train_dataset = datasets.MNIST(
    root = '../datasets/mnist', 
    train = True,
    download = True,
    transform = transform
)
##读取训练数据集
train_dataloader = DataLoader(
    dataset= train_dataset,
    shuffle=True,
    batch_size=batch_size
)
##测试数据集位置,如果不存在直接下载
test_dataset = datasets.MNIST(
    root= '../datasets/mnist',
    train= False,
    download=True,
    transform= transform
)
##读取测试数据集
test_dataloader = DataLoader(
    dataset= test_dataset,
    shuffle= True,
    batch_size=batch_size
)




##设置选择训练模型,因为python用的是3.9,用不了match case语法
def switch(train_name):
    if train_name == 'LinearNet':
        return LinearNet()
    elif train_name == 'Conv2dNet':
        return Conv2dNet()
    elif train_name == 'GoogleNet':
        return GoogleNet()
    elif train_name == 'ResNet':
        return ResNet()




##定义训练模型
class Net(torch.nn.Module):
    def __init__(self, train_name):
        super(Net, self).__init__()
        self.model = switch(train_name= train_name)
        self.criterion = self.model.criterion


    def forward(self, x):
        x = self.model(x)
        return x

traintoonnx.py

import torch
from torchvision import transforms
from torchvision import datasets
from torch.utils.data import DataLoader
import trainModel as tm




##获取输入参数
data = iter(tm.test_dataloader)
dummy_inputs, labels = next(data)
print(dummy_inputs.shape)


##加载模型
model = tm.Net(tm.train_name)
model.load_state_dict(torch.load(tm.savemodel_name))
print(model)


##加载的模型测试效果
outputs = model(dummy_inputs)
print(outputs)
##预测返回的是两列,第一列是下标就是0-9的值,第二列为预测值,下面的dim=1就是找维度1(第二列)最大值输出
_, predicted = torch.max(outputs.data, dim=1)
print(_)
print(predicted)
outlabels = predicted.numpy().tolist()
print(outlabels)


##定义输出输出的参数名
input_name = ["input"]
output_name = ["output"]


onnx_name = tm.train_name + '.onnx'


torch.onnx.export(
    model,
    dummy_inputs,
    onnx_name,
    verbose=True,
    input_names=input_name,
    output_names=output_name
)

划重点

01

加载模型后导出

导出Onnx的模型,在《超简单的pyTorch训练->onnx模型->C++ OpenCV DNN推理(附源码地址)》有说过,那里面是先训练完后直接导出了,而traintoonnx.py中则是前面训练已经保存好模型了,这里我们直接加载模型读取,然后再进行导出。

aab374a59dcfb82bbc5df79fcc08a3a3.png

02

导出ONNX模型在使用OpenCV推理时,x.view不能用

这个是比较关键的,原来的我们的训练模型中,在前向传播里面用的都是x.view这个,如下图

607c9295419d119e2becd1d738fa006f.png

导出ONNX在OpenCV中推理时直接报错了,所以这里要改为x = x.flatten(1)

407f29007636193b7bc5627047c7920c.png

微卡智享

C++ OpenCV推理

使用OpenCV DNN进行推理时,就不像《超简单的pyTorch训练->onnx模型->C++ OpenCV DNN推理(附源码地址)》中那么简单了,因为是手写的数字识别,并且Minist训练时的图像都是1X28X28的样本,所以推理前需要将图像进行预处理,下面是实现的思路。

#思路
1读取图像,做灰度处理,高斯模糊,二值化
2形态学操作,使用膨胀(防止轮廓查找有问题)
3轮廓查找,根据顺序排序截图图像
4
排序后的图像进行处理缩放为(28X28)
5
使用DNN传入处理后的图像进行推理

C++推理源码

#pragma once
#include<iostream>
#include<opencv2/opencv.hpp>
#include<opencv2/dnn/dnn.hpp>


using namespace cv;
using namespace std;


dnn::Net net;


//排序矩形
void SortRect(vector<Rect>& inputrects) {
  for (int i = 0; i < inputrects.size(); ++i) {
    for (int j = i; j < inputrects.size(); ++j) {
      //说明顺序在上方,这里不用变
      if (inputrects[i].y + inputrects[i].height < inputrects[i].y) {


      }
      //同一排
      else if (inputrects[i].y <= inputrects[j].y + inputrects[j].height) {
        if (inputrects[i].x > inputrects[j].x) {
          swap(inputrects[i], inputrects[j]);
        }
      }
      //下一排
      else if (inputrects[i].y > inputrects[j].y + inputrects[j].height) {
        swap(inputrects[i], inputrects[j]);
      }
    }
  }
}


//处理DNN检测的MINIST图像,防止长方形图像直接转为28*28扁了
void DealInputMat(Mat& src, int row = 28, int col = 28, int tmppadding=5) {
  int w = src.cols;
  int h = src.rows;
  //看图像的宽高对比,进行处理,先用padding填充黑色,保证图像接近正方形,这样缩放28*28比例不会失衡
  if (w > h) {
    int tmptopbottompadding = (w-h) / 2 + tmppadding;
    copyMakeBorder(src, src, tmptopbottompadding, tmptopbottompadding, tmppadding, tmppadding,
      BORDER_CONSTANT, Scalar(0));
  }
  else {
    int tmpleftrightpadding = (h-w) / 2+ tmppadding;
    copyMakeBorder(src, src, tmppadding, tmppadding, tmpleftrightpadding, tmpleftrightpadding,
      BORDER_CONSTANT, Scalar(0));


  }
  resize(src, src, Size(row, col));
}


int main(int argc, char** argv) {
  //定义onnx文件
  string onnxfile = "D:/Business/DemoTEST/CPP/OpenCVMinistDNN/torchminist/ResNet.onnx";


  //测试图片文件
  string testfile = "D:/Business/DemoTEST/CPP/OpenCVMinistDNN/test5.png";


  net = dnn::readNetFromONNX(onnxfile);
  if (net.empty()) {
    cout << "加载Onnx文件失败!" << endl;
    return -1;
  }


  //读取图片,灰度,高斯模糊
  Mat src = imread(testfile);
  //备份源图
  Mat backsrc;
  src.copyTo(backsrc);
  cvtColor(src, src, COLOR_BGR2GRAY);
  GaussianBlur(src, src, Size(3, 3), 0.5, 0.5);
  //二值化图片,注意用THRESH_BINARY_INV改为黑底白字,对应MINIST
  threshold(src, src, 0, 255, THRESH_BINARY_INV | THRESH_OTSU);


  //做彭账处理,防止手写的数字没有连起来,这里做了3次膨胀处理
  Mat kernel = getStructuringElement(MORPH_RECT, Size(5, 5));
  morphologyEx(src, src, MORPH_DILATE, kernel, Point(-1,-1), 3);
  imshow("src", src);


  vector<vector<Point>> contours;
  vector<Vec4i> hierarchy;
  vector<Rect> rects;


  //查找轮廓
  findContours(src, contours, hierarchy, RETR_EXTERNAL, CHAIN_APPROX_NONE);
  for (int i = 0; i < contours.size(); ++i) {
    RotatedRect rect = minAreaRect(contours[i]);
    Rect outrect = rect.boundingRect();
    //插入到矩形列表中
    rects.push_back(outrect);
  }


  //按从左到右,从上到下排序
  SortRect(rects);
  //要输出的图像参数
  for (int i = 0; i < rects.size(); ++i) {
    Mat tmpsrc = src(rects[i]);
    DealInputMat(tmpsrc);


    //Mat inputBlob = dnn::blobFromImage(tmpsrc, 0.3081, Size(28, 28), Scalar(0.1307), false, false);
    Mat inputBlob = dnn::blobFromImage(tmpsrc, 1, Size(28, 28), Scalar(), false, false);


    //输入参数值
    net.setInput(inputBlob, "input");
    //预测结果 
    Mat output = net.forward("output");


    //查找出结果中推理的最大值
    Point maxLoc;
    minMaxLoc(output, NULL, NULL, NULL, &maxLoc);


    cout << "预测值:" << maxLoc.x << endl;


    //画出截取图像位置,并显示识别的数字
    rectangle(backsrc, rects[i], Scalar(255, 0, 255));
    putText(backsrc, to_string(maxLoc.x), Point(rects[i].x, rects[i].y), FONT_HERSHEY_PLAIN, 5, Scalar(255, 0, 255), 1, -1);


  }


  imshow("backsrc", backsrc);




  waitKey(0);
  return 0;
}

划重点

01

二值化时使用THRESH_BINARY_INV

Minist训练集中的图片都全用的黑底白字,所以在二值化的时候需要使用THRESH_BINARY_INV直接变为黑底白字。

c44c2cf7a5228bd82f2375231191903e.png

3c32797b55465ed099a9e1541f1952bc.png

02

形态学操作膨胀

使用膨胀主要是防止手写的数字断开,造成轮廓查找时变为两个轮廓了

1961252682a527ea8fe4c38de2e44ca0.png

这里用5X5的卷积,膨胀了三次,使用膨胀和没使用的对比:

5d869623c4d7a517bdca2f6faada7631.png

使用膨胀处理

31fcf740fdf7b060ff874a2c19289ce0.png

未使用膨胀,多识别了一个轮廓

03

轮廓排序

如果直接使用查出轮廓输出,在图片中显示识别的数字是没问题,不过输出的顺序就会有问题,像刚才上面的这个图,5,6,7三个数字,如果直接查找 轮廓,按contours的序号来排序的话,顺序是7,5,6

a32dcfa5c1a608e0bec9c9fc4fd17f3f.png

如果是按顺序输出文本,明明我手写的是567,输了同的结果确是756,这样会有问题,所以这里就需要将查找出的轮廓进行排序,排序的方式就是按从左到右,从上到下的顺序。

63b35f401e77bdbc0b102e0f4b8094b7.png

轮廓排序的方法

a6141bba2bccca4803bef10e642b0158.png

04

缩放图片到28X28

336f2ccc28ee99dbc1e418a91b4060d0.png

像上中图,特别是数字1查找的轮廓,如果直接缩放直28X28,图像的比例会失衡,所以这里需要对提取的轮廓图像先进行处理。

判断宽高,差额补齐。比如上图中的数字1,宽度比高度差了好多,那我们用现在的高度减去宽度,再除2(除2是让左右两边平均填充上),这样比例就接近正方形了,缩放时比例不会失衡。填充使用的函数copyMakeBorder。

防止缩放后数字直接贴边了,我们在提取的轮廓四周再填充一个阈值,全部用黑色填充,最后再进行缩放。效果大概如下:

a87023c2dd77d7b1a98375b3ef235aca.png

轮廓提取图像

处理前

bc552315b8802fb7a1c3a303755fdddd.png

填充后的图像

处理后

4743bd17cf594c2d68721f2c8d6cffbf.png

05

OpenCV DNN推理

推理时先使用blobFromImage将图像进行预处理,然后再用DNN进行推理,最终返回的结果需要再通过minMaxLoc提取到最大值,判断推理的数字。

61f2eba5d1ed3d7ee3e745c9727a5218.png

上面的步骤后,C++ OpenCV进行手写数字识别就可以完成了,这一系列完结时,会将源码统一放到GitHub中。

31f153b8f03a6fb965066c371322e87d.png

622518075c46099be7addbc22a24c058.png

往期精彩回顾

9ca48bb45e643ac6682893af624490f0.jpeg

pyTorch入门(三)——GoogleNet和ResNet训练


43ca244b4ffafd65a8c83794e0457932.jpeg

pyTorch入门(二)——常用网络层函数及卷积神经网络训练


7656bad80f2480490bf9188ce14fe093.jpeg

pyTorch入门(一)——Minist手写数据识别训练全连接网络


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

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

相关文章

基于JSP数码产品交易

开发工具(eclipse/idea/vscode等)&#xff1a; 数据库(sqlite/mysql/sqlserver等)&#xff1a; 功能模块(请用文字描述&#xff0c;至少200字)&#xff1a; 网站前台&#xff1a;网站介绍、帮助信息、数码资讯、数码产品、讨论信息 管理员功能&#xff1a; 1、管理网站介绍、帮…

运维基础【黑马系列笔记整理分享(上)】

运维基础【黑马系列笔记整理分享(上&#xff09;】 因为笔记所记内容太多&#xff0c;所以我分成了上下两部分来分享给大家查看学习&#xff01;同时我会持续更新博客发布更加优质的文章&#xff0c;希望大家多多支持与关注&#xff01;感谢&#xff01; 一、运维概述 1、运维…

基于PHP+Mysql全品类在线购物商城设计

开发软件&#xff1a;VsCode/Dreamweaver等都行&#xff0c;mysql数据库&#xff0c;apache服务器 开发技术&#xff1a;PHP MVC模式&#xff0c;DIVCSS,Jquery等 这是一个全品类购物商城&#xff0c;可以销售所有品类的商品&#xff0c;包括前端用户和后端管理员2个身份&…

[ 妙用css ]:用css变量解决开发实际问题

各位看官&#xff0c;如何实现以上这种方块的移动&#xff0c;相信对于大家来说并不陌生&#xff0c;无非是几个步骤 1.设置动画 2.进行移动 3.动画循环 <div class"f-box"><div class"box"></div> </div> <style> .f-box…

Springboot工厂制造业WMS仓库管理系统源码为工厂仓库提供高效率管理带小程序和调试视频完全开源 可以二开

系统全开源&#xff0c;无任何加密&#xff0c;适合学习和二次开发 1. 开发语言&#xff1a;JAVA 2. 数据库&#xff1a;MySQL 3. 后端框架&#xff1a;springboot 4. 前端框架&#xff1a;VUE 5. 带小程序端 6. 带调试视频 7. 带部署文档 项目运行环境&#xff1a;JDK1…

使用Echarts完成对中国地图的绘制

目录前言1.什么是Echarts插件2.如何在vue中使用Echarts3.中国地图的具体样式4.如何使用Echarts来完成中国地图的绘制5.总结前言 我们在使用代码绘画地图的时候通常使用的是canvas&#xff0c;但是canvas是H5新增的东西&#xff0c;用起来不免有些麻烦&#xff0c;代码多&#…

聊一聊MySQL的记录锁、间隙锁与 Next-Key Lock

有小伙伴在微信上表示面试时被问到了 Next-Key Lock 是啥&#xff0c;结果一脸懵逼&#xff0c;那么今天我们来捋一捋 MySQL 中的记录锁、间隙锁以及 Next-Key Lock。 1. Record Lock Record Lock 也就是我们所说的记录锁&#xff0c;记录锁是对索引记录的锁&#xff0c;注意…

如何在小程序中完成支付进件?

1. 完成企业认证 1.1. 创建试用小程序 打开一个待发布的项目。点击顶部导航栏的 发布。手机扫码生成试用小程序。点击二维码底部的 发布应用。 1.2. 使用企业主体 转正小程序选择转正类型为 企业认证。 公司代码。 公司名称。 法人姓名。 法人身份证。 法人微信号。 点…

〖产品思维训练白宝书 - 产品思维认知篇②〗- 破局高手都具备的一种底层认知 - 产品思维

大家好&#xff0c;我是 哈士奇 &#xff0c;一位工作了十年的"技术混子"&#xff0c; 致力于为开发者赋能的UP主, 目前正在运营着 TFS_CLUB社区。 &#x1f4ac; 人生格言&#xff1a;优于别人,并不高贵,真正的高贵应该是优于过去的自己。&#x1f4ac; &#x1f4e…

C代码中花括号写成这种风格竟被吐槽~

正文大家好&#xff0c;我是bug菌~最近来了位新同事&#xff0c;闲暇时分聊了几句&#xff0c;其中有一点让我记忆特别深刻&#xff0c;说:"怎么我们这边代码中的花括号风格都独立另起一行&#xff0c;看代码的时候挺不适应的~"&#xff0c;我笑着说:"习惯就好了…

CMD CD命令失效,无法到达指定目录?

方法1&#xff1a; a.先进入另一盘的首层。&#xff08;想进入同盘目录可忽略这步&#xff09; b.使用cd 进入指定目录。 方法2&#xff1a; 直接进入目录 e:\>cd /d F:\2022F:\2022>cmd的其他指令 内容含义盘符:例如想进入D盘 d:cd进入到当前盘某个目录cd | 进入当前…

[附源码]计算机毕业设计Python个性化名片网站(程序+源码+LW文档)

该项目含有源码、文档、程序、数据库、配套开发软件、软件安装教程 项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等…

在SpringBoot中操作Redis数据库之StringRedisTemplate

一、RedisTemplate与StringRedisTemplate 二、Redis的五大数据类型 String List Hash Set SortedSet 首先想要在SpringBoot中操作Redis数据库需要先在pom.xml中导入坐标/引入依赖 <dependency><groupId>org.springframework.boot</groupId><arti…

“一体两翼”能否帮助贝壳穿越行业周期?

2022年的房地产行业处于逆周期中&#xff0c;供需双弱和融资困难笼罩在行业上空。数据显示&#xff0c;前三季度&#xff0c;国内百强房企全口径销售金额5.31万亿元&#xff0c;同比下降 45.8%&#xff1b;权益口径销售3.71万亿元&#xff0c;同比下降 46.3%。传统的销售旺季“…

前端:vue-element-admin 搭建踩坑笔记

❤️作者主页&#xff1a;IT技术分享社区 ❤️作者简介&#xff1a;大家好,我是IT技术分享社区的博主&#xff0c;从事C#、Java开发九年&#xff0c;对数据库、C#、Java、前端、运维、电脑技巧等经验丰富。 ❤️个人荣誉&#xff1a; 数据库领域优质创作者&#x1f3c6;&#x…

【MySQL基础教程】MySQL概述、安装与数据模型

前言 本文为 【MySQL基础教程】 相关内容介绍&#xff0c;下边将对MySQL概述&#xff0c;MySQL数据库介绍与安装&#xff08;包括&#xff1a;MySQL数据库版本、MySQL数据库下载、MySQL数据库安装、MySQL启动与停止、客户端连接&#xff09;&#xff0c;数据模型&#xff08;包…

深入浅出学习Linux

Linux作为自由软件有两个特点&#xff1a;一是它免费提供源代码&#xff0c;二是爱好者可以根据自己的需要自由修改、复制和发布源码 Linux的发行版说简单点就是将Linux内核与应用软件做一个打包。 1、Red Hat Linux&#xff08;小红帽&#xff09;&#xff1a;创作于1993年 2…

程序人生与世界杯的火花

卡塔尔世界杯 文章目录1.第一次了解世界杯2.世界杯使用了哪些新技术3.AI 艺术画4.Python代码画了个球状5.踢球和软件团队开发软件有什么异同6.体育是一种国际语言1.第一次了解世界杯 2010年南非世界杯&#xff0c;那会好像记得上大二&#xff0c;学校包车去的五棵松体育馆&…

CSAPP Architecture Lab PartC满分

CSAPP Architecture Lab 此lab涉及Y86-64的实现&#xff0c;具体Y86的内容可查看CSAPP第四章,做完本实验可以提高你对处理器设计以及软件与硬件的理解。 从CMU官网下载完所需实验包后&#xff0c;参考实验所给的官方文档simguide.pdf&#xff0c;首先建立实验环境&#xff0c…

Zookeeper生产常用命令大全(最新3.8.0版本)

文章目录官方文档一、服务端二、客户端1、连接客户端2、help3、create1> 创建持久节点2> 创建临时节点3> 创建持久有序节点4> 创建临时有序节点5> 创建ttl节点6> 创建容器节点4、get5、set6、ls7、stat8、删除节点1> delete2> deleteall8、其他命令二、…