Skip to content

Commit 2bd4468

Browse files
committed
Added face keypoint to demos. Need to clean up.
1 parent f613228 commit 2bd4468

File tree

9 files changed

+1070
-0
lines changed

9 files changed

+1070
-0
lines changed

face-keypoint/README.md

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
##Face Keypoint Detector
2+
3+
> TODO: Clean up and reorganize code
4+
5+
If you want to run the demo:
6+
7+
1. Download model from '''http://tiny.cc/z4o8oy''' and unzip
8+
9+
2. Run '''python3 webcam.py <path to model>'''
10+

face-keypoint/data/ls3d.py

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import numpy as np
2+
from random import random
3+
from glob import glob
4+
from pathlib import Path
5+
from PIL import Image
6+
7+
import imgaug as ia
8+
from imgaug import augmenters as iaa
9+
10+
import torch
11+
from torch.utils.data import Dataset
12+
from torch.utils.serialization import load_lua
13+
from torchvision import transforms
14+
15+
def ann2hm(ann, size):
16+
h, w = size
17+
sigma = 5e-3 * max(h, w)
18+
size = 6 * sigma + 1
19+
x = np.arange(0, size, 1, float)
20+
y = x[:, np.newaxis]
21+
x0 = y0 = size // 2
22+
g = torch.Tensor(np.exp(- ((x - x0) ** 2 + (y - y0) ** 2) / (2 * (sigma ** 2))))
23+
24+
hm = torch.zeros((68, h, w))
25+
for i in range(68):
26+
x, y = ann[i]
27+
ul = [int(x - 3 * sigma), int(y - 3 * sigma)]
28+
br = [int(x + 3 * sigma + 1), int(y + 3 * sigma + 1)]
29+
if (ul[0] >= w or ul[1] >= h or
30+
br[0] < 0 or br[1] < 0):
31+
continue
32+
33+
g_x = max(0, -ul[0]), min(br[0], w) - ul[0]
34+
g_y = max(0, -ul[1]), min(br[1], h) - ul[1]
35+
img_x = max(0, ul[0]), min(br[0], w)
36+
img_y = max(0, ul[1]), min(br[1], h)
37+
if (img_x[1] - img_x[0] <= 0 or img_y[1] - img_y[0] <= 0 or
38+
g_x[1] - g_x[0] <= 0 or g_y[1] - g_y[0] <= 0):
39+
continue
40+
hm[i,img_y[0]:img_y[1], img_x[0]:img_x[1]] = g[g_y[0]:g_y[1], g_x[0]:g_x[1]]
41+
42+
return hm
43+
44+
class LS3D(Dataset):
45+
46+
def __init__(self, root, start = 0, end = 1, image_size = 256):
47+
48+
root = Path(root)
49+
imgs = glob((root / '**' / '*.jpg').as_posix(),
50+
recursive = True)
51+
anns = glob((root / '**' / '*.t7').as_posix(),
52+
recursive = True)
53+
54+
imgs = sorted(imgs)
55+
anns = sorted(anns)
56+
57+
start = int(start*len(imgs))
58+
end = int(end*len(imgs))
59+
60+
self.imgs = imgs[start:end]
61+
self.anns = anns[start:end]
62+
self.image_size = image_size
63+
self.normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406],
64+
std=[0.229, 0.224, 0.225])
65+
self.scale = transforms.Scale(image_size)
66+
self.tensor = transforms.ToTensor()
67+
68+
self.aug = iaa.SomeOf(3, [
69+
iaa.CropAndPad(percent=(-0.25, 0.25)),
70+
iaa.GaussianBlur(sigma=(0.0, 1.0)),
71+
iaa.Dropout(p=(0, 0.2)),
72+
iaa.CoarseDropout((0.0, 0.05), size_percent=(0.02, 0.25)),
73+
iaa.Sometimes(0.50, iaa.Affine(scale=(0.5, 1.5))),
74+
iaa.Sometimes(0.50, iaa.Affine(rotate=(-60, 60))),
75+
iaa.Sometimes(0.50, iaa.Affine(shear=(-10, 10)))
76+
])
77+
78+
def __getitem__(self, index):
79+
80+
img = self.imgs[index]
81+
ann = self.anns[index]
82+
83+
img = Image.open(img).convert('RGB')
84+
ann = load_lua(ann)
85+
86+
ow, oh = img.size
87+
img = self.scale(img)
88+
nw, nh = img.size
89+
sx, sy = nw/ow, nh/oh
90+
91+
# scale annotation to image scale
92+
ann[:, 0] = ann[:, 0] * sx
93+
ann[:, 1] = ann[:, 1] * sy
94+
kpts = []
95+
for x, y in ann:
96+
kpts.append(ia.Keypoint(x=int(x), y=int(y)))
97+
98+
# apply imgaug transforms
99+
img = np.asarray(img)
100+
kpts = ia.KeypointsOnImage(kpts, shape=img.shape)
101+
aug = self.aug.to_deterministic()
102+
img = aug.augment_images([img])[0]
103+
kpts = aug.augment_keypoints([kpts])[0]
104+
105+
for i in range(len(kpts.keypoints)):
106+
kp = kpts.keypoints[i]
107+
ann[i,0] = int(kp.x); ann[i,1] = int(kp.y)
108+
109+
img = Image.fromarray(img)
110+
111+
# Random crop around annotation
112+
min_x, min_y = list(ann.min(0)[0].float())
113+
max_x, max_y = list(ann.max(0)[0].float())
114+
115+
off_x = (self.image_size - (max_x - min_x)) * random()
116+
off_y = (self.image_size - (max_y - min_y)) * random()
117+
118+
x1 = int(min_x - off_x)
119+
y1 = int(min_y - off_y)
120+
x2 = x1 + self.image_size
121+
y2 = y1 + self.image_size
122+
123+
pad_x1 = -x1 if x1 < 0 else 0
124+
pad_y1 = -y1 if y1 < 0 else 0
125+
pad_x2 = x2-nw if x2 > nw else 0
126+
pad_y2 = y2-nh if y2 > nh else 0
127+
128+
pad = transforms.Pad((pad_x1, pad_y1, pad_x2, pad_y2), 0)
129+
img = pad(img)
130+
131+
img = self.tensor(img)
132+
133+
ann[:, 0] = ann[:, 0] + pad_x1
134+
ann[:, 1] = ann[:, 1] + pad_y1
135+
136+
x1 = 0 if x1 < 0 else x1
137+
y1 = 0 if y1 < 0 else y1
138+
x2 = x1 + self.image_size
139+
y2 = y1 + self.image_size
140+
141+
ann[:, 0] = ann[:, 0] - x1
142+
ann[:, 1] = ann[:, 1] - y1
143+
144+
img = img[:, y1:y2, x1:x2]
145+
146+
#img = torch.from_numpy(img).permute(2,0,1).contiguous()
147+
#img = self.normalize(img)
148+
149+
return img, ann2hm(ann, (self.image_size, self.image_size))
150+
151+
def __len__(self):
152+
return min(len(self.imgs), len(self.anns))

