1

数据介绍

项目来自天池竞赛, 这是项目地址
项目数据来自Google街景图像中的门牌号数据集(The Street View House Numbers Dataset, SVHN), 对应的Kaggle竞赛地址

该数据来自真实场景的门牌号。训练集数据包括3W张照片,验证集数据包括1W张照片,每张照片包括颜色图像和对应的编码类别和具体位置;为了保证比赛的公平性,测试集A包括4W张照片,测试集B包括4W张照片。
官方已经帮我们划分好训练集和验证集。

enter image description here
如下图所示,所示的文件分别为

mchar_train.zip: 训练图片
mchar_val.zip: 验证图片
mchar_test_a.zip: 测试图片
mchar_train.json: 训练图片标注
mchar_val.json: 验证图片标注
mchar_sample_submit_A.csv: 提交格式文件

image.png

图片标注为json格式, 字段含义如下

Field Description
top 左上角坐标X
height 字符高度
left 左上角坐标Y
width 字符宽度
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/My Drive/Data/Datawhale-DigitsRecognition/mchar_train.json',
    'val_label': '/content/drive/My Drive/Data/Datawhale-DigitsRecognition/mchar_val.json',
    'submit_file': '/content/drive/My Drive/Data/Datawhale-DigitsRecognition/mchar_sample_submit_A.csv'
}
  • 查看图片数量

    def data_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('train image counts: %d'%len(train_list))
        print('val image counts: %d'%len(val_list))
        print('test image counts: %d'%len(test_list))
    
    data_summary()
    train image counts: 30000
    val image counts: 10000
    test image counts: 40000
    
  • 查看标注文件信息

    def look_train_json():
        with open(data_dir['train_label'], 'r', encoding='utf-8') as f:
            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]}
    
  • 查看结果文件提交格式

    def look_submit():
        df = pd.read_csv(data_dir['submit_file'], sep=',')
        print(df.head(5))
    
    look_submit()
        file_name  file_code
    0  000000.png          0
    1  000001.png          0
    2  000002.png          0
    3  000003.png          0
    4  000004.png          0
    
  • 在图片上查看标注框

    
    def plot_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())
        for i in range(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'],
                mark['width'],
                mark['height']]
            )
            ax[i//2, i%2].imshow(img)
            for j in range(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()

    image.png

  • 查看训练图片的长宽分布

    def img_size_summary():
        sizes = []
    
        for img in glob(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('image width-height summary')
        plt.show()
        return np.mean(sizes, axis=0), np.median(sizes, axis=0)
    
    mean, median = img_size_summary()
    print('mean: ', mean)
    print('median: ', median)
    

    image.png
    可以看到,训练图片之间的尺寸差异非常大,且基本上都是宽要比高大,宽之间的差异大于高之间的差异。后续确定网络输入大小,可以结合中位数或平均值确定网络输入大小。

  • 查看边界框大小分布

    def bbox_summary():
        marks = json.loads(open(data_dir['train_label'], 'r').read())
        bboxes = []
    
        for img, mark in marks.items():
            for i in range(len(mark['label'])):
            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('bbox width-height summary')
        ax.set_xlabel('width')
        ax.set_ylabel('height')
        plt.show()
    
    bbox_summary()

    image.png
    如果采用目标检测的思路实现字符识别,可以使用Kmeans聚类的方式来对边界框来确定anchor尺寸。

  • 查看不同字符类别的数目

    def label_nums_summary():
        marks = json.load(open(data_dir['train_label'], 'r'))
    
        dicts = {i: 0 for i in range(10)}
        for img, mark in marks.items():
            for lb in mark['label']:
            dicts[lb] += 1
    
        xticks = 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()
        return dicts
    
    print(label_nums_summary())

    image.png

    可以看出不同类别之间差别总体差异不大,除了数字1的出现次数较大。没有出现极端不平衡的情况。后期分类可以考虑使用Weighted-CrossEntropy损失。

  • 查看每个图片出现的数字个数

    def label_summary():
        marks = json.load(open(data_dir['train_label'], 'r'))
    
        dicts = {}
        for img, mark in marks.items():
            if len(mark['label']) not in dicts:
            dicts[len(mark['label'])] = 0
            dicts[len(mark['label'])] += 1
        dicts = sorted(dicts.items(), key=lambda x: x[0])
        for k, v in dicts:
            print('%d个数字的图片数目: %d'%(k, v))
    
    label_summary()
    1个数字的图片数目: 4636
    2个数字的图片数目: 16262
    3个数字的图片数目: 7813
    4个数字的图片数目: 1280
    5个数字的图片数目: 8
    6个数字的图片数目: 1
    

    可以看到,只有一个图片包含数字为6个,可能是异常值,可以不予考虑。几乎全部1~4个数字的图片几乎占了训练图片的全部。

构建数据集

这里,我们借鉴Datawhale提供的Baseline, 由于每个图片最多只包含不到6个数字,为了简化,将字符识别当做一个分类问题来处理。

这里自定义数据集,DigitsDataset继承自torch.utils.data.Dataset,数据增强使用自带的torchvison.transforms。这里只进行了常规的增强操作,比如旋转,随机转灰度,随机调整HSV等。

class DigitsDataset(Dataset):
    """
    
    DigitsDataset
    
    Params:
        data_dir(string): data directory
    
        label_path(string): label path
    
        aug(bool): wheather do image augmentation, default: 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 = aug
    
        self.size = size
        if label_path == None:
            self.labels = None
        else:
            self.labels = json.load(open(label_path, 'r'))
            self.imgs = [(img, self.labels[os.path.split(img)[-1]]) for img in self.imgs if os.path.split(img)[-1] in self.labels]
        
    def __getitem__(self, idx):
        if self.labels:
            img, label = self.imgs[idx]
        else:
            img = self.imgs[idx]
            label = None
        
            img = 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])) else self.size[1]
            trans1 = [
                transforms.Resize(min_size),    
                transforms.CenterCrop(self.size)
                ]
    
        if 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)
    
        if self.labels:
            return img, t.tensor(label['label'][:5] + (5 - len(label['label']))*[10]).long()
        else:
            return img, self.imgs[idx]
    
    
    def __len__(self):
        return len(self.imgs)

查看一下数据增强的效果

fig, ax = plt.subplots(figsize=(6, 12), nrows=4, ncols=2)
for i in range(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()

image.png

总结

这里主要介绍了数据的准备和数据集的构建,并未使用比较高级复杂的操作,目的是为了搭建一个基础的数据框架,后续可以更加方便的在此基础上增加其他的操作。

下一篇我会专门介绍数据增强,实现一些更复杂的操作。

Reference

[1] 天池项目地址
[2] Kaggle竞赛地址
[3] Pytorch数据集构建Tutorial
[4] Datawhale字符识别Baseline地址


mhxin
84 声望15 粉丝