【OCR技术系列之八】端到端不定长文本识别CRNN代码实现

CRNN是OCR领域非常经典且被广泛使用的识别算法,其理论基础可以参考我上一篇文章,本文将着重讲解CRNN代码实现过程以及识别效果。

数据处理

利用图像处理技术我们手工大批量生成文字图像,一共360万张图像样本,效果如下:

1093303-20190201114020358-28433004.png

我们划分了训练集和测试集(10:1),并单独存储为两个文本文件:

1093303-20190201114032962-2122780781.png

文本文件里的标签格式如下:

1093303-20190201114044022-49545368.png

我们获取到的是最原始的数据集,在图像深度学习训练中我们一般都会把原始数据集转化为lmdb格式以方便后续的网络训练。因此我们也需要对该数据集进行lmdb格式转化。下面代码就是用于lmdb格式转化,思路比较简单,就是首先读入图像和对应的文本标签,先使用字典将该组合存储起来(cache),再利用lmdb包的put函数把字典(cache)存储的k,v写成lmdb格式存储好(cache当有了1000个元素就put一次)。


import lmdb
import cv2
import numpy as np
import osdef checkImageIsValid(imageBin):if imageBin is None:return Falsetry:imageBuf = np.fromstring(imageBin, dtype=np.uint8)img = cv2.imdecode(imageBuf, cv2.IMREAD_GRAYSCALE)imgH, imgW = img.shape[0], img.shape[1]except:return Falseelse:if imgH * imgW == 0:return Falsereturn Truedef writeCache(env, cache):with env.begin(write=True) as txn:for k, v in cache.items():txn.put(k, v)def createDataset(outputPath, imagePathList, labelList, lexiconList=None, checkValid=True):"""Create LMDB dataset for CRNN training.ARGS:outputPath    : LMDB output pathimagePathList : list of image pathlabelList     : list of corresponding groundtruth textslexiconList   : (optional) list of lexicon listscheckValid    : if true, check the validity of every image"""assert (len(imagePathList) == len(labelList))nSamples = len(imagePathList)env = lmdb.open(outputPath, map_size=1099511627776)cache = {}cnt = 1for i in range(nSamples):imagePath = ''.join(imagePathList[i]).split()[0].replace('\n', '').replace('\r\n', '')# print(imagePath)label = ''.join(labelList[i])print(label)# if not os.path.exists(imagePath):#     print('%s does not exist' % imagePath)#     continuewith open('.' + imagePath, 'r') as f:imageBin = f.read()if checkValid:if not checkImageIsValid(imageBin):print('%s is not a valid image' % imagePath)continueimageKey = 'image-%09d' % cntlabelKey = 'label-%09d' % cntcache[imageKey] = imageBincache[labelKey] = labelif lexiconList:lexiconKey = 'lexicon-%09d' % cntcache[lexiconKey] = ' '.join(lexiconList[i])if cnt % 1000 == 0:writeCache(env, cache)cache = {}print('Written %d / %d' % (cnt, nSamples))cnt += 1print(cnt)nSamples = cnt - 1cache['num-samples'] = str(nSamples)writeCache(env, cache)print('Created dataset with %d samples' % nSamples)OUT_PATH = '../crnn_train_lmdb'
IN_PATH = './train.txt'if __name__ == '__main__':outputPath = OUT_PATHif not os.path.exists(OUT_PATH):os.mkdir(OUT_PATH)imgdata = open(IN_PATH)imagePathList = list(imgdata)labelList = []for line in imagePathList:word = line.split()[1]labelList.append(word)createDataset(outputPath, imagePathList, labelList)

我们运行上面的代码,可以得到训练集和测试集的lmdb

1093303-20190201114057511-1297523306.png

在数据准备部分还有一个操作需要强调的,那就是文字标签数字化,即我们用数字来表示每一个文字(汉字,英文字母,标点符号)。比如“我”字对应的id是1,“l”对应的id是1000,“?”对应的id是90,如此类推,这种编解码工作使用字典数据结构存储即可,训练时先把标签编码(encode),预测时就将网络输出结果解码(decode)成文字输出。