face-keypoint/models/facenet.py

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import torch
2+
import torch.nn as nn
3+
from torch.nn import functional as F
4+
from torchvision.models import resnet
5+
6+
class FaceNet(nn.Module):
7+
8+
def __init__(self):
9+
super().__init__()
10+
11+
base_net = resnet.resnet34(pretrained=True)
12+
13+
self.in_block = nn.Sequential(
14+
base_net.conv1,
15+
base_net.bn1,
16+
base_net.relu,
17+
base_net.maxpool
18+
)
19+
20+
self.encoder = nn.ModuleList([
21+
base_net.layer1,
22+
base_net.layer2,
23+
base_net.layer3,
24+
base_net.layer4
25+
])
26+
27+
self.decoder_start = nn.Sequential(
28+
nn.Conv2d(512, 256, 1),
29+
nn.BatchNorm2d(256),
30+
nn.ReLU(inplace=True),
31+
nn.Conv2d(256, 256, 3, padding=1),
32+
nn.BatchNorm2d(256),
33+
nn.ReLU(inplace=True)
34+
)
35+
36+
self.decoder = nn.ModuleList([])
37+
self.lateral = nn.ModuleList([])
38+
self.upsampler = nn.ModuleList([])
39+
40+
for in_channels in reversed([64, 128, 256]):
41+
d = nn.Sequential(
42+
nn.Conv2d(256, 256, 3, padding=1),
43+
nn.BatchNorm2d(256),
44+
nn.ReLU(inplace=True)
45+
)
46+
47+
u = nn.Sequential(
48+
nn.ConvTranspose2d(256, 256, 3, stride=2, padding=1, output_padding=1),
49+
nn.BatchNorm2d(256),
50+
nn.ReLU(inplace=True)
51+
)
52+
53+
l = nn.Sequential(
54+
nn.Conv2d(in_channels, 256, 1),
55+
nn.BatchNorm2d(256),
56+
nn.ReLU(inplace=True)
57+
)
58+
self.decoder.append(d)
59+
self.upsampler.append(u)
60+
self.lateral.append(l)
61+
62+
self.classifier = nn.ModuleList([
63+
nn.Sequential(nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(inplace=True)),
64+
nn.Sequential(nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(inplace=True)),
65+
nn.Sequential(nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(inplace=True)),
66+
nn.Sequential(nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(inplace=True)),
67+
nn.Conv2d(256, 68, 3, padding=1)
68+
])
69+
70+
def _cat(self, x, y):
71+
_, _, h, w = y.size()
72+
return torch.cat((x[:, :, :h, :w], y), 1)
73+
74+
def _add(self, x, y):
75+
_,_,h,w = y.size()
76+
return F.upsample(x, size=(h,w), mode='bilinear') + y
77+
78+
def forward(self, x):
79+
x = self.in_block(x)
80+
residuals = []
81+
for e in self.encoder:
82+
x = e(x)
83+
residuals.append(x)
84+
x = self.decoder_start(x)
85+
result = []
86+
for i, (l, u, d) in enumerate(zip(self.lateral, self.upsampler, self.decoder)):
87+
r = l(residuals[-(i+2)])
88+
x = u(x)
89+
x = self._add(x, r)
90+
x = d(x)
91+
features = x
92+
f = x
93+
for i, c in enumerate(self.classifier):
94+
if i == 0: f = c(f)
95+
else: f = c(self._add(f, features))
96+
result.append(f)
97+
return result
98+
99+
if __name__ == "__main__":
100+
101+
from torch.autograd import Variable as V
102+
103+
x = torch.zeros((1,3,256,256))
104+
m = FaceNet()
105+
m.forward(V(x))
106+
107+

