如何用PyTorch处理人脸姿态的数据?

简介: 在解决机器学习问题时, 我们需要付出很多努力来准备数据, 为了使代码更具可读性, PyTorch提供了许多工具来使数据加载变得简单易行。在本教程中, 我们将要学习如何对 一个重要的数据集进行加载、预处理数据增强。

一、数据加载和处理教程

在解决机器学习问题时, 我们需要付出很多努力来准备数据, 为了使代码更具可读性, PyTorch提供了许多工具来使数据加载变得简单易行。在本教程中, 我们将要学习如何对 一个重要的数据集进行加载、预处理数据增强。

为了运行这个教程,请确认下列包已安装:

  • scikit-image :用来读取图片和图像变换
  • pandas: 更方便地解析csv文件
from __future__ import print_function,division

import os
import torch
import pandas as pd
from skimage import io,transform
import numpy as np
import matplotlib.pyplot as plt

from torch.utils.data import Dataset,DataLoader
from torchvision import transforms,utils

# 忽略警告
import warnings

warnings.filterwarnings('ignore')

#开启交换模式
plt.ion()

我们将要处理的数据是人脸姿态。这就是对人脸的表示如下:

image.png

每张人脸图像上, 总共有68个不同的标注点被标记出来。

提示

打开http://t.cn/EaOQdfy 下载数据集, 这些图像在目录 'faces/'下. 这个数据集实际上是从imagenet数据集中选取标记为人脸的一些图片, 使用'dlib’s pose estimation 方法生成的.

### 下载图片数据
 
import os
import os.path
import errno
url ='https://download.pytorch.org/tutorial/faces.zip'
filename='faces.zip'
 
def download(root):
    '''
    下载数据人脸图像和标注点的压缩包。
    使用zipfile包解压。
    '''
    root = os.path.expanduser(root)
    import zipfile
    
    #下载图片压缩包到指定路径
    download_url(url,root,filename)
    
    print("数据下载完毕!")
    #获得当前路径
    cwd = os.getcwd()    
    path = os.path.join(root, filename)
    tar = zipfile.ZipFile(path, "r")
    #解压文件
    tar.extractall(root)
    tar.close()
    #切换到当前工作路径
    os.chdir(cwd)
 
def download_url(url, root, filename):
    from six.moves import urllib
    root = os.path.expanduser(root)
    fpath = os.path.join(root, filename)
    
    try:
        os.makedirs(root)
    except OSError as e:
        if e.errno == errno.EEXIST:
            pass
        else:
            raise
    
    # downloads file
    if os.path.isfile(fpath) :
        print('使用已下载文件: ' + fpath)
    else:
        try:
            print('下载 ' + url + ' 到 ' + fpath)
            urllib.request.urlretrieve(url, fpath)
        except:
            if url[:5] == 'https':
                url = url.replace('https:', 'http:')
                print('下载失败。尝试将https -> http'
                      ' 下载  ' + url + ' 到 ' + fpath)
                urllib.request.urlretrieve(url, fpath)
 
download('./')

image.png

数据集中的csv文件记录着标注信息, 像下面这样:

image_name,part_0_x,part_0_y,part_1_x,part_1_y,part_2_x, ... ,part_67_x,part_67_y

0805personali01.jpg,27,83,27,98, ... 84,134

1084239450_e76e00b7e7.jpg,70,236,71,257, ... ,128,312

让我们快速地读取CSV文件, 以(N,2)的数组形式获得标记点, 其中N表示标记点的个数.

  1. 读取标记点CSV文件,使用pandas的read_csv方法。
landmarks_frame = pd.read_csv('faces/face_landmarks.csv')
  1. 观察数据的结构,可以使用headinfo等方法,head()默认返回前5行,info()显示数据名称和数量。
landmarks_frame.info()

image.png

landmarks_frame.head()

image.png

由上表可知,每行表示一张图片和标记点的X、Y坐标,共有68个标记点。

  1. 已第4张图片为例。首先获得图片名称,其次获取所有标记点信息,首先转换成行向量(1×136),最后向量转换成N行2列的浮点型矩阵。