class strLabelConverter(object):"""Convert between str and label.NOTE:Insert `blank` to the alphabet for CTC.Args:alphabet (str): set of the possible characters.ignore_case (bool, default=True): whether or not to ignore all of the case."""def __init__(self, alphabet, ignore_case=False):self._ignore_case = ignore_caseif self._ignore_case:alphabet = alphabet.lower()self.alphabet = alphabet + '-'  # for `-1` indexself.dict = {}for i, char in enumerate(alphabet):# NOTE: 0 is reserved for 'blank' required by wrap_ctcself.dict[char] = i + 1def encode(self, text):"""Support batch or single str.Args:text (str or list of str): texts to convert.Returns:torch.IntTensor [length_0 + length_1 + ... length_{n - 1}]: encoded texts.torch.IntTensor [n]: length of each text."""length = []result = []for item in text:item = item.decode('utf-8', 'strict')length.append(len(item))for char in item:index = self.dict[char]result.append(index)text = result# print(text,length)return (torch.IntTensor(text), torch.IntTensor(length))def decode(self, t, length, raw=False):"""Decode encoded texts back into strs.Args:torch.IntTensor [length_0 + length_1 + ... length_{n - 1}]: encoded texts.torch.IntTensor [n]: length of each text.Raises:AssertionError: when the texts and its length does not match.Returns:text (str or list of str): texts to convert."""if length.numel() == 1:length = length[0]assert t.numel() == length, "text with length: {} does not match declared length: {}".format(t.numel(),length)if raw:return ''.join([self.alphabet[i - 1] for i in t])else:char_list = []for i in range(length):if t[i] != 0 and (not (i > 0 and t[i - 1] == t[i])):char_list.append(self.alphabet[t[i] - 1])return ''.join(char_list)else:# batch modeassert t.numel() == length.sum(), "texts with length: {} does not match declared length: {}".format(t.numel(), length.sum())texts = []index = 0for i in range(length.numel()):l = length[i]texts.append(self.decode(t[index:index + l], torch.IntTensor([l]), raw=raw))index += lreturn texts

网络设计

根据CRNN的论文描述,CRNN是由CNN-》RNN-》CTC三大部分架构而成,分别对应卷积层、循环层和转录层。首先CNN部分用于底层的特征提取,RNN采取了BiLSTM,用于学习关联序列信息并预测标签分布,CTC用于序列对齐,输出预测结果。

1093303-20190201114112588-1804223534.png

为了将特征输入到Recurrent Layers,做如下处理:

  • 首先会将图像缩放到 32×W×3 大小
  • 然后经过CNN后变为 1×(W/4)× 512
  • 接着针对LSTM,设置 T=(W/4) , D=512 ,即可将特征输入LSTM。

以上是理想训练时的操作,但是CRNN论文提到的网络输入是归一化好的100×32大小的灰度图像,即高度统一为32个像素。下面是CRNN的深度神经网络结构图,CNN采取了经典的VGG16,值得注意的是,在VGG16的第3第4个max pooling层CRNN采取的是1×2的矩形池化窗口(w×h),这有别于经典的VGG16的2×2的正方形池化窗口,这个改动是因为文本图像多数都是高较小而宽较长,所以其feature map也是这种高小宽长的矩形形状,如果使用1×2的池化窗口则更适合英文字母识别(比如区分i和l)。VGG16部分还引入了BatchNormalization模块,旨在加速模型收敛。还有值得注意一点,CRNN的输入是灰度图像,即图像深度为1。CNN部分的输出是512x1x16(c×h×w)的特征向量。

1093303-20190201114123645-976232952.png