face-keypoint/models/linknet.py

+104
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import torch
2+
import torch.nn as nn
3+
from torch.nn import functional as F
4+
from torchvision.models import resnet
5+
6+
class DecoderBlock(nn.Module):
7+
def __init__(self, in_channels, out_channels, kernel_size, stride):
8+
super().__init__()
9+
padding = kernel_size // 2
10+
output_padding = stride // 2
11+
self.model = nn.Sequential(
12+
nn.Conv2d(in_channels, in_channels//4, 1, 1, padding=0, dilation=1),
13+
nn.BatchNorm2d(in_channels//4),
14+
nn.ReLU(inplace=True),
15+
16+
nn.ConvTranspose2d(in_channels//4, in_channels//4, 3, stride, padding=1, dilation=1,
17+
output_padding=output_padding),
18+
nn.BatchNorm2d(in_channels//4),
19+
nn.ReLU(inplace=True),
20+
21+
nn.Conv2d(in_channels//4, out_channels, 1, 1, padding=0, dilation=1),
22+
nn.BatchNorm2d(out_channels),
23+
nn.ReLU(inplace=True)
24+
)
25+
def forward(self, x): return self.model(x)
26+
27+
class FaceNet(nn.Module):
28+
29+
def __init__(self):
30+
super().__init__()
31+
32+
base_net = resnet.resnet34(pretrained=True)
33+
34+
self.in_block = nn.Sequential(
35+
base_net.conv1,
36+
base_net.bn1,
37+
base_net.relu,
38+
base_net.maxpool
39+
)
40+
41+
self.encoder = nn.ModuleList([
42+
base_net.layer1,
43+
base_net.layer2,
44+
base_net.layer3,
45+
base_net.layer4
46+
])
47+
48+
enc_channels = list(reversed([64, 128, 256, 512]))
49+
50+
self.decoder = nn.ModuleList([])
51+
self.lateral = nn.ModuleList([])
52+
for in_channel, out_channel in zip(enc_channels[:-1], enc_channels[1:]):
53+
d = DecoderBlock(in_channel, out_channel, 3, 2)
54+
l = nn.Sequential(
55+
nn.Conv2d(out_channel, 256, 3, padding=1),
56+
nn.BatchNorm2d(256),
57+
nn.ReLU(inplace=True)
58+
)
59+
self.decoder.append(d)
60+
self.lateral.append(l)
61+
62+
self.classifier = nn.ModuleList([
63+
nn.Sequential(nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(inplace=True)),
64+
nn.Sequential(nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(inplace=True)),
65+
nn.Sequential(nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(inplace=True)),
66+
nn.Sequential(nn.Conv2d(256, 256, 3, padding=1), nn.BatchNorm2d(256), nn.ReLU(inplace=True)),
67+
nn.Conv2d(256, 68, 3, padding=1)
68+
])
69+
70+
def _add(self, x, y):
71+
_,_,h,w = y.size()
72+
return F.upsample(x, size=(h,w), mode='bilinear') + y
73+
74+
def forward(self, x):
75+
x = self.in_block(x)
76+
77+
residuals = []
78+
for e in self.encoder:
79+
x = e(x)
80+
residuals.append(x)
81+
82+
result = []
83+
for i, (d, l) in enumerate(zip(self.decoder, self.lateral)):
84+
r = residuals[-(i+1)]
85+
x = d(x)
86+
87+
features = l(x)
88+
f = features
89+
for i, c in enumerate(self.classifier):
90+
if i == 0: f = c(f)
91+
else: f = c(self._add(f, features))
92+
result.append(f)
93+
94+
return result
95+
96+
if __name__ == "__main__":
97+
98+
from torch.autograd import Variable as V
99+
100+
x = torch.zeros((1,3,256,256))
101+
m = FaceNet()
102+
m.forward(V(x))
103+
104+

0 commit comments

Comments
 (0)