ImagePRO-Python 1.2.2__py3-none-any.whl → 1.3.1__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 +2 -2
- ImagePRO/human_analysis/body_analysis/__init__.py +4 -0
- ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +26 -5
- ImagePRO/human_analysis/body_analysis/hand_tracking.py +28 -6
- ImagePRO/human_analysis/face_analysis/__init__.py +5 -1
- ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +22 -4
- ImagePRO/human_analysis/face_analysis/face_comparison.py +15 -3
- ImagePRO/human_analysis/face_analysis/face_detection.py +5 -2
- ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +36 -18
- ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +14 -4
- ImagePRO/object_analysis/__init__.py +3 -0
- ImagePRO/object_analysis/object_detection.py +24 -13
- ImagePRO/pre_processing/__init__.py +4 -1
- ImagePRO/pre_processing/blur.py +10 -10
- ImagePRO/pre_processing/contrast.py +4 -7
- ImagePRO/pre_processing/crop.py +4 -5
- ImagePRO/pre_processing/dataset_generator.py +16 -10
- ImagePRO/pre_processing/histogram.py +36 -48
- ImagePRO/pre_processing/resize.py +2 -2
- ImagePRO/pre_processing/rotate.py +6 -7
- ImagePRO/pre_processing/sharpen.py +16 -13
- ImagePRO/utils/image.py +7 -6
- ImagePRO/utils/result.py +10 -7
- {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/METADATA +46 -18
- imagepro_python-1.3.1.dist-info/RECORD +31 -0
- {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/WHEEL +1 -1
- imagepro_python-1.2.2.dist-info/RECORD +0 -31
- {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {imagepro_python-1.2.2.dist-info → imagepro_python-1.3.1.dist-info}/top_level.txt +0 -0
ImagePRO/__init__.py
CHANGED
|
@@ -15,7 +15,7 @@ Features:
|
|
|
15
15
|
- Real-time processing capabilities
|
|
16
16
|
"""
|
|
17
17
|
|
|
18
|
-
__version__ = "1.
|
|
18
|
+
__version__ = "1.3.1"
|
|
19
19
|
__author__ = "Parsa Safaie"
|
|
20
20
|
__email__ = "parsasafaie.2568@proton.me"
|
|
21
21
|
|
|
@@ -27,7 +27,7 @@ from . import object_analysis
|
|
|
27
27
|
|
|
28
28
|
__all__ = [
|
|
29
29
|
"utils",
|
|
30
|
-
"pre_processing",
|
|
30
|
+
"pre_processing",
|
|
31
31
|
"human_analysis",
|
|
32
32
|
"object_analysis"
|
|
33
33
|
]
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Body Analysis Module
|
|
2
2
|
# Provides body pose estimation and hand tracking capabilities
|
|
3
|
+
#
|
|
4
|
+
# The mediapipe dependency is optional and imported lazily inside the
|
|
5
|
+
# functions that need it, so both submodules are safe to import without
|
|
6
|
+
# any AI extras installed.
|
|
3
7
|
|
|
4
8
|
from . import body_pose_estimation
|
|
5
9
|
from . import hand_tracking
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -13,10 +14,11 @@ from ImagePRO.utils.image import Image
|
|
|
13
14
|
from ImagePRO.utils.result import Result
|
|
14
15
|
|
|
15
16
|
import cv2
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
19
|
+
import mediapipe as mp
|
|
17
20
|
|
|
18
21
|
# Constants
|
|
19
|
-
mp_pose = mp.solutions.pose
|
|
20
22
|
TOTAL_LANDMARKS = 33
|
|
21
23
|
DEFAULT_CONFIDENCE = 0.7
|
|
22
24
|
LANDMARK_RADIUS = 3
|
|
@@ -64,11 +66,21 @@ def detect_body_pose(
|
|
|
64
66
|
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
65
67
|
|
|
66
68
|
if landmarks_idx is not None and (
|
|
67
|
-
not isinstance(landmarks_idx, list)
|
|
69
|
+
not isinstance(landmarks_idx, list)
|
|
68
70
|
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
69
71
|
):
|
|
70
72
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
71
73
|
|
|
74
|
+
try:
|
|
75
|
+
import mediapipe as mp
|
|
76
|
+
except ImportError as err:
|
|
77
|
+
raise ImportError(
|
|
78
|
+
"The optional 'mediapipe' dependency is required for body pose "
|
|
79
|
+
'detection. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
80
|
+
) from err
|
|
81
|
+
|
|
82
|
+
mp_pose = mp.solutions.pose
|
|
83
|
+
|
|
72
84
|
# Initialize detector if needed
|
|
73
85
|
if pose_obj is None:
|
|
74
86
|
pose_obj = mp_pose.Pose(
|
|
@@ -97,7 +109,8 @@ def detect_body_pose(
|
|
|
97
109
|
img_copy,
|
|
98
110
|
result.pose_landmarks,
|
|
99
111
|
mp_pose.POSE_CONNECTIONS,
|
|
100
|
-
landmark_drawing_spec=mp.solutions.drawing_styles
|
|
112
|
+
landmark_drawing_spec=mp.solutions.drawing_styles
|
|
113
|
+
.get_default_pose_landmarks_style()
|
|
101
114
|
)
|
|
102
115
|
else:
|
|
103
116
|
# Draw only selected points
|
|
@@ -132,13 +145,21 @@ def detect_body_pose_live() -> None:
|
|
|
132
145
|
Raises:
|
|
133
146
|
RuntimeError: If camera cannot be accessed
|
|
134
147
|
"""
|
|
148
|
+
try:
|
|
149
|
+
import mediapipe as mp
|
|
150
|
+
except ImportError as err:
|
|
151
|
+
raise ImportError(
|
|
152
|
+
"The optional 'mediapipe' dependency is required for body pose "
|
|
153
|
+
'detection. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
154
|
+
) from err
|
|
155
|
+
|
|
135
156
|
# Initialize camera
|
|
136
157
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
137
158
|
if not cap.isOpened():
|
|
138
159
|
raise RuntimeError("Cannot access webcam")
|
|
139
160
|
|
|
140
161
|
# Initialize pose detector in tracking mode
|
|
141
|
-
pose_obj =
|
|
162
|
+
pose_obj = mp.solutions.pose.Pose(
|
|
142
163
|
min_detection_confidence=DEFAULT_CONFIDENCE,
|
|
143
164
|
static_image_mode=False
|
|
144
165
|
)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -13,10 +14,11 @@ from ImagePRO.utils.image import Image
|
|
|
13
14
|
from ImagePRO.utils.result import Result
|
|
14
15
|
|
|
15
16
|
import cv2
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
19
|
+
import mediapipe as mp
|
|
17
20
|
|
|
18
21
|
# Constants
|
|
19
|
-
mp_hands = mp.solutions.hands
|
|
20
22
|
DEFAULT_MAX_HANDS = 2
|
|
21
23
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
22
24
|
TOTAL_HAND_LANDMARKS = 21
|
|
@@ -74,11 +76,21 @@ def detect_hands(
|
|
|
74
76
|
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
75
77
|
|
|
76
78
|
if landmarks_idx is not None and (
|
|
77
|
-
not isinstance(landmarks_idx, list)
|
|
79
|
+
not isinstance(landmarks_idx, list)
|
|
78
80
|
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
79
81
|
):
|
|
80
82
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
81
83
|
|
|
84
|
+
try:
|
|
85
|
+
import mediapipe as mp
|
|
86
|
+
except ImportError as err:
|
|
87
|
+
raise ImportError(
|
|
88
|
+
"The optional 'mediapipe' dependency is required for hand "
|
|
89
|
+
'tracking. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
90
|
+
) from err
|
|
91
|
+
|
|
92
|
+
mp_hands = mp.solutions.hands
|
|
93
|
+
|
|
82
94
|
# Initialize detector if needed
|
|
83
95
|
if hands_obj is None:
|
|
84
96
|
hands_obj = mp_hands.Hands(
|
|
@@ -109,8 +121,10 @@ def detect_hands(
|
|
|
109
121
|
image=img_copy,
|
|
110
122
|
landmark_list=hand_landmarks,
|
|
111
123
|
connections=mp_hands.HAND_CONNECTIONS,
|
|
112
|
-
landmark_drawing_spec=mp.solutions.drawing_styles
|
|
113
|
-
|
|
124
|
+
landmark_drawing_spec=mp.solutions.drawing_styles
|
|
125
|
+
.get_default_hand_landmarks_style(),
|
|
126
|
+
connection_drawing_spec=mp.solutions.drawing_styles
|
|
127
|
+
.get_default_hand_connections_style()
|
|
114
128
|
)
|
|
115
129
|
else:
|
|
116
130
|
# Draw only selected points
|
|
@@ -166,13 +180,21 @@ def detect_hands_live(
|
|
|
166
180
|
if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
|
|
167
181
|
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
168
182
|
|
|
183
|
+
try:
|
|
184
|
+
import mediapipe as mp
|
|
185
|
+
except ImportError as err:
|
|
186
|
+
raise ImportError(
|
|
187
|
+
"The optional 'mediapipe' dependency is required for hand "
|
|
188
|
+
'tracking. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
189
|
+
) from err
|
|
190
|
+
|
|
169
191
|
# Initialize camera
|
|
170
192
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
171
193
|
if not cap.isOpened():
|
|
172
194
|
raise RuntimeError("Cannot access webcam")
|
|
173
195
|
|
|
174
196
|
# Initialize hand detector in tracking mode
|
|
175
|
-
hands_obj =
|
|
197
|
+
hands_obj = mp.solutions.hands.Hands(
|
|
176
198
|
min_detection_confidence=min_confidence,
|
|
177
199
|
max_num_hands=max_hands,
|
|
178
200
|
static_image_mode=False
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Face Analysis Module
|
|
2
2
|
# Provides facial landmark detection, pose estimation, and eye status analysis
|
|
3
|
+
#
|
|
4
|
+
# The mediapipe and insightface dependencies are optional and imported
|
|
5
|
+
# lazily inside the functions that need them, so every submodule is safe
|
|
6
|
+
# to import regardless of which extras are installed.
|
|
3
7
|
|
|
4
8
|
from . import eye_status_analysis
|
|
5
9
|
from . import face_comparison
|
|
@@ -10,7 +14,7 @@ from . import head_pose_estimation
|
|
|
10
14
|
__all__ = [
|
|
11
15
|
"eye_status_analysis",
|
|
12
16
|
"face_comparison",
|
|
13
|
-
"face_detection",
|
|
17
|
+
"face_detection",
|
|
14
18
|
"face_mesh_analysis",
|
|
15
19
|
"head_pose_estimation"
|
|
16
20
|
]
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -14,10 +15,11 @@ from ImagePRO.utils.image import Image
|
|
|
14
15
|
from ImagePRO.utils.result import Result
|
|
15
16
|
|
|
16
17
|
import cv2
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
20
|
+
import mediapipe as mp
|
|
18
21
|
|
|
19
22
|
# Constants
|
|
20
|
-
mp_face_mesh = mp.solutions.face_mesh
|
|
21
23
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
22
24
|
DEFAULT_THRESHOLD = 0.2
|
|
23
25
|
RIGHT_EYE_INDICES = [386, 374, 263, 362] # MediaPipe right eye landmarks
|
|
@@ -65,12 +67,20 @@ def analyze_eye_status(
|
|
|
65
67
|
if not 0 <= min_confidence <= 1:
|
|
66
68
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
67
69
|
|
|
70
|
+
try:
|
|
71
|
+
import mediapipe as mp
|
|
72
|
+
except ImportError as err:
|
|
73
|
+
raise ImportError(
|
|
74
|
+
"The optional 'mediapipe' dependency is required for eye status "
|
|
75
|
+
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
76
|
+
) from err
|
|
77
|
+
|
|
68
78
|
# Get image dimensions
|
|
69
79
|
h, w = image.shape[:2]
|
|
70
80
|
|
|
71
81
|
# Initialize detector if needed
|
|
72
82
|
if face_mesh_obj is None:
|
|
73
|
-
face_mesh_obj =
|
|
83
|
+
face_mesh_obj = mp.solutions.face_mesh.FaceMesh(
|
|
74
84
|
min_detection_confidence=min_confidence,
|
|
75
85
|
refine_landmarks=True,
|
|
76
86
|
static_image_mode=True
|
|
@@ -173,13 +183,21 @@ def analyze_eye_status_live(
|
|
|
173
183
|
if not 0 <= min_confidence <= 1:
|
|
174
184
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
175
185
|
|
|
186
|
+
try:
|
|
187
|
+
import mediapipe as mp
|
|
188
|
+
except ImportError as err:
|
|
189
|
+
raise ImportError(
|
|
190
|
+
"The optional 'mediapipe' dependency is required for eye status "
|
|
191
|
+
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
192
|
+
) from err
|
|
193
|
+
|
|
176
194
|
# Initialize webcam
|
|
177
195
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
178
196
|
if not cap.isOpened():
|
|
179
197
|
raise RuntimeError("Failed to access webcam")
|
|
180
198
|
|
|
181
199
|
# Initialize face mesh detector for video
|
|
182
|
-
face_mesh =
|
|
200
|
+
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
|
183
201
|
max_num_faces=1,
|
|
184
202
|
min_detection_confidence=min_confidence,
|
|
185
203
|
refine_landmarks=True,
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -13,11 +14,13 @@ import os
|
|
|
13
14
|
|
|
14
15
|
import cv2
|
|
15
16
|
import numpy as np
|
|
16
|
-
from insightface.app import FaceAnalysis
|
|
17
17
|
|
|
18
18
|
from ImagePRO.utils.image import Image
|
|
19
19
|
from ImagePRO.utils.result import Result
|
|
20
20
|
|
|
21
|
+
if TYPE_CHECKING: # insightface is imported lazily inside the function
|
|
22
|
+
from insightface.app import FaceAnalysis
|
|
23
|
+
|
|
21
24
|
# Constants
|
|
22
25
|
DEFAULT_SIMILARITY_THRESHOLD = 0.5
|
|
23
26
|
DEFAULT_MODEL_NAME = "buffalo_l"
|
|
@@ -62,6 +65,15 @@ def compare_faces(
|
|
|
62
65
|
if not isinstance(image_2, Image):
|
|
63
66
|
raise TypeError("'image_2' must be an Image instance")
|
|
64
67
|
|
|
68
|
+
try:
|
|
69
|
+
from insightface.app import FaceAnalysis
|
|
70
|
+
except ImportError as err:
|
|
71
|
+
raise ImportError(
|
|
72
|
+
"The optional 'insightface' dependency is required for face "
|
|
73
|
+
'comparison. Install it with: pip install '
|
|
74
|
+
'"ImagePRO-Python[insightface]"'
|
|
75
|
+
) from err
|
|
76
|
+
|
|
65
77
|
# Initialize model if needed
|
|
66
78
|
if app is None:
|
|
67
79
|
app = FaceAnalysis(
|
|
@@ -85,8 +97,8 @@ def compare_faces(
|
|
|
85
97
|
|
|
86
98
|
try:
|
|
87
99
|
# Save images to disk if needed
|
|
88
|
-
path_1 = save_temp_image(image_1
|
|
89
|
-
path_2 = save_temp_image(image_2
|
|
100
|
+
path_1 = save_temp_image(image_1, 'tmp1.jpg')
|
|
101
|
+
path_2 = save_temp_image(image_2, 'tmp2.jpg')
|
|
90
102
|
|
|
91
103
|
# Load and preprocess images
|
|
92
104
|
img1 = load_rgb_image(path_1)
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -10,13 +11,15 @@ if str(_src_path) not in sys.path:
|
|
|
10
11
|
sys.path.insert(0, str(_src_path))
|
|
11
12
|
|
|
12
13
|
import cv2
|
|
13
|
-
import mediapipe as mp
|
|
14
14
|
import numpy as np
|
|
15
15
|
|
|
16
16
|
from ImagePRO.human_analysis.face_analysis.face_mesh_analysis import analyze_face_mesh
|
|
17
17
|
from ImagePRO.utils.image import Image
|
|
18
18
|
from ImagePRO.utils.result import Result
|
|
19
19
|
|
|
20
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside analyze_face_mesh
|
|
21
|
+
import mediapipe as mp
|
|
22
|
+
|
|
20
23
|
|
|
21
24
|
# Constants
|
|
22
25
|
DEFAULT_MAX_FACES = 1
|
|
@@ -29,7 +32,7 @@ FACE_OUTLINE_INDICES = [
|
|
|
29
32
|
|
|
30
33
|
|
|
31
34
|
def detect_faces(
|
|
32
|
-
image: Image,
|
|
35
|
+
image: Image,
|
|
33
36
|
*,
|
|
34
37
|
max_faces: int = DEFAULT_MAX_FACES,
|
|
35
38
|
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -13,12 +14,11 @@ from ImagePRO.utils.image import Image
|
|
|
13
14
|
from ImagePRO.utils.result import Result
|
|
14
15
|
|
|
15
16
|
import cv2
|
|
16
|
-
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
19
|
+
import mediapipe as mp
|
|
17
20
|
|
|
18
21
|
# Constants
|
|
19
|
-
mp_face_mesh = mp.solutions.face_mesh
|
|
20
|
-
mp_drawing_utils = mp.solutions.drawing_utils
|
|
21
|
-
mp_drawing_styles = mp.solutions.drawing_styles
|
|
22
22
|
DEFAULT_MAX_FACES = 1
|
|
23
23
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
24
24
|
TOTAL_FACE_LANDMARKS = 468
|
|
@@ -71,7 +71,6 @@ def analyze_face_mesh(
|
|
|
71
71
|
- Coordinates are normalized [0,1]. Multiply by width/height for pixels
|
|
72
72
|
- Full mesh (468 points) shows tessellation, specific points show dots
|
|
73
73
|
"""
|
|
74
|
-
|
|
75
74
|
# Validate inputs
|
|
76
75
|
if not isinstance(image, Image):
|
|
77
76
|
raise TypeError("'image' must be an Image instance")
|
|
@@ -83,11 +82,23 @@ def analyze_face_mesh(
|
|
|
83
82
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
84
83
|
|
|
85
84
|
if landmarks_idx is not None and (
|
|
86
|
-
not isinstance(landmarks_idx, list)
|
|
85
|
+
not isinstance(landmarks_idx, list)
|
|
87
86
|
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
88
87
|
):
|
|
89
88
|
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
90
89
|
|
|
90
|
+
try:
|
|
91
|
+
import mediapipe as mp
|
|
92
|
+
except ImportError as err:
|
|
93
|
+
raise ImportError(
|
|
94
|
+
"The optional 'mediapipe' dependency is required for face mesh "
|
|
95
|
+
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
96
|
+
) from err
|
|
97
|
+
|
|
98
|
+
mp_face_mesh = mp.solutions.face_mesh
|
|
99
|
+
mp_drawing_utils = mp.solutions.drawing_utils
|
|
100
|
+
mp_drawing_styles = mp.solutions.drawing_styles
|
|
101
|
+
|
|
91
102
|
# Initialize detector if needed
|
|
92
103
|
if face_mesh_obj is None:
|
|
93
104
|
face_mesh = mp_face_mesh.FaceMesh(
|
|
@@ -99,11 +110,8 @@ def analyze_face_mesh(
|
|
|
99
110
|
else:
|
|
100
111
|
face_mesh = face_mesh_obj
|
|
101
112
|
|
|
102
|
-
#
|
|
103
|
-
|
|
104
|
-
img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
|
|
105
|
-
|
|
106
|
-
# Detect facial landmarks
|
|
113
|
+
# Detect facial landmarks (cvtColor allocates a new RGB array)
|
|
114
|
+
img_rgb = cv2.cvtColor(image._data, cv2.COLOR_BGR2RGB)
|
|
107
115
|
results = face_mesh.process(img_rgb)
|
|
108
116
|
|
|
109
117
|
# Handle no detections
|
|
@@ -121,11 +129,14 @@ def analyze_face_mesh(
|
|
|
121
129
|
}
|
|
122
130
|
)
|
|
123
131
|
|
|
132
|
+
# Allocate the annotation canvas only when a face was found
|
|
133
|
+
img_copy = image._data.copy()
|
|
134
|
+
|
|
124
135
|
# Use all landmarks if none specified
|
|
125
136
|
landmarks_idx = landmarks_idx or list(range(TOTAL_FACE_LANDMARKS))
|
|
126
137
|
# Process each detected face
|
|
127
138
|
landmarks = []
|
|
128
|
-
|
|
139
|
+
|
|
129
140
|
for face_id, face_landmarks in enumerate(results.multi_face_landmarks):
|
|
130
141
|
# Draw landmarks
|
|
131
142
|
if len(landmarks_idx) == TOTAL_FACE_LANDMARKS:
|
|
@@ -146,11 +157,10 @@ def analyze_face_mesh(
|
|
|
146
157
|
cv2.circle(img_copy, (cx, cy), 3, (0, 0, 255), -1)
|
|
147
158
|
|
|
148
159
|
# Extract coordinates
|
|
149
|
-
face_data = [
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
]
|
|
160
|
+
face_data = []
|
|
161
|
+
for idx in landmarks_idx:
|
|
162
|
+
lm = face_landmarks.landmark[idx]
|
|
163
|
+
face_data.append([face_id, idx, lm.x, lm.y, lm.z])
|
|
154
164
|
landmarks.append(face_data)
|
|
155
165
|
|
|
156
166
|
return Result(
|
|
@@ -196,13 +206,21 @@ def analyze_face_mesh_live(
|
|
|
196
206
|
if not isinstance(min_confidence, (int, float)) or not (0 <= min_confidence <= 1):
|
|
197
207
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
198
208
|
|
|
209
|
+
try:
|
|
210
|
+
import mediapipe as mp
|
|
211
|
+
except ImportError as err:
|
|
212
|
+
raise ImportError(
|
|
213
|
+
"The optional 'mediapipe' dependency is required for face mesh "
|
|
214
|
+
'analysis. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
215
|
+
) from err
|
|
216
|
+
|
|
199
217
|
# Initialize camera
|
|
200
218
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
201
219
|
if not cap.isOpened():
|
|
202
220
|
raise RuntimeError("Cannot access webcam")
|
|
203
221
|
|
|
204
222
|
# Initialize detector in tracking mode for better performance
|
|
205
|
-
face_mesh =
|
|
223
|
+
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
|
206
224
|
max_num_faces=max_faces,
|
|
207
225
|
min_detection_confidence=min_confidence,
|
|
208
226
|
refine_landmarks=True,
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -14,10 +15,11 @@ from ImagePRO.utils.image import Image
|
|
|
14
15
|
from ImagePRO.utils.result import Result
|
|
15
16
|
|
|
16
17
|
import cv2
|
|
17
|
-
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING: # mediapipe is imported lazily inside the functions
|
|
20
|
+
import mediapipe as mp
|
|
18
21
|
|
|
19
22
|
# Constants
|
|
20
|
-
mp_face_mesh = mp.solutions.face_mesh
|
|
21
23
|
DEFAULT_MAX_FACES = 1
|
|
22
24
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
23
25
|
HEAD_POSE_INDICES = [1, 152, 33, 263, 168] # nose_tip, chin, left_eye, right_eye, nasion
|
|
@@ -97,7 +99,7 @@ def estimate_head_pose(
|
|
|
97
99
|
for face in landmarks:
|
|
98
100
|
# Map landmarks by index
|
|
99
101
|
points = {lm[1]: lm for lm in face}
|
|
100
|
-
|
|
102
|
+
|
|
101
103
|
try:
|
|
102
104
|
# Extract key points
|
|
103
105
|
nose_x, nose_y = points[1][2:4] # Nose tip
|
|
@@ -169,13 +171,21 @@ def estimate_head_pose_live(
|
|
|
169
171
|
if not 0 <= min_confidence <= 1:
|
|
170
172
|
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
171
173
|
|
|
174
|
+
try:
|
|
175
|
+
import mediapipe as mp
|
|
176
|
+
except ImportError as err:
|
|
177
|
+
raise ImportError(
|
|
178
|
+
"The optional 'mediapipe' dependency is required for head pose "
|
|
179
|
+
'estimation. Install it with: pip install "ImagePRO-Python[mediapipe]"'
|
|
180
|
+
) from err
|
|
181
|
+
|
|
172
182
|
# Initialize webcam
|
|
173
183
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
174
184
|
if not cap.isOpened():
|
|
175
185
|
raise RuntimeError("Failed to access webcam")
|
|
176
186
|
|
|
177
187
|
# Initialize face mesh detector for video
|
|
178
|
-
face_mesh =
|
|
188
|
+
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
|
179
189
|
max_num_faces=max_faces,
|
|
180
190
|
min_detection_confidence=min_confidence,
|
|
181
191
|
refine_landmarks=True,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Object Analysis Module
|
|
2
2
|
# Provides object detection capabilities using YOLO models
|
|
3
|
+
#
|
|
4
|
+
# The ultralytics dependency is optional and imported lazily inside the
|
|
5
|
+
# functions that need it, so this package imports cleanly without it.
|
|
3
6
|
|
|
4
7
|
from . import object_detection
|
|
5
8
|
|
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
7
|
# Add src directory to path for absolute imports
|
|
7
8
|
_file_path = Path(__file__).resolve()
|
|
@@ -9,17 +10,17 @@ _src_path = _file_path.parents[2] # Go up to src directory
|
|
|
9
10
|
if str(_src_path) not in sys.path:
|
|
10
11
|
sys.path.insert(0, str(_src_path))
|
|
11
12
|
|
|
12
|
-
from ultralytics import YOLO
|
|
13
|
-
|
|
14
13
|
from ImagePRO.utils.image import Image
|
|
15
14
|
from ImagePRO.utils.result import Result
|
|
16
15
|
|
|
16
|
+
if TYPE_CHECKING: # ultralytics is imported lazily inside the function
|
|
17
|
+
from ultralytics import YOLO
|
|
18
|
+
|
|
17
19
|
# Constants
|
|
18
20
|
DEFAULT_ACCURACY_LEVEL = 1
|
|
19
|
-
DEFAULT_CONFIDENCE = 0.5
|
|
20
21
|
MODEL_MAPPING = {
|
|
21
22
|
1: "yolo11n.pt",
|
|
22
|
-
2: "yolo11s.pt",
|
|
23
|
+
2: "yolo11s.pt",
|
|
23
24
|
3: "yolo11m.pt",
|
|
24
25
|
4: "yolo11l.pt",
|
|
25
26
|
5: "yolo11x.pt"
|
|
@@ -56,6 +57,7 @@ def detect_objects(
|
|
|
56
57
|
Result object with detections and metadata:
|
|
57
58
|
- image: Original with bounding boxes drawn
|
|
58
59
|
- data: List of [class_id, [x1,y1,x2,y2], confidence]
|
|
60
|
+
with box coordinates normalized to [0, 1]
|
|
59
61
|
- meta: Operation info and model used
|
|
60
62
|
|
|
61
63
|
Raises:
|
|
@@ -65,20 +67,29 @@ def detect_objects(
|
|
|
65
67
|
if not isinstance(image, Image):
|
|
66
68
|
raise TypeError("'image' must be an Image instance")
|
|
67
69
|
|
|
70
|
+
# accuracy_level only applies when no model is given
|
|
71
|
+
if model is None and accuracy_level not in MODEL_MAPPING:
|
|
72
|
+
raise ValueError(
|
|
73
|
+
f"'accuracy_level' must be in {list(MODEL_MAPPING.keys())}, "
|
|
74
|
+
f"got {accuracy_level}"
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
from ultralytics import YOLO
|
|
79
|
+
except ImportError as err:
|
|
80
|
+
raise ImportError(
|
|
81
|
+
"The optional 'ultralytics' dependency is required for object "
|
|
82
|
+
'detection. Install it with: pip install "ImagePRO-Python[yolo]"'
|
|
83
|
+
) from err
|
|
84
|
+
|
|
68
85
|
# Initialize model
|
|
69
86
|
model_name = None
|
|
70
87
|
if model is None:
|
|
71
|
-
if accuracy_level not in MODEL_MAPPING:
|
|
72
|
-
raise ValueError(
|
|
73
|
-
f"'accuracy_level' must be in {list(MODEL_MAPPING.keys())}, "
|
|
74
|
-
f"got {accuracy_level}"
|
|
75
|
-
)
|
|
76
|
-
|
|
77
88
|
model_name = MODEL_MAPPING[accuracy_level]
|
|
78
89
|
model = YOLO(model=model_name)
|
|
79
90
|
|
|
80
|
-
# Run inference
|
|
81
|
-
result = model(image._data
|
|
91
|
+
# Run inference (ultralytics does not mutate the input array)
|
|
92
|
+
result = model(image._data)[0]
|
|
82
93
|
boxes = result.boxes
|
|
83
94
|
|
|
84
95
|
# Process detections
|
|
@@ -92,7 +103,7 @@ def detect_objects(
|
|
|
92
103
|
# Show visualization if requested
|
|
93
104
|
if show_result:
|
|
94
105
|
result.show()
|
|
95
|
-
|
|
106
|
+
|
|
96
107
|
return Result(
|
|
97
108
|
image=result.plot(),
|
|
98
109
|
data=detections,
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
# Pre-processing Module
|
|
2
2
|
# Provides image manipulation, filtering, and enhancement capabilities
|
|
3
|
+
#
|
|
4
|
+
# dataset_generator uses the optional mediapipe dependency, imported
|
|
5
|
+
# lazily inside its functions, so it is safe to import here regardless.
|
|
3
6
|
|
|
4
7
|
from . import blur
|
|
5
8
|
from . import contrast
|
|
@@ -12,7 +15,7 @@ from . import sharpen
|
|
|
12
15
|
|
|
13
16
|
__all__ = [
|
|
14
17
|
"blur",
|
|
15
|
-
"contrast",
|
|
18
|
+
"contrast",
|
|
16
19
|
"crop",
|
|
17
20
|
"dataset_generator",
|
|
18
21
|
"grayscale",
|