接下来分析RNN层。RNN部分使用了双向LSTM,隐藏层单元数为256,CRNN采用了两层BiLSTM来组成这个RNN层,RNN层的输出维度将是(s,b,class_num) ,其中class_num为文字类别总数。

值得注意的是:Pytorch里的LSTM单元接受的输入都必须是3维的张量(Tensors).每一维代表的意思不能弄错。第一维体现的是序列(sequence)结构,第二维度体现的是小块(mini-batch)结构,第三位体现的是输入的元素(elements of input)。如果在应用中不适用小块结构,那么可以将输入的张量中该维度设为1,但必须要体现出这个维度。

LSTM的输入

input of shape (seq_len, batch, input_size): tensor containing the features of the input sequence. 
The input can also be a packed variable length sequence.
input shape(a,b,c)
a:seq_len  -> 序列长度
b:batch
c:input_size   输入特征数目 

根据LSTM的输入要求,我们要对CNN的输出做些调整,即把CNN层的输出调整为[seq_len, batch, input_size]形式,下面为具体操作:先使用squeeze函数移除h维度,再使用permute函数调整各维顺序,即从原来[w, b, c]的调整为[seq_len, batch, input_size],具体尺寸为[16,batch,512],调整好之后即可以将该矩阵送入RNN层。


x = self.cnn(x)
b, c, h, w = x.size()
# print(x.size()): b,c,h,w
assert h == 1   # "the height of conv must be 1"
x = x.squeeze(2)  # remove h dimension, b *512 * width
x = x.permute(2, 0, 1)  # [w, b, c] = [seq_len, batch, input_size]
x = self.rnn(x)

RNN层输出格式如下,因为我们采用的是双向BiLSTM,所以输出维度将是hidden_unit * 2

Outputs: output, (h_n, c_n)
output of shape (seq_len, batch, num_directions * hidden_size)
h_n of shape (num_layers * num_directions, batch, hidden_size)
c_n (num_layers * num_directions, batch, hidden_size) 

然后我们再通过线性变换操作self.embedding1 = torch.nn.Linear(hidden_unit * 2, 512)是的输出维度再次变为512,继续送入第二个LSTM层。第二个LSTM层后继续接线性操作torch.nn.Linear(hidden_unit * 2, class_num)使得整个RNN层的输出为文字类别总数。

