blinklinmult 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- blinklinmult/__init__.py +12 -0
- blinklinmult/models/BlinkLinMulT.py +164 -0
- blinklinmult/models/__init__.py +23 -0
- blinklinmult/models/backbone.py +94 -0
- blinklinmult/models/clip_wrapper.py +37 -0
- blinklinmult/models/efficientnetv2.py +27 -0
- blinklinmult/models/gru.py +40 -0
- blinklinmult/preprocess/__init__.py +0 -0
- blinklinmult/preprocess/cew.py +84 -0
- blinklinmult/preprocess/eyeblink8.py +117 -0
- blinklinmult/preprocess/mrl.py +53 -0
- blinklinmult/preprocess/reader.py +507 -0
- blinklinmult/preprocess/rn.py +137 -0
- blinklinmult/preprocess/rtgene.py +139 -0
- blinklinmult/preprocess/talkingface.py +73 -0
- blinklinmult/preprocess/zju.py +71 -0
- blinklinmult-1.0.0.dist-info/METADATA +187 -0
- blinklinmult-1.0.0.dist-info/RECORD +20 -0
- blinklinmult-1.0.0.dist-info/WHEEL +4 -0
- blinklinmult-1.0.0.dist-info/licenses/LICENSE +21 -0
blinklinmult/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import toml
|
|
2
|
+
from os import PathLike
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Module level constants
|
|
6
|
+
PROJECT_ROOT = Path(__file__).parents[1]
|
|
7
|
+
WEIGHTS_DIR = Path().home() / '.cache' / 'torch' / 'hub' / 'checkpoints' / 'blink'
|
|
8
|
+
|
|
9
|
+
__version__ = toml.load(PROJECT_ROOT / 'pyproject.toml')['project']['version']
|
|
10
|
+
|
|
11
|
+
# Type aliases
|
|
12
|
+
PathType = str | PathLike
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from abc import abstractmethod
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import logging
|
|
4
|
+
import torch
|
|
5
|
+
import torch.nn as nn
|
|
6
|
+
from torchvision import models, transforms
|
|
7
|
+
from linmult import LinMulT, LinT
|
|
8
|
+
from exordium.utils.ckpt import download_file
|
|
9
|
+
from blinklinmult import PathType, WEIGHTS_DIR
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
logging.basicConfig(level=logging.INFO,
|
|
13
|
+
format="%(asctime)s %(levelname)s %(message)s",
|
|
14
|
+
datefmt="%Y-%m-%d %H:%M:%S")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
PRETRAINED_WEIGHTS = {
|
|
18
|
+
'densenet121-union': 'https://github.com/fodorad/LinMulT/releases/download/v1.0.0/densenet121-union-64.pt',
|
|
19
|
+
'blinklint-union': 'https://github.com/fodorad/LinMulT/releases/download/v1.0.0/densenetlint-union-64.pt',
|
|
20
|
+
'blinklinmult-union': 'https://github.com/fodorad/LinMulT/releases/download/v1.0.0/blinklinmult-union.pt'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
preprocess_eye_fcn = transforms.Compose([
|
|
25
|
+
transforms.ToPILImage(),
|
|
26
|
+
transforms.Resize((64, 64), transforms.InterpolationMode.BICUBIC),
|
|
27
|
+
transforms.ToTensor(),
|
|
28
|
+
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
|
|
29
|
+
])
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class AbstractModule(nn.Module):
|
|
33
|
+
|
|
34
|
+
def __init__(self):
|
|
35
|
+
super().__init__()
|
|
36
|
+
self.features = nn.Identity()
|
|
37
|
+
self.classifier = nn.Identity()
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def _init_weights(modules):
|
|
41
|
+
for m in modules:
|
|
42
|
+
if isinstance(m, (nn.Linear, nn.LazyLinear)):
|
|
43
|
+
nn.init.kaiming_uniform_(m.weight, mode="fan_in", nonlinearity="relu")
|
|
44
|
+
nn.init.zeros_(m.bias)
|
|
45
|
+
|
|
46
|
+
def freeze_feature_extractor(self):
|
|
47
|
+
for layer in self.features.parameters():
|
|
48
|
+
layer.requires_grad = False
|
|
49
|
+
|
|
50
|
+
def unfreeze_feature_extractor(self):
|
|
51
|
+
for layer in self.features.parameters():
|
|
52
|
+
layer.requires_grad = True
|
|
53
|
+
|
|
54
|
+
def forward(self, x):
|
|
55
|
+
x = self.features(x)
|
|
56
|
+
x = self.classifier(x)
|
|
57
|
+
return x
|
|
58
|
+
|
|
59
|
+
def load_weights(self, weights_path: PathType) -> None:
|
|
60
|
+
local_path = Path(weights_path)
|
|
61
|
+
|
|
62
|
+
if local_path.name in PRETRAINED_WEIGHTS.keys():
|
|
63
|
+
remote_path = PRETRAINED_WEIGHTS[local_path.name]
|
|
64
|
+
local_path = WEIGHTS_DIR / Path(remote_path).name
|
|
65
|
+
download_file(remote_path, local_path)
|
|
66
|
+
|
|
67
|
+
state_dict = torch.load(str(local_path), map_location="cpu")
|
|
68
|
+
self.load_state_dict(state_dict)
|
|
69
|
+
logging.info(f'Weights are loaded from {local_path}')
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class DenseNet121(AbstractModule):
|
|
73
|
+
|
|
74
|
+
def __init__(self, output_dim: int = 1,
|
|
75
|
+
weights: PathType | None = 'densenet121-union',
|
|
76
|
+
freeze: bool = False):
|
|
77
|
+
super().__init__()
|
|
78
|
+
densenet121 = models.densenet121(weights=models.DenseNet121_Weights.DEFAULT)
|
|
79
|
+
features = [module for module in densenet121.features]
|
|
80
|
+
features.append(nn.ReLU(inplace=True))
|
|
81
|
+
features.append(nn.AdaptiveAvgPool2d(output_size=(1, 1)))
|
|
82
|
+
features.append(nn.Flatten())
|
|
83
|
+
self.features = nn.Sequential(*features)
|
|
84
|
+
|
|
85
|
+
self.classifier = nn.Sequential(
|
|
86
|
+
nn.LazyLinear(512),
|
|
87
|
+
nn.BatchNorm1d(512, momentum=0.999, eps=1e-3),
|
|
88
|
+
nn.GELU(),
|
|
89
|
+
nn.Dropout(p=0.6),
|
|
90
|
+
nn.Linear(512, output_dim),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if freeze:
|
|
94
|
+
self.freeze_feature_extractor()
|
|
95
|
+
|
|
96
|
+
if weights is not None:
|
|
97
|
+
self.load_weights(weights)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class BlinkLinMulT(AbstractModule):
|
|
101
|
+
|
|
102
|
+
def __init__(self, input_dim: int = 160,
|
|
103
|
+
output_dim: int = 1,
|
|
104
|
+
weights: PathType | None = 'blinklinmult-union',
|
|
105
|
+
weights_backbone: PathType | None = 'densenet121-union',
|
|
106
|
+
**kwargs):
|
|
107
|
+
super().__init__()
|
|
108
|
+
self.img_backbone = DenseNet121(output_dim=output_dim, weights=weights_backbone)
|
|
109
|
+
self.img_backbone = self.img_backbone.features
|
|
110
|
+
logging.info(self.img_backbone)
|
|
111
|
+
|
|
112
|
+
self.rnn_backbone = LinMulT(
|
|
113
|
+
input_modality_channels=[1024, input_dim],
|
|
114
|
+
output_dim=output_dim,
|
|
115
|
+
projected_modality_dim=32,
|
|
116
|
+
number_of_layers=5,
|
|
117
|
+
add_projection_fusion=False,
|
|
118
|
+
aggregation='meanpooling',
|
|
119
|
+
**kwargs,
|
|
120
|
+
)
|
|
121
|
+
logging.info(self.rnn_backbone)
|
|
122
|
+
|
|
123
|
+
if weights is not None:
|
|
124
|
+
self.load_weights(weights)
|
|
125
|
+
|
|
126
|
+
def forward(self, x):
|
|
127
|
+
time_dim = x[0].size(1)
|
|
128
|
+
rgb_texture = x[0] # (B, L, C, H, W)
|
|
129
|
+
high_level_features = x[1] # (B, L, C)
|
|
130
|
+
|
|
131
|
+
eyes_x = []
|
|
132
|
+
for t in range(time_dim):
|
|
133
|
+
eyes_x.append(torch.flatten(self.img_backbone(rgb_texture[:, t, :, :, :]), 1))
|
|
134
|
+
|
|
135
|
+
x0 = torch.stack(eyes_x, dim=1)
|
|
136
|
+
x_seq = self.rnn_backbone([x0, high_level_features])
|
|
137
|
+
return x_seq
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
class BlinkLinT(AbstractModule):
|
|
141
|
+
|
|
142
|
+
def __init__(self, output_dim: int = 1,
|
|
143
|
+
weights: str | Path | None = 'blinklint-union'):
|
|
144
|
+
super().__init__()
|
|
145
|
+
self.img_backbone = DenseNet121(output_dim=output_dim, weights='densenet121-union')
|
|
146
|
+
self.img_backbone = self.img_backbone.features
|
|
147
|
+
self.rnn_backbone = LinT(1024, projected_modality_dim=32, number_of_layers=5, output_dim=output_dim)
|
|
148
|
+
|
|
149
|
+
logging.info(self.img_backbone)
|
|
150
|
+
logging.info(self.rnn_backbone)
|
|
151
|
+
|
|
152
|
+
if weights is not None:
|
|
153
|
+
self.load_weights(weights)
|
|
154
|
+
|
|
155
|
+
def forward(self, rgb_texture):
|
|
156
|
+
time_dim = rgb_texture.size(1) # (B, L, C, H, W)
|
|
157
|
+
|
|
158
|
+
eyes_x = []
|
|
159
|
+
for t in range(time_dim):
|
|
160
|
+
eyes_x.append(torch.flatten(self.img_backbone(rgb_texture[:, t, :, :, :]), 1))
|
|
161
|
+
|
|
162
|
+
x0 = torch.stack(eyes_x, dim=1)
|
|
163
|
+
x_seq = self.rnn_backbone(x0)
|
|
164
|
+
return x_seq
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from .BlinkLinMulT import (AbstractModule,
|
|
2
|
+
DenseNet121,
|
|
3
|
+
BlinkLinT,
|
|
4
|
+
LinT,
|
|
5
|
+
BlinkLinMulT,
|
|
6
|
+
preprocess_eye_fcn)
|
|
7
|
+
|
|
8
|
+
from .backbone import (EyeNet,
|
|
9
|
+
ResNet50,
|
|
10
|
+
Dense)
|
|
11
|
+
|
|
12
|
+
SEQUENCE_MODELS = {
|
|
13
|
+
"lint": LinT,
|
|
14
|
+
"blinklint": BlinkLinT,
|
|
15
|
+
"blinklinmult": BlinkLinMulT,
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
BACKBONE_MODELS = {
|
|
19
|
+
"dense": Dense,
|
|
20
|
+
"eyenet": EyeNet,
|
|
21
|
+
"resnet50": ResNet50,
|
|
22
|
+
"densenet121": DenseNet121
|
|
23
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import torch.nn as nn
|
|
2
|
+
from torchvision import models
|
|
3
|
+
from blinklinmult.models import AbstractModule
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Dense(AbstractModule):
|
|
7
|
+
|
|
8
|
+
def __init__(self, input_dim: int, output_dim: int = 1):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.features = nn.Identity()
|
|
11
|
+
self.classifier = nn.Sequential(
|
|
12
|
+
nn.Linear(input_dim, 128),
|
|
13
|
+
nn.ReLU(inplace=True),
|
|
14
|
+
nn.Dropout(p=0.6),
|
|
15
|
+
nn.Linear(128, output_dim),
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class EyeNet(AbstractModule):
|
|
20
|
+
"""EyeNet: An Improved Eye States Classification System using Convolutional Neural Network (2020)
|
|
21
|
+
|
|
22
|
+
Paper: https://www.researchgate.net/publication/340757067
|
|
23
|
+
"""
|
|
24
|
+
def __init__(self, output_dim: int = 1, freeze: bool = False):
|
|
25
|
+
super().__init__()
|
|
26
|
+
|
|
27
|
+
self.features = nn.Sequential(
|
|
28
|
+
nn.Conv2d(3, 32, 5, bias=False),
|
|
29
|
+
nn.BatchNorm2d(32),
|
|
30
|
+
nn.ReLU(),
|
|
31
|
+
nn.Conv2d(32, 64, 5, bias=False),
|
|
32
|
+
nn.BatchNorm2d(64),
|
|
33
|
+
nn.ReLU(),
|
|
34
|
+
nn.Conv2d(64, 128, 3, bias=False),
|
|
35
|
+
nn.BatchNorm2d(128),
|
|
36
|
+
nn.ReLU(),
|
|
37
|
+
nn.MaxPool2d(2),
|
|
38
|
+
nn.Conv2d(128, 256, 3, bias=False),
|
|
39
|
+
nn.BatchNorm2d(256),
|
|
40
|
+
nn.ReLU(),
|
|
41
|
+
nn.MaxPool2d(2),
|
|
42
|
+
nn.Conv2d(256, 384, 3, bias=False),
|
|
43
|
+
nn.BatchNorm2d(384),
|
|
44
|
+
nn.ReLU(),
|
|
45
|
+
nn.MaxPool2d(2),
|
|
46
|
+
nn.Conv2d(384, 512, 3, bias=False),
|
|
47
|
+
nn.BatchNorm2d(512),
|
|
48
|
+
nn.ReLU(),
|
|
49
|
+
nn.AdaptiveAvgPool2d((1, 1)),
|
|
50
|
+
nn.Flatten(),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
self.classifier = nn.Sequential(
|
|
54
|
+
nn.LazyLinear(84),
|
|
55
|
+
nn.ReLU(),
|
|
56
|
+
nn.Dropout(0.25),
|
|
57
|
+
nn.Linear(84, 32),
|
|
58
|
+
nn.ReLU(),
|
|
59
|
+
nn.Linear(32, output_dim),
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if freeze:
|
|
63
|
+
self.freeze_feature_extractor()
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class ResNet50(AbstractModule):
|
|
67
|
+
|
|
68
|
+
def __init__(self, output_dim: int = 1, freeze: bool = False):
|
|
69
|
+
super().__init__()
|
|
70
|
+
|
|
71
|
+
resnet50 = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)
|
|
72
|
+
self.features = nn.Sequential(
|
|
73
|
+
resnet50.conv1,
|
|
74
|
+
resnet50.bn1,
|
|
75
|
+
resnet50.relu,
|
|
76
|
+
resnet50.maxpool,
|
|
77
|
+
resnet50.layer1,
|
|
78
|
+
resnet50.layer2,
|
|
79
|
+
resnet50.layer3,
|
|
80
|
+
resnet50.layer4,
|
|
81
|
+
resnet50.avgpool,
|
|
82
|
+
nn.Flatten(),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
self.classifier = nn.Sequential(
|
|
86
|
+
nn.LazyLinear(512),
|
|
87
|
+
nn.BatchNorm1d(512, momentum=0.999, eps=1e-3),
|
|
88
|
+
nn.ReLU(inplace=True),
|
|
89
|
+
nn.Dropout(p=0.6),
|
|
90
|
+
nn.Linear(512, output_dim),
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
if freeze:
|
|
94
|
+
self.freeze_feature_extractor()
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from torch import nn
|
|
3
|
+
import clip
|
|
4
|
+
from blinklinmult.models.backbone import AbstractModule
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CLIP(AbstractModule):
|
|
8
|
+
|
|
9
|
+
def __init__(self, backbone: str = "ViT-B/32",
|
|
10
|
+
output_dim: int = 1,
|
|
11
|
+
freeze: bool = False):
|
|
12
|
+
super().__init__()
|
|
13
|
+
|
|
14
|
+
model, preprocess = clip.load(backbone, jit=False, device=torch.device("cpu"))
|
|
15
|
+
|
|
16
|
+
self.preprocess = preprocess
|
|
17
|
+
self.features = model.visual
|
|
18
|
+
|
|
19
|
+
self.classifier = nn.Sequential(
|
|
20
|
+
nn.LazyLinear(512),
|
|
21
|
+
nn.BatchNorm1d(512, momentum=0.999, eps=1e-3),
|
|
22
|
+
nn.ReLU(inplace=True),
|
|
23
|
+
nn.Dropout(p=0.6),
|
|
24
|
+
nn.Linear(512, output_dim),
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
if freeze:
|
|
28
|
+
self.freeze_feature_extractor()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == '__main__':
|
|
32
|
+
|
|
33
|
+
x = torch.zeros((32, 3, 224, 224)).to("cuda:0")
|
|
34
|
+
for backbone in ["RN50", "ViT-B/32", "ViT-L/14"]:
|
|
35
|
+
model = CLIP(backbone=backbone).to("cuda:0")
|
|
36
|
+
y_pred = model(x)
|
|
37
|
+
assert y_pred.shape == (32, 1)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
from torch import nn
|
|
2
|
+
from torchvision import models
|
|
3
|
+
from blinklinmult.models.backbone import AbstractModule
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EfficientNetV2(AbstractModule):
|
|
7
|
+
|
|
8
|
+
def __init__(self, output_dim: int = 1, freeze: bool = False):
|
|
9
|
+
super().__init__()
|
|
10
|
+
self.model = models.efficientnet_v2_m(weights=models.EfficientNet_V2_M_Weights.DEFAULT)
|
|
11
|
+
|
|
12
|
+
self.features = nn.Sequential(
|
|
13
|
+
self.model.features,
|
|
14
|
+
nn.AdaptiveAvgPool2d(output_size=(1, 1)),
|
|
15
|
+
nn.Flatten()
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
self.classifier = nn.Sequential(
|
|
19
|
+
nn.Linear(1280, 512),
|
|
20
|
+
nn.BatchNorm1d(512, momentum=0.999, eps=1e-3),
|
|
21
|
+
nn.ReLU(inplace=True),
|
|
22
|
+
nn.Dropout(p=0.6),
|
|
23
|
+
nn.Linear(512, output_dim),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if freeze:
|
|
27
|
+
self.freeze_feature_extractor()
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from torch import nn
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class GRU(nn.Module):
|
|
6
|
+
|
|
7
|
+
def __init__(self, input_dim,
|
|
8
|
+
hidden_dim: int = 256,
|
|
9
|
+
layer_dim: int = 3,
|
|
10
|
+
dropout_prob: float = 0.2,
|
|
11
|
+
return_sequences: bool = True):
|
|
12
|
+
super().__init__()
|
|
13
|
+
self.layer_dim = layer_dim
|
|
14
|
+
self.hidden_dim = hidden_dim
|
|
15
|
+
self.return_sequences = return_sequences
|
|
16
|
+
self.gru = nn.GRU(
|
|
17
|
+
input_dim, hidden_dim, layer_dim, batch_first=True, dropout=dropout_prob
|
|
18
|
+
)
|
|
19
|
+
# self.fc = nn.Linear(hidden_dim, output_dim)
|
|
20
|
+
|
|
21
|
+
def forward(self, x):
|
|
22
|
+
|
|
23
|
+
# Initializing hidden state for first input with zeros
|
|
24
|
+
h0 = torch.zeros(self.layer_dim, x.size(0), self.hidden_dim).requires_grad_()
|
|
25
|
+
|
|
26
|
+
h0 = h0.to(x.device)
|
|
27
|
+
self.gru = self.gru.to(x.device)
|
|
28
|
+
|
|
29
|
+
# Forward propagation by passing in the input and hidden state into the model
|
|
30
|
+
out, _ = self.gru(x, h0)
|
|
31
|
+
|
|
32
|
+
# Reshaping the outputs in the shape of (batch_size, seq_length, hidden_size)
|
|
33
|
+
# so that it can fit into the fully connected layer
|
|
34
|
+
if not self.return_sequences:
|
|
35
|
+
out = out[:, -1, :]
|
|
36
|
+
|
|
37
|
+
# Convert the final state to our desired output shape (batch_size, output_dim)
|
|
38
|
+
# out = self.fc(out)
|
|
39
|
+
|
|
40
|
+
return out
|
|
File without changes
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import pickle
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
import cv2
|
|
4
|
+
import numpy as np
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
from exordium.video.iris import IrisWrapper
|
|
7
|
+
from exordium.video.tddfa_v2 import TDDFA_V2
|
|
8
|
+
from exordium.utils.decorator import timer
|
|
9
|
+
from blinklinmult import PathType
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
DB_DIR = Path('data/db/CEW')
|
|
13
|
+
DB_DIR_OUT = Path('data/db_processed/cew')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@timer
|
|
17
|
+
def save_eye_crops(output_path: PathType, bb_size: int = 40) -> None:
|
|
18
|
+
# read closed eye filenames and eye coordinates
|
|
19
|
+
with open(DB_DIR / 'dataset_B_FacialImages' / 'EyeCoordinatesInfo_ClosedFace.txt', 'r') as f:
|
|
20
|
+
lines = f.readlines()
|
|
21
|
+
lines = [line.replace('\n', '').split(' ') for line in lines]
|
|
22
|
+
closed_file_coords = {
|
|
23
|
+
elem[0]: (1, np.array(elem[1:]).astype(int))
|
|
24
|
+
for elem in lines
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# read open eye filenames and eye coordinates
|
|
28
|
+
with open(DB_DIR / 'dataset_B_FacialImages' / 'EyeCoordinatesInfo_OpenFace.txt', 'r') as f:
|
|
29
|
+
lines = f.readlines()
|
|
30
|
+
lines = [line.replace('\n', '').split(' ') for line in lines]
|
|
31
|
+
open_file_coords = {
|
|
32
|
+
elem[0]: (0, np.array(elem[1:]).astype(int))
|
|
33
|
+
for elem in lines
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
file_coords = open_file_coords | closed_file_coords
|
|
37
|
+
|
|
38
|
+
face_model = TDDFA_V2()
|
|
39
|
+
eye_model = IrisWrapper()
|
|
40
|
+
|
|
41
|
+
closed_face_dir = DB_DIR / 'dataset_B_FacialImages' / 'ClosedFace'
|
|
42
|
+
open_face_dir = DB_DIR / 'dataset_B_FacialImages' / 'OpenFace'
|
|
43
|
+
eye_dir = DB_DIR_OUT / 'eyes'
|
|
44
|
+
eye_dir.mkdir(parents=True, exist_ok=True)
|
|
45
|
+
(eye_dir / 'left').mkdir(parents=True, exist_ok=True)
|
|
46
|
+
(eye_dir / 'right').mkdir(parents=True, exist_ok=True)
|
|
47
|
+
|
|
48
|
+
samples = []
|
|
49
|
+
for id, (name, (label, coords)) in enumerate(tqdm(file_coords.items(), total=len(file_coords), desc='[CEW] eye crops')):
|
|
50
|
+
face_dir = closed_face_dir if label else open_face_dir
|
|
51
|
+
face_path = str(face_dir / name)
|
|
52
|
+
face = cv2.imread(face_path)
|
|
53
|
+
|
|
54
|
+
sample = {'id': id, 'name': name, 'path': face_path, 'label': label, 'headpose': face_model(face)['headpose']}
|
|
55
|
+
left_eye_path = str(eye_dir / 'left' / f'{id:06d}.png')
|
|
56
|
+
right_eye_path = str(eye_dir / 'right' / f'{id:06d}.png')
|
|
57
|
+
|
|
58
|
+
# eye crop rgb
|
|
59
|
+
y_min = max(coords[1] - bb_size // 2, 0)
|
|
60
|
+
y_max = min(100, coords[1] + bb_size // 2)
|
|
61
|
+
x_min = max(coords[0] - bb_size // 2, 0)
|
|
62
|
+
x_max = min(100, coords[0] + bb_size // 2)
|
|
63
|
+
left_eye = face[y_min:y_max, x_min:x_max, :]
|
|
64
|
+
y_min = max(coords[3] - bb_size // 2, 0)
|
|
65
|
+
y_max = min(100, coords[3] + bb_size // 2)
|
|
66
|
+
x_min = max(coords[2] - bb_size // 2, 0)
|
|
67
|
+
x_max = min(100, coords[2] + bb_size // 2)
|
|
68
|
+
right_eye = face[y_min:y_max, x_min:x_max, :]
|
|
69
|
+
cv2.imwrite(left_eye_path, left_eye)
|
|
70
|
+
cv2.imwrite(right_eye_path, right_eye)
|
|
71
|
+
|
|
72
|
+
left_eye_features = eye_model.eye_to_features(left_eye_path)
|
|
73
|
+
right_eye_features = eye_model.eye_to_features(right_eye_path)
|
|
74
|
+
sample |= {'left_eye': left_eye_features, 'right_eye': right_eye_features}
|
|
75
|
+
samples.append(sample)
|
|
76
|
+
|
|
77
|
+
with open(str(output_path), 'wb') as f:
|
|
78
|
+
pickle.dump(samples, f)
|
|
79
|
+
|
|
80
|
+
print(f'[CEW] feature extraction is done: {str(output_path)}')
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == '__main__':
|
|
84
|
+
save_eye_crops(output_path=DB_DIR_OUT / '0_data.pkl')
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pickle
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from tqdm import tqdm
|
|
5
|
+
from exordium.video.io import video2frames
|
|
6
|
+
from exordium.utils.decorator import timer
|
|
7
|
+
from exordium.video.tddfa_v2 import TDDFA_V2
|
|
8
|
+
from exordium.video.iris import IrisWrapper
|
|
9
|
+
from blinklinmult.preprocess.reader import Tag
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
DB_DIR = Path('data/db/eyeblink8')
|
|
13
|
+
DB_DIR_OUT = Path('data/db_processed/eyeblink8')
|
|
14
|
+
IDS = os.listdir(DB_DIR)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def extract_frames():
|
|
18
|
+
videos = sorted(list(DB_DIR.rglob('*/*.avi')))
|
|
19
|
+
|
|
20
|
+
for video in videos:
|
|
21
|
+
id = video.parent.name
|
|
22
|
+
output_dir = DB_DIR_OUT / 'frames' / id
|
|
23
|
+
video2frames(video, output_dir, fps=30)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def save_samples():
|
|
27
|
+
tag_paths = list(Path(DB_DIR).glob('*/*.tag'))
|
|
28
|
+
samples = []
|
|
29
|
+
|
|
30
|
+
for tag_path in tqdm(tag_paths, desc='Save mp4'):
|
|
31
|
+
id = tag_path.parent.name
|
|
32
|
+
frames_dir = DB_DIR_OUT / 'frames' / id
|
|
33
|
+
output_dir = DB_DIR_OUT / 'visualize'
|
|
34
|
+
tag = Tag(tag_path=tag_path, frames_dir=frames_dir)
|
|
35
|
+
samples += tag.generate_positive_samples(output_dir=output_dir, fps=30)
|
|
36
|
+
|
|
37
|
+
return samples
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def save_face_crops():
|
|
41
|
+
tag_paths = list(Path(DB_DIR).glob('*/*.tag'))
|
|
42
|
+
for tag_path in tqdm(tag_paths, desc='Save faces'):
|
|
43
|
+
id = tag_path.parent.name
|
|
44
|
+
frames_dir = DB_DIR_OUT / 'frames' / id
|
|
45
|
+
faces_dir = DB_DIR_OUT / 'faces' / id
|
|
46
|
+
tag = Tag(tag_path=tag_path, frames_dir=frames_dir)
|
|
47
|
+
tag.save_annotated_face_crops(faces_dir)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def save_eye_crops():
|
|
51
|
+
tag_paths = list(Path(DB_DIR).glob('*/*.tag'))
|
|
52
|
+
for tag_path in tqdm(tag_paths, desc='Save faces'):
|
|
53
|
+
id = tag_path.parent.name
|
|
54
|
+
frames_dir = DB_DIR_OUT / 'frames' / id
|
|
55
|
+
eyes_dir = DB_DIR_OUT / 'eyes' / id
|
|
56
|
+
tag = Tag(tag_path=tag_path, frames_dir=frames_dir)
|
|
57
|
+
tag.save_annotated_eye_crops(eyes_dir)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@timer
|
|
61
|
+
def extract_features(tag_path: str | Path,
|
|
62
|
+
face_dir: str | Path,
|
|
63
|
+
left_eye_dir: str | Path,
|
|
64
|
+
right_eye_dir: str | Path,
|
|
65
|
+
output_path: str | Path):
|
|
66
|
+
|
|
67
|
+
face_paths = [str(Path(face_dir) / elem) for elem in sorted(os.listdir(face_dir))]
|
|
68
|
+
left_eye_paths = [str(Path(left_eye_dir) / elem) for elem in sorted(os.listdir(left_eye_dir))]
|
|
69
|
+
right_eye_paths = [str(Path(right_eye_dir) / elem) for elem in sorted(os.listdir(right_eye_dir))]
|
|
70
|
+
|
|
71
|
+
face_model = TDDFA_V2()
|
|
72
|
+
eye_model = IrisWrapper()
|
|
73
|
+
|
|
74
|
+
headposes = []
|
|
75
|
+
for face_path in tqdm(face_paths, desc='Extract headpose feature'):
|
|
76
|
+
headposes.append({'id': int(Path(face_path).stem), 'headpose': face_model.inference(face_path)['headpose']})
|
|
77
|
+
|
|
78
|
+
eyes = []
|
|
79
|
+
for left_eye_path, right_eye_path in tqdm(zip(left_eye_paths, right_eye_paths), total=len(left_eye_paths), desc='Extract eye features'):
|
|
80
|
+
eyes.append({'id': int(Path(left_eye_path).stem),
|
|
81
|
+
'left_eye': eye_model.eye_to_features(left_eye_path),
|
|
82
|
+
'right_eye': eye_model.eye_to_features(right_eye_path)})
|
|
83
|
+
|
|
84
|
+
tag_path = Path(tag_path)
|
|
85
|
+
tag = Tag(tag_path=tag_path, frames_dir=DB_DIR_OUT / 'frames' / tag_path.parent.name)
|
|
86
|
+
ids = sorted([elem['id'] for elem in headposes])
|
|
87
|
+
|
|
88
|
+
features = []
|
|
89
|
+
for id in tqdm(ids, total=len(ids), desc='Merge headpose and eye features, then save to pickle'):
|
|
90
|
+
label = tag.blink_label(id)
|
|
91
|
+
headpose = next((elem for elem in headposes if elem['id'] == id))
|
|
92
|
+
eye = next((elem for elem in eyes if elem['id'] == id))
|
|
93
|
+
features.append({'label': label} | headpose | eye)
|
|
94
|
+
|
|
95
|
+
with open(output_path, 'wb') as f:
|
|
96
|
+
pickle.dump(features, f)
|
|
97
|
+
|
|
98
|
+
print(f'Feature extraction is done: {output_path}')
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
if __name__ == '__main__':
|
|
102
|
+
extract_frames()
|
|
103
|
+
save_samples()
|
|
104
|
+
save_face_crops()
|
|
105
|
+
save_eye_crops()
|
|
106
|
+
|
|
107
|
+
tag_paths = list(Path(DB_DIR).glob('*/*.tag'))
|
|
108
|
+
|
|
109
|
+
for id in IDS:
|
|
110
|
+
print(f'Started id {id}')
|
|
111
|
+
tag_path = next((elem for elem in tag_paths if elem.parent.name == id))
|
|
112
|
+
extract_features(tag_path=tag_path,
|
|
113
|
+
face_dir=DB_DIR_OUT / 'faces' / id,
|
|
114
|
+
left_eye_dir=DB_DIR_OUT / 'eyes'/ id / 'left',
|
|
115
|
+
right_eye_dir=DB_DIR_OUT / 'eyes'/ id / 'right',
|
|
116
|
+
output_path=DB_DIR_OUT / f'{id}_data.pkl')
|
|
117
|
+
print('EyeBlink8 is done.')
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from tqdm import tqdm
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
import pickle
|
|
5
|
+
from exordium.video.iris import IrisWrapper
|
|
6
|
+
from exordium.utils.decorator import timer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
DB_DIR = Path('data/db/mrl_eye')
|
|
10
|
+
DB_DIR_OUT = Path('data/db_processed/mrl')
|
|
11
|
+
IDS = [elem for elem in os.listdir('data/db/mrl_eye/mrlEyes_2018_01')
|
|
12
|
+
if len(elem) == 5 and elem[0] == 's']
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@timer
|
|
16
|
+
def save_features(input_path: str | Path, output_path: str | Path):
|
|
17
|
+
paths = [Path(input_path) / elem for elem in os.listdir(str(input_path))]
|
|
18
|
+
eye_model = IrisWrapper()
|
|
19
|
+
|
|
20
|
+
samples = []
|
|
21
|
+
for path in tqdm(paths, total=len(paths), desc=f'[MRL] {Path(input_path).name} features'):
|
|
22
|
+
|
|
23
|
+
data = Path(path).stem.split('_')
|
|
24
|
+
participant_id = int(data[0][1:]) # participant id
|
|
25
|
+
sample = {
|
|
26
|
+
'participant_id': participant_id,
|
|
27
|
+
'id': int(data[1]), # image number
|
|
28
|
+
'name': Path(path).stem, # image name
|
|
29
|
+
'gender': int(data[2]), # 0=male, 1=female
|
|
30
|
+
'glasses': int(data[3]), # 0=no, 1=yes
|
|
31
|
+
'label': int(not bool(int(data[4]))), # switch original blink label from 0=close, 1=open to 1=close, 0=open
|
|
32
|
+
'reflection': int(data[5]), # 0=none, 1=low, 2=high
|
|
33
|
+
'lightning': int(data[6]), # 0=bad, 1=good
|
|
34
|
+
'sensor': int(data[7]), # 1=RealSense SR300 640x480, 2=IDS Imaging, 1280x1024, 3=Aptina Imagin 752x480
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
eye_features = eye_model.eye_to_features(path)
|
|
38
|
+
sample |= eye_features
|
|
39
|
+
samples.append(sample)
|
|
40
|
+
|
|
41
|
+
output_path = str(output_path)
|
|
42
|
+
|
|
43
|
+
with open(output_path, 'wb') as f:
|
|
44
|
+
pickle.dump(samples, f)
|
|
45
|
+
|
|
46
|
+
print(f'[MRL] feature extraction is done: {output_path}')
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
if __name__ == '__main__':
|
|
50
|
+
|
|
51
|
+
for id in IDS:
|
|
52
|
+
save_features(input_path=DB_DIR / 'mrlEyes_2018_01' / id,
|
|
53
|
+
output_path=DB_DIR_OUT / f'{int(id[1:])}_data.pkl')
|