3. 使用PyTorch深度学习库训练第一个卷积神经网络CNN

news/2024/9/22 11:21:21

这篇博客将介绍如何使用PyTorch深度学习库训练第一个卷积神经网络(CNN)。训练CNN使用 KMNIST 数据集(MNIST digits数据集的替代品,内置在PyTorch中)识别手写平假名字符(handwritten Hiragana characters)。

在图像数据集上训练CNN与在数字数据上训练基本的多层感知器(MLP)并没有什么不同。将包括:

  • 定义模型架构
  • 从磁盘加载数据集
  • 熟练使用PyTorch的数据加载器DataLoader
  • 循环纪元和数据批次
  • 预测并计算损失
  • 将梯度适当归零,执行反向传播,并更新模型参数
  • 将训练完成的PyTorch模型保存到磁盘
  • 在单独的Python脚本中从磁盘加载模型
  • 使用PyTorch模型对图像进行预测

谈到从磁盘加载保存的PyTorch模型,下篇博客将介绍如何使用预先训练的PyTorch模型来识别日常生活中经常遇到的1000个图像类。这些模型可以节省大量时间和麻烦-它们非常精确,不需要手动训练它们。

1. 效果图

训练CNN≈我的CPU上有271秒。使用GPU的培训时间减少到≈130秒。
在最后一个阶段结束时,获得了99.68%的训练精度和98.03%的验证精度。
当在测试集上进行评估时,达到了≈94%的准确率。考虑到平假名字符的复杂性和浅层网络架构的简单性,这是非常好的(使用更深层次的网络,如VGG启发的模型或类似ResNet的模型,可以获得更高的准确率,但这些模型更复杂)。

[INFO] loading the KMNIST dataset...
Downloading http://codh.rois.ac.jp/kmnist/dataset/kmnist/train-images-idx3-ubyte.gz
Downloading http://codh.rois.ac.jp/kmnist/dataset/kmnist/train-images-idx3-ubyte.gz to data\KMNIST\raw\train-images-idx3-ubyte.gz
18165760it [00:40, 443555.23it/s]                              
Extracting data\KMNIST\raw\train-images-idx3-ubyte.gz to data\KMNIST\rawDownloading http://codh.rois.ac.jp/kmnist/dataset/kmnist/train-labels-idx1-ubyte.gz
Downloading http://codh.rois.ac.jp/kmnist/dataset/kmnist/train-labels-idx1-ubyte.gz to data\KMNIST\raw\train-labels-idx1-ubyte.gz
29696it [00:00, 298773.88it/s]           
Extracting data\KMNIST\raw\train-labels-idx1-ubyte.gz to data\KMNIST\rawDownloading http://codh.rois.ac.jp/kmnist/dataset/kmnist/t10k-images-idx3-ubyte.gz
Downloading http://codh.rois.ac.jp/kmnist/dataset/kmnist/t10k-images-idx3-ubyte.gz to data\KMNIST\raw\t10k-images-idx3-ubyte.gz
3041280it [00:13, 222871.32it/s]                             
Extracting data\KMNIST\raw\t10k-images-idx3-ubyte.gz to data\KMNIST\rawDownloading http://codh.rois.ac.jp/kmnist/dataset/kmnist/t10k-labels-idx1-ubyte.gz
Downloading http://codh.rois.ac.jp/kmnist/dataset/kmnist/t10k-labels-idx1-ubyte.gz to data\KMNIST\raw\t10k-labels-idx1-ubyte.gz
100%|██████████| 5120/5120 [00:00<?, ?it/s]
Extracting data\KMNIST\raw\t10k-labels-idx1-ubyte.gz to data\KMNIST\raw[INFO] generating the train/validation split...
[INFO] initializing the LeNet model...
[INFO] training the network...
[INFO] EPOCH: 1/10
Train loss: 0.344304, Train accuracy: 0.8940
Val loss: 0.148819, Val accuracy: 0.9535[INFO] EPOCH: 2/10
Train loss: 0.100273, Train accuracy: 0.9696
Val loss: 0.080464, Val accuracy: 0.9773[INFO] EPOCH: 3/10
Train loss: 0.058563, Train accuracy: 0.9824
Val loss: 0.089331, Val accuracy: 0.9728[INFO] EPOCH: 4/10
Train loss: 0.037242, Train accuracy: 0.9883
Val loss: 0.087128, Val accuracy: 0.9757[INFO] EPOCH: 5/10
Train loss: 0.026911, Train accuracy: 0.9910
Val loss: 0.093474, Val accuracy: 0.9756[INFO] EPOCH: 6/10
Train loss: 0.019837, Train accuracy: 0.9937
Val loss: 0.075803, Val accuracy: 0.9807[INFO] EPOCH: 7/10
Train loss: 0.015524, Train accuracy: 0.9948
Val loss: 0.066136, Val accuracy: 0.9834[INFO] EPOCH: 8/10
Train loss: 0.012120, Train accuracy: 0.9959
Val loss: 0.078302, Val accuracy: 0.9814[INFO] EPOCH: 9/10
Train loss: 0.010891, Train accuracy: 0.9964
Val loss: 0.089176, Val accuracy: 0.9811[INFO] EPOCH: 10/10
Train loss: 0.009471, Train accuracy: 0.9968
Val loss: 0.087064, Val accuracy: 0.9803[INFO] total time taken to train the model: 271.64s
[INFO] evaluating network...precision    recall  f1-score   supporto       0.93      0.97      0.95      1000ki       0.97      0.92      0.94      1000su       0.90      0.91      0.90      1000tsu       0.96      0.96      0.96      1000na       0.97      0.91      0.94      1000ha       0.97      0.92      0.94      1000ma       0.87      0.98      0.92      1000ya       0.98      0.94      0.96      1000re       0.97      0.96      0.97      1000wo       0.96      0.96      0.96      1000accuracy                           0.94     10000macro avg       0.95      0.94      0.94     10000
weighted avg       0.95      0.94      0.94     10000