import torch
import torch.nn.functional as Fclass Vgg_16(torch.nn.Module):def __init__(self):super(Vgg_16, self).__init__()self.convolution1 = torch.nn.Conv2d(1, 64, 3, padding=1)self.pooling1 = torch.nn.MaxPool2d(2, stride=2)self.convolution2 = torch.nn.Conv2d(64, 128, 3, padding=1)self.pooling2 = torch.nn.MaxPool2d(2, stride=2)self.convolution3 = torch.nn.Conv2d(128, 256, 3, padding=1)self.convolution4 = torch.nn.Conv2d(256, 256, 3, padding=1)self.pooling3 = torch.nn.MaxPool2d((1, 2), stride=(2, 1)) # notice stride of the non-square poolingself.convolution5 = torch.nn.Conv2d(256, 512, 3, padding=1)self.BatchNorm1 = torch.nn.BatchNorm2d(512)self.convolution6 = torch.nn.Conv2d(512, 512, 3, padding=1)self.BatchNorm2 = torch.nn.BatchNorm2d(512)self.pooling4 = torch.nn.MaxPool2d((1, 2), stride=(2, 1))self.convolution7 = torch.nn.Conv2d(512, 512, 2)def forward(self, x):x = F.relu(self.convolution1(x), inplace=True)x = self.pooling1(x)x = F.relu(self.convolution2(x), inplace=True)x = self.pooling2(x)x = F.relu(self.convolution3(x), inplace=True)x = F.relu(self.convolution4(x), inplace=True)x = self.pooling3(x)x = self.convolution5(x)x = F.relu(self.BatchNorm1(x), inplace=True)x = self.convolution6(x)x = F.relu(self.BatchNorm2(x), inplace=True)x = self.pooling4(x)x = F.relu(self.convolution7(x), inplace=True)return x  # b*512x1x16class RNN(torch.nn.Module):def __init__(self, class_num, hidden_unit):super(RNN, self).__init__()self.Bidirectional_LSTM1 = torch.nn.LSTM(512, hidden_unit, bidirectional=True)self.embedding1 = torch.nn.Linear(hidden_unit * 2, 512)self.Bidirectional_LSTM2 = torch.nn.LSTM(512, hidden_unit, bidirectional=True)self.embedding2 = torch.nn.Linear(hidden_unit * 2, class_num)def forward(self, x):x = self.Bidirectional_LSTM1(x)   # LSTM output: output, (h_n, c_n)T, b, h = x[0].size()   # x[0]: (seq_len, batch, num_directions * hidden_size)x = self.embedding1(x[0].view(T * b, h))  # pytorch view() reshape as [T * b, nOut]x = x.view(T, b, -1)  # [16, b, 512]x = self.Bidirectional_LSTM2(x)T, b, h = x[0].size()x = self.embedding2(x[0].view(T * b, h))x = x.view(T, b, -1)return x  # [16,b,class_num]# output: [s,b,class_num]
class CRNN(torch.nn.Module):def __init__(self, class_num, hidden_unit=256):super(CRNN, self).__init__()self.cnn = torch.nn.Sequential()self.cnn.add_module('vgg_16', Vgg_16())self.rnn = torch.nn.Sequential()self.rnn.add_module('rnn', RNN(class_num, hidden_unit))def forward(self, x):x = self.cnn(x)b, c, h, w = x.size()# print(x.size()): b,c,h,wassert h == 1   # "the height of conv must be 1"x = x.squeeze(2)  # remove h dimension, b *512 * widthx = x.permute(2, 0, 1)  # [w, b, c] = [seq_len, batch, input_size]# x = x.transpose(0, 2)# x = x.transpose(1, 2)x = self.rnn(x)return x

损失函数设计

刚刚完成了CNN层和RNN层的设计,现在开始设计转录层,即将RNN层输出的结果翻译成最终的识别文字结果,从而实现不定长的文字识别。pytorch没有内置的CTC loss,所以只能去Github下载别人实现的CTC loss来完成损失函数部分的设计。安装CTC-loss的方式如下:

git clone https://github.com/SeanNaren/warp-ctc.git
cd warp-ctc
mkdir build; cd build
cmake ..
make
cd ../pytorch_binding/
python setup.py install
cd ../build
cp libwarpctc.so ../../usr/lib

待安装完毕后,我们可以直接调用CTC loss了,以一个小例子来说明ctc loss的用法。

import torch
from warpctc_pytorch import CTCLoss
ctc_loss = CTCLoss()
# expected shape of seqLength x batchSize x alphabet_size
probs = torch.FloatTensor([[[0.1, 0.6, 0.1, 0.1, 0.1], [0.1, 0.1, 0.6, 0.1, 0.1]]]).transpose(0, 1).contiguous()
labels = torch.IntTensor([1, 2])
label_sizes = torch.IntTensor([2])
probs_sizes = torch.IntTensor([2])
probs.requires_grad_(True)  # tells autograd to compute gradients for probs
cost = ctc_loss(probs, labels, probs_sizes, label_sizes)
cost.backward()
CTCLoss(size_average=False, length_average=False)# size_average (bool): normalize the loss by the batch size (default: False)# length_average (bool): normalize the loss by the total number of frames in the batch. If True, supersedes size_average (default: False)forward(acts, labels, act_lens, label_lens)# acts: Tensor of (seqLength x batch x outputDim) containing output activations from network (before softmax)# labels: 1 dimensional Tensor containing all the targets of the batch in one large sequence# act_lens: Tensor of size (batch) containing size of each output sequence from the network# label_lens: Tensor of (batch) containing label length of each example

