Spaces:
Runtime error
Runtime error
import time | |
from pathlib import Path | |
from typing import Iterable | |
from typing import NamedTuple | |
from typing import Optional | |
from typing import Tuple | |
import cv2 | |
import numpy as np | |
import torch | |
import torch.nn.functional as F | |
from skimage import transform as skt | |
from .scrfd_detect import SCRFD | |
# ---frontal | |
src = np.array( | |
[ | |
[39.730, 51.138], | |
[72.270, 51.138], | |
[56.000, 68.493], | |
[42.463, 87.010], | |
[69.537, 87.010], | |
], | |
dtype=np.float32, | |
) | |
class alignFace: | |
def __init__(self) -> None: | |
self.src_map = src | |
def estimate_norm(self, lmk, image_size=112): | |
assert lmk.shape == (5, 2) | |
tform = skt.SimilarityTransform() | |
src_ = self.src_map * image_size / 112 | |
tform.estimate(lmk, src_) | |
M = tform.params[0:2, :] | |
return M | |
def align_face( | |
self, img: np.ndarray, key_points: np.ndarray, crop_size: int | |
) -> Tuple[Iterable[np.ndarray], Iterable[np.ndarray]]: | |
transform_matrix = self.estimate_norm(key_points, crop_size) | |
align_img = cv2.warpAffine(img, transform_matrix, (crop_size, crop_size), borderValue=0.0) | |
return align_img, transform_matrix | |
class Detection(NamedTuple): | |
bbox: Optional[np.ndarray] | |
score: Optional[np.ndarray] | |
key_points: Optional[np.ndarray] | |
class FaceDetector: | |
def __init__( | |
self, | |
model_path: Path, | |
det_thresh: float = 0.5, | |
det_size: Tuple[int, int] = (640, 640), | |
mode: str = "None", | |
device: str = "cuda", | |
): | |
self.det_thresh = det_thresh | |
self.mode = mode | |
self.device = device | |
self.handler = SCRFD(str(model_path), device=self.device, det_thresh=det_thresh) | |
ctx_id = -1 if device == "cpu" else 0 | |
self.handler.prepare(ctx_id, input_size=det_size) | |
def __call__(self, img: np.ndarray, max_num: int = 0) -> Detection: | |
bboxes, kpss = self.handler.detect(img, max_num=max_num, metric="default") | |
if bboxes.shape[0] == 0: | |
return Detection(None, None, None) | |
return Detection(bboxes[..., :-1], bboxes[..., -1], kpss) | |
def tensor2img(tensor): | |
tensor = tensor.detach().cpu().numpy() | |
img = tensor.transpose(0, 2, 3, 1)[0] | |
img = np.clip(img * 255, 0.0, 255.0).astype(np.uint8) | |
return img | |
def inverse_transform_batch(mat: torch.Tensor, device="cuda") -> torch.Tensor: | |
# inverse the Affine transformation matrix | |
inv_mat = torch.zeros_like(mat).to(device) | |
div1 = mat[:, 0, 0] * mat[:, 1, 1] - mat[:, 0, 1] * mat[:, 1, 0] | |
inv_mat[:, 0, 0] = mat[:, 1, 1] / div1 | |
inv_mat[:, 0, 1] = -mat[:, 0, 1] / div1 | |
inv_mat[:, 0, 2] = -(mat[:, 0, 2] * mat[:, 1, 1] - mat[:, 0, 1] * mat[:, 1, 2]) / div1 | |
div2 = mat[:, 0, 1] * mat[:, 1, 0] - mat[:, 0, 0] * mat[:, 1, 1] | |
inv_mat[:, 1, 0] = mat[:, 1, 0] / div2 | |
inv_mat[:, 1, 1] = -mat[:, 0, 0] / div2 | |
inv_mat[:, 1, 2] = -(mat[:, 0, 2] * mat[:, 1, 0] - mat[:, 0, 0] * mat[:, 1, 2]) / div2 | |
return inv_mat | |
class SoftErosion(torch.nn.Module): | |
def __init__(self, kernel_size: int = 15, threshold: float = 0.6, iterations: int = 1): | |
super(SoftErosion, self).__init__() | |
r = kernel_size // 2 | |
self.padding = r | |
self.iterations = iterations | |
self.threshold = threshold | |
# Create kernel | |
y_indices, x_indices = torch.meshgrid(torch.arange(0.0, kernel_size), torch.arange(0.0, kernel_size)) | |
dist = torch.sqrt((x_indices - r) ** 2 + (y_indices - r) ** 2) | |
kernel = dist.max() - dist | |
kernel /= kernel.sum() | |
kernel = kernel.view(1, 1, *kernel.shape) | |
self.register_buffer("weight", kernel) | |
def forward(self, x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]: | |
for i in range(self.iterations - 1): | |
x = torch.min( | |
x, | |
F.conv2d(x, weight=self.weight, groups=x.shape[1], padding=self.padding), | |
) | |
x = F.conv2d(x, weight=self.weight, groups=x.shape[1], padding=self.padding) | |
mask = x >= self.threshold | |
x[mask] = 1.0 | |
# add small epsilon to avoid Nans | |
x[~mask] /= x[~mask].max() + 1e-7 | |
return x, mask | |