如下图所示,训练损失/准确度的历史图是平滑的,表明几乎没有发生过拟合。
在这里插入图片描述

在测试集上随机选取10个样本,可以看到均能成功检测,效果图如下:
识别正确蓝色,识别错误红色。

在这里插入图片描述

2. 环境配置

pip install torch torchvision
pip install opencv-contrib-python
pip install scikit-learn

3. KMNIST数据集

  • 使用Kuzushiji MNIST数据集,简称KMNIST(MNIST digits数据集的替代品)。此数据集旨在替代标准MNIST数字识别数据集。

  • KMNIST数据集由70000张图像及其相应标签组成(60000张用于培训,10000张用于测试)。

  • KMNIST数据集中共有10个类(表示10个字符),每个类都是均匀分布和表示的。目标是训练一个能够准确分类这10个字符的CNN。

  • KMNIST数据集内置在PyTorch中。

4. 源码

lenet.py: 使用PyTorch实现了著名的LeNet CNN结构
train_lenet.py: 使用PyTorch在KMNIST数据集上训练LeNet CNN网络,并将训练过的模型序列化到磁盘
predict.py: 从磁盘加载模型,在测试集照片上预测结果,展示结果到屏幕
output/: 存放训练/验证损失/准确度图和模型;

4.1 lenet.py