从上面的代码可以看出,CTCLoss的输入为[probs, labels, probs_sizes, label_sizes],即预测结果、标签、预测结果的数目和标签数目。那么我们仿照这个例子开始设计CRNN的CTC LOSS。


preds = net(image)
preds_size = Variable(torch.IntTensor([preds.size(0)] * batch_size))  # preds.size(0)=w=16
cost = criterion(preds, text, preds_size, length) / batch_size   # 这里的length就是包含每个文本标签的长度的list,除以batch_size来求平均loss
cost.backward()

网络训练设计

接下来我们需要完善具体的训练流程,我们还写了个trainBatch函数用于bacth形式的梯度更新。

def trainBatch(net, criterion, optimizer, train_iter):data = train_iter.next()cpu_images, cpu_texts = databatch_size = cpu_images.size(0)lib.dataset.loadData(image, cpu_images)t, l = converter.encode(cpu_texts)lib.dataset.loadData(text, t)lib.dataset.loadData(length, l)preds = net(image)#print("preds.size=%s" % preds.size)preds_size = Variable(torch.IntTensor([preds.size(0)] * batch_size))  # preds.size(0)=w=22cost = criterion(preds, text, preds_size, length) / batch_size  # length= a list that contains the len of text label in a batchnet.zero_grad()cost.backward()optimizer.step()return cost

整个网络训练的流程如下:CTC-LOSS对象->CRNN网络对象->image,text,len的tensor初始化->优化器初始化,然后开始循环每个epoch,指定迭代次数就进行模型验证和模型保存。CRNN论文提到所采用的优化器是Adadelta,但是经过我实验看来,Adadelta的收敛速度非常慢,所以改用了RMSprop优化器,模型收敛速度大幅度提升。

criterion = CTCLoss()net = Net.CRNN(n_class)print(net)net.apply(lib.utility.weights_init)image = torch.FloatTensor(Config.batch_size, 3, Config.img_height, Config.img_width)text = torch.IntTensor(Config.batch_size * 5)length = torch.IntTensor(Config.batch_size)if cuda:net.cuda()image = image.cuda()criterion = criterion.cuda()image = Variable(image)text = Variable(text)length = Variable(length)loss_avg = lib.utility.averager()optimizer = optim.RMSprop(net.parameters(), lr=Config.lr)#optimizer = optim.Adadelta(net.parameters(), lr=Config.lr)#optimizer = optim.Adam(net.parameters(), lr=Config.lr,#betas=(Config.beta1, 0.999))for epoch in range(Config.epoch):train_iter = iter(train_loader)i = 0while i < len(train_loader):for p in net.parameters():p.requires_grad = Truenet.train()cost = trainBatch(net, criterion, optimizer, train_iter)loss_avg.add(cost)i += 1if i % Config.display_interval == 0:print('[%d/%d][%d/%d] Loss: %f' %(epoch, Config.epoch, i, len(train_loader), loss_avg.val()))loss_avg.reset()if i % Config.test_interval == 0:val(net, test_dataset, criterion)# do checkpointingif i % Config.save_interval == 0:torch.save(net.state_dict(), '{0}/netCRNN_{1}_{2}.pth'.format(Config.model_dir, epoch, i))

训练过程与测试设计

下面这幅图表示的就是CRNN训练过程,文字类别数为6732,一共训练20个epoch,batch_Szie设置为64,所以一共是51244次迭代/epoch。

1093303-20190201114156403-472538759.png

在迭代4个epoch时,loss降到0.1左右,acc上升到0.98。

1093303-20190201114205932-659285318.png

接下来我们设计推断预测部分的代码,首先需初始化CRNN网络,载入训练好的模型,读入待预测的图像并resize为高为32的灰度图像,接着讲该图像送入网络,最后再将网络输出解码成文字即可输出。