n = 3
img_name = landmarks_frame.iloc[n, 0]
landmarks = landmarks_frame.iloc[n, 1:].as_matrix()
landmarks = landmarks.astype('float').reshape(-1, 2)

print('Image name: {}'.format(img_name))
print('Landmarks shape: {}'.format(landmarks.shape))
print('First 4 Landmarks: {}'.format(landmarks[:4]))

image.png

我们写一个函数来显示一张图片和它的标记点, 然后用这个函数来显示一个样本。

def show_landmarks(image, landmarks):
    """显示带标记点的图片"""
    plt.imshow(image)
    plt.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
    plt.pause(0.001)  # 暂停一下, 使plots更新

plt.figure()
show_landmarks(io.imread(os.path.join('faces/', img_name)),
               landmarks)
plt.show()

image.png

二、Dataset 类

orch.utils.data.Dataset 是一个表示数据集的抽象类。你自定义dataset类需继承Dataset并重写下列方法:

__ len __ 使用len(dataset)可以返回数据集的大小。

__ getitem __ 支持索引, 以便于使用 dataset[i] 可以 获取第i个样本。

让我们为landmars 数据集构建一个dataset类。我们将在__ init 中读取csv文件,在 getitem __ 中读取图像。这可以高效利用内存,因为所有图像不会立即存储在内存中,而是根据需要读取。

我们的数据集样本将是一个dict {'image':image,'landmarks':landmarks}。我们的数据集将采用可选的参数变换,以便可以对样本应用任何所需的处理。我们将在下一节中看到变换的有用性。

class FaceLandmarksDataset(Dataset):
    ''' Face Landmarks Dataset '''
    def __init__(self,csv_file,root_dir,transform=None):
        """
        Args:
            csv_file (string): Path to the csv file with annotations.
            root_dir (string): Directory with all the images.
            transform (callable, optional): Optional transform to be applied
                on a sample.
        """
        self.landmarks_frame = pd.read_csv(csv_file)
        self.root_dir = root_dir
        self.transform = transform
    
    def __len__(self):
        return len(self.landmarks_frame)
    
    def __getitem__(self,idx):
        #获得指定索引图片的路径
        img_name = os.path.join(self.root_dir,
                                self.landmarks_frame.iloc[idx,0])
        #读取图片数据
        image = io.imread(img_name)
        #将标注点数据转换成矩阵(行向量)
        landmarks = self.landmarks_frame.iloc[idx,1:].as_matrix()
        #将矩阵转换成N行2列的矩阵。
        landmarks = landmarks.astype('float').reshape(-1,2)
        
        sample = {'image':image,'landmarks':landmarks}
        
        if self.transform:
            sample = self.transform(sample)
        
        return sample

我们实例化这个数据集并迭代数据样本。我们将打印前4个样本并显示它们的标注点。

face_dataset = FaceLandmarksDataset(csv_file='faces/face_landmarks.csv',
                                    root_dir='faces/')

fig = plt.figure()

for i in range(len(face_dataset)):
    sample = face_dataset[i]

    print(i, sample['image'].shape, sample['landmarks'].shape)

    ax = plt.subplot(1, 4, i + 1)
    plt.tight_layout()
    ax.set_title('Sample #{}'.format(i))
    ax.axis('off')
    show_landmarks(**sample)

    if i == 3:
        plt.show()
        break

image.png

三、Transforms

我们从上面可以发现一个问题是样本的尺寸(指宽和高)不相同。大部分的神经网络都期望一个固定尺寸的图像。所以,我们需要写一些预处理的代码。

让我们写三个 transform:

Rescale: 缩放图像

RandomCrop: 从图像中随机裁剪。这是数据增加。

ToTensor: 将numpy形式的图像数据转换成torch形式的图像数据(我们需要交换轴)。

我们将它们编写为可调用类而不是简单函数,这样每次调用时都不需要传递 transform 的参数。所以,我们只需要实现 call 方法,同时如果需要,也可实现 init 方法。我们可以像这样使用 transform:

