mirror of
https://github.com/yakhyo/uniface.git
synced 2025-12-30 09:02:25 +00:00
fix: Fix type conversion and remove redundant type conversion (#29)
* ref: Remove type conversion and update face class * fix: change the type to float32 * chore: Update all examples, testing with latest version * docs: Update docs reflecting the recent changes
This commit is contained in:
committed by
GitHub
parent
f4458f0550
commit
3982d677a9
@@ -56,3 +56,4 @@ Example notebooks demonstrating library usage:
|
||||
|
||||
Open an issue or start a discussion on GitHub.
|
||||
|
||||
|
||||
|
||||
@@ -251,9 +251,9 @@ landmarks = landmarker.get_landmarks(image, bbox)
|
||||
from uniface import AgeGender
|
||||
|
||||
predictor = AgeGender()
|
||||
gender_id, age = predictor.predict(image, bbox)
|
||||
# Returns: (gender_id, age_in_years)
|
||||
# gender_id: 0 for Female, 1 for Male
|
||||
gender, age = predictor.predict(image, bbox)
|
||||
# Returns: (gender, age_in_years)
|
||||
# gender: 0 for Female, 1 for Male
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
@@ -199,9 +199,9 @@ faces = detector.detect(image)
|
||||
|
||||
# Predict attributes
|
||||
for i, face in enumerate(faces):
|
||||
gender_id, age = age_gender.predict(image, face['bbox'])
|
||||
gender = 'Female' if gender_id == 0 else 'Male'
|
||||
print(f"Face {i+1}: {gender}, {age} years old")
|
||||
gender, age = age_gender.predict(image, face['bbox'])
|
||||
gender_str = 'Female' if gender == 0 else 'Male'
|
||||
print(f"Face {i+1}: {gender_str}, {age} years old")
|
||||
```
|
||||
|
||||
**Output:**
|
||||
|
||||
@@ -147,9 +147,9 @@ detector = RetinaFace()
|
||||
age_gender = AgeGender()
|
||||
|
||||
faces = detector.detect(image)
|
||||
gender_id, age = age_gender.predict(image, faces[0]['bbox'])
|
||||
gender = 'Female' if gender_id == 0 else 'Male'
|
||||
print(f"{gender}, {age} years old")
|
||||
gender, age = age_gender.predict(image, faces[0]['bbox'])
|
||||
gender_str = 'Female' if gender == 0 else 'Male'
|
||||
print(f"{gender_str}, {age} years old")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -13,9 +13,17 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Note: you may need to restart the kernel to use updated packages.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%pip install -q uniface"
|
||||
]
|
||||
@@ -29,14 +37,14 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1.3.0\n"
|
||||
"1.3.1\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -65,7 +73,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -95,7 +103,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -136,7 +144,7 @@
|
||||
"\n",
|
||||
" # Print face attributes\n",
|
||||
" for i, face in enumerate(faces, 1):\n",
|
||||
" print(f' Face {i}: {face.gender}, {face.age}y')\n",
|
||||
" print(f' Face {i}: {face.sex}, {face.age}y')\n",
|
||||
"\n",
|
||||
" # Prepare visualization (without text overlay)\n",
|
||||
" vis_image = image.copy()\n",
|
||||
@@ -159,7 +167,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -186,7 +194,7 @@
|
||||
" axes[1, idx].axis('off')\n",
|
||||
" info_text = f'{len(faces)} face(s)\\n'\n",
|
||||
" for i, face in enumerate(faces, 1):\n",
|
||||
" info_text += f'Face {i}: {face.gender}, {face.age}y\\n'\n",
|
||||
" info_text += f'Face {i}: {face.sex}, {face.age}y\\n'\n",
|
||||
"\n",
|
||||
" axes[1, idx].text(0.5, 0.5, info_text,\n",
|
||||
" ha='center', va='center',\n",
|
||||
@@ -207,7 +215,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -236,7 +244,7 @@
|
||||
" print(f' - Confidence: {face.confidence:.3f}')\n",
|
||||
" print(f' - Landmarks shape: {face.landmarks.shape}')\n",
|
||||
" print(f' - Age: {face.age} years')\n",
|
||||
" print(f' - Gender: {face.gender}')\n",
|
||||
" print(f' - Gender: {face.sex}')\n",
|
||||
" print(f' - Embedding shape: {face.embedding.shape}')\n",
|
||||
" print(f' - Embedding dimension: {face.embedding.shape[1]}D')"
|
||||
]
|
||||
@@ -252,14 +260,14 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Similarity between faces: 0.1201\n",
|
||||
"Similarity between faces: 0.1135\n",
|
||||
"Same person: No (threshold=0.6)\n"
|
||||
]
|
||||
}
|
||||
@@ -283,7 +291,7 @@
|
||||
"\n",
|
||||
"- `analyzer.analyze()` performs detection, recognition, and attribute prediction in one call\n",
|
||||
"- Each `Face` object contains: `bbox`, `confidence`, `landmarks`, `embedding`, `age`, `gender`\n",
|
||||
"- Gender is available as both ID (0=Female, 1=Male) and string via `face.gender` property\n",
|
||||
"- Gender is available as both ID (0=Female, 1=Male) and string via `face.sex` property\n",
|
||||
"- Face embeddings are L2-normalized (norm ≈ 1.0) for similarity computation\n",
|
||||
"- Use `face.compute_similarity(other_face)` to compare faces (returns cosine similarity)\n",
|
||||
"- Typical similarity threshold: 0.6 (same person if similarity > 0.6)"
|
||||
@@ -297,7 +305,7 @@
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "Python 3",
|
||||
"display_name": "base",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
@@ -311,7 +319,7 @@
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.10.0"
|
||||
"version": "3.13.5"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
|
||||
@@ -13,9 +13,17 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"execution_count": 1,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Note: you may need to restart the kernel to use updated packages.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%pip install -q uniface"
|
||||
]
|
||||
@@ -29,14 +37,14 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1.3.0\n"
|
||||
"1.3.1\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -61,7 +69,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -88,7 +96,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -99,7 +107,7 @@
|
||||
"<PIL.JpegImagePlugin.JpegImageFile image mode=RGB size=1024x624>"
|
||||
]
|
||||
},
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
@@ -119,7 +127,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -175,7 +183,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"execution_count": 6,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -222,7 +230,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 6,
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -11,15 +11,6 @@
|
||||
"## 1. Install UniFace"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"%pip install -q uniface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
@@ -29,7 +20,24 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1.3.0\n"
|
||||
"Note: you may need to restart the kernel to use updated packages.\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"%pip install -q uniface"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"1.3.1\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -56,7 +64,7 @@
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"execution_count": 3,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -72,12 +80,12 @@
|
||||
"analyzer = FaceAnalyzer(\n",
|
||||
" detector=RetinaFace(conf_thresh=0.5),\n",
|
||||
" recognizer=ArcFace()\n",
|
||||
")\n"
|
||||
")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 3,
|
||||
"execution_count": 4,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -99,12 +107,12 @@
|
||||
"faces1 = analyzer.analyze(image1)\n",
|
||||
"faces2 = analyzer.analyze(image2)\n",
|
||||
"\n",
|
||||
"print(f'Detected {len(faces1)} and {len(faces2)} faces')\n"
|
||||
"print(f'Detected {len(faces1)} and {len(faces2)} faces')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -130,31 +138,7 @@
|
||||
"axes[1].axis('off')\n",
|
||||
"\n",
|
||||
"plt.tight_layout()\n",
|
||||
"plt.show()\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Similarity: 0.1201\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"if faces1 and faces2:\n",
|
||||
" face1 = faces1[0]\n",
|
||||
" face2 = faces2[0]\n",
|
||||
"\n",
|
||||
" similarity = face1.compute_similarity(face2)\n",
|
||||
" print(f'Similarity: {similarity:.4f}')\n",
|
||||
"else:\n",
|
||||
" print('Error: Could not detect faces')\n"
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
@@ -166,7 +150,31 @@
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Similarity: 0.1201\n",
|
||||
"Similarity: 0.1135\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"if faces1 and faces2:\n",
|
||||
" face1 = faces1[0]\n",
|
||||
" face2 = faces2[0]\n",
|
||||
"\n",
|
||||
" similarity = face1.compute_similarity(face2)\n",
|
||||
" print(f'Similarity: {similarity:.4f}')\n",
|
||||
"else:\n",
|
||||
" print('Error: Could not detect faces')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Similarity: 0.1135\n",
|
||||
"Threshold: 0.6\n",
|
||||
"Result: Different people\n"
|
||||
]
|
||||
@@ -180,12 +188,12 @@
|
||||
"\n",
|
||||
" print(f'Similarity: {similarity:.4f}')\n",
|
||||
" print(f'Threshold: {THRESHOLD}')\n",
|
||||
" print(f'Result: {\"Same person\" if is_same_person else \"Different people\"}')\n"
|
||||
" print(f'Result: {\"Same person\" if is_same_person else \"Different people\"}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 7,
|
||||
"execution_count": 8,
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
@@ -193,9 +201,9 @@
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Comparing multiple pairs:\n",
|
||||
"image0.jpg vs image1.jpg: 0.1201\n",
|
||||
"image0.jpg vs image2.jpg: 0.0951\n",
|
||||
"image1.jpg vs image2.jpg: -0.0047\n"
|
||||
"image0.jpg vs image1.jpg: 0.1135\n",
|
||||
"image0.jpg vs image2.jpg: 0.0833\n",
|
||||
"image1.jpg vs image2.jpg: -0.0082\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -220,7 +228,7 @@
|
||||
" img1_name = img1_path.split('/')[-1]\n",
|
||||
" img2_name = img2_path.split('/')[-1]\n",
|
||||
"\n",
|
||||
" print(f'{img1_name} vs {img2_name}: {sim:.4f}')\n"
|
||||
" print(f'{img1_name} vs {img2_name}: {sim:.4f}')"
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "uniface"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
description = "UniFace: A Comprehensive Library for Face Detection, Recognition, Landmark Analysis, Age, and Gender Detection"
|
||||
readme = "README.md"
|
||||
license = { text = "MIT" }
|
||||
|
||||
@@ -31,7 +31,7 @@ def process_image(detector, image_path: Path, output_path: Path, threshold: floa
|
||||
bboxes = [f['bbox'] for f in faces]
|
||||
scores = [f['confidence'] for f in faces]
|
||||
landmarks = [f['landmarks'] for f in faces]
|
||||
draw_detections(image, bboxes, scores, landmarks, vis_threshold=threshold)
|
||||
draw_detections(image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True)
|
||||
|
||||
cv2.putText(
|
||||
image,
|
||||
|
||||
@@ -43,7 +43,7 @@ def process_image(
|
||||
bboxes = [f['bbox'] for f in faces]
|
||||
scores = [f['confidence'] for f in faces]
|
||||
landmarks = [f['landmarks'] for f in faces]
|
||||
draw_detections(image, bboxes, scores, landmarks, vis_threshold=threshold)
|
||||
draw_detections(image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True)
|
||||
|
||||
for i, face in enumerate(faces):
|
||||
gender_id, age = age_gender.predict(image, face['bbox'])
|
||||
|
||||
@@ -51,7 +51,7 @@ def run_webcam(detector, threshold: float = 0.6):
|
||||
bboxes = [f['bbox'] for f in faces]
|
||||
scores = [f['confidence'] for f in faces]
|
||||
landmarks = [f['landmarks'] for f in faces]
|
||||
draw_detections(frame, bboxes, scores, landmarks, vis_threshold=threshold, draw_score=True, fancy_bbox=True)
|
||||
draw_detections(image=frame, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, draw_score=True, fancy_bbox=True)
|
||||
|
||||
cv2.putText(
|
||||
frame,
|
||||
|
||||
@@ -42,7 +42,7 @@ def process_image(
|
||||
bboxes = [f['bbox'] for f in faces]
|
||||
scores = [f['confidence'] for f in faces]
|
||||
landmarks = [f['landmarks'] for f in faces]
|
||||
draw_detections(image, bboxes, scores, landmarks, vis_threshold=threshold)
|
||||
draw_detections(image=image, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True)
|
||||
|
||||
for i, face in enumerate(faces):
|
||||
emotion, confidence = emotion_predictor.predict(image, face['landmarks'])
|
||||
|
||||
@@ -16,8 +16,8 @@ def draw_face_info(image, face, face_id):
|
||||
"""Draw face ID and attributes above bounding box."""
|
||||
x1, y1, x2, y2 = map(int, face.bbox)
|
||||
lines = [f'ID: {face_id}', f'Conf: {face.confidence:.2f}']
|
||||
if face.age and face.gender:
|
||||
lines.append(f'{face.gender}, {face.age}y')
|
||||
if face.age and face.sex:
|
||||
lines.append(f'{face.sex}, {face.age}y')
|
||||
|
||||
for i, line in enumerate(lines):
|
||||
y_pos = y1 - 10 - (len(lines) - 1 - i) * 25
|
||||
@@ -41,7 +41,7 @@ def process_image(analyzer, image_path: str, save_dir: str = 'outputs', show_sim
|
||||
return
|
||||
|
||||
for i, face in enumerate(faces, 1):
|
||||
info = f' Face {i}: {face.gender}, {face.age}y' if face.age and face.gender else f' Face {i}'
|
||||
info = f' Face {i}: {face.sex}, {face.age}y' if face.age and face.sex else f' Face {i}'
|
||||
if face.embedding is not None:
|
||||
info += f' (embedding: {face.embedding.shape})'
|
||||
print(info)
|
||||
@@ -82,7 +82,7 @@ def process_image(analyzer, image_path: str, save_dir: str = 'outputs', show_sim
|
||||
bboxes = [f.bbox for f in faces]
|
||||
scores = [f.confidence for f in faces]
|
||||
landmarks = [f.landmarks for f in faces]
|
||||
draw_detections(image, bboxes, scores, landmarks)
|
||||
draw_detections(image=image, bboxes=bboxes, scores=scores, landmarks=landmarks,fancy_bbox=True)
|
||||
|
||||
for i, face in enumerate(faces, 1):
|
||||
draw_face_info(image, face, i)
|
||||
|
||||
@@ -55,7 +55,7 @@ def process_video(
|
||||
bboxes = [f['bbox'] for f in faces]
|
||||
scores = [f['confidence'] for f in faces]
|
||||
landmarks = [f['landmarks'] for f in faces]
|
||||
draw_detections(frame, bboxes, scores, landmarks, vis_threshold=threshold)
|
||||
draw_detections(image=frame, bboxes=bboxes, scores=scores, landmarks=landmarks, vis_threshold=threshold, fancy_bbox=True)
|
||||
|
||||
cv2.putText(
|
||||
frame,
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
__license__ = 'MIT'
|
||||
__author__ = 'Yakhyokhuja Valikhujaev'
|
||||
__version__ = '1.3.0'
|
||||
__version__ = '1.3.1'
|
||||
|
||||
|
||||
from uniface.face_utils import compute_similarity, face_alignment
|
||||
|
||||
@@ -53,12 +53,11 @@ class FaceAnalyzer:
|
||||
except Exception as e:
|
||||
Logger.warning(f' Face {idx + 1}: Failed to extract embedding: {e}')
|
||||
|
||||
age, gender_id = None, None
|
||||
age, gender = None, None
|
||||
if self.age_gender is not None:
|
||||
try:
|
||||
gender_id, age = self.age_gender.predict(image, bbox)
|
||||
gender_str = 'Female' if gender_id == 0 else 'Male'
|
||||
Logger.debug(f' Face {idx + 1}: Age={age}, Gender={gender_str}')
|
||||
gender, age = self.age_gender.predict(image, bbox)
|
||||
Logger.debug(f' Face {idx + 1}: Age={age}, Gender={gender}')
|
||||
except Exception as e:
|
||||
Logger.warning(f' Face {idx + 1}: Failed to predict age/gender: {e}')
|
||||
|
||||
@@ -68,7 +67,7 @@ class FaceAnalyzer:
|
||||
landmarks=landmarks,
|
||||
embedding=embedding,
|
||||
age=age,
|
||||
gender_id=gender_id,
|
||||
gender=gender,
|
||||
)
|
||||
faces.append(face)
|
||||
|
||||
|
||||
@@ -230,9 +230,9 @@ class RetinaFace(BaseDetector):
|
||||
faces = []
|
||||
for i in range(detections.shape[0]):
|
||||
face_dict = {
|
||||
'bbox': detections[i, :4].astype(np.float32),
|
||||
'bbox': detections[i, :4],
|
||||
'confidence': float(detections[i, 4]),
|
||||
'landmarks': landmarks[i].astype(np.float32),
|
||||
'landmarks': landmarks[i],
|
||||
}
|
||||
faces.append(face_dict)
|
||||
|
||||
@@ -293,7 +293,7 @@ class RetinaFace(BaseDetector):
|
||||
landmarks[: self.post_nms_topk],
|
||||
)
|
||||
|
||||
landmarks = landmarks.reshape(-1, 5, 2).astype(np.int32)
|
||||
landmarks = landmarks.reshape(-1, 5, 2).astype(np.float32)
|
||||
|
||||
return detections, landmarks
|
||||
|
||||
|
||||
@@ -251,7 +251,7 @@ class SCRFD(BaseDetector):
|
||||
|
||||
detections = pre_det[keep, :]
|
||||
landmarks = landmarks[order, :, :]
|
||||
landmarks = landmarks[keep, :, :].astype(np.int32)
|
||||
landmarks = landmarks[keep, :, :].astype(np.float32)
|
||||
|
||||
if 0 < max_num < detections.shape[0]:
|
||||
# Calculate area of detections
|
||||
@@ -281,9 +281,9 @@ class SCRFD(BaseDetector):
|
||||
faces = []
|
||||
for i in range(detections.shape[0]):
|
||||
face_dict = {
|
||||
'bbox': detections[i, :4].astype(np.float32),
|
||||
'bbox': detections[i, :4],
|
||||
'confidence': float(detections[i, 4]),
|
||||
'landmarks': landmarks[i].astype(np.float32),
|
||||
'landmarks': landmarks[i],
|
||||
}
|
||||
faces.append(face_dict)
|
||||
|
||||
|
||||
@@ -331,9 +331,9 @@ class YOLOv5Face(BaseDetector):
|
||||
faces = []
|
||||
for i in range(detections.shape[0]):
|
||||
face_dict = {
|
||||
'bbox': detections[i, :4].astype(np.float32),
|
||||
'bbox': detections[i, :4],
|
||||
'confidence': float(detections[i, 4]),
|
||||
'landmarks': landmarks[i].astype(np.float32),
|
||||
'landmarks': landmarks[i],
|
||||
}
|
||||
faces.append(face_dict)
|
||||
|
||||
|
||||
@@ -14,14 +14,18 @@ __all__ = ['Face']
|
||||
|
||||
@dataclass
|
||||
class Face:
|
||||
"""Detected face with analysis results."""
|
||||
|
||||
"""
|
||||
Detected face with analysis results.
|
||||
"""
|
||||
# Required attributes
|
||||
bbox: np.ndarray
|
||||
confidence: float
|
||||
landmarks: np.ndarray
|
||||
|
||||
# Optional attributes
|
||||
embedding: Optional[np.ndarray] = None
|
||||
age: Optional[int] = None
|
||||
gender_id: Optional[int] = None # 0: Female, 1: Male
|
||||
gender: Optional[int] = None # 0 or 1
|
||||
|
||||
def compute_similarity(self, other: 'Face') -> float:
|
||||
"""Compute cosine similarity with another face."""
|
||||
@@ -34,18 +38,28 @@ class Face:
|
||||
return asdict(self)
|
||||
|
||||
@property
|
||||
def gender(self) -> str:
|
||||
def sex(self) -> str:
|
||||
"""Get gender as a string label (Female or Male)."""
|
||||
if self.gender_id is None:
|
||||
if self.gender is None:
|
||||
return None
|
||||
return 'Female' if self.gender_id == 0 else 'Male'
|
||||
return 'Female' if self.gender == 0 else 'Male'
|
||||
|
||||
@property
|
||||
def bbox_xyxy(self) -> np.ndarray:
|
||||
"""Get bounding box coordinates in (x1, y1, x2, y2) format."""
|
||||
return self.bbox.copy()
|
||||
|
||||
@property
|
||||
def bbox_xywh(self) -> np.ndarray:
|
||||
"""Get bounding box coordinates in (x1, y1, w, h) format."""
|
||||
return np.array([self.bbox[0], self.bbox[1], self.bbox[2] - self.bbox[0], self.bbox[3] - self.bbox[1]])
|
||||
|
||||
def __repr__(self) -> str:
|
||||
parts = [f'Face(confidence={self.confidence:.3f}']
|
||||
if self.age is not None:
|
||||
parts.append(f'age={self.age}')
|
||||
if self.gender_id is not None:
|
||||
parts.append(f'gender={self.gender}')
|
||||
if self.gender is not None:
|
||||
parts.append(f'sex={self.sex}')
|
||||
if self.embedding is not None:
|
||||
parts.append(f'embedding_dim={self.embedding.shape[0]}')
|
||||
return ', '.join(parts) + ')'
|
||||
|
||||
Reference in New Issue
Block a user