import time
import torch
import os
from torch.autograd import Variable
import lib.convert
import lib.dataset
from PIL import Image
import Net.net as Net
import alphabets
import sys
import Configos.environ['CUDA_VISIBLE_DEVICES'] = "4"crnn_model_path = './bs64_model/netCRNN_9_48000.pth'
IMG_ROOT = './test_images'
running_mode = 'gpu'
alphabet = alphabets.alphabet
nclass = len(alphabet) + 1def crnn_recognition(cropped_image, model):converter = lib.convert.strLabelConverter(alphabet)  # 标签转换image = cropped_image.convert('L')  # 图像灰度化### Testing images are scaled to have height 32. Widths are# proportionally scaled with heights, but at least 100 pixelsw = int(image.size[0] / (280 * 1.0 / Config.infer_img_w))#scale = image.size[1] * 1.0 / Config.img_height#w = int(image.size[0] / scale)transformer = lib.dataset.resizeNormalize((w, Config.img_height))image = transformer(image)if torch.cuda.is_available():image = image.cuda()image = image.view(1, *image.size())image = Variable(image)model.eval()preds = model(image)_, preds = preds.max(2)preds = preds.transpose(1, 0).contiguous().view(-1)preds_size = Variable(torch.IntTensor([preds.size(0)]))sim_pred = converter.decode(preds.data, preds_size.data, raw=False)  # 预测输出解码成文字print('results: {0}'.format(sim_pred))if __name__ == '__main__':# crnn networkmodel = Net.CRNN(nclass)# 载入训练好的模型,CPU和GPU的载入方式不一样,需分开处理if running_mode == 'gpu' and torch.cuda.is_available():model = model.cuda()model.load_state_dict(torch.load(crnn_model_path))else:model.load_state_dict(torch.load(crnn_model_path, map_location='cpu'))print('loading pretrained model from {0}'.format(crnn_model_path))files = sorted(os.listdir(IMG_ROOT))  # 按文件名排序for file in files:started = time.time()full_path = os.path.join(IMG_ROOT, file)print("=============================================")print("ocr image is %s" % full_path)image = Image.open(full_path)crnn_recognition(image, model)finished = time.time()print('elapsed time: {0}'.format(finished - started))

识别效果和总结

首先我从测试集中抽取几张图像送入模型识别,识别全部正确。
1093303-20190201114217446-57510191.png

1093303-20190201114227177-381481834.png

1093303-20190201114236060-1239024360.png

1093303-20190201114246363-1079605888.png

我也随机在一些文档图片、扫描图像上截取了一段文字图像送入我们该模型进行识别,识别效果也挺好的,基本识别正确,表明模型泛化能力很强。

1093303-20190201114255099-1423697748.png

1093303-20190201114304776-1608758159.png

1093303-20190201114314233-617002960.png

1093303-20190201114325031-1088981924.png

我还截取了增值税扫描发票上的文本图像来看看我们的模型能否还可以表现出稳定的识别效果:

1093303-20190201121051257-1560896978.png

1093303-20190201121100365-1769792647.png

1093303-20190201121107279-1457096988.png

这里做个小小的总结:对于端到端不定长的文字识别,CRNN是最为经典的识别算法,而且实战看来效果非常不错。上面识别结果可以看出,虽然我们用于训练的数据集是自己生成的,但是我们该模型对于pdf文档、扫描图像等都有很不错的识别结果,如果需要继续提升对特定领域的文本图像的识别,直接大量加入该类图像用于训练即可。CRNN的完整代码可以参考我的Github。

转载于:https://www.cnblogs.com/skyfsm/p/10345305.html

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

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

相关文章

杜比服务器系统安装教程,win10杜比音效如何安装?win10安装杜比音效的详细教程...

杜比音效想必大家都不陌生&#xff0c;听歌或者看电影开启杜比音效可以给人一种身临其境的感觉。不少朋友都升级了win10系统却不知道如何安装杜比音效&#xff1f;如何为自己的系统安装杜比音效呢&#xff1f;感兴趣的小伙伴请看下面的操作步骤。win10安装杜比音效的方法&#…