tsfm = Transform(params)

transform_sample = tsfm(sample)

请注意以下这些 transforms 必须如何应用于图像和landmarks。

class Rescale(object):
    '''
    将样本图像缩放至给定尺寸。
    Args:
        output_size(tuple or int):期望的输出尺寸。如果是tuple,
        则输出与output_size匹配。如果是int,则较小的图像边缘
        与output_size匹配,保持纵横比相同。
    '''
    
    def __init__(self,output_size):
        assert isinstance(output_size,(int,tuple))
        self.output_size = output_size
    
    def __call__(self,sample):
        image ,landmarks = sample['image'],sample['landmarks']
        
        h , w = image.shape[:2]
        
                
        if isinstance(self.output_size, int):            
            if h > w:
                # new_h/new_w = h/w ,new_w = output_size
                new_h , new_w = self.output_size * h / w , self.output_size
            else:
              # 与上面的相反
                new_h , new_w = self.output_size, self.output_size * w / h
        
        else:
            # 直接以给定的数据为新的w、h
            new_h , new_w = self.output_size
        
        new_h , new_w = int(new_h) , int(new_w)
        #使用skimage包中tansform类的resize方法缩放图像。
        img = transform.resize(image,(new_h,new_w))
        
        # 将landmarks的位置按照图像的缩放比较,就行缩放。
        landmarks = landmarks * [new_w / w, new_h / h]
        
        return {'image': img, 'landmarks': landmarks}
                
import numpy as np
class RandomCrop(object):
    '''
    随机修剪样本图像。
    
    Args:
        output_size(tuple or int):期望的输出尺寸。如果为int,
        则进行方形裁剪。
    '''
    
    def __init__(self,output_size):
        assert isinstance(output_size, (int, tuple))
        if isinstance(output_size, int):
            self.output_size = (output_size, output_size)
        else:
            assert len(output_size) == 2
            self.output_size = output_size
   
    
    def __call__(self,sample):
        image, landmarks = sample['image'], sample['landmarks']
        
        #获得图像的尺寸。
        h, w = image.shape[:2]
        new_h, new_w = self.output_size

        top = np.random.randint(0, h - new_h)
        left = np.random.randint(0, w - new_w)

        image = image[top: top + new_h,
                      left: left + new_w]
        landmarks = landmarks - [left, top]

        return {'image': image, 'landmarks': landmarks}
      

                
class ToTensor(object):
    """Convert ndarrays in sample to Tensors."""

    def __call__(self, sample):
        image, landmarks = sample['image'], sample['landmarks']

        # 交换颜色的轴
        # numpy image: H x W x C
        #轴对应编号:0,1,2
        # torch image: C X H X W
        #轴对应编号:2,0,1
        
        '''
        transpose()的操作对象是矩阵。
        我们用一个例子来说明这个函数:
        [[[0 1] 
        [2 3]]

        [[4 5] 
        [6 7]]]

        这是一个shape为(2,2,2)的矩阵,现在对它进行transpose操作。
        首先我们对矩阵的维度进行编号,上述矩阵有三个维度,则编号分别为0,1,2,而transpose函数的参数输         入就是基于这个编号的,如果我们调用transpose(0,1,2),那么矩阵将不发生变化,如果我们不输入参         数,直接调用transpose(),其效果就是将矩阵进行转置,起作用等价与transpose(2,1,0)。

        在举个例子,对上面那个矩阵调用transpose(0,2,1) 
        下面为结果 
        [[[0 2] 
        [1 3]]

        [[4 6] 
        [5 7]]] 
        其实就是矩阵中每个元素按照一样的规则进行位置变换。
        '''
        
        image = image.transpose((2, 0, 1))
        return {'image': torch.from_numpy(image),
                'landmarks': torch.from_numpy(landmarks)}

Compose transforms

现在,我们已经在样本上使用了 transforms。让我们看看,我们想将图像的短边缩短至256在随机从图像中裁剪一个224的方形。我们想组成Rescale 和RandomCrop的联合transforms。torchvision.transforms.Compose是一个允许我们做这些的简单可调用类。