# lenet.py
# 这里用PyTorch实现的卷积神经网络(CNN)是一种开创性的LeNet架构
# LeNet是一个非常浅层的神经网络,由以下几层组成:(CONV=>RELU=>POOL)*2=>FC=>RELU=>FC=>SOFTMAX# 导入必要的包
from torch import flatten  # 展平多维卷(例如CONV或POOL层)的输出,以便可以对其应用完全连接的层
from torch.nn import Conv2d  # PyTorch实现卷积层
from torch.nn import Linear  # 完全连接的层
from torch.nn import LogSoftmax  # 用于构建softmax分类器以返回每个类的预测概率
from torch.nn import MaxPool2d  # 应用MaxPool2d以减少输入层的空间维度from torch.nn import Module  # 与使用Sequential PyTorch类来实现LeNet不同,将对模块对象进行子类化,以便可以看到PyTorch如何使用类来实现神经网络
from torch.nn import ReLU  # ReLU激活函数class LeNet(Module):# 模块的构造函数仅初始化层类型。PyTorch跟踪这些变量,但它不知道这些层是如何相互连接的。# 为了让PyTorch了解您正在构建的网络架构,需要定义forward功能。# 在forward函数中,获取构造函数中初始化的变量并连接它们。# 借助autograd模块,PyTorch可以使用您的网络进行预测并执行自动反向传播def __init__(self, numChannels, classes):# 调用构造器super(LeNet, self).__init__()# 第一个CONV层共学习20个过滤器,每个过滤器为5×5。然后应用ReLU激活函数,然后是2×2最大池层和2×2步幅,以减少输入图像的空间维度。# 初始化第一层:CONV => RELU => POOL layersself.conv1 = Conv2d(in_channels=numChannels, out_channels=20,kernel_size=(5, 5))self.relu1 = ReLU()self.maxpool1 = MaxPool2d(kernel_size=(2, 2), stride=(2, 2))# 第二组CONV=>RELU=>POOL层。我们将CONV层中学习的过滤器数量增加到50个,但保持5×5的内核大小。再次应用ReLU激活,然后是最大池。# 初始化第二层:CONV => RELU => POOL layersself.conv2 = Conv2d(in_channels=20, out_channels=50,kernel_size=(5, 5))self.relu2 = ReLU()self.maxpool2 = MaxPool2d(kernel_size=(2, 2), stride=(2, 2))# 初始化第一组(也是唯一一组) FC => RELU layers# 第一组也是唯一一组完全连接的层。定义层的输入数量(800)以及所需的输出节点数量(500)。在FC层之后进行ReLu激活。self.fc1 = Linear(in_features=800, out_features=500)self.relu3 = ReLU()# 初始化 softmax 分类器# 最后应用softmax分类器。features的数量设置为500,这是前一层的输出维度。# 然后应用LogSoftmax,以便在评估期间获得预测概率。self.fc2 = Linear(in_features=500, out_features=classes)self.logSoftmax = LogSoftmax(dim=1)# forward方法接受单个参数x,它是网络的一批输入数据。# 然后,将conv1、relu1和maxpool1层连接在一起,形成网络的第一个CONV=>RELU=>POOL层。# 构建了第二组CONV=>RELU=>POOL层。# 此时,变量x是多维张量;然而,为了创建完全连接的层,需要将这个张量“展平”为基本上相当于1D值列表的张量。# 将fc1和relu3层连接到网络架构,然后连接最终的fc2和logSoftmax。## 重要的是要理解,__init__所做的只是初始化变量。PyTorch完全不知道网络架构是什么,只知道LeNet类定义中存在一些变量。# 为了构建网络体系结构本身(即输入到其他层的是哪一层),需要重写模块类的forward方法。# forward功能有:# 将层/子网络从类的构造函数(即__init__)中定义的变量连接在一起# 定义了网络体系结构本身# 允许模型向前传递,从而产生输出预测# 由于PyTorch的autograd模块,它允许执行自动微分并更新模型权重def forward(self, x):# 将input输入到第一层 CONV => RELU => POOL layersx = self.conv1(x)x = self.relu1(x)x = self.maxpool1(x)# 将上一层的输出传递到第二层 CONV => RELU => POOL layersx = self.conv2(x)x = self.relu2(x)x = self.maxpool2(x)# 展开上一层的输出,传递到唯一的完全连接层 FC => RELU layersx = flatten(x, 1)x = self.fc1(x)x = self.relu3(x)# 传递输出到sofmax分类器,以获取预测结果x = self.fc2(x)output = self.logSoftmax(x)# 返回预测的输出结果return output

4.2 train_lenet.py