前端if else_应该记录的一些项目代码(前端)

1.共享登录&#xff08;单点登录&#xff09;主要是前端部分主要是根据是否有cookie来判断是否已经登录主系统&#xff0c;然后再根据是否有当前系统的登录信息来&#xff08;这块主要是sessionStorage做的&#xff09;判断是否要再登录当前系统。设置、读取和设置cookie的方法…

Mac端解决(含修改8.0.13版的密码):Can't connect to local MySQL server through socket '/tmp/mysql.sock' (2)...

1. 安装mysql但是从来没启动过&#xff0c;今天一启动就报错&#xff1a; Cant connect to local MySQL server through socket /tmp/mysql.sock (2) 其实是mysql服务没起来。。。 localhost:~ miaoying$ mysql.server start Starting MySQL ... SUCCESS! 然后再去sudo mysql就…

塔塔建网站服务器,塔塔帝国忘记哪个区怎么办

7条解答1.在哪个区玩战舰帝国忘记了怎么办?忘了的话可以去官网登陆看看自己的 充值 或者礼包记录 有没有对应的区服 或者电话联系问问客服 通过账号 角色名字来查询2.我忘记在哪个区怎么找如果你有游戏人生资格的话&#xff0c;就很容易找了&#xff0c;在游戏人生的个人主页里…

Ixia推出首款太比特级网络安全测试平台

2016年11月18日&#xff0c;Ixia宣布推出全新CloudStorm平台。作为首款太比特级网络安全测试平台&#xff0c;该平台拥有前所未有的非凡性能&#xff0c;可用于测试及验证超大规模云数据中心不断扩大的容量、效率以及弹性。 ▲Ixia CloudStorm安全测试平台 CloudStorm的正式面市…

服务器选择重装系统,云服务器重装系统选择

云服务器重装系统选择 内容精选换一换将外部镜像文件注册成云平台的私有镜像后&#xff0c;您可以使用该镜像创建新的云服务器&#xff0c;或对已有云服务器的系统进行重装和更换。本节介绍使用镜像创建云服务器的操作。您可以按照通过镜像创建云服务器中的操作指导创建弹性云服…

Gartner Q2服务器市场报告5大要点

服务器场景调查 根据市场研究公司Gartner的调查报告&#xff0c;第二季度Dell的服务器市场取得了丰富的成果&#xff0c;HPE的市场份额比去年同期略有下降&#xff0c;但仍保留了其全球服务器市场第一的位置。 Gartner表示&#xff0c;全球服务器销售收入在第二季度与去年同期相…

力扣——键盘行

给定一个单词列表&#xff0c;只返回可以使用在键盘同一行的字母打印出来的单词。键盘如下图所示。 示例&#xff1a; 输入: ["Hello", "Alaska", "Dad", "Peace"] 输出: ["Alaska", "Dad"]注意&#xff1a; 你可…

Jmeter 通过json Extracted 来获取 指定的值的id

在没有 精确或模糊查询的接口时可以使用jmeter 获取指定的值的ID import java.lang.String ; String getTargetName"iphone632g"; //判读相应结果中是否包含指定值&#xff1a;iphone632g boolean containsCategoryprev.getResponseDataAsString().contains(getTarge…

Do you have an English name? 你有英文名吗?

文中提到的所有人名都是虚构的&#xff0c;如有雷同&#xff0c;纯属巧合。当然&#xff0c;你的洋名儿也可能是德文、法文、意大利文&#xff0c;等々々々。 全球化时代&#xff0c;和老外的交流也多了。“高端”的程序员想要进欧美系外企&#xff0c;想要出国看世界&#xff…

网络安全不是奢侈品,而是必需品

