ImagePRO-Python 0.5.2__py3-none-any.whl → 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.
- ImagePRO/__init__.py +1 -1
- ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +104 -72
- ImagePRO/human_analysis/body_analysis/hand_tracking.py +118 -74
- ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +149 -109
- ImagePRO/human_analysis/face_analysis/face_comparison.py +100 -37
- ImagePRO/human_analysis/face_analysis/face_detection.py +83 -76
- ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +153 -145
- ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +153 -113
- ImagePRO/object_analysis/object_detection.py +59 -64
- ImagePRO/pre_processing/blur.py +173 -155
- ImagePRO/pre_processing/contrast.py +129 -127
- ImagePRO/pre_processing/crop.py +45 -53
- ImagePRO/pre_processing/dataset_generator.py +97 -85
- ImagePRO/pre_processing/grayscale.py +42 -42
- ImagePRO/pre_processing/resize.py +41 -49
- ImagePRO/pre_processing/rotate.py +131 -102
- ImagePRO/pre_processing/sharpen.py +94 -73
- ImagePRO/utils/image.py +118 -0
- ImagePRO/utils/result.py +146 -0
- {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.0.dist-info}/METADATA +1 -1
- imagepro_python-1.0.0.dist-info/RECORD +30 -0
- ImagePRO/utils/io_handler.py +0 -180
- imagepro_python-0.5.2.dist-info/RECORD +0 -29
- {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.0.dist-info}/WHEEL +0 -0
- {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -1,15 +1,13 @@
|
|
|
1
|
-
import
|
|
2
|
-
from pathlib import Path
|
|
1
|
+
from __future__ import annotations
|
|
3
2
|
|
|
4
3
|
import cv2
|
|
4
|
+
import mediapipe as mp
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
from ImagePRO.human_analysis.face_analysis.face_mesh_analysis import analyze_face_mesh
|
|
8
|
+
from ImagePRO.utils.image import Image
|
|
9
|
+
from ImagePRO.utils.result import Result
|
|
10
10
|
|
|
11
|
-
from utils.io_handler import IOHandler
|
|
12
|
-
from human_analysis.face_analysis.face_mesh_analysis import analyze_face_mesh
|
|
13
11
|
|
|
14
12
|
# Constants
|
|
15
13
|
DEFAULT_MAX_FACES = 1
|
|
@@ -22,93 +20,102 @@ FACE_OUTLINE_INDICES = [
|
|
|
22
20
|
|
|
23
21
|
|
|
24
22
|
def detect_faces(
|
|
23
|
+
image: Image,
|
|
24
|
+
*,
|
|
25
25
|
max_faces: int = DEFAULT_MAX_FACES,
|
|
26
26
|
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
27
|
+
face_mesh_obj: mp.solutions.face_mesh.FaceMesh | None = None
|
|
28
|
+
) -> Result:
|
|
29
|
+
"""Detect and crop face regions using facial landmarks.
|
|
30
|
+
|
|
31
|
+
Uses face mesh to locate face outline points,
|
|
32
|
+
then extracts rectangular regions containing each face.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
image: Input image to process.
|
|
36
|
+
max_faces: Maximum number of faces to detect.
|
|
37
|
+
Must be positive.
|
|
38
|
+
Default: 1
|
|
39
|
+
min_confidence: Detection confidence threshold.
|
|
40
|
+
Must be between 0 and 1.
|
|
41
|
+
Default: 0.7
|
|
42
|
+
face_mesh_obj: Pre-initialized face mesh detector.
|
|
43
|
+
If None, creates new instance.
|
|
44
|
+
Default: None
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Result object with detections:
|
|
48
|
+
- image: List of cropped face images
|
|
49
|
+
None if no faces detected
|
|
50
|
+
- data: List of face outline polygons
|
|
51
|
+
None if no faces detected
|
|
52
|
+
- meta: Operation info and parameters
|
|
53
|
+
Includes error info if detection fails
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
TypeError: If image is not an Image instance
|
|
57
|
+
ValueError: If max_faces is not positive
|
|
58
|
+
ValueError: If min_confidence not in [0,1]
|
|
32
59
|
"""
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
----------
|
|
37
|
-
max_faces : int, default=1
|
|
38
|
-
Maximum number of faces to detect.
|
|
39
|
-
min_confidence : float, default=0.7
|
|
40
|
-
Detection confidence threshold in [0, 1].
|
|
41
|
-
src_image_path : str | None, optional
|
|
42
|
-
Path to input image file.
|
|
43
|
-
src_np_image : np.ndarray | None, optional
|
|
44
|
-
Image array in BGR format. If both provided, `src_np_image` takes precedence.
|
|
45
|
-
output_image_path : str | None, optional
|
|
46
|
-
If provided, saves cropped faces to disk (single path used as base name for multiple faces).
|
|
47
|
-
face_mesh_obj : mediapipe.python.solutions.face_mesh.FaceMesh | None, optional
|
|
48
|
-
Optional reusable FaceMesh instance.
|
|
49
|
-
|
|
50
|
-
Returns
|
|
51
|
-
-------
|
|
52
|
-
list[np.ndarray] | str
|
|
53
|
-
If `output_image_path` is provided, returns the save message from
|
|
54
|
-
`IOHandler.save_image`. Otherwise, returns a list of cropped face images.
|
|
55
|
-
|
|
56
|
-
Raises
|
|
57
|
-
------
|
|
58
|
-
ValueError
|
|
59
|
-
* If inputs are invalid or no face landmarks are detected (this function).
|
|
60
|
-
* From IOHandler.load_image when both inputs are None or image loading fails.
|
|
61
|
-
TypeError
|
|
62
|
-
From IOHandler.load_image/save_image on invalid argument types.
|
|
63
|
-
FileNotFoundError
|
|
64
|
-
From IOHandler.load_image when `src_image_path` does not exist.
|
|
65
|
-
IOError
|
|
66
|
-
From IOHandler.save_image when saving the image fails.
|
|
67
|
-
"""
|
|
68
|
-
# --- Validate inputs -----------------------------------------------------
|
|
60
|
+
if not isinstance(image, Image):
|
|
61
|
+
raise TypeError("'image' must be an Image instance")
|
|
62
|
+
|
|
69
63
|
if not isinstance(max_faces, int) or max_faces <= 0:
|
|
70
|
-
raise ValueError("'max_faces' must be
|
|
64
|
+
raise ValueError("'max_faces' must be positive")
|
|
71
65
|
|
|
72
66
|
if not isinstance(min_confidence, (int, float)) or not (0 <= min_confidence <= 1):
|
|
73
|
-
raise ValueError("'min_confidence' must be
|
|
67
|
+
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
74
68
|
|
|
75
|
-
#
|
|
76
|
-
|
|
77
|
-
height, width = np_image.shape[:2]
|
|
69
|
+
# Get image dimensions
|
|
70
|
+
height, width = image.shape[:2]
|
|
78
71
|
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
# --- Run face mesh analysis ----------------------------------------------
|
|
83
|
-
_, raw_landmarks = analyze_face_mesh(
|
|
72
|
+
# Get face landmarks
|
|
73
|
+
result_mesh = analyze_face_mesh(
|
|
74
|
+
image=image,
|
|
84
75
|
max_faces=max_faces,
|
|
85
76
|
min_confidence=min_confidence,
|
|
86
|
-
landmarks_idx=
|
|
87
|
-
src_np_image=np_image,
|
|
77
|
+
landmarks_idx=FACE_OUTLINE_INDICES,
|
|
88
78
|
face_mesh_obj=face_mesh_obj
|
|
89
79
|
)
|
|
80
|
+
raw_landmarks = result_mesh.data
|
|
90
81
|
|
|
82
|
+
# Handle no detections
|
|
91
83
|
if not raw_landmarks:
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
84
|
+
return Result(
|
|
85
|
+
image=None,
|
|
86
|
+
data=None,
|
|
87
|
+
meta={
|
|
88
|
+
"source": image,
|
|
89
|
+
"operation": "detect_faces",
|
|
90
|
+
"max_faces": max_faces,
|
|
91
|
+
"min_confidence": min_confidence,
|
|
92
|
+
"error": "No face landmarks detected"
|
|
93
|
+
}
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
# Convert landmarks to pixel coordinates
|
|
97
|
+
face_polygons = []
|
|
96
98
|
for face in raw_landmarks:
|
|
97
99
|
polygon = [
|
|
98
100
|
(int(x * width), int(y * height))
|
|
99
101
|
for _, _, x, y, _ in face
|
|
100
102
|
]
|
|
101
|
-
|
|
103
|
+
face_polygons.append(np.array(polygon, dtype=np.int32))
|
|
102
104
|
|
|
103
|
-
#
|
|
104
|
-
|
|
105
|
-
for polygon in
|
|
105
|
+
# Extract face regions
|
|
106
|
+
face_regions = []
|
|
107
|
+
for polygon in face_polygons:
|
|
106
108
|
x, y, w, h = cv2.boundingRect(polygon)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
face_region = image._data[y:y + h, x:x + w]
|
|
110
|
+
face_regions.append(face_region)
|
|
111
|
+
|
|
112
|
+
return Result(
|
|
113
|
+
image=face_regions,
|
|
114
|
+
data=face_polygons,
|
|
115
|
+
meta={
|
|
116
|
+
"source": image,
|
|
117
|
+
"operation": "detect_faces",
|
|
118
|
+
"max_faces": max_faces,
|
|
119
|
+
"min_confidence": min_confidence
|
|
120
|
+
}
|
|
121
|
+
)
|
|
@@ -1,14 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
from pathlib import Path
|
|
1
|
+
from __future__ import annotations
|
|
3
2
|
|
|
4
3
|
import cv2
|
|
5
4
|
import mediapipe as mp
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
sys.path.append(str(parent_dir))
|
|
10
|
-
|
|
11
|
-
from utils.io_handler import IOHandler
|
|
6
|
+
from ImagePRO.utils.image import Image
|
|
7
|
+
from ImagePRO.utils.result import Result
|
|
12
8
|
|
|
13
9
|
# Constants
|
|
14
10
|
mp_face_mesh = mp.solutions.face_mesh
|
|
@@ -20,210 +16,222 @@ TOTAL_FACE_LANDMARKS = 468
|
|
|
20
16
|
|
|
21
17
|
|
|
22
18
|
def analyze_face_mesh(
|
|
19
|
+
image: Image,
|
|
20
|
+
*,
|
|
23
21
|
max_faces: int = DEFAULT_MAX_FACES,
|
|
24
22
|
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
25
|
-
landmarks_idx: list | None = None,
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
------
|
|
67
|
-
ValueError
|
|
68
|
-
* If inputs are invalid or no face landmarks are detected (this function).
|
|
69
|
-
* From ``IOHandler.load_image`` when both inputs are ``None`` or image loading fails.
|
|
70
|
-
TypeError
|
|
71
|
-
* If ``landmarks_idx`` is not a list of integers (this function).
|
|
72
|
-
* From ``IOHandler.load_image``/``save_image``/``save_csv`` on invalid argument types.
|
|
73
|
-
FileNotFoundError
|
|
74
|
-
From ``IOHandler.load_image`` when ``image_path`` does not exist.
|
|
75
|
-
IOError
|
|
76
|
-
From ``IOHandler.save_image`` when saving the image fails.
|
|
77
|
-
|
|
78
|
-
Notes
|
|
79
|
-
-----
|
|
80
|
-
* The x/y/z coordinates from MediaPipe are **normalized** to [0, 1] in image
|
|
81
|
-
space; multiply by width/height to get pixel coordinates.
|
|
82
|
-
* When ``landmarks_idx`` covers all 468 points, the full tessellation is drawn.
|
|
83
|
-
Otherwise, only the requested points are highlighted.
|
|
23
|
+
landmarks_idx: list[int] | None = None,
|
|
24
|
+
face_mesh_obj: mp.solutions.face_mesh.FaceMesh | None = None
|
|
25
|
+
) -> Result:
|
|
26
|
+
"""Detect facial landmarks using MediaPipe FaceMesh.
|
|
27
|
+
|
|
28
|
+
Extracts 3D facial mesh coordinates for precise face analysis.
|
|
29
|
+
Can focus on specific landmarks or detect full 468-point mesh.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
image: Input image to process.
|
|
33
|
+
max_faces: Maximum number of faces to detect.
|
|
34
|
+
Must be positive.
|
|
35
|
+
Default: 1
|
|
36
|
+
min_confidence: Detection confidence threshold.
|
|
37
|
+
Must be between 0 and 1.
|
|
38
|
+
Default: 0.7
|
|
39
|
+
landmarks_idx: Specific landmark indices to detect.
|
|
40
|
+
If None, uses all 468 points.
|
|
41
|
+
Default: None
|
|
42
|
+
face_mesh_obj: Pre-initialized face mesh detector.
|
|
43
|
+
If None, creates new instance.
|
|
44
|
+
Default: None
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
Result object with detections and visualization:
|
|
48
|
+
- image: Input image with landmarks drawn
|
|
49
|
+
None if no faces detected
|
|
50
|
+
- data: List of [face_id, point_id, x, y, z]
|
|
51
|
+
None if no faces detected
|
|
52
|
+
- meta: Operation info and parameters
|
|
53
|
+
Includes error info if detection fails
|
|
54
|
+
|
|
55
|
+
Raises:
|
|
56
|
+
TypeError: If image is not an Image instance
|
|
57
|
+
ValueError: If max_faces is not positive
|
|
58
|
+
ValueError: If min_confidence not in [0,1]
|
|
59
|
+
TypeError: If landmarks_idx is not list[int]
|
|
60
|
+
|
|
61
|
+
Notes:
|
|
62
|
+
- Coordinates are normalized [0,1]. Multiply by width/height for pixels
|
|
63
|
+
- Full mesh (468 points) shows tessellation, specific points show dots
|
|
84
64
|
"""
|
|
85
65
|
|
|
86
|
-
#
|
|
66
|
+
# Validate inputs
|
|
67
|
+
if not isinstance(image, Image):
|
|
68
|
+
raise TypeError("'image' must be an Image instance")
|
|
69
|
+
|
|
87
70
|
if not isinstance(max_faces, int) or max_faces <= 0:
|
|
88
|
-
raise ValueError("'max_faces' must be
|
|
71
|
+
raise ValueError("'max_faces' must be positive")
|
|
89
72
|
|
|
90
73
|
if not isinstance(min_confidence, (int, float)) or not (0 <= min_confidence <= 1):
|
|
91
|
-
raise ValueError("'min_confidence' must be
|
|
74
|
+
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
92
75
|
|
|
93
|
-
if landmarks_idx is not None
|
|
94
|
-
|
|
95
|
-
|
|
76
|
+
if landmarks_idx is not None and (
|
|
77
|
+
not isinstance(landmarks_idx, list)
|
|
78
|
+
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
79
|
+
):
|
|
80
|
+
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
96
81
|
|
|
97
|
-
#
|
|
82
|
+
# Initialize detector if needed
|
|
98
83
|
if face_mesh_obj is None:
|
|
99
|
-
|
|
84
|
+
face_mesh = mp_face_mesh.FaceMesh(
|
|
100
85
|
max_num_faces=max_faces,
|
|
101
86
|
min_detection_confidence=min_confidence,
|
|
102
87
|
refine_landmarks=True,
|
|
103
|
-
static_image_mode=True
|
|
88
|
+
static_image_mode=True
|
|
104
89
|
)
|
|
105
90
|
else:
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
# --- Load image (path or ndarray) ---------------------------------------
|
|
109
|
-
np_image = IOHandler.load_image(image_path=src_image_path, np_image=src_np_image)
|
|
91
|
+
face_mesh = face_mesh_obj
|
|
110
92
|
|
|
111
|
-
#
|
|
112
|
-
|
|
113
|
-
|
|
93
|
+
# Prepare image
|
|
94
|
+
img_copy = image._data.copy()
|
|
95
|
+
img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
|
|
96
|
+
|
|
97
|
+
# Detect facial landmarks
|
|
98
|
+
results = face_mesh.process(img_rgb)
|
|
114
99
|
|
|
100
|
+
# Handle no detections
|
|
115
101
|
if not results.multi_face_landmarks:
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
102
|
+
return Result(
|
|
103
|
+
image=None,
|
|
104
|
+
data=None,
|
|
105
|
+
meta={
|
|
106
|
+
"source": image,
|
|
107
|
+
"operation": "analyze_face_mesh",
|
|
108
|
+
"landmarks_idx": landmarks_idx,
|
|
109
|
+
"max_faces": max_faces,
|
|
110
|
+
"min_confidence": min_confidence,
|
|
111
|
+
"error": "No face landmarks detected"
|
|
112
|
+
}
|
|
113
|
+
)
|
|
122
114
|
|
|
115
|
+
# Use all landmarks if none specified
|
|
116
|
+
landmarks_idx = landmarks_idx or list(range(TOTAL_FACE_LANDMARKS))
|
|
117
|
+
# Process each detected face
|
|
118
|
+
landmarks = []
|
|
119
|
+
|
|
123
120
|
for face_id, face_landmarks in enumerate(results.multi_face_landmarks):
|
|
124
|
-
# Draw
|
|
121
|
+
# Draw landmarks
|
|
125
122
|
if len(landmarks_idx) == TOTAL_FACE_LANDMARKS:
|
|
123
|
+
# Draw full face mesh
|
|
126
124
|
mp_drawing_utils.draw_landmarks(
|
|
127
|
-
image=
|
|
125
|
+
image=img_copy,
|
|
128
126
|
landmark_list=face_landmarks,
|
|
129
127
|
connections=mp_face_mesh.FACEMESH_TESSELATION,
|
|
130
128
|
landmark_drawing_spec=None,
|
|
131
|
-
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_tesselation_style()
|
|
129
|
+
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_tesselation_style()
|
|
132
130
|
)
|
|
133
131
|
else:
|
|
134
|
-
|
|
132
|
+
# Draw specific points
|
|
133
|
+
h, w = img_copy.shape[:2]
|
|
135
134
|
for idx in landmarks_idx:
|
|
136
135
|
lm = face_landmarks.landmark[idx]
|
|
137
136
|
cx, cy = int(w * lm.x), int(h * lm.y)
|
|
138
|
-
cv2.circle(
|
|
137
|
+
cv2.circle(img_copy, (cx, cy), 3, (0, 0, 255), -1)
|
|
139
138
|
|
|
140
|
-
#
|
|
139
|
+
# Extract coordinates
|
|
141
140
|
face_data = [
|
|
142
|
-
[face_id, idx,
|
|
141
|
+
[face_id, idx, lm.x, lm.y, lm.z]
|
|
143
142
|
for idx in landmarks_idx
|
|
143
|
+
for lm in [face_landmarks.landmark[idx]]
|
|
144
144
|
]
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
145
|
+
landmarks.append(face_data)
|
|
146
|
+
|
|
147
|
+
return Result(
|
|
148
|
+
image=img_copy,
|
|
149
|
+
data=landmarks,
|
|
150
|
+
meta={
|
|
151
|
+
"source": image,
|
|
152
|
+
"operation": "analyze_face_mesh",
|
|
153
|
+
"landmarks_idx": landmarks_idx,
|
|
154
|
+
"max_faces": max_faces,
|
|
155
|
+
"min_confidence": min_confidence
|
|
156
|
+
}
|
|
157
|
+
)
|
|
157
158
|
|
|
158
159
|
|
|
159
|
-
def analyze_face_mesh_live(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
160
|
+
def analyze_face_mesh_live(
|
|
161
|
+
*,
|
|
162
|
+
max_faces: int = DEFAULT_MAX_FACES,
|
|
163
|
+
min_confidence: float = DEFAULT_MIN_CONFIDENCE
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Start live webcam feed with real-time face mesh visualization.
|
|
166
|
+
|
|
167
|
+
Opens default camera and shows face landmarks in real-time.
|
|
168
|
+
Uses tracking mode for better performance on video.
|
|
169
|
+
Press ESC to exit.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
max_faces: Maximum number of faces to track.
|
|
173
|
+
Must be positive.
|
|
174
|
+
Default: 1
|
|
175
|
+
min_confidence: Detection confidence threshold.
|
|
176
|
+
Must be between 0 and 1.
|
|
177
|
+
Default: 0.7
|
|
178
|
+
|
|
179
|
+
Raises:
|
|
180
|
+
ValueError: If parameters are invalid
|
|
181
|
+
RuntimeError: If camera cannot be accessed
|
|
181
182
|
"""
|
|
183
|
+
# Validate parameters
|
|
182
184
|
if not isinstance(max_faces, int) or max_faces <= 0:
|
|
183
|
-
raise ValueError("'max_faces' must be
|
|
185
|
+
raise ValueError("'max_faces' must be positive")
|
|
184
186
|
|
|
185
187
|
if not isinstance(min_confidence, (int, float)) or not (0 <= min_confidence <= 1):
|
|
186
|
-
raise ValueError("'min_confidence' must be between 0 and 1
|
|
188
|
+
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
187
189
|
|
|
190
|
+
# Initialize camera
|
|
188
191
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
189
192
|
if not cap.isOpened():
|
|
190
|
-
raise RuntimeError("
|
|
193
|
+
raise RuntimeError("Cannot access webcam")
|
|
191
194
|
|
|
192
|
-
#
|
|
193
|
-
|
|
195
|
+
# Initialize detector in tracking mode for better performance
|
|
196
|
+
face_mesh = mp_face_mesh.FaceMesh(
|
|
194
197
|
max_num_faces=max_faces,
|
|
195
198
|
min_detection_confidence=min_confidence,
|
|
196
199
|
refine_landmarks=True,
|
|
197
|
-
static_image_mode=False
|
|
200
|
+
static_image_mode=False
|
|
198
201
|
)
|
|
199
202
|
|
|
200
203
|
try:
|
|
201
204
|
while True:
|
|
205
|
+
# Get frame
|
|
202
206
|
success, frame = cap.read()
|
|
203
207
|
if not success:
|
|
204
|
-
print("
|
|
208
|
+
print("Failed to capture frame, skipping...")
|
|
205
209
|
continue
|
|
206
210
|
|
|
211
|
+
# Process frame
|
|
207
212
|
try:
|
|
208
|
-
|
|
213
|
+
result = analyze_face_mesh(
|
|
214
|
+
image=Image.from_array(frame),
|
|
209
215
|
max_faces=max_faces,
|
|
210
216
|
min_confidence=min_confidence,
|
|
211
|
-
|
|
212
|
-
face_mesh_obj=face_Mesh,
|
|
217
|
+
face_mesh_obj=face_mesh
|
|
213
218
|
)
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
219
|
+
display_frame = result.image if result.image is not None else frame
|
|
220
|
+
except (TypeError, ValueError):
|
|
221
|
+
# Fall back to raw frame if detection fails
|
|
222
|
+
display_frame = frame
|
|
217
223
|
|
|
218
|
-
|
|
224
|
+
# Show result
|
|
225
|
+
cv2.imshow("ImagePRO - Face Mesh", display_frame)
|
|
219
226
|
|
|
220
|
-
#
|
|
227
|
+
# Check for ESC key
|
|
221
228
|
if cv2.waitKey(1) & 0xFF == 27:
|
|
222
229
|
break
|
|
223
230
|
finally:
|
|
231
|
+
# Clean up resources
|
|
224
232
|
cap.release()
|
|
225
233
|
cv2.destroyAllWindows()
|
|
226
234
|
|
|
227
235
|
|
|
228
236
|
if __name__ == "__main__":
|
|
229
|
-
analyze_face_mesh_live(
|
|
237
|
+
analyze_face_mesh_live()
|