# USAGE
# python train_lenet.py --model output/model.pth --plot output/plot.png# 设置matplotlib backend以保存图片到磁盘
import matplotlibmatplotlib.use("Agg")# 导入必要的包
from p220625.pyimagesearch.lenet import LeNet  # LeNet CNN的PyTorch实现
from sklearn.metrics import classification_report  # 用于在测试集上显示详细的分类报告
from torch.utils.data import random_split  # 从输入数据集构造随机训练/测试集拆分
from torch.utils.data import DataLoader  # 强大的数据加载器允许轻松构建数据管道来训练CNN
from torchvision.transforms import ToTensor  # 预处理函数,可自动将输入数据转换为PyTorch张量
from torchvision.datasets import KMNIST  # Kuzushiji MNIST数据集加载器内置在PyTorch库中from torch.optim import Adam  # 用来训练神经网络的优化器
from torch import nn  # PyTorch的神经网络实现
import matplotlib.pyplot as plt
import numpy as np
import argparse
import torch
import time# 构建命令行参数及解析
# --model 序列化模型的存放路径
# --plot 损失/准确度图存放路径
ap = argparse.ArgumentParser()
ap.add_argument("-m", "--model", type=str, required=False, default='output/model.pth',help="path to output trained model")
ap.add_argument("-p", "--plot", type=str, required=False, default='output/plot.png',help="path to output loss/accuracy plot")
args = vars(ap.parse_args())# 定义训练超参数(hyperparameters)初始学习率、批量大小和要训练的纪元数
INIT_LR = 1e-3
BATCH_SIZE = 64
EPOCHS = 10# 定义训练和测试级拆分比率 75%训练集,25%测试集
TRAIN_SPLIT = 0.75
VAL_SPLIT = 1 - TRAIN_SPLIT# 设置训练模型要使用的设备(CPU or GPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 使用PyTorch的内置KMNIST类加载KMNIST数据集
# download=True标志表示,如果之前没有下载KMNIST数据集,PyTorch将自动下载并将其缓存到磁盘。
# 还要注意transform参数-这里可以应用许多数据转换。这里需要的唯一转换是将PyTorch加载的NumPy数组转换为张量数据类型。
print("[INFO] loading the KMNIST dataset...")
trainData = KMNIST(root="data", train=True, download=True,transform=ToTensor())
testData = KMNIST(root="data", train=False, download=True,transform=ToTensor())# 计算训练/测试样本数
print("[INFO] generating the train/validation split...")
numTrainSamples = int(len(trainData) * TRAIN_SPLIT)
numValSamples = int(len(trainData) * VAL_SPLIT)
(trainData, valData) = random_split(trainData,[numTrainSamples, numValSamples],generator=torch.Generator().manual_seed(42))# 现在有三组数据:训练、验证、测试
# 为每个数据集创建一个数据加载器
# 仅为trainDataLoader设置shuffle=True,因为验证和测试集不需要洗牌。
trainDataLoader = DataLoader(trainData, shuffle=True,batch_size=BATCH_SIZE)
valDataLoader = DataLoader(valData, batch_size=BATCH_SIZE)
testDataLoader = DataLoader(testData, batch_size=BATCH_SIZE)# 为训练和验证集计算每个纪元的步长
trainSteps = len(trainDataLoader.dataset) // BATCH_SIZE
valSteps = len(valDataLoader.dataset) // BATCH_SIZE# 初始化LeNet模型
print("[INFO] initializing the LeNet model...")
model = LeNet(numChannels=1,classes=len(trainData.dataset.classes)).to(device)# 初始化优化器和损失函数
# 在模型定义中,组合nn.NLLLoss类与LogSoftmax,得到了分类交叉熵损失(这相当于训练一个具有输出线性层和nn.CrossEntropyLoss损失的模型)
opt = Adam(model.parameters(), lr=INIT_LR)
lossFn = nn.NLLLoss()# 初始化字典存储训练历史
H = {"train_loss": [],"train_acc": [],"val_loss": [],"val_acc": []
}# 计算训练模型耗时
print("[INFO] training the network...")
startTime = time.time()# 遍历纪元
for e in range(0, EPOCHS):# 设置模型为训练模式model.train()# 初始化总的训练损失和验证损失totalTrainLoss = 0totalValLoss = 0# 初始化训练准确率和验证准确率trainCorrect = 0valCorrect = 0# 在DataLoader上遍历训练集# PyTorch自动生成一批训练数据。在幕后数据加载器也在洗牌训练数据(如果做任何额外的预处理或数据扩充,也在这里进行)。for (x, y) in trainDataLoader:# 传递输入到设备(x, y) = (x.to(device), y.to(device))# 执行预测,计算训练损失pred = model(x)loss = lossFn(pred, y)# 梯度归零,执行反向传播,更新模型参数(权重)opt.zero_grad()loss.backward()opt.step()# 将损失添加到迄今为止的培训总损失中,并计算正确预测的数量totalTrainLoss += losstrainCorrect += (pred.argmax(1) == y).type(torch.float).sum().item()# 训练完,在验证集上验证模型# 转换到自动微分以进行验证with torch.no_grad():  # 使用torch.no_grad()以关闭梯度追踪和计算的上下文# 设置模型到验证模式model.eval()# 遍历验证数据集for (x, y) in valDataLoader:# 传递输入到设备(x, y) = (x.to(device), y.to(device))# 预测并计算验证损失pred = model(x)totalValLoss += lossFn(pred, y)# 计算正确的预测数valCorrect += (pred.argmax(1) == y).type(torch.float).sum().item()# 计算平均验证和训练损失avgTrainLoss = totalTrainLoss / trainStepsavgValLoss = totalValLoss / valSteps# 计算平均训练和验证准确率trainCorrect = trainCorrect / len(trainDataLoader.dataset)valCorrect = valCorrect / len(valDataLoader.dataset)# 更新训练历史字典H["train_loss"].append(avgTrainLoss.cpu().detach().numpy())H["train_acc"].append(trainCorrect)H["val_loss"].append(avgValLoss.cpu().detach().numpy())H["val_acc"].append(valCorrect)# 打印模型训练和验证信息# 在终端上显示训练损失、训练精度、验证损失和验证精度print("[INFO] EPOCH: {}/{}".format(e + 1, EPOCHS))print("Train loss: {:.6f}, Train accuracy: {:.4f}".format(avgTrainLoss, trainCorrect))print("Val loss: {:.6f}, Val accuracy: {:.4f}\n".format(avgValLoss, valCorrect))# 结束耗时统计,并显示耗时
endTime = time.time()
print("[INFO] total time taken to train the model: {:.2f}s".format(endTime - startTime))# 在测试集上验证cnn模型
print("[INFO] evaluating network...")# 关闭自动微分以进行测试集验证
with torch.no_grad():# 设置模型为验证模式model.eval()# 初始化list以存储预测结果preds = []# 遍历测试集for (x, y) in testDataLoader:# 传递数据到设备x = x.to(device)# 预测并将顶级预测结果添加到结果listpred = model(x)preds.extend(pred.argmax(axis=1).cpu().numpy())# 生成分类报告
print(classification_report(testData.targets.cpu().numpy(),np.array(preds), target_names=testData.classes))# 为训练历史生成matplotlib图(绘制训练损失和准确度图)
# 然后将模型权重序列化到磁盘:
plt.style.use("ggplot")
plt.figure()
plt.plot(H["train_loss"], label="train_loss")
plt.plot(H["val_loss"], label="val_loss")
plt.plot(H["train_acc"], label="train_acc")
plt.plot(H["val_acc"], label="val_acc")
plt.title("Training Loss and Accuracy on Dataset")
plt.xlabel("Epoch #")
plt.ylabel("Loss/Accuracy")
plt.legend(loc="lower left")
plt.savefig(args["plot"])# 序列化模型到磁盘
torch.save(model, args["model"])

