+xlib.facemeta and refactoring

This commit is contained in:
iperov 2021-10-18 16:56:04 +04:00
commit 63adc2995e
28 changed files with 962 additions and 613 deletions

View file

@ -0,0 +1,6 @@
from enum import IntEnum
class ELandmarks2D(IntEnum):
L5 = 0
L68 = 1
L468 = 2

View file

@ -0,0 +1,4 @@
from enum import IntEnum
class EMaskType(IntEnum):
UNDEFINED = 0

View file

@ -1,24 +1,20 @@
from enum import IntEnum
from typing import Tuple
import cv2
import numpy as np
import numpy.linalg as npla
from ..math import Affine2DMat, Affine2DUniMat
from .ELandmarks2D import ELandmarks2D
from .FRect import FRect
class FaceULandmarks:
"""
Describes 2D face landmarks in uniform coordinates
"""
class Type(IntEnum):
LANDMARKS_5 = 0
LANDMARKS_68 = 1
LANDMARKS_468 = 2
class FLandmarks2D:
def __init__(self):
self._type : FaceULandmarks.Type = None
"""
Describes 2D face landmarks in uniform float coordinates
"""
self._type : ELandmarks2D = None
self._ulmrks : np.ndarray = None
def __getstate__(self):
@ -29,14 +25,13 @@ class FaceULandmarks:
self.__dict__.update(d)
@staticmethod
def create( type : 'FaceULandmarks.Type', ulmrks : np.ndarray):
def create( type : ELandmarks2D, ulmrks : np.ndarray):
"""
ulmrks np.ndarray (*,2|3)
"""
if not isinstance(type, FaceULandmarks.Type):
raise ValueError('type must be an FaceULandmarks.Type')
if not isinstance(type, ELandmarks2D):
raise ValueError('type must be ELandmarks2D')
ulmrks = np.float32(ulmrks)
if len(ulmrks.shape) != 2:
@ -46,22 +41,22 @@ class FaceULandmarks:
raise ValueError('ulmrks dim must be == 2')
ulmrks_count = ulmrks.shape[0]
if type == FaceULandmarks.Type.LANDMARKS_5:
if type == ELandmarks2D.L5:
if ulmrks_count != 5:
raise ValueError('ulmrks_count must be == 5')
elif type == FaceULandmarks.Type.LANDMARKS_68:
elif type == ELandmarks2D.L68:
if ulmrks_count != 68:
raise ValueError('ulmrks_count must be == 68')
elif type == FaceULandmarks.Type.LANDMARKS_468:
elif type == ELandmarks2D.L468:
if ulmrks_count != 468:
raise ValueError('ulmrks_count must be == 468')
face_ulmrks = FaceULandmarks()
face_ulmrks = FLandmarks2D()
face_ulmrks._type = type
face_ulmrks._ulmrks = ulmrks
return face_ulmrks
def get_type(self) -> 'FaceULandmarks.Type': return self._type
def get_type(self) -> ELandmarks2D: return self._type
def get_count(self) -> int: return self._ulmrks.shape[0]
def as_numpy(self, w_h = None):
@ -76,9 +71,9 @@ class FaceULandmarks:
return ulmrks
def transform(self, mat, invert=False) -> 'FaceULandmarks':
def transform(self, mat, invert=False) -> 'FLandmarks2D':
"""
Tranforms FaceULandmarks using affine mat and returns new FaceULandmarks()
Tranforms FLandmarks2D using affine mat and returns new FLandmarks2D()
mat : np.ndarray
"""
@ -93,9 +88,20 @@ class FaceULandmarks:
ulmrks = np.expand_dims(ulmrks, axis=1)
ulmrks = cv2.transform(ulmrks, mat, ulmrks.shape).squeeze()
return FaceULandmarks.create(type=self._type, ulmrks=ulmrks)
return FLandmarks2D.create(type=self._type, ulmrks=ulmrks)
def get_FRect(self, coverage=1.6) -> FRect:
"""
create FRect from landmarks with given coverage
"""
_, uni_mat = self.calc_cut( (1,1), coverage, 1, exclude_moving_parts=False)
xlt, xlb, xrb, xrt = uni_mat.invert().transform_points([[0,0], [0,1], [1,1], [1,0]])
l = min(xlt[0], xlb[0])
t = min(xlt[1], xrt[1])
r = max(xrt[0], xrb[0])
b = max(xlb[1], xrb[1])
return FRect.from_ltrb( (l,t,r,b) )
def calc_cut(self, w_h, coverage : float, output_size : int,
exclude_moving_parts : bool,
head_yaw : float = None,
@ -112,9 +118,9 @@ class FaceULandmarks:
lmrks = (self._ulmrks * w_h).astype(np.float32)
# estimate landmarks transform from global space to local aligned space with bounds [0..1]
if type == FaceULandmarks.Type.LANDMARKS_68:
if type == ELandmarks2D.L68:
mat = Affine2DMat.umeyama( np.concatenate ([ lmrks[17:49] , lmrks[54:55] ]), uni_landmarks_68)
elif type == FaceULandmarks.Type.LANDMARKS_468:
elif type == ELandmarks2D.L468:
src_lmrks = lmrks
dst_lmrks = uni_landmarks_468
if exclude_moving_parts:
@ -1297,4 +1303,4 @@ uni_landmarks_468 = np.array(
# # time.sleep(1.0)
# import code
# code.interact(local=dict(globals(), **locals()))
# code.interact(local=dict(globals(), **locals()))

40
xlib/facemeta/FMask.py Normal file
View file

@ -0,0 +1,40 @@
import uuid
from typing import Union
from enum import IntEnum
class FMask:
class Type(IntEnum):
WHOLE_FACE = 0
def __init__(self, _from_pickled=False):
"""
"""
self._uuid : Union[bytes, None] = uuid.uuid4().bytes_le if not _from_pickled else None
self._mask_type : Union[FMask.Type, None] = None
self._FImage_uuid : Union[bytes, None] = None
def __getstate__(self):
return self.__dict__.copy()
def __setstate__(self, d):
self.__init__(_from_pickled=True)
self.__dict__.update(d)
def get_uuid(self) -> Union[bytes, None]: return self._uuid
def set_uuid(self, uuid : Union[bytes, None]):
if uuid is not None and not isinstance(uuid, bytes):
raise ValueError(f'uuid must be an instance of bytes or None')
self._uuid = uuid
def get_mask_type(self) -> Union['FMask.Type', None]: return self._mask_type
def set_mask_type(self, mask_type : Union['FMask.Type', None]):
if mask_type is not None and not isinstance(mask_type, 'FMask.Type'):
raise ValueError(f'mask_type must be an instance of FMask.Type or None')
self._mask_type = mask_type
def get_FImage_uuid(self) -> Union[bytes, None]: return self._FImage_uuid
def set_FImage_uuid(self, FImage_uuid : Union[bytes, None]):
if FImage_uuid is not None and not isinstance(FImage_uuid, bytes):
raise ValueError(f'FImage_uuid must be an instance of bytes or None')
self._FImage_uuid = FImage_uuid

View file

@ -3,7 +3,7 @@ import numpy as np
from .. import math as lib_math
class FacePose:
class FPose:
"""
Describes face pitch/yaw/roll
"""
@ -33,7 +33,7 @@ class FacePose:
def from_radians(pitch, yaw, roll):
"""
"""
face_rect = FacePose()
face_rect = FPose()
face_rect._pyr = np.array([pitch, yaw, roll], np.float32)
return face_rect
@ -47,4 +47,4 @@ class FacePose:
mat[2,:] = np.cross(mat[0, :], mat[1, :])
pitch, yaw, roll = lib_math.rotation_matrix_to_euler(mat)
return FacePose.from_radians(pitch, yaw, roll)
return FPose.from_radians(pitch, yaw, roll)

View file

@ -8,8 +8,7 @@ import numpy.linalg as npla
from .. import math as lib_math
from ..math import Affine2DMat, Affine2DUniMat
class FaceURect:
class FRect:
"""
Describes face rectangle in uniform float coordinates
"""
@ -24,9 +23,9 @@ class FaceURect:
self.__dict__.update(d)
@staticmethod
def sort_by_area_size(rects : List['FaceURect']):
def sort_by_area_size(rects : List['FRect']):
"""
sort list of FaceURect by largest area descend
sort list of FRect by largest area descend
"""
rects = [ (rect, rect.get_area()) for rect in rects ]
rects = sorted(rects, key=operator.itemgetter(1), reverse=True )
@ -34,9 +33,9 @@ class FaceURect:
return rects
@staticmethod
def sort_by_dist_from_center(rects : List['FaceURect']):
def sort_by_dist_from_center(rects : List['FRect']):
"""
sort list of FaceURect by nearest distance from center to center of rects descent
sort list of FRect by nearest distance from center to center of rects descent
"""
c = np.float32([0.5,0.5])
@ -48,7 +47,7 @@ class FaceURect:
@staticmethod
def from_4pts(pts : Iterable):
"""
Construct FaceURect from 4 pts
Construct FRect from 4 pts
0--3
| |
1--2
@ -60,14 +59,14 @@ class FaceURect:
if pts.shape != (4,2):
raise ValueError('pts must have (4,2) shape')
face_rect = FaceURect()
face_rect = FRect()
face_rect._pts = pts
return face_rect
@staticmethod
def from_ltrb(ltrb : Iterable):
"""
Construct FaceURect from l,t,r,b list of float values
Construct FRect from l,t,r,b list of float values
t
l-|-r
b
@ -76,7 +75,7 @@ class FaceURect:
raise ValueError('ltrb must be Iterable')
l,t,r,b = ltrb
return FaceURect.from_4pts([ [l,t], [l,b], [r,b], [r,t] ])
return FRect.from_4pts([ [l,t], [l,b], [r,b], [r,t] ])
def get_area(self, w_h = None) -> float:
@ -124,9 +123,9 @@ class FaceURect:
return self._pts * w_h
return self._pts.copy()
def transform(self, mat, invert=False) -> 'FaceURect':
def transform(self, mat, invert=False) -> 'FRect':
"""
Tranforms FaceURect using affine mat and returns new FaceURect()
Tranforms FRect using affine mat and returns new FRect()
mat : np.ndarray should be uniform affine mat
"""
@ -141,7 +140,7 @@ class FaceURect:
pts = np.expand_dims(pts, axis=1)
pts = cv2.transform(pts, mat, pts.shape).squeeze()
return FaceURect.from_4pts(pts)
return FRect.from_4pts(pts)
def cut(self, img : np.ndarray, coverage : float, output_size : int) -> Tuple[Affine2DMat, Affine2DUniMat]:
"""

View file

@ -1,95 +1,237 @@
import pickle
import sqlite3
from pathlib import Path
from typing import Tuple
from typing import Generator, List, Union
import cv2
from .. import cv as lib_cv
from .. import path as lib_path
from ..io import FormattedFileIO
import numpy as np
from .face import FaceMark
from .FMask import FMask
from .UFaceMark import UFaceMark
from .UImage import UImage
from .UPerson import UPerson
class Faceset:
"""
Faceset is a class to store and manage multiple FaceMark()'s
arguments:
path path to directory
raises
Exception, ValueError
"""
def __init__(self, path):
"""
Faceset is a class to store and manage face related data.
arguments:
path path to faceset .dfs file
"""
self._path = path = Path(path)
if not path.is_dir():
raise ValueError('Path must be a directory.')
self.reload()
if path.suffix != '.dfs':
raise ValueError('Path must be a .dfs file')
def reload(self):
self._conn = conn = sqlite3.connect(path, isolation_level=None)
self._cur = cur = conn.cursor()
cur.execute('BEGIN IMMEDIATE')
if not self._is_table_exists('FacesetInfo'):
self.clear_db(transaction=False)
cur.execute('COMMIT')
def close(self):
self._cur.close()
self._cur = None
self._conn.close()
self._conn = None
def _is_table_exists(self, name):
return self._cur.execute(f"SELECT count(*) FROM sqlite_master WHERE type='table' AND name=?", [name]).fetchone()[0] != 0
def shrink(self):
self._cur.execute('VACUUM')
def clear_db(self, transaction=True):
"""
reload the faceset
raises
Exception
delete all data and recreate DB
"""
self._is_packed = False
cur = self._cur
filepaths = lib_path.get_files_paths(self._path)
if transaction:
cur.execute('BEGIN IMMEDIATE')
face_filepaths = self._face_filepaths = set()
for table_name, in cur.execute("SELECT name from sqlite_master where type = 'table';").fetchall():
cur.execute(f'DROP TABLE {table_name}')
for filepath in filepaths:
suffix = filepath.suffix
if suffix == '.face':
if self._is_packed:
raise Exception(f'{self._path} contains .faceset and .face but only one type is allowed.')
(cur.execute('CREATE TABLE FacesetInfo (version INT)')
.execute('INSERT INTO FacesetInfo VALUES (1)')
face_filepaths.add(filepath.name)
elif suffix == '.faceset':
if self._is_packed:
raise Exception(f'{self._path} contains more than one .faceset.')
.execute('CREATE TABLE UImage (uuid BLOB, name TEXT, format TEXT, data BLOB)')
.execute('CREATE TABLE UPerson (uuid BLOB, name TEXT, age NUMERIC)')
.execute('CREATE TABLE UFaceMark (uuid BLOB, UImage_uuid BLOB, UPerson_uuid BLOB, pickled_bytes BLOB)')
)
if len(face_filepaths) != 0:
raise Exception(f'{self._path} contains .faceset and .face but only one type is allowed.')
self._is_packed = True
def pack(self):
if transaction:
cur.execute('COMMIT')
###################
### UFaceMark
###################
def _UFaceMark_from_db_row(self, db_row) -> UFaceMark:
uuid, UImage_uuid, UPerson_uuid, pickled_bytes = db_row
return pickle.loads(pickled_bytes)
def add_UFaceMark(self, fm : UFaceMark):
"""
Pack faceset.
add or update UFaceMark in DB
"""
pickled_bytes = pickle.dumps(fm)
uuid = fm.get_uuid()
UImage_uuid = fm.get_UImage_uuid()
UPerson_uuid = fm.get_UPerson_uuid()
def unpack(self):
"""
unpack faceset.
"""
def is_packed(self) -> bool: return self._is_packed
def get_face_count(self): return len(self._face_filepaths)
def get_faces(self) -> Tuple[FaceMark]:
"""
returns a tuple of FaceMark()'s
"""
def save_face(self, face : FaceMark):
filepath = self._path / (face.get_name() + '.face')
with FormattedFileIO(filepath, 'w+') as f:
f.write_pickled(face)
self._face_filepaths.add(filepath.name)
def save_image(self, name, img, type='jpg'):
filepath = self._path / (name + f'.{type}')
if type == 'jpg':
lib_cv.imwrite(filepath, img, [int(cv2.IMWRITE_JPEG_QUALITY), 100] )
cur = self._cur
cur.execute('BEGIN IMMEDIATE')
if cur.execute('SELECT COUNT(*) from UFaceMark where uuid=?', [uuid] ).fetchone()[0] != 0:
cur.execute('UPDATE UFaceMark SET UImage_uuid=?, UPerson_uuid=?, pickled_bytes=? WHERE uuid=?',
[UImage_uuid, UPerson_uuid, pickled_bytes, uuid])
else:
raise ValueError(f'Unknown type {type}')
cur.execute('INSERT INTO UFaceMark VALUES (?, ?, ?, ?)', [uuid, UImage_uuid, UPerson_uuid, pickled_bytes])
cur.execute('COMMIT')
def get_UFaceMark_count(self) -> int:
return self._cur.execute('SELECT COUNT(*) FROM UFaceMark').fetchone()[0]
def get_all_UFaceMark(self) -> List[UFaceMark]:
return [ pickle.loads(pickled_bytes) for pickled_bytes, in self._cur.execute('SELECT pickled_bytes FROM UFaceMark').fetchall() ]
def iter_UFaceMark(self) -> Generator[UFaceMark, None, None]:
"""
returns Generator of UFaceMark
"""
for db_row in self._cur.execute('SELECT * FROM UFaceMark').fetchall():
yield self._UFaceMark_from_db_row(db_row)
def delete_all_UFaceMark(self):
"""
deletes all UFaceMark from DB
"""
(self._cur.execute('BEGIN IMMEDIATE')
.execute('DELETE FROM UFaceMark')
.execute('COMMIT') )
###################
### UPerson
###################
def add_UPerson(self, uperson : UPerson):
"""
add or update UPerson in DB
"""
uuid = uperson.get_uuid()
name = uperson.get_name()
age = uperson.get_age()
cur = self._conn.cursor()
cur.execute('BEGIN IMMEDIATE')
if cur.execute('SELECT COUNT(*) from UPerson where uuid=?', [uuid]).fetchone()[0] != 0:
cur.execute('UPDATE UPerson SET name=?, age=? WHERE uuid=?', [name, age, uuid])
else:
cur.execute('INSERT INTO UPerson VALUES (?, ?, ?)', [uuid, name, age])
cur.execute('COMMIT')
cur.close()
def iter_UPerson(self) -> Generator[UPerson, None, None]:
"""
iterator of all UPerson's
"""
for uuid, name, age in self._cur.execute('SELECT * FROM UPerson').fetchall():
uperson = UPerson()
uperson.set_uuid(uuid)
uperson.set_name(name)
uperson.set_age(age)
yield uperson
def delete_all_UPerson(self):
"""
deletes all UPerson from DB
"""
(self._cur.execute('BEGIN IMMEDIATE')
.execute('DELETE FROM UPerson')
.execute('COMMIT') )
###################
### UImage
###################
def _UImage_from_db_row(self, db_row) -> UImage:
uuid, name, format, data_bytes = db_row
img = cv2.imdecode(np.frombuffer(data_bytes, dtype=np.uint8), flags=cv2.IMREAD_UNCHANGED)
uimg = UImage()
uimg.set_uuid(uuid)
uimg.set_name(name)
uimg.assign_image(img)
return uimg
def add_UImage(self, uimage : UImage, format : str = 'webp', quality : int = 100):
"""
add or update UImage in DB
uimage UImage object
format('png') webp ( does not support lossless on 100 quality ! )
png ( lossless )
jpg
jp2 ( jpeg2000 )
quality(100) 0-100 for formats jpg,jp2,webp
"""
if format not in ['webp','png', 'jpg', 'jp2']:
raise ValueError(f'format {format} is unsupported')
if format in ['jpg','jp2'] and quality < 0 or quality > 100:
raise ValueError('quality must be in range [0..100]')
img = uimage.get_image()
uuid = uimage.get_uuid()
if format == 'webp':
imencode_args = [int(cv2.IMWRITE_WEBP_QUALITY), quality]
elif format == 'jpg':
imencode_args = [int(cv2.IMWRITE_JPEG_QUALITY), quality]
elif format == 'jp2':
imencode_args = [int(cv2.IMWRITE_JPEG2000_COMPRESSION_X1000), quality*10]
else:
imencode_args = []
ret, data_bytes = cv2.imencode( f'.{format}', img, imencode_args)
if not ret:
raise Exception(f'Unable to encode image format {format}')
cur = self._cur
cur.execute('BEGIN IMMEDIATE')
if cur.execute('SELECT COUNT(*) from UImage where uuid=?', [uuid] ).fetchone()[0] != 0:
cur.execute('UPDATE UImage SET name=?, format=?, data=? WHERE uuid=?', [uimage.get_name(), format, data_bytes.data, uuid])
else:
cur.execute('INSERT INTO UImage VALUES (?, ?, ?, ?)', [uuid, uimage.get_name(), format, data_bytes.data])
cur.execute('COMMIT')
def get_UImage_count(self) -> int: return self._cur.execute('SELECT COUNT(*) FROM UImage').fetchone()[0]
def get_UImage_by_uuid(self, uuid : bytes) -> Union[UImage, None]:
"""
"""
db_row = self._cur.execute('SELECT * FROM UImage where uuid=?', [uuid]).fetchone()
if db_row is None:
return None
return self._UImage_from_db_row(db_row)
def iter_UImage(self) -> Generator[UImage, None, None]:
"""
iterator of all UImage's
"""
for db_row in self._cur.execute('SELECT * FROM UImage').fetchall():
yield self._UImage_from_db_row(db_row)
def delete_all_UImage(self):
"""
deletes all UImage from DB
"""
(self._cur.execute('BEGIN IMMEDIATE')
.execute('DELETE FROM UImage')
.execute('COMMIT') )

View file

@ -0,0 +1,99 @@
import uuid
from typing import List, Tuple, Union
from ..math import Affine2DMat
from .ELandmarks2D import ELandmarks2D
from .EMaskType import EMaskType
from .FLandmarks2D import FLandmarks2D
from .FPose import FPose
from .FRect import FRect
class UFaceMark:
def __init__(self, _from_pickled=False):
"""
Describes single face in the image.
"""
self._uuid : Union[bytes, None] = uuid.uuid4().bytes_le if not _from_pickled else None
self._UImage_uuid : Union[bytes, None] = None
self._UPerson_uuid : Union[bytes, None] = None
self._FRect : Union[FRect, None] = None
self._FLandmarks2D_list : List[FLandmarks2D] = []
self._FPose : Union[FPose, None] = None
self._mask_info_list : List = []
def __getstate__(self):
return self.__dict__.copy()
def __setstate__(self, d):
self.__init__(_from_pickled=True)
self.__dict__.update(d)
def __str__(self):
s = "Masks: "
return f"UFaceMark UUID:[...{self._uuid[-4:].hex()}]"
def __repr__(self): return self.__str__()
def get_uuid(self) -> Union[bytes, None]: return self._uuid
def set_uuid(self, uuid : Union[bytes, None]):
if uuid is not None and not isinstance(uuid, bytes):
raise ValueError(f'uuid must be an instance of bytes or None')
self._uuid = uuid
def get_UImage_uuid(self) -> Union[bytes, None]: return self._UImage_uuid
def set_UImage_uuid(self, UImage_uuid : Union[bytes, None]):
if UImage_uuid is not None and not isinstance(UImage_uuid, bytes):
raise ValueError(f'UImage_uuid must be an instance of bytes or None')
self._UImage_uuid = UImage_uuid
def get_UPerson_uuid(self) -> Union[bytes, None]: return self._UPerson_uuid
def set_UPerson_uuid(self, UPerson_uuid : Union[bytes, None]):
if UPerson_uuid is not None and not isinstance(UPerson_uuid, bytes):
raise ValueError(f'UPerson_uuid must be an instance of bytes or None')
self._UPerson_uuid = UPerson_uuid
def get_FRect(self) -> Union['FRect', None]: return self._FRect
def set_FRect(self, face_urect : Union['FRect', None]):
if face_urect is not None and not isinstance(face_urect, FRect):
raise ValueError(f'face_urect must be an instance of FRect or None')
self._FRect = face_urect
def get_all_FLandmarks2D(self) -> List[FLandmarks2D]: return self._FLandmarks2D_list
def get_FLandmarks2D_by_type(self, type : ELandmarks2D) -> Union[FLandmarks2D, None]:
"""get FLandmarks2D from list by type"""
if not isinstance(type, ELandmarks2D):
raise ValueError(f'type must be an instance of ELandmarks2D')
for ulmrks in self._FLandmarks2D_list:
if ulmrks.get_type() == type:
return ulmrks
return None
def add_FLandmarks2D(self, flmrks : FLandmarks2D):
if not isinstance(flmrks, FLandmarks2D):
raise ValueError('flmrks must be an instance of FLandmarks2D')
if self.get_FLandmarks2D_by_type(flmrks.get_type()) is not None:
raise Exception(f'_FLandmarks2D_list already contains type {flmrks.get_type()}.')
self._FLandmarks2D_list.append(flmrks)
def get_FPose(self) -> Union[FPose, None]: return self._FPose
def set_FPose(self, face_pose : FPose):
if not isinstance(face_pose, FPose):
raise ValueError('face_pose must be an instance of FPose')
self._FPose = face_pose
def get_mask_info_list(self) -> List[Tuple[EMaskType, bytes, Affine2DMat]]:
return self._mask_info_list
def add_mask_info(self, mask_type : EMaskType, UImage_uuid : bytes, mask_to_mark_uni_mat : Affine2DMat):
if not isinstance(mask_type, EMaskType):
raise ValueError('mask_type must be an instance of EMaskType')
if not isinstance(UImage_uuid, bytes):
raise ValueError('UImage_uuid must be an instance of bytes')
if not isinstance(mask_to_mark_uni_mat, Affine2DMat):
raise ValueError('mask_to_mark_uni_mat must be an instance of Affine2DMat')
self._mask_info_list.append( (mask_type, UImage_uuid, mask_to_mark_uni_mat) )

59
xlib/facemeta/UImage.py Normal file
View file

@ -0,0 +1,59 @@
import uuid
from typing import Union
import numpy as np
class UImage:
def __init__(self):
"""
represents uncompressed image uint8 HWC ( 1/3/4 channels )
"""
self._uuid : Union[bytes, None] = uuid.uuid4().bytes_le
self._name : Union[str, None] = None
self._image : np.ndarray = None
def __getstate__(self):
return self.__dict__.copy()
def __setstate__(self, d):
self.__init__()
self.__dict__.update(d)
def __str__(self): return f"UImage UUID:[...{self._uuid[-4:].hex()}] name:[{self._name}] image:[{ (self._image.shape, self._image.dtype) if self._image is not None else None}]"
def __repr__(self): return self.__str__()
def get_uuid(self) -> Union[bytes, None]: return self._uuid
def set_uuid(self, uuid : Union[bytes, None]):
if uuid is not None and not isinstance(uuid, bytes):
raise ValueError(f'uuid must be an instance of bytes or None')
self._uuid = uuid
def get_name(self) -> Union[str, None]: return self._name
def set_name(self, name : Union[str, None]):
if name is not None and not isinstance(name, str):
raise ValueError(f'name must be an instance of str or None')
self._name = name
def get_image(self) -> Union[np.ndarray, None]: return self._image
def assign_image(self, image : Union[np.ndarray, None]):
"""
assign np.ndarray image , or remove(None)
It's mean you should not to change provided image nd.array after assigning, or do the copy before.
Image must be uint8 and HWC 1/3/4 channels.
"""
if image is not None:
if image.ndim == 2:
image = image[...,None]
if image.ndim != 3:
raise ValueError('image must have ndim == 3')
_,_,C = image.shape
if C not in [1,3,4]:
raise ValueError('image channels must be 1,3,4')
if image.dtype != np.uint8:
raise ValueError('image dtype must be uint8')
self._image = image

39
xlib/facemeta/UPerson.py Normal file
View file

@ -0,0 +1,39 @@
import uuid
from typing import Union
class UPerson:
def __init__(self, _from_pickled=False):
"""
"""
self._uuid : Union[bytes, None] = uuid.uuid4().bytes_le if not _from_pickled else None
self._name : Union[str, None] = None
self._age : Union[int, None] = None
def __getstate__(self):
return self.__dict__.copy()
def __setstate__(self, d):
self.__init__(_from_pickled=True)
self.__dict__.update(d)
def __str__(self): return f"UPerson UUID:[...{self._uuid[-4:].hex()}] name:[{self._name}] age:[{self._age}]"
def __repr__(self): return self.__str__()
def get_uuid(self) -> Union[bytes, None]: return self._uuid
def set_uuid(self, uuid : Union[bytes, None]):
if uuid is not None and not isinstance(uuid, bytes):
raise ValueError(f'uuid must be an instance of bytes or None')
self._uuid = uuid
def get_name(self) -> Union[str, None]: return self._name
def set_name(self, name : Union[str, None]):
if name is not None and not isinstance(name, str):
raise ValueError(f'name must be an instance of str or None')
self._name = name
def get_age(self) -> Union[str, None]: return self._age
def set_age(self, age : Union[int, None]):
if age is not None and not isinstance(age, int):
raise ValueError(f'age must be an instance of int or None')
self._age = age

View file

@ -1,48 +1,61 @@
"""
facemeta
facemeta library
Trying to standartize face description.
Contains classes for effectively storing, manage, and transfering all face related data.
It is not perfect structure, but enough for current tasks.
All classes are picklable and expandable.
All classes have noneable members accessed via get/set. No properties.
FaceURect and FaceULandmarks mean uniform coordinates in order to apply them to any resolution.
E-classes are enums.
U-classes are unique, have uuid and can be saved in Faceset.
Overall structure:
ELandmarks2D L5
L68
L468
EMaskType UNDEFINED, ..., ...
FaceMark - (mean single face data referencing any image)
.image_name - image reference
.person_name - optional name of person
.FaceURect - a rectangle of the face in source image space
.list[FaceULandmarks] - a list of unique types of landmarks of the face in source image space
types:
LANDMARKS_5
LANDMARKS_68
LANDMARKS_468
.FaceAlign - an aligned face from FaceMark
.image_name - image reference
.person_name - optional name of person
.coverage - coverage value used to align
.source_source_face_ulandmarks_type - type of FaceULandmarks from which this FaceAlign was produced
.source_to_aligned_uni_mat - uniform AffineMat to FaceMark image space to FaceAlign image space
.FaceURect - a rectangle of the face in aligned image space
.list[FaceULandmarks] - a list of unique types of landmarks of the face in aligned image space
.FaceMask - grayscale image to mask the face in FaceAlign image space
.image_name - image reference
.FaceSwap - face image of other person in the same as FaceAlign image space
.image_name - image reference
.person_name - optional name of person
.FaceMask - grayscale image to mask the swapped face in FaceSwap image space
.image_name
FRect rectangle of the face in uniform float coordinates
FLandmarks2D 2D landmarks of the face in uniform float coordinates
FPose pitch/yaw/roll values
UPerson - person info
.uuid
.name
.age
UImage - image
.uuid
.name
.data (H,W,C 1/3/4 ) of uint8[0..255]
UFaceMark - face mark info referencing UImage from which the face was detected
.uuid
.UImage_uuid - reference to FImage
.UPerson_uuid - reference to FPerson
.FRect
.List[FLandmarks2D]
.FPose
.List[ (EMaskType, FImage_uuid, uni_mat) ] - list of FMask and AffineMat to transform mask image space to UFaceMark image space
Faceset
.List[UImage]
.List[UFaceMark]
.List[UPerson]
"""
from .face import FaceMark, FaceAlign, FaceSwap, FaceMask, FaceURect, FaceULandmarks, FacePose
from .ELandmarks2D import ELandmarks2D
from .EMaskType import EMaskType
from .Faceset import Faceset
from .UImage import UImage
from .FLandmarks2D import FLandmarks2D
from .UFaceMark import UFaceMark
from .FMask import FMask
from .UPerson import UPerson
from .FPose import FPose
from .FRect import FRect

View file

@ -1,211 +0,0 @@
from typing import List, Union
from .. import math as lib_math
from .FaceULandmarks import FaceULandmarks
from .FaceURect import FaceURect
from .FacePose import FacePose
class _part_picklable_expandable:
def __getstate__(self):
return self.__dict__.copy()
def __setstate__(self, d):
self.__init__()
self.__dict__.update(d)
class _part_image_name:
def __init__(self):
self._image_name : Union[str, None] = None
def get_image_name(self) -> Union[str, None]: return self._image_name
def set_image_name(self, image_name : Union[str, None]):
if image_name is not None and not isinstance(image_name, str):
raise ValueError(f'image_name must be an instance of str or None')
self._image_name = image_name
class _part_person_name:
def __init__(self):
self._person_name : Union[str, None] = None
def get_person_name(self) -> Union[str, None]: return self._person_name
def set_person_name(self, person_name : Union[str, None]):
if person_name is not None and not isinstance(person_name, str):
raise ValueError(f'person_name must be an instance of str or None')
self._person_name = person_name
class _part_face_align:
def __init__(self):
self._face_align : Union['FaceAlign', None] = None
def get_face_align(self) -> Union['FaceAlign', None]: return self._face_align
def set_face_align(self, face_align : 'FaceAlign'):
"""add FaceAlign"""
if not isinstance(face_align, FaceAlign):
raise ValueError('face_align must be an instance of FaceAlign')
self._face_align = face_align
class _part_face_urect:
def __init__(self):
self._face_urect : Union[FaceURect, None] = None
def get_face_urect(self) -> Union['FaceURect', None]:
"""get uniform FaceURect"""
return self._face_urect
def set_face_urect(self, face_urect : Union['FaceURect', None]):
if face_urect is not None and not isinstance(face_urect, FaceURect):
raise ValueError(f'face_urect must be an instance of FaceURect or None')
self._face_urect = face_urect
class _part_face_ulandmarks_list:
def __init__(self):
self._face_ulmrks_list : List[FaceULandmarks] = []
def get_face_ulandmarks_list(self) -> List['FaceULandmarks']: return self._face_ulmrks_list
def get_face_ulandmarks_by_type(self, type : 'FaceULandmarks.Type') -> Union['FaceULandmarks', None]:
"""get FaceULandmarks from list by type"""
if not isinstance(type, FaceULandmarks.Type):
raise ValueError(f'type must be an instance of FaceULandmarks.Type')
for ulmrks in self._face_ulmrks_list:
if ulmrks.get_type() == type:
return ulmrks
return None
def add_face_ulandmarks(self, face_ulmrks : 'FaceULandmarks'):
if not isinstance(face_ulmrks, FaceULandmarks):
raise ValueError('face_ulmrks must be an instance of FaceULandmarks')
if self.get_face_ulandmarks_by_type(face_ulmrks.get_type()) is not None:
raise Exception(f'_face_ulmrks_list already contains type {face_ulmrks.get_type()}.')
self._face_ulmrks_list.append(face_ulmrks)
class _part_source_source_face_ulandmarks_type:
def __init__(self):
self._source_face_ulandmarks_type : Union[FaceULandmarks.Type, None] = None
def get_source_face_ulandmarks_type(self) -> Union[FaceULandmarks.Type, None]: return self._source_face_ulandmarks_type
def set_source_face_ulandmarks_type(self, source_face_ulandmarks_type : Union[FaceULandmarks.Type, None]):
if source_face_ulandmarks_type is not None and not isinstance(source_face_ulandmarks_type, FaceULandmarks.Type):
raise ValueError('source_face_ulandmarks_type must be an instance of FaceULandmarks.Type')
self._source_face_ulandmarks_type = source_face_ulandmarks_type
class _part_coverage:
def __init__(self):
self._coverage : Union[float, None] = None
def get_coverage(self) -> Union[float, None]: return self._coverage
def set_coverage(self, coverage : Union[float, None]):
if coverage is not None and not isinstance(coverage, float):
raise ValueError('coverage must be an instance of float')
self._coverage = coverage
class _part_source_to_aligned_uni_mat:
def __init__(self):
self._source_to_aligned_uni_mat : Union[lib_math.Affine2DUniMat, None] = None
def get_source_to_aligned_uni_mat(self) -> Union[lib_math.Affine2DUniMat, None]: return self._source_to_aligned_uni_mat
def set_source_to_aligned_uni_mat(self, source_to_aligned_uni_mat : Union[lib_math.Affine2DUniMat, None]):
if source_to_aligned_uni_mat is not None and not isinstance(source_to_aligned_uni_mat, lib_math.Affine2DUniMat):
raise ValueError('source_to_aligned_uni_mat must be an instance of lib_math.Affine2DUniMat')
self._source_to_aligned_uni_mat = source_to_aligned_uni_mat
class _part_face_mask:
def __init__(self):
self._face_mask : Union['FaceMask', None] = None
def get_face_mask(self) -> Union['FaceMask', None]: return self._face_mask
def set_face_mask(self, face_mask : 'FaceMask'):
if not isinstance(face_mask, FaceMask):
raise ValueError('face_mask must be an instance of FaceMask')
self._face_mask = face_mask
class _part_face_swap:
def __init__(self):
self._face_swap : Union['FaceSwap', None] = None
def get_face_swap(self) -> Union['FaceSwap', None]: return self._face_swap
def set_face_swap(self, face_swap : 'FaceSwap'):
if not isinstance(face_swap, FaceSwap):
raise ValueError('face_swap must be an instance of FaceSwap')
self._face_swap = face_swap
class _part_face_pose:
def __init__(self):
self._face_pose : Union['FacePose', None] = None
def get_face_pose(self) -> Union['FacePose', None]: return self._face_pose
def set_face_pose(self, face_pose : 'FacePose'):
if not isinstance(face_pose, FacePose):
raise ValueError('face_pose must be an instance of FacePose')
self._face_pose = face_pose
class FaceMark(_part_picklable_expandable,
_part_image_name,
_part_person_name,
_part_face_urect,
_part_face_ulandmarks_list,
_part_face_align,
_part_face_pose,
):
"""
Describes meta data of single face.
must not contain any images or large buffers, only references or filenames of them.
"""
def __init__(self):
_part_image_name.__init__(self)
_part_person_name.__init__(self)
_part_face_urect.__init__(self)
_part_face_ulandmarks_list.__init__(self)
_part_face_align.__init__(self)
_part_face_pose.__init__(self)
class FaceAlign(_part_picklable_expandable,
_part_image_name,
_part_person_name,
_part_face_urect,
_part_face_ulandmarks_list,
_part_coverage,
_part_source_source_face_ulandmarks_type,
_part_source_to_aligned_uni_mat,
_part_face_mask,
_part_face_swap,
):
def __init__(self):
_part_image_name.__init__(self)
_part_person_name.__init__(self)
_part_coverage.__init__(self)
_part_source_source_face_ulandmarks_type.__init__(self)
_part_source_to_aligned_uni_mat.__init__(self)
_part_face_urect.__init__(self)
_part_face_ulandmarks_list.__init__(self)
_part_face_mask.__init__(self)
_part_face_swap.__init__(self)
class FaceSwap(_part_picklable_expandable,
_part_image_name,
_part_person_name,
_part_face_mask,
):
def __init__(self):
_part_image_name.__init__(self)
_part_person_name.__init__(self)
_part_face_mask.__init__(self)
class FaceMask(_part_picklable_expandable,
_part_image_name,
):
def __init__(self):
_part_image_name.__init__(self)