scale = Rescale(256)
crop = RandomCrop(100)
composed = transforms.Compose([Rescale(256),
                               RandomCrop(102)])

# Apply each of the above transforms on sample.
fig = plt.figure()
sample = face_dataset[65]
for i, tsfrm in enumerate([scale, crop, composed]):
    transformed_sample = tsfrm(sample)

    ax = plt.subplot(1, 3, i + 1)
    plt.tight_layout()
    ax.set_title(type(tsfrm).__name__)
    show_landmarks(**transformed_sample)

plt.show()

image.png

四、迭代数据

Iterating through the dataset

让我们把这些组合在一起创建一个包含组合转换器(composed transforms)的数据集(dataset)。

  • 总而言之, 每次取样数据集时:
  • 即时从文件中读取图像
  • 变换应用于读取的图像
  • 由于其中一个变换是随机的,因此在采样时会增加数据
transformed_dataset = FaceLandmarksDataset(csv_file='faces/face_landmarks.csv',
                                    root_dir='faces/',
                                    transform =transforms.Compose([
                                        Rescale(256),
                                        RandomCrop(204),
                                        ToTensor(),
                                    ]))
for i in range(len(transformed_dataset)):
    sample = transformed_dataset[i]
    
    print(i,sample['image'].size(),sample['landmarks'].size())
    
    if i == 3:
     break

image.png

然而,我们使用简单的for循环来迭代数据将会就丢失很多特性。事实上,我们错过了:

  • 批处理数据
  • 打乱(洗牌)数据
  • 使用multiprocessing作业,并行加载数据。

torch.utils.data.DataLoader 是提供了所有特性的迭代器。下面使用的参数应该是清楚的。感兴趣的一个参数是collate_fn。您可以使用collate_fn指定需要批量处理样品的准确程度。但是,对于大多数用例,默认整理应该可以正常工作。

#定义数据加载器,数据需要打乱,每次取4个样本。
#num_workers,我暂时无法理解。
dataloader = DataLoader(transformed_dataset,batch_size=4,
                        shuffle=True,num_workers=4)

# 帮助类,展示一个批次的数据。

def show_landmarks_batch(sample_batched):
    '''
    显示一个批次的图像。
    '''
    images_batch , landmarks_batch = \
        sample_batched['image'],sample_batched['landmarks']
    
    batch_size = len(sample_batched)
    
    im_size = images_batch.size(2)
    
    grid = utils.make_grid(images_batch)
    
    plt.imshow(grid.numpy().transpose((1,2,0)))
    
    for i in range(batch_size):
        plt.scatter(landmarks_batch[i, :, 0].numpy() + i * im_size,
                    landmarks_batch[i, :, 1].numpy(),
                    s=10, marker='.', c='r')
        plt.title('Batch from dataloader')
        
        
for i_batch,sample_batched in enumerate(dataloader):
    print(i_batch, sample_batched['image'].size(),
          sample_batched['landmarks'].size())

    # observe 4th batch and stop.
    if i_batch == 3:
        plt.figure()
        show_landmarks_batch(sample_batched)
        plt.axis('off')
        plt.ioff()
        plt.show()
        break

image.png

后记:torchvision

在这个教程中, 我们学习了如何写和使用数据集, 图像变换和dataloder. torchvision 提供了常用的数据集和图像变换, 或许你甚至不必写自定义的类和变换. 在torchvision中一个最经常用的数据集是ImageFolder. 它要求数据按下面的形式存放:

/root/hymenoptera_data/ants/xxx.png

/root/hymenoptera_data/ants/xxy.jpeg

/root/hymenoptera_data/ants/xxz.png

.

.

.

/root/hymenoptera_data/bees/123.jpg

/root/hymenoptera_data/bees/nsdf3.png

/root/hymenoptera_data/bees/asd932_.png