4.3 predict.py

# USAGE
# python predict.py --model output/model.pth# 设置numpy种子以获得更好的再现性 reproducibility(设置NumPy随机种子,以提高跨机器的再现性。)
import numpy as npnp.random.seed(42)# 导入必要的包
from torch.utils.data import DataLoader  # 强大的数据加载器允许轻松构建数据管道来训练CNN,用于加载KMNIST测试数据
from torch.utils.data import Subset  # 构建测试数据的子集
from torchvision.transforms import ToTensor  # 将输入数据转换为PyTorch张量数据类型
from torchvision.datasets import KMNIST  # PyTorch库中内置的Kuzushiji MNIST数据集加载器
import argparse
import imutils
import torch
import cv2  # OpenCV绑定,用于在屏幕上进行基本绘图和显示输出图像# 构建命令行参数及解析
# --model 磁盘的模型文件路径
ap = argparse.ArgumentParser()
ap.add_argument("-m", "--model", type=str, required=False, default='output/model.pth',help="path to the trained PyTorch model")
args = vars(ap.parse_args())# 设置训练模型要使用的设备(CPU or GPU)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")# 加载KMNIST数据集,随机获取10个数据点
print("[INFO] loading the KMNIST test dataset...")
testData = KMNIST(root="data", train=False, download=True,transform=ToTensor())
idxs = np.random.choice(range(0, len(testData)), size=(10,))
# 使用Subset类(创建完整测试数据的较小“视图”)
testData = Subset(testData, idxs)# 初始化测试集加载器
testDataLoader = DataLoader(testData, batch_size=1)# 加载模型,并设置为验证模式
model = torch.load(args["model"]).to(device)
model.eval()# 转换自动微分
# 使用torch.no_grad()以关闭梯度追踪和计算的上下文
with torch.no_grad():# 遍历测试集for i, (image, label) in enumerate(testDataLoader):# 获取原始图像及实际标签# 抓取当前图像并将其转换为NumPy数组(以便稍后使用OpenCV绘制)origImage = image.numpy().squeeze(axis=(0, 1))gtLabel = testData.dataset.classes[label.numpy()[0]]# 传递数据到设备,并用经过训练的LeNet模型进行预测image = image.to(device)pred = model(image)# 提取具有最高预测概率的类标签idx = pred.argmax(axis=1).cpu().numpy()[0]predLabel = testData.dataset.classes[idx]# KMNIST数据集中的每个图像都是单通道灰度图像;然而希望使用OpenCV的cv2。putText函数在图像上绘制预测类标签和实际的真实值标签。# 要在灰度图像上绘制RGB颜色,首先需要通过将灰度图像按深度累计三次来创建灰度图像的RGB表示# 此外调整了原始图像的大小,以便在屏幕上更容易看到它(默认情况下,KMNIST图像只有28×28像素,这可能很难看到,尤其是在高分辨率监视器上)。# 转换灰度图为RGB图像以进行绘制# 保留宽高比的缩放为宽度128origImage = np.dstack([origImage] * 3)origImage = imutils.resize(origImage, width=128)# 绘制预测的类标签在图像上color = (0, 255, 0) if gtLabel == predLabel else (0, 0, 255)cv2.putText(origImage, gtLabel, (2, 25),cv2.FONT_HERSHEY_SIMPLEX, 0.95, color, 2)# 打印结果在终端,并展示原始图像print("[INFO] ground truth label: {}, predicted label: {}".format(gtLabel, predLabel))cv2.imshow("image" + str(i + 1), origImage)cv2.waitKey(0)