2016年国家网络安全宣传周于9月19日至25日在武汉隆重举办。《长江日报》记者高萌采访了思科全球副总裁、大中华区首席技术官曹图强&#xff0c;以下是9月19日《长江日报》刊登的采访全文&#xff1a; 思科全球副总裁、大中华区首席技术官曹图强昨日下午&#xff0c;思科全球副总…

AS 自定义 Gradle plugin 插件 案例 MD

Markdown版本笔记我的GitHub首页我的博客我的微信我的邮箱MyAndroidBlogsbaiqiantaobaiqiantaobqt20094baiqiantaosina.comAS 自定义 Gradle plugin 插件 案例 MD 目录 目录AS 中自定义 Gradle plugin编写插件传递参数发布插件到仓库使用插件AS 中自定义 Gradle plugin 参考1 参…

中英文对照 —— 机械

0. 汽车 relay&#xff1a;继电器&#xff0c;clutch&#xff1a;离合&#xff1b; motor&#xff1a;发动机&#xff08;马达&#xff09;&#xff1b;档位&#xff1a; park&#xff1a;停车挡braking&#xff1a;制动&#xff08;也就是刹车&#xff09;空挡&#xff1a;neu…

机票垂直搜索引擎的性能优化

机票垂直搜索引擎的性能优化 原文:机票垂直搜索引擎的性能优化一、行业背景与垂直搜索我们先了解一下机票的行业背景&#xff0c;下图是由中航信统计的数据&#xff0c;蓝色的曲线代表平均每公里的票价&#xff0c;红色曲线指的是客运量。从2011年到2016年&#xff0c;无论是国…

Unity3D 装备系统学习Inventory Pro 2.1.2 基础篇

前言 前一篇 Unity3D 装备系统学习Inventory Pro 2.1.2 总结 基本泛泛的对于Inventory Pro 这个插件进行了讲解&#xff0c;主要是想提炼下通用装备系统结构和类体系。前两天又读了另一个插件 C# Inventory-uGui v2.0.1的源码&#xff08;应该也是老外写的&#xff09;&#xf…

Java项目问题_Java项目出现的问题01----学习

0 运行环境MyEcplise2016Tomcat8.01今天在html的表格提交跳转时发现&#xff0c;想要提交到自己写servlet程序中&#xff0c;却发现总是出错http://localhost:8080/Test4/Test4/fail.html多出一个项目路径/Test4&#xff0c;但是程序没有任何问题&#xff0c;最后发现是在Ecpli…

混合云:公共云和私有云之间取得平衡的方式?

在可预见的未来&#xff0c;混合云是现实的&#xff0c;但真正的收获是试图找出企业最终朝向公共或私人资源是否平衡。 你在与任何技术供应商沟通时&#xff0c;也许会涉及到数据中心&#xff0c;但大多会提到云计算的三种方式&#xff1a;私有云&#xff0c;公共云&#xff0c…

腾腾流氓,云云更流氓(问微信怎样接入支付宝支付),手贱的赶紧点,你会感谢我的...

草原上的两匹马&#xff01; 打从当年微信开始布局公众号之初时&#xff0c;估计就已经想到了与支付宝正面冲突的场面&#xff0c;所以微信先来个瞒天过海&#xff0c;在春晚搞了个微信红包&#xff0c;那叫一个火呀&#xff0c;此时的云云隐隐感觉到些许不安。 早期的微信开发…

java中的string是什么_什么是String

2017-07-28String和StringBufString namenew String("HuangWeiFeng");System。out。println(name"is my name");看似已经很精简了&#xff0c;其实并非如此。为了生成二进制的代码&#xff0c;要进行如下的步骤和操作&#xff1a;(1) 生成新的字符串 new S…

MD5与SHA1

一、MD5 MD5消息摘要算法&#xff08;英语&#xff1a;MD5 Message-Digest Algorithm&#xff09;&#xff0c;一种被广泛使用的密码散列函数&#xff0c;可以产生出一个128位&#xff08;16字节&#xff09;的散列值&#xff08;hash value&#xff09;&#xff0c;用于确保信…