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
# Ignore warnings
import warnings
warnings.filterwarnings("ignore")
載入圖片和坐標(biāo)
landmarks_frame = pd.read_csv('data/faces/face_landmarks.csv')
n=65
img_name = landmarks_frame.iloc[n, 0] #獲取圖片的名稱
landmarks = landmarks_frame.iloc[n, 1:].as_matrix() #獲取點(diǎn)的位置
landmarks = landmarks.astype('float').reshape(-1, 2)
landmarks_frame.iloc[:3, :] #展示一下csv里面的格式
<div>
<style scoped>
.dataframe tbody tr th:only-of-type {
vertical-align: middle;
}
.dataframe tbody tr th {
vertical-align: top;
}
.dataframe thead th {
text-align: right;
}
</style>
<table border="1" class="dataframe">
<thead>
<tr style="text-align: right;">
<th></th>
<th>image_name</th>
<th>part_0_x</th>
<th>part_0_y</th>
<th>part_1_x</th>
<th>part_1_y</th>
<th>part_2_x</th>
<th>part_2_y</th>
<th>part_3_x</th>
<th>part_3_y</th>
<th>part_4_x</th>
<th>...</th>
<th>part_63_x</th>
<th>part_63_y</th>
<th>part_64_x</th>
<th>part_64_y</th>
<th>part_65_x</th>
<th>part_65_y</th>
<th>part_66_x</th>
<th>part_66_y</th>
<th>part_67_x</th>
<th>part_67_y</th>
</tr>
</thead>
<tbody>
<tr>
<th>0</th>
<td>0805personali01.jpg</td>
<td>27</td>
<td>83</td>
<td>27</td>
<td>98</td>
<td>29</td>
<td>113</td>
<td>33</td>
<td>127</td>
<td>39</td>
<td>...</td>
<td>93</td>
<td>136</td>
<td>100</td>
<td>141</td>
<td>93</td>
<td>135</td>
<td>89</td>
<td>135</td>
<td>84</td>
<td>134</td>
</tr>
<tr>
<th>1</th>
<td>1084239450_e76e00b7e7.jpg</td>
<td>70</td>
<td>236</td>
<td>71</td>
<td>257</td>
<td>75</td>
<td>278</td>
<td>82</td>
<td>299</td>
<td>90</td>
<td>...</td>
<td>148</td>
<td>311</td>
<td>179</td>
<td>308</td>
<td>149</td>
<td>312</td>
<td>137</td>
<td>314</td>
<td>128</td>
<td>312</td>
</tr>
<tr>
<th>2</th>
<td>10comm-decarlo.jpg</td>
<td>66</td>
<td>114</td>
<td>65</td>
<td>128</td>
<td>67</td>
<td>142</td>
<td>68</td>
<td>156</td>
<td>72</td>
<td>...</td>
<td>128</td>
<td>162</td>
<td>136</td>
<td>167</td>
<td>127</td>
<td>166</td>
<td>121</td>
<td>165</td>
<td>116</td>
<td>164</td>
</tr>
</tbody>
</table>
<p>3 rows × 137 columns</p>
</div>
接下來,是如何展示圖片朴下,以及把點(diǎn)畫在圖片之上
def show_landmarks(image, landmarks):
fig, ax = plt.subplots()
ax.imshow(image)
ax.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
plt.pause(0.001) #暫停讓圖片更新?
plt.show()
show_landmarks(io.imread(os.path.join('data/faces/', img_name)),
landmarks)
torch.utils.data.Dataset是一個(gè)抽象基類表示一個(gè)數(shù)據(jù)集,我們需要為其設(shè)定_len方法和_getitem方法.
class FaceLandmarksDataset(Dataset):
def __init__(self, csv_file, root_dir, transform=None):
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:]
landmarks = np.array([landmarks])
landmarks = landmarks.astype('float').reshape(-1, 2)
sample = {'image': image, 'landmarks': landmarks}
if self.transform:
sample = self.transform(sample)
return sample
利用這個(gè)類图毕,我們來展示一下前4幅圖像
face_dataset = FaceLandmarksDataset(csv_file='data/faces/face_landmarks.csv',
root_dir='data/faces/')
def show_landmarks(image, landmarks):
plt.imshow(image)
plt.scatter(landmarks[:, 0], landmarks[:, 1], s=10, marker='.', c='r')
plt.pause(0.001) #暫停讓圖片更新?
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
0 (324, 215, 3) (68, 2)
1 (500, 333, 3) (68, 2)
2 (250, 258, 3) (68, 2)
3 (434, 290, 3) (68, 2)
Transforms
很多時(shí)候,我們需要對(duì)圖片進(jìn)行一些變化悼尾,比方說大小的調(diào)整等等
利用函子(_call_)能夠很好很方便對(duì)圖片進(jìn)行處理
class Rescale(object):
"""Rescale the image in a sample to a given size.
Args:
output_size (tuple or int): Desired output size. If tuple, output is
matched to output_size. If int, smaller of image edges is matched
to output_size keeping aspect ratio the same.
"""
def __init__(self, output_size):
assert isinstance(output_size, (int, tuple)) #output_size應(yīng)當(dāng)是一個(gè)整數(shù)或者元組
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): #如果是一個(gè)整數(shù)缔逛,那么縮放的邏輯是要保持比例
if h > w:
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: #否則就直接等于就好了
new_h, new_w = self.output_size
new_h, new_w = int(new_h), int(new_w)
img = transform.resize(image, (new_h, new_w))
# h and w are swapped for landmarks because for images,
# x and y axes are axis 1 and 0 respectively
landmarks = landmarks * [new_w / w, new_h / h] #坐標(biāo)也要相應(yīng)改變大小
return {'image': img, 'landmarks': landmarks}
class RandomCrop(object): #隨機(jī)裁剪遭垛,但是實(shí)際上是一整塊來的
"""Crop randomly the image in a sample.
Args:
output_size (tuple or int): Desired output size. If int, square crop
is made.
"""
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']
# swap color axis because
# numpy image: H x W x C
# torch image: C X H X W
image = image.transpose((2, 0, 1)) #把ndarray轉(zhuǎn)換為tensor需要改變順序
return {'image': torch.from_numpy(image),
'landmarks': torch.from_numpy(landmarks)}
Compose transforms
利用torchvision.transforms.Compose可以幫助我們對(duì)一個(gè)圖片進(jìn)行多個(gè)操作
scale = Rescale(256)
crop = RandomCrop(128)
composed = transforms.Compose([Rescale(256),
RandomCrop(224)])
# 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()
數(shù)據(jù)集的迭代
我們可以用 for ... in ... 來迭代數(shù)據(jù)集,但是這么做并不方便节槐,因?yàn)楹芏鄷r(shí)候訓(xùn)練神經(jīng)網(wǎng)絡(luò)是要分批和打亂順序的torch.utils.data.DataLoader可以幫助我們完成這一個(gè)目標(biāo)
transformed_dataset = FaceLandmarksDataset(csv_file='data/faces/face_landmarks.csv',
root_dir='data/faces/',
transform=transforms.Compose([
Rescale(256),
RandomCrop(224),
ToTensor()
]))
dataloader = DataLoader(transformed_dataset, batch_size=4,
shuffle=True, num_workers=0) #batch_size: batch的大小 shuffle=True表示順序打亂
def show_landmarks_batch(sample_batched):
"""Show image with landmarks for a batch of samples."""
images_batch, landmarks_batch = \
sample_batched['image'], sample_batched['landmarks']
batch_size = len(images_batch)
im_size = images_batch.size(2)
grid_border_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 + (i + 1) * grid_border_size, #既然圖片加了邊框搀庶,而且并排放置,所以我們需要把這部分加上去
landmarks_batch[i, :, 1].numpy() + grid_border_size,
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())
if i_batch == 0:
plt.figure()
show_landmarks_batch(sample_batched)
plt.axis('off')
plt.ioff()
plt.show()
break
0 torch.Size([4, 3, 224, 224]) torch.Size([4, 68, 2])
224