数据介绍项目来自天池大赛,这是项目地址。项目数据来自谷歌街景图片中的StreetViewHouseNumbersDataset(SVHN),对应Kaggle比赛地址。数据来源于真实场景的门牌号。训练集数据包括3W张照片,验证集数据包括1W张照片,每张照片包括一张彩色图像以及对应的编码类别和具体位置;为了保证比赛的公平性,测试集A包括4W张照片,测试集B包括4W张照片。官方已经帮我们划分了训练集和验证集。如下图所示,显示的文件是mchar_train.zip:训练图像mchar_val.zip:验证图像mchar_test_a.zip:测试图像mchar_train.json:训练图像标注mchar_val.json:验证图像标注mchar_sample_submit_A.csv:提交格式文件图片标注为json格式,字段含义如下FieldDescription左上角坐标Xheight字符高度左上角坐标Ywidth字符宽度label字符编码查看数据在构建数据集之前,我们首先将数据可视化为有对数据的一般理解。文件路径如下,将其保存为字典data_dir={'train_data':'/content/data/mchar_train/','val_data':'/content/data/mchar_val/','test_data':'/content/data/mchar_test_a/','train_label':'/content/drive/MyDrive/Data/Datawhale-DigitsRecognition/mchar_train.json','val_label':'/content/drive/MyDrive/Data/Datawhale-DigitsRecognition/mchar_val.json','submit_file':'/content/drive/MyDrive/Data/Datawhale-DigitsRecognition/mchar_sample_submit_A.csv'}查看图片数量defdata_summary():train_list=glob(data_dir['train_data']+'*.png')test_list=glob(data_dir['test_data']+'*.png')val_list=glob(data_dir['val_data']+'*.png')print('trainimagecounts:%d'%len(train_list))print('valimagecounts:%d'%len(val_list))print('testimagecounts:%d'%len(test_list))data_summary()trainimagecounts:30000valimagecounts:10000testimagecounts:40000查看标记文件deflook_train_json():withopen(data_dir['train_label'],'r',encoding='utf-8')asf:content=f.read()#loads将字符串转换成字典content=json.loads(content)print(content['000000.png'])look_train_json(){'height':[219,219],'label':[1,9],'left':[246,323],'top':[77,81],'width':[81,96]}查看结果文件提交格式deflook_submit():df=pd.read_csv(data_dir['submit_file'],sep=',')print(df.head(5))look_submit()file_namefile_code0000000.png01000001.png02000002.png03000003.png04000004.png0查看图片上的标签框defplot_samples():imgs=glob(data_dir['train_data']+'*.png')fig,ax=plt.subplots(figsize=(12,8),ncols=2,nrows=2)marks=json.loads(open(data_dir['train_label'],'r').read())foriinrange(4):img_name=os.path.split(imgs[i])[-1]mark=marks[img_name]img=Image.open(imgs[i])img=np.array(img)bboxes=np.array([mark['left'],mark['top'],标记['宽度'],标记['height']])ax[i//2,i%2].imshow(img)forjinrange(len(mark['label'])):#定义一个矩形rect=patch.Rectangle(bboxes[:,j][:2],bboxes[:,j][2],bboxes[:,j][3],facecolor='none',edgecolor='r')ax[i//2,i%2].text(bboxes[:,j][0],bboxes[:,j][1],mark['label'][j])#绘制矩形ax[i//2,i%2].add_patch(rect)plt.show()plot_samples()查看训练图片的长宽分布defimg_size_summary():sizes=[]forimginglob(data_dir['train_data']+'*.png'):img=Image.open(img)sizes.append(img.size)sizes=np.array(sizes)plt.figure(figsize=(10,8))plt.scatter(sizes[:,0],sizes[:,1])plt.xlabel('Width')plt.ylabel('Height')plt.title('imagewidth-heightsummary')plt.show()returnnp.mean(sizes,axis=0),np.median(sizes,axis=0)mean,median=img_size_summary()print('mean:',mean)print('median:',median)可以看出训练图片的尺寸差异很大,并且基本上是宽大于高,两者之间的区别een宽度大于高度之间的差异网络输入大小的后续确定可以结合中值或平均值来确定网络输入大小。查看边界框大小分布defbbox_summary():marks=json.loads(open(data_dir['train_label'],'r').read())bboxes=[]forimg,markinmarks.items():for我在范围内(len(标记['标签'])):bboxes。append([mark['left'][i],mark['top'][i],mark['width'][i],mark['height'][i]])bboxes=np.array(bboxes)fig,ax=plt.subplots(figsize=(12,8))ax.scatter(bboxes[:,2],bboxes[:,3])ax.set_title('bboxwidth-heightsummary')ax.set_xlabel('width')ax.set_ylabel('height')plt.show()bbox_summary()如果使用目标检测的思想来实现字符识别,可以使用Kmeans聚类来确定anchorsizeforbounding盒子。查看不同字符类别的个数deflabel_nums_summary():marks=json.load(open(data_dir['train_label'],'r'))dicts={i:0foriinrange(10)}forimg,mark在marks.items():forlbinmark['label']:dicts[lb]+=1xticks=list(range(10))fig,ax=plt.subplots(figsize=(10,8))ax.bar(x=list(dicts.keys()),height=list(dicts.values()))ax.set_xticks(xticks)plt.show()returndictsprint(label_nums_summary())可以看到不同类别的区别还有除了数字1的出现次数较多之外,总体上没有太大差异。没有出现极端不平衡。后期分类可以考虑使用Weighted-CrossEntropyloss。检查每个图像中的数字deflabel_summary():marks=json.load(open(data_dir['train_label'],'r'))dicts={}forimg,markinmarks.items():iflen(mark['label'])不在字典中:dicts[len(mark['label'])]=0dicts[len(mark['label'])]+=1dicts=sorted(dicts.items(),key=lambdax:x[0])fork,vindicts:print('有%d个数字的图片数量:%d'%(k,v))label_summary()有1个数字的图片数量:46362号码的图片数量:162623号码的图片数量:78134号码的图片数量:12805号码的图片数量:86号码的图片数量:1可以看出只有一张图片包含6数字,也许是异常值,可以忽略。几乎所有1~4个数字的图片几乎占了训练图片的全部。构建数据集这里,我们参考Datawhale提供的Baseline。由于每张图片包含的数字不超过6个,为简单起见,将字符识别视为一个分类问题。这里的自定义数据集,DigitsDataset继承自torch.utils.data.Dataset,数据增强使用内置的torchvison.transforms。这里只进行常规的增强操作,如旋转、随机灰度转换、HSV随机调整等。classDigitsDataset(Dataset):"""DigitsDatasetParams:data_dir(string):datadirectorylabel_path(string):labelpathaug(bool):是否进行图像增强,默认值:True"""def__init__(self,data_dir,label_path,size=(64,128),aug=True:super(DigitsDataset,self).__init__()self.imgs=glob(data_dir+'*.png')self.aug=augself.size=size如果label_path==无:self.labels=无其他:self.labels=json.load(open(label_path,'r'))self.imgs=[(img,self.labels[os.path.split(img)[-1]])forimginself.imgsifos.path.split(img)[-1]inself.labels]def__getitem__(self,idx):ifself.labels:img,label=self.imgs[idx]否则:img=self.imgs[idx]label=Noneimg=Image.open(img)trans0=[transforms.ToTensor(),transforms.Normalize(mean=[0.485,0.456,0.406],std=[0.229,0.224,0.225])]min_size=self.size[0]if(img.size[1]/self.size[0])<((img.size[0]/self.size[1]))elseself.size[1]trans1=[transforms.Resize(min_size),transforms.CenterCrop(self.size)]如果self.aug:trans1.extend([transforms.ColorJitter(0.1,0.1,0.1),transforms.RandomGrayscale(0.1),transforms.RandomAffine(10,translate=(0.05,0.1),shear=5)])trans1.extend(trans0)img=transforms.Compose(trans1)(img)如果self.labels:返回img,t.tensor(label['label'][:5]+(5-len(label['label']))*[10]).long()else:返回img,self.imgs[idx]def__len__(self):returnlen(self.imgs)检查数据增强效果fig,ax=plt.subplots(figsize=(6,12),nrows=4,ncols=2)foriinrange(8):img,label=dataset[i]#这些需要去规范化img=img*t.tensor([0.229,0.224,0.225]).view(3,1,1)+t.tensor([0.485,0.456,0.406]).view(3,1,1)ax[i//2,i%2].imshow(img.permute(1,2,0).numpy())ax[i//2,i%2].set_xticks([])ax[i//2,i%2].set_yticks([])plt.show()总结这里主要介绍数据的准备和数据集的构建,没有使用更高级复杂的操作,目的是搭建一个基础数据框,以后在此基础上添加其他操作会更方便。在下一篇文章中,我将专门介绍数据增强,以实现一些更复杂的操作。参考[1]天池项目地址[2]Kaggle比赛地址[3]Pytorch数据集搭建教程[4]Datawhale字符识别基线地址