‘ants’, ‘bees’ 等是图像的类标. 同样, PIL.Image 中出现的一般的图像变换像 RandomHorizontalFlip, Scale 也是可以使用的. 你可以像下面这样用这些函数来写dataloader:

import torch
from torchvision import transforms, datasets

data_transform = transforms.Compose([
        transforms.RandomSizedCrop(224),
        transforms.RandomHorizontalFlip(),
        transforms.ToTensor(),
        transforms.Normalize(mean=[0.485, 0.456, 0.406],
                             std=[0.229, 0.224, 0.225])
    ])
hymenoptera_dataset = datasets.ImageFolder(root='./root/hymenoptera_data/train',
                                           transform=data_transform)
dataset_loader = torch.utils.data.DataLoader(hymenoptera_dataset,
                                             batch_size=4, shuffle=True,
                                             num_workers=4)

⭐总结

自定义dataset。首先需要继承torch.utils.data.Dataset 类;其次要实现__ len (使用len(dataset)可以返回数据集的大小)和 getitem __ (支持索引, 以便于使用 dataset[i] 可以 获取第i个样本)方法。

自定义transform。首先要要继承object类;其次实现__ call 方法,需要时可实现 init __ 方法。

目录
相关文章
|
6月前
|
机器学习/深度学习 数据采集 PyTorch
使用自定义 PyTorch 运算符优化深度学习数据输入管道
使用自定义 PyTorch 运算符优化深度学习数据输入管道
37 0
|
4月前
|
数据采集 PyTorch 算法框架/工具
PyTorch基础之数据模块Dataset、DataLoader用法详解(附源码)
PyTorch基础之数据模块Dataset、DataLoader用法详解(附源码)
400 0
|
8月前
|
PyTorch 算法框架/工具 索引
Pytorch学习笔记(2):数据读取机制(DataLoader与Dataset)
Pytorch学习笔记(2):数据读取机制(DataLoader与Dataset)
400 0
Pytorch学习笔记(2):数据读取机制(DataLoader与Dataset)
|
3月前
|
机器学习/深度学习 PyTorch TensorFlow
PyTorch使用一维卷积对时间序列数据分类
PyTorch使用一维卷积对时间序列数据分类
|
8月前
|
机器学习/深度学习 PyTorch 算法框架/工具
使用Pytorch和OpenCV实现视频人脸替换
“DeepFaceLab”项目已经发布了很长时间了,作为研究的目的,本文将介绍他的原理,并使用Pytorch和OpenCV创建一个简化版本。
106 0
|
10月前
|
机器学习/深度学习 PyTorch 算法框架/工具
Pytorch使用专题 | 2 :Pytorch中数据读取-Dataset、Dataloader 、TensorDataset 和 Sampler 的使用
介绍Pytorch中数据读取-Dataset、Dataloader 、TensorDataset 和 Sampler 的使用
|
5月前
|
机器学习/深度学习 PyTorch 算法框架/工具
时间序列pytorch搭建lstm用电量预测 完整代码数据
时间序列pytorch搭建lstm用电量预测 完整代码数据
117 0
|
8月前
|
数据采集 自然语言处理 PyTorch
全套解决方案:基于pytorch、transformers的中文NLP训练框架,支持大模型训练和文本生成,快速上手,海量训练数据!
全套解决方案:基于pytorch、transformers的中文NLP训练框架,支持大模型训练和文本生成,快速上手,海量训练数据!
全套解决方案:基于pytorch、transformers的中文NLP训练框架,支持大模型训练和文本生成,快速上手,海量训练数据!
|
9月前
|
PyTorch 算法框架/工具 异构计算
PyTorch高级教程:自定义模型、数据加载及设备间数据移动
在深入理解了PyTorch的核心组件之后,我们将进一步学习一些高级主题,包括如何自定义模型、加载自定义数据集,以及如何在设备(例如CPU和GPU)之间移动数据。
|
9月前
|
PyTorch 算法框架/工具 索引
Pytorch: 数据读取机制Dataloader与Dataset
Pytorch: 数据读取机制Dataloader与Dataset
179 0