mirror of
https://github.com/serengil/deepface.git
synced 2025-06-08 04:25:21 +00:00
224 lines
7.4 KiB
Python
224 lines
7.4 KiB
Python
# Minivision's Silent-Face-Anti-Spoofing Repo licensed under Apache License 2.0
|
|
# Ref: github.com/minivision-ai/Silent-Face-Anti-Spoofing/blob/master/src/model_lib/MiniFASNet.py
|
|
|
|
# built-in dependencies
|
|
from typing import Union
|
|
|
|
# 3rd party dependencies
|
|
import cv2
|
|
import numpy as np
|
|
|
|
# project dependencies
|
|
from deepface.commons import folder_utils, file_utils, logger as log
|
|
|
|
logger = log.get_singletonish_logger()
|
|
|
|
# pylint: disable=line-too-long, too-few-public-methods
|
|
class Fasnet:
|
|
"""
|
|
Mini Face Anti Spoofing Net Library from repo: github.com/minivision-ai/Silent-Face-Anti-Spoofing
|
|
"""
|
|
|
|
def __init__(self):
|
|
# pytorch is an opitonal dependency, enforce it to be installed if class imported
|
|
try:
|
|
import torch
|
|
except Exception as err:
|
|
raise ValueError(
|
|
"You must install torch with `pip install pytorch` command to use face anti spoofing module"
|
|
) from err
|
|
|
|
home = folder_utils.get_deepface_home()
|
|
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
|
self.device = device
|
|
|
|
# download pre-trained models if not installed yet
|
|
file_utils.download_external_file(
|
|
file_name="2.7_80x80_MiniFASNetV2.pth",
|
|
exact_file_path=f"{home}/.deepface/weights/2.7_80x80_MiniFASNetV2.pth",
|
|
url="https://github.com/minivision-ai/Silent-Face-Anti-Spoofing/raw/master/resources/anti_spoof_models/2.7_80x80_MiniFASNetV2.pth",
|
|
)
|
|
|
|
file_utils.download_external_file(
|
|
file_name="4_0_0_80x80_MiniFASNetV1SE.pth",
|
|
exact_file_path=f"{home}/.deepface/weights/4_0_0_80x80_MiniFASNetV1SE.pth",
|
|
url="https://github.com/minivision-ai/Silent-Face-Anti-Spoofing/raw/master/resources/anti_spoof_models/4_0_0_80x80_MiniFASNetV1SE.pth",
|
|
)
|
|
|
|
# guarantees Fasnet imported and torch installed
|
|
from deepface.spoofmodels import FasNetBackbone
|
|
|
|
# Fasnet will use 2 distinct models to predict, then it will find the sum of predictions
|
|
# to make a final prediction
|
|
|
|
first_model = FasNetBackbone.MiniFASNetV2(conv6_kernel=(5, 5)).to(device)
|
|
second_model = FasNetBackbone.MiniFASNetV1SE(conv6_kernel=(5, 5)).to(device)
|
|
|
|
# load model weight for first model
|
|
state_dict = torch.load(
|
|
f"{home}/.deepface/weights/2.7_80x80_MiniFASNetV2.pth", map_location=device
|
|
)
|
|
keys = iter(state_dict)
|
|
first_layer_name = keys.__next__()
|
|
|
|
if first_layer_name.find("module.") >= 0:
|
|
from collections import OrderedDict
|
|
|
|
new_state_dict = OrderedDict()
|
|
for key, value in state_dict.items():
|
|
name_key = key[7:]
|
|
new_state_dict[name_key] = value
|
|
first_model.load_state_dict(new_state_dict)
|
|
else:
|
|
first_model.load_state_dict(state_dict)
|
|
|
|
# load model weight for second model
|
|
state_dict = torch.load(
|
|
f"{home}/.deepface/weights/4_0_0_80x80_MiniFASNetV1SE.pth", map_location=device
|
|
)
|
|
keys = iter(state_dict)
|
|
first_layer_name = keys.__next__()
|
|
|
|
if first_layer_name.find("module.") >= 0:
|
|
from collections import OrderedDict
|
|
|
|
new_state_dict = OrderedDict()
|
|
for key, value in state_dict.items():
|
|
name_key = key[7:]
|
|
new_state_dict[name_key] = value
|
|
second_model.load_state_dict(new_state_dict)
|
|
else:
|
|
second_model.load_state_dict(state_dict)
|
|
|
|
# evaluate models
|
|
_ = first_model.eval()
|
|
_ = second_model.eval()
|
|
|
|
self.first_model = first_model
|
|
self.second_model = second_model
|
|
|
|
def analyze(self, img: np.ndarray, facial_area: Union[list, tuple]):
|
|
"""
|
|
Analyze a given image spoofed or not
|
|
Args:
|
|
img (np.ndarray): pre loaded image
|
|
facial_area (list or tuple): facial rectangle area coordinates with x, y, w, h respectively
|
|
Returns:
|
|
result (tuple): a result tuple consisting of is_real and score
|
|
"""
|
|
import torch
|
|
import torch.nn.functional as F
|
|
|
|
x, y, w, h = facial_area
|
|
first_img = crop(img, (x, y, w, h), 2.7, 80, 80)
|
|
second_img = crop(img, (x, y, w, h), 4, 80, 80)
|
|
|
|
test_transform = Compose(
|
|
[
|
|
ToTensor(),
|
|
]
|
|
)
|
|
|
|
first_img = test_transform(first_img)
|
|
first_img = first_img.unsqueeze(0).to(self.device)
|
|
|
|
second_img = test_transform(second_img)
|
|
second_img = second_img.unsqueeze(0).to(self.device)
|
|
|
|
with torch.no_grad():
|
|
first_result = self.first_model.forward(first_img)
|
|
first_result = F.softmax(first_result).cpu().numpy()
|
|
|
|
second_result = self.second_model.forward(second_img)
|
|
second_result = F.softmax(second_result).cpu().numpy()
|
|
|
|
prediction = np.zeros((1, 3))
|
|
prediction += first_result
|
|
prediction += second_result
|
|
|
|
label = np.argmax(prediction)
|
|
is_real = True if label == 1 else False # pylint: disable=simplifiable-if-expression
|
|
score = prediction[0][label] / 2
|
|
|
|
return is_real, score
|
|
|
|
|
|
# subsdiary classes and functions
|
|
|
|
|
|
def to_tensor(pic):
|
|
"""Convert a ``numpy.ndarray`` to tensor.
|
|
|
|
See ``ToTensor`` for more details.
|
|
|
|
Args:
|
|
pic (PIL Image or numpy.ndarray): Image to be converted to tensor.
|
|
|
|
Returns:
|
|
Tensor: Converted image.
|
|
"""
|
|
import torch
|
|
|
|
# handle numpy array
|
|
# IR image channel=1: modify by lzc --> 20190730
|
|
if pic.ndim == 2:
|
|
pic = pic.reshape((pic.shape[0], pic.shape[1], 1))
|
|
|
|
img = torch.from_numpy(pic.transpose((2, 0, 1)))
|
|
# backward compatibility
|
|
# return img.float().div(255) modify by zkx
|
|
return img.float()
|
|
|
|
|
|
class Compose:
|
|
def __init__(self, transforms):
|
|
self.transforms = transforms
|
|
|
|
def __call__(self, img):
|
|
for t in self.transforms:
|
|
img = t(img)
|
|
return img
|
|
|
|
|
|
class ToTensor:
|
|
def __call__(self, pic):
|
|
return to_tensor(pic)
|
|
|
|
|
|
def _get_new_box(src_w, src_h, bbox, scale):
|
|
x = bbox[0]
|
|
y = bbox[1]
|
|
box_w = bbox[2]
|
|
box_h = bbox[3]
|
|
scale = min(
|
|
(src_h - 1) / box_h, min((src_w - 1) / box_w, scale)
|
|
) # pylint: disable=nested-min-max
|
|
new_width = box_w * scale
|
|
new_height = box_h * scale
|
|
center_x, center_y = box_w / 2 + x, box_h / 2 + y
|
|
left_top_x = center_x - new_width / 2
|
|
left_top_y = center_y - new_height / 2
|
|
right_bottom_x = center_x + new_width / 2
|
|
right_bottom_y = center_y + new_height / 2
|
|
if left_top_x < 0:
|
|
right_bottom_x -= left_top_x
|
|
left_top_x = 0
|
|
if left_top_y < 0:
|
|
right_bottom_y -= left_top_y
|
|
left_top_y = 0
|
|
if right_bottom_x > src_w - 1:
|
|
left_top_x -= right_bottom_x - src_w + 1
|
|
right_bottom_x = src_w - 1
|
|
if right_bottom_y > src_h - 1:
|
|
left_top_y -= right_bottom_y - src_h + 1
|
|
right_bottom_y = src_h - 1
|
|
return int(left_top_x), int(left_top_y), int(right_bottom_x), int(right_bottom_y)
|
|
|
|
|
|
def crop(org_img, bbox, scale, out_w, out_h):
|
|
src_h, src_w, _ = np.shape(org_img)
|
|
left_top_x, left_top_y, right_bottom_x, right_bottom_y = _get_new_box(src_w, src_h, bbox, scale)
|
|
img = org_img[left_top_y : right_bottom_y + 1, left_top_x : right_bottom_x + 1]
|
|
dst_img = cv2.resize(img, (out_w, out_h))
|
|
return dst_img
|