参考

  • https://pyimagesearch.com/2021/07/19/pytorch-training-your-first-convolutional-neural-network-cnn/

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

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

相关文章

小手段:开启 GNOME 的窗口分组效果

Toy Posted in Tips用过 Windows XP 的伴侣除夜要都晓得它有一项分组雷同义务栏按钮的效果&#xff0c;该效果主动将同类窗口的义务按钮折叠为一个单独的按钮&#xff0c;从而无效处置义务栏的窗口拥堵后果。其实&#xff0c;在 Linux 的 GNOME 桌面情况中也有雷同的效果──窗…

Tile Racer — 3D 赛车游戏

Toy Posted in Tile Racer 是一款可免用度于 Linux 及 Windows 平台的 3D 赛车游戏。它不只具有十分逼真的效果&#xff0c;并且包罗用来创设新 Maps 的赛道编辑器。玩家可置身于游戏之中足够觉获得赛车的快乐喜爱。Tile Racer 此后最新版本为 0.6&#xff0c;你可以从这里下载…

使用Python爬取分析腾讯新冠疫情数据,并对json格式进行校验

写这篇博客源于 博友的提问 由于禁止反爬&#xff0c;所以返回的json格式是错误的&#xff0c;因此没法直接转换。 可以观察规律进行json串格式校正。json格式需要 [], {} [{}] 成对存在。 1. 效果图 简单看一下&#xff0c;格式是否正确&#xff1b;可以看到返回值中 {} […

Python 有序排列permutations,无序组合combinations,阶乘factorial函数

写这篇博客源于博友的提问&#xff1a;将介绍使用Python 进行 有序排列&#xff0c;无序组合排列&#xff0c;阶乘的函数。 1. 问题及解决 问题&#xff1a; 40个球&#xff0c;四个盒子&#xff0c;一个盒子十个球搞排列组合&#xff0c;每个球和盒子都是不可分辨的&#xf…

中国挪动批改KPI查核制度将器重客户满意度

飞象网讯&#xff08;魏德龄/文&#xff09;记者从相关动静处置解到&#xff0c;中国挪动2011年的KPI查核将大幅缩减&#xff0c;并消除数据业务、TD用户数和集体客户等目标&#xff0c;重点调高了利润和客户满意度目标&#xff0c;并加入了相关EVA财务目标查核。去年&#xff…

一起学WP7 XNA游戏开发(八. 让3d model动起来)

如何让3d model动起来&#xff0c;其实就是要给model的bone设置动作&#xff0c;这样整个model就会动起来了。一&#xff0e;获取Bones在fbx文件中可以看到所有bones的名称&#xff0c;这样就可以通过名称来获取到bones。turretBone tankModel.Bones["turret_geo"];…

cv2.threshold() 阈值:使用Python,OpenCV进行简单的图像分割

图像分割有多种形式。 聚类、压缩、边缘检测、区域增长、图分区、分水岭等等&#xff1b;&#xff08;Clustering. Compression. Edge detection. Region-growing. Graph partitioning. Watershed. The list goes on.&#xff09; 但是最开始只有最基本的一种&#xff1a;就是…

文件读写操作

如图&#xff0c;点击选择文件则读取文件路径&#xff0c;读取时将文件内容显示到文本框中&#xff0c;写入时将文本框内容写入文件 View Code namespace 文件操作{ public partial class Form1 : Form { public Form1() { InitializeComponent(); }/// <summary>/// 选…