ImagePRO-Python 0.5.2__tar.gz → 1.0.1__tar.gz
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_python-0.5.2/src/ImagePRO_Python.egg-info → imagepro_python-1.0.1}/PKG-INFO +2 -1
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/pyproject.toml +3 -1
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/__init__.py +1 -1
- imagepro_python-1.0.1/src/ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +170 -0
- imagepro_python-1.0.1/src/ImagePRO/human_analysis/body_analysis/hand_tracking.py +202 -0
- imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +226 -0
- imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/face_comparison.py +129 -0
- imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/face_detection.py +121 -0
- imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +237 -0
- imagepro_python-1.0.1/src/ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +221 -0
- imagepro_python-1.0.1/src/ImagePRO/object_analysis/object_detection.py +95 -0
- imagepro_python-1.0.1/src/ImagePRO/pre_processing/blur.py +230 -0
- imagepro_python-1.0.1/src/ImagePRO/pre_processing/contrast.py +161 -0
- imagepro_python-1.0.1/src/ImagePRO/pre_processing/crop.py +72 -0
- imagepro_python-1.0.1/src/ImagePRO/pre_processing/dataset_generator.py +198 -0
- imagepro_python-1.0.1/src/ImagePRO/pre_processing/grayscale.py +50 -0
- imagepro_python-1.0.1/src/ImagePRO/pre_processing/resize.py +54 -0
- imagepro_python-1.0.1/src/ImagePRO/pre_processing/rotate.py +148 -0
- imagepro_python-1.0.1/src/ImagePRO/pre_processing/sharpen.py +115 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/utils/__init__.py +3 -2
- imagepro_python-1.0.1/src/ImagePRO/utils/image.py +118 -0
- imagepro_python-1.0.1/src/ImagePRO/utils/result.py +146 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1/src/ImagePRO_Python.egg-info}/PKG-INFO +2 -1
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO_Python.egg-info/SOURCES.txt +2 -1
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO_Python.egg-info/requires.txt +1 -0
- imagepro_python-0.5.2/src/ImagePRO/human_analysis/body_analysis/body_pose_estimation.py +0 -138
- imagepro_python-0.5.2/src/ImagePRO/human_analysis/body_analysis/hand_tracking.py +0 -158
- imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/eye_status_analysis.py +0 -186
- imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/face_comparison.py +0 -66
- imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/face_detection.py +0 -114
- imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/face_mesh_analysis.py +0 -229
- imagepro_python-0.5.2/src/ImagePRO/human_analysis/face_analysis/head_pose_estimation.py +0 -181
- imagepro_python-0.5.2/src/ImagePRO/object_analysis/object_detection.py +0 -100
- imagepro_python-0.5.2/src/ImagePRO/pre_processing/blur.py +0 -212
- imagepro_python-0.5.2/src/ImagePRO/pre_processing/contrast.py +0 -159
- imagepro_python-0.5.2/src/ImagePRO/pre_processing/crop.py +0 -80
- imagepro_python-0.5.2/src/ImagePRO/pre_processing/dataset_generator.py +0 -186
- imagepro_python-0.5.2/src/ImagePRO/pre_processing/grayscale.py +0 -50
- imagepro_python-0.5.2/src/ImagePRO/pre_processing/resize.py +0 -62
- imagepro_python-0.5.2/src/ImagePRO/pre_processing/rotate.py +0 -119
- imagepro_python-0.5.2/src/ImagePRO/pre_processing/sharpen.py +0 -94
- imagepro_python-0.5.2/src/ImagePRO/utils/io_handler.py +0 -180
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/LICENSE +0 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/setup.cfg +0 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/human_analysis/__init__.py +0 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/human_analysis/body_analysis/__init__.py +0 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/human_analysis/face_analysis/__init__.py +0 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/object_analysis/__init__.py +0 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO/pre_processing/__init__.py +0 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO_Python.egg-info/dependency_links.txt +0 -0
- {imagepro_python-0.5.2 → imagepro_python-1.0.1}/src/ImagePRO_Python.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ImagePRO-Python
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: Professional & modular image processing library with OpenCV, MediaPipe, and YOLO integration.
|
|
5
5
|
Author-email: Parsa Safaie <parsasafaie.2568@proton.me>
|
|
6
6
|
License: MIT
|
|
@@ -27,4 +27,5 @@ Requires-Dist: opencv-python<5.0,>=4.8
|
|
|
27
27
|
Requires-Dist: ultralytics<9.0,>=8.2.0
|
|
28
28
|
Requires-Dist: mediapipe<1.0,>=0.10.0
|
|
29
29
|
Requires-Dist: insightface<1.0,>=0.7
|
|
30
|
+
Requires-Dist: onnxruntime<2.0,>=1.16
|
|
30
31
|
Dynamic: license-file
|
|
@@ -7,7 +7,7 @@ where = ["src"]
|
|
|
7
7
|
|
|
8
8
|
[project]
|
|
9
9
|
name = "ImagePRO-Python"
|
|
10
|
-
version = "0.
|
|
10
|
+
version = "1.0.1"
|
|
11
11
|
description = "Professional & modular image processing library with OpenCV, MediaPipe, and YOLO integration."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
requires-python = "==3.10.*"
|
|
@@ -33,8 +33,10 @@ dependencies = [
|
|
|
33
33
|
"ultralytics>=8.2.0,<9.0",
|
|
34
34
|
"mediapipe>=0.10.0,<1.0",
|
|
35
35
|
"insightface>=0.7,<1.0",
|
|
36
|
+
"onnxruntime>=1.16,<2.0",
|
|
36
37
|
]
|
|
37
38
|
|
|
39
|
+
|
|
38
40
|
[project.urls]
|
|
39
41
|
Homepage = "https://github.com/parsasafaie/ImagePRO"
|
|
40
42
|
Repository = "https://github.com/parsasafaie/ImagePRO.git"
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import cv2
|
|
4
|
+
import mediapipe as mp
|
|
5
|
+
|
|
6
|
+
from ImagePRO.utils.image import Image
|
|
7
|
+
from ImagePRO.utils.result import Result
|
|
8
|
+
|
|
9
|
+
# Constants
|
|
10
|
+
mp_pose = mp.solutions.pose
|
|
11
|
+
TOTAL_LANDMARKS = 33
|
|
12
|
+
DEFAULT_CONFIDENCE = 0.7
|
|
13
|
+
LANDMARK_RADIUS = 3
|
|
14
|
+
LANDMARK_COLOR = (0, 0, 255) # Red color for landmarks
|
|
15
|
+
|
|
16
|
+
def detect_body_pose(
|
|
17
|
+
image: Image,
|
|
18
|
+
*,
|
|
19
|
+
min_confidence: float = DEFAULT_CONFIDENCE,
|
|
20
|
+
landmarks_idx: list[int] | None = None,
|
|
21
|
+
pose_obj: mp.solutions.pose.Pose | None = None
|
|
22
|
+
) -> Result:
|
|
23
|
+
"""Detect body landmarks in an image using MediaPipe Pose.
|
|
24
|
+
|
|
25
|
+
Extracts 3D body landmark coordinates and optionally draws them.
|
|
26
|
+
Can focus on specific landmarks or detect all 33 points.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
image: Input image to process.
|
|
30
|
+
min_confidence: Detection confidence threshold.
|
|
31
|
+
Must be between 0 and 1.
|
|
32
|
+
Default: 0.7
|
|
33
|
+
landmarks_idx: Specific landmark indices to detect.
|
|
34
|
+
If None, detects all 33 landmarks.
|
|
35
|
+
Default: None
|
|
36
|
+
pose_obj: Pre-initialized pose detector.
|
|
37
|
+
If None, creates new instance.
|
|
38
|
+
Default: None
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Result object with detections and visualization:
|
|
42
|
+
- image: Input image with landmarks drawn
|
|
43
|
+
- data: List of [idx, x, y, z] coordinates
|
|
44
|
+
- meta: Operation info and parameters
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
TypeError: If image is not an Image instance
|
|
48
|
+
ValueError: If min_confidence not in [0,1]
|
|
49
|
+
TypeError: If landmarks_idx is not list[int]
|
|
50
|
+
"""
|
|
51
|
+
if not isinstance(image, Image):
|
|
52
|
+
raise TypeError("'image' must be an Image instance")
|
|
53
|
+
|
|
54
|
+
if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
|
|
55
|
+
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
56
|
+
|
|
57
|
+
if landmarks_idx is not None and (
|
|
58
|
+
not isinstance(landmarks_idx, list)
|
|
59
|
+
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
60
|
+
):
|
|
61
|
+
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
62
|
+
|
|
63
|
+
# Initialize detector if needed
|
|
64
|
+
if pose_obj is None:
|
|
65
|
+
pose_obj = mp_pose.Pose(
|
|
66
|
+
min_detection_confidence=min_confidence,
|
|
67
|
+
static_image_mode=True
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Use all landmarks if none specified
|
|
71
|
+
if landmarks_idx is None:
|
|
72
|
+
landmarks_idx = list(range(TOTAL_LANDMARKS))
|
|
73
|
+
|
|
74
|
+
# Prepare image
|
|
75
|
+
h, w = image.shape[:2]
|
|
76
|
+
img_copy = image._data.copy()
|
|
77
|
+
img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
|
|
78
|
+
|
|
79
|
+
# Detect pose
|
|
80
|
+
result = pose_obj.process(img_rgb)
|
|
81
|
+
landmarks = []
|
|
82
|
+
|
|
83
|
+
if result.pose_landmarks:
|
|
84
|
+
# Visualize landmarks
|
|
85
|
+
if len(landmarks_idx) == TOTAL_LANDMARKS:
|
|
86
|
+
# Draw full skeleton
|
|
87
|
+
mp.solutions.drawing_utils.draw_landmarks(
|
|
88
|
+
img_copy,
|
|
89
|
+
result.pose_landmarks,
|
|
90
|
+
mp_pose.POSE_CONNECTIONS,
|
|
91
|
+
landmark_drawing_spec=mp.solutions.drawing_styles.get_default_pose_landmarks_style()
|
|
92
|
+
)
|
|
93
|
+
else:
|
|
94
|
+
# Draw only selected points
|
|
95
|
+
for idx in landmarks_idx:
|
|
96
|
+
lm = result.pose_landmarks.landmark[idx]
|
|
97
|
+
x, y = int(w * lm.x), int(h * lm.y)
|
|
98
|
+
cv2.circle(img_copy, (x, y), LANDMARK_RADIUS, LANDMARK_COLOR, -1)
|
|
99
|
+
|
|
100
|
+
# Extract coordinates
|
|
101
|
+
for idx in landmarks_idx:
|
|
102
|
+
lm = result.pose_landmarks.landmark[idx]
|
|
103
|
+
landmarks.append([idx, lm.x, lm.y, lm.z])
|
|
104
|
+
|
|
105
|
+
return Result(
|
|
106
|
+
image=img_copy,
|
|
107
|
+
data=landmarks,
|
|
108
|
+
meta={
|
|
109
|
+
"source": image,
|
|
110
|
+
"operation": "detect_body_pose",
|
|
111
|
+
"min_confidence": min_confidence,
|
|
112
|
+
"landmarks_idx": landmarks_idx
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def detect_body_pose_live() -> None:
|
|
118
|
+
"""Start live webcam feed with real-time body pose detection.
|
|
119
|
+
|
|
120
|
+
Opens the default camera and shows pose landmarks in real-time.
|
|
121
|
+
Press ESC to exit the application.
|
|
122
|
+
|
|
123
|
+
Raises:
|
|
124
|
+
RuntimeError: If camera cannot be accessed
|
|
125
|
+
"""
|
|
126
|
+
# Initialize camera
|
|
127
|
+
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
128
|
+
if not cap.isOpened():
|
|
129
|
+
raise RuntimeError("Cannot access webcam")
|
|
130
|
+
|
|
131
|
+
# Initialize pose detector in tracking mode
|
|
132
|
+
pose_obj = mp_pose.Pose(
|
|
133
|
+
min_detection_confidence=DEFAULT_CONFIDENCE,
|
|
134
|
+
static_image_mode=False
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
try:
|
|
138
|
+
while True:
|
|
139
|
+
# Get frame
|
|
140
|
+
success, frame = cap.read()
|
|
141
|
+
if not success:
|
|
142
|
+
print("Failed to capture frame, skipping...")
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
# Process frame
|
|
146
|
+
try:
|
|
147
|
+
result = detect_body_pose(
|
|
148
|
+
image=Image.from_array(frame),
|
|
149
|
+
min_confidence=DEFAULT_CONFIDENCE,
|
|
150
|
+
pose_obj=pose_obj
|
|
151
|
+
)
|
|
152
|
+
display_frame = result.image
|
|
153
|
+
except (TypeError, ValueError):
|
|
154
|
+
# Fall back to raw frame if detection fails
|
|
155
|
+
display_frame = frame
|
|
156
|
+
|
|
157
|
+
# Show result
|
|
158
|
+
cv2.imshow('ImagePRO - Live Body Pose Detection', display_frame)
|
|
159
|
+
|
|
160
|
+
# Check for ESC key
|
|
161
|
+
if cv2.waitKey(5) & 0xFF == 27:
|
|
162
|
+
break
|
|
163
|
+
finally:
|
|
164
|
+
# Clean up resources
|
|
165
|
+
cap.release()
|
|
166
|
+
cv2.destroyAllWindows()
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
if __name__ == "__main__":
|
|
170
|
+
detect_body_pose_live()
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import cv2
|
|
4
|
+
import mediapipe as mp
|
|
5
|
+
|
|
6
|
+
from ImagePRO.utils.image import Image
|
|
7
|
+
from ImagePRO.utils.result import Result
|
|
8
|
+
|
|
9
|
+
# Constants
|
|
10
|
+
mp_hands = mp.solutions.hands
|
|
11
|
+
DEFAULT_MAX_HANDS = 2
|
|
12
|
+
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
13
|
+
TOTAL_HAND_LANDMARKS = 21
|
|
14
|
+
LANDMARK_RADIUS = 3
|
|
15
|
+
LANDMARK_COLOR = (0, 0, 255) # Red color for landmarks
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def detect_hands(
|
|
19
|
+
image: Image,
|
|
20
|
+
*,
|
|
21
|
+
max_hands: int = DEFAULT_MAX_HANDS,
|
|
22
|
+
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
23
|
+
landmarks_idx: list[int] | None = None,
|
|
24
|
+
hands_obj: mp.solutions.hands.Hands | None = None
|
|
25
|
+
) -> Result:
|
|
26
|
+
"""Detect hand landmarks in an image using MediaPipe Hands.
|
|
27
|
+
|
|
28
|
+
Identifies hand positions and extracts 3D coordinates of
|
|
29
|
+
key points (joints, fingertips, etc.).
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
image: Input image to process.
|
|
33
|
+
max_hands: Maximum number of hands to detect.
|
|
34
|
+
Must be positive.
|
|
35
|
+
Default: 2
|
|
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, detects all 21 points per hand.
|
|
41
|
+
Default: None
|
|
42
|
+
hands_obj: Pre-initialized hand 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
|
+
- data: List of [hand_id, point_id, x, y, z]
|
|
50
|
+
- meta: Operation info and parameters
|
|
51
|
+
|
|
52
|
+
Raises:
|
|
53
|
+
TypeError: If image is not an Image instance
|
|
54
|
+
ValueError: If max_hands is not positive
|
|
55
|
+
ValueError: If min_confidence not in [0,1]
|
|
56
|
+
TypeError: If landmarks_idx is not list[int]
|
|
57
|
+
"""
|
|
58
|
+
if not isinstance(image, Image):
|
|
59
|
+
raise TypeError("'image' must be an Image instance")
|
|
60
|
+
|
|
61
|
+
if not isinstance(max_hands, int) or max_hands <= 0:
|
|
62
|
+
raise ValueError("'max_hands' must be positive")
|
|
63
|
+
|
|
64
|
+
if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
|
|
65
|
+
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
66
|
+
|
|
67
|
+
if landmarks_idx is not None and (
|
|
68
|
+
not isinstance(landmarks_idx, list)
|
|
69
|
+
or not all(isinstance(i, int) for i in landmarks_idx)
|
|
70
|
+
):
|
|
71
|
+
raise TypeError("'landmarks_idx' must be a list of integers")
|
|
72
|
+
|
|
73
|
+
# Initialize detector if needed
|
|
74
|
+
if hands_obj is None:
|
|
75
|
+
hands_obj = mp_hands.Hands(
|
|
76
|
+
min_detection_confidence=min_confidence,
|
|
77
|
+
max_num_hands=max_hands,
|
|
78
|
+
static_image_mode=True
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
# Use all landmarks if none specified
|
|
82
|
+
if landmarks_idx is None:
|
|
83
|
+
landmarks_idx = list(range(TOTAL_HAND_LANDMARKS))
|
|
84
|
+
|
|
85
|
+
# Prepare image
|
|
86
|
+
img_copy = image._data.copy()
|
|
87
|
+
img_rgb = cv2.cvtColor(img_copy, cv2.COLOR_BGR2RGB)
|
|
88
|
+
|
|
89
|
+
# Detect hands
|
|
90
|
+
results = hands_obj.process(img_rgb)
|
|
91
|
+
landmarks = []
|
|
92
|
+
|
|
93
|
+
# Process detections
|
|
94
|
+
if results.multi_hand_landmarks:
|
|
95
|
+
for hand_id, hand_landmarks in enumerate(results.multi_hand_landmarks):
|
|
96
|
+
# Visualize landmarks
|
|
97
|
+
if len(landmarks_idx) == TOTAL_HAND_LANDMARKS:
|
|
98
|
+
# Draw full hand skeleton
|
|
99
|
+
mp.solutions.drawing_utils.draw_landmarks(
|
|
100
|
+
image=img_copy,
|
|
101
|
+
landmark_list=hand_landmarks,
|
|
102
|
+
connections=mp_hands.HAND_CONNECTIONS,
|
|
103
|
+
landmark_drawing_spec=mp.solutions.drawing_styles.get_default_hand_landmarks_style(),
|
|
104
|
+
connection_drawing_spec=mp.solutions.drawing_styles.get_default_hand_connections_style()
|
|
105
|
+
)
|
|
106
|
+
else:
|
|
107
|
+
# Draw only selected points
|
|
108
|
+
h, w, _ = img_copy.shape
|
|
109
|
+
for idx in landmarks_idx:
|
|
110
|
+
lm = hand_landmarks.landmark[idx]
|
|
111
|
+
x, y = int(w * lm.x), int(h * lm.y)
|
|
112
|
+
cv2.circle(img_copy, (x, y), LANDMARK_RADIUS, LANDMARK_COLOR, -1)
|
|
113
|
+
|
|
114
|
+
# Extract coordinates
|
|
115
|
+
for idx in landmarks_idx:
|
|
116
|
+
lm = hand_landmarks.landmark[idx]
|
|
117
|
+
landmarks.append([hand_id, idx, lm.x, lm.y, lm.z])
|
|
118
|
+
|
|
119
|
+
return Result(
|
|
120
|
+
image=img_copy,
|
|
121
|
+
data=landmarks,
|
|
122
|
+
meta={
|
|
123
|
+
"source": image,
|
|
124
|
+
"operation": "detect_hands",
|
|
125
|
+
"max_hands": max_hands,
|
|
126
|
+
"min_confidence": min_confidence,
|
|
127
|
+
"landmarks_idx": landmarks_idx
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def detect_hands_live(
|
|
133
|
+
max_hands: int = DEFAULT_MAX_HANDS,
|
|
134
|
+
min_confidence: float = DEFAULT_MIN_CONFIDENCE
|
|
135
|
+
) -> None:
|
|
136
|
+
"""Start live webcam feed with real-time hand detection.
|
|
137
|
+
|
|
138
|
+
Opens the default camera and shows hand landmarks in real-time.
|
|
139
|
+
Press ESC to exit.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
max_hands: Maximum number of hands to track.
|
|
143
|
+
Must be positive.
|
|
144
|
+
Default: 2
|
|
145
|
+
min_confidence: Detection confidence threshold.
|
|
146
|
+
Must be between 0 and 1.
|
|
147
|
+
Default: 0.7
|
|
148
|
+
|
|
149
|
+
Raises:
|
|
150
|
+
ValueError: If parameters are invalid
|
|
151
|
+
RuntimeError: If camera cannot be accessed
|
|
152
|
+
"""
|
|
153
|
+
# Validate parameters
|
|
154
|
+
if not isinstance(max_hands, int) or max_hands <= 0:
|
|
155
|
+
raise ValueError("'max_hands' must be positive")
|
|
156
|
+
|
|
157
|
+
if not isinstance(min_confidence, (int, float)) or not (0.0 <= min_confidence <= 1.0):
|
|
158
|
+
raise ValueError("'min_confidence' must be between 0.0 and 1.0")
|
|
159
|
+
|
|
160
|
+
# Initialize camera
|
|
161
|
+
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
162
|
+
if not cap.isOpened():
|
|
163
|
+
raise RuntimeError("Cannot access webcam")
|
|
164
|
+
|
|
165
|
+
# Initialize hand detector in tracking mode
|
|
166
|
+
hands_obj = mp_hands.Hands(
|
|
167
|
+
min_detection_confidence=min_confidence,
|
|
168
|
+
max_num_hands=max_hands,
|
|
169
|
+
static_image_mode=False
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
try:
|
|
173
|
+
while True:
|
|
174
|
+
# Get frame
|
|
175
|
+
success, frame = cap.read()
|
|
176
|
+
if not success:
|
|
177
|
+
print("Failed to capture frame, skipping...")
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
# Process frame
|
|
181
|
+
result = detect_hands(
|
|
182
|
+
image=Image.from_array(frame),
|
|
183
|
+
max_hands=max_hands,
|
|
184
|
+
min_confidence=min_confidence,
|
|
185
|
+
hands_obj=hands_obj
|
|
186
|
+
)
|
|
187
|
+
display_frame = result.image if result.image is not None else frame
|
|
188
|
+
|
|
189
|
+
# Show result
|
|
190
|
+
cv2.imshow('ImagePRO - Live Hand Detection', display_frame)
|
|
191
|
+
|
|
192
|
+
# Check for ESC key
|
|
193
|
+
if cv2.waitKey(5) & 0xFF == 27:
|
|
194
|
+
break
|
|
195
|
+
finally:
|
|
196
|
+
# Clean up resources
|
|
197
|
+
cap.release()
|
|
198
|
+
cv2.destroyAllWindows()
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
if __name__ == "__main__":
|
|
202
|
+
detect_hands_live()
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import cv2
|
|
4
|
+
import mediapipe as mp
|
|
5
|
+
|
|
6
|
+
from ImagePRO.human_analysis.face_analysis.face_mesh_analysis import analyze_face_mesh
|
|
7
|
+
from ImagePRO.utils.image import Image
|
|
8
|
+
from ImagePRO.utils.result import Result
|
|
9
|
+
|
|
10
|
+
# Constants
|
|
11
|
+
mp_face_mesh = mp.solutions.face_mesh
|
|
12
|
+
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
13
|
+
DEFAULT_THRESHOLD = 0.2
|
|
14
|
+
RIGHT_EYE_INDICES = [386, 374, 263, 362] # MediaPipe right eye landmarks
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def analyze_eye_status(
|
|
18
|
+
image: Image,
|
|
19
|
+
*,
|
|
20
|
+
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
21
|
+
threshold: float = DEFAULT_THRESHOLD,
|
|
22
|
+
face_mesh_obj: mp.solutions.face_mesh.FaceMesh | None = None,
|
|
23
|
+
) -> Result:
|
|
24
|
+
"""Analyze if the right eye is open using Eye Aspect Ratio (EAR).
|
|
25
|
+
|
|
26
|
+
Calculates eye openness by comparing vertical to horizontal distance
|
|
27
|
+
between key eye landmarks. Uses FaceMesh landmark detection.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
image: Input image to process.
|
|
31
|
+
min_confidence: Detection confidence threshold.
|
|
32
|
+
Must be between 0 and 1.
|
|
33
|
+
Default: 0.7
|
|
34
|
+
threshold: EAR threshold for open vs closed.
|
|
35
|
+
Eye considered open if EAR > threshold.
|
|
36
|
+
Default: 0.2
|
|
37
|
+
face_mesh_obj: Pre-initialized face mesh detector.
|
|
38
|
+
If None, creates new instance in static mode.
|
|
39
|
+
Default: None
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Result object with eye status:
|
|
43
|
+
- data: True if eye is open, False if closed
|
|
44
|
+
None if detection fails
|
|
45
|
+
- meta: Operation info and error details if failed
|
|
46
|
+
|
|
47
|
+
Raises:
|
|
48
|
+
TypeError: If image is not an Image instance
|
|
49
|
+
ValueError: If min_confidence not in [0,1]
|
|
50
|
+
"""
|
|
51
|
+
if not isinstance(image, Image):
|
|
52
|
+
raise TypeError("'image' must be an Image instance")
|
|
53
|
+
|
|
54
|
+
if not isinstance(min_confidence, (int, float)):
|
|
55
|
+
raise TypeError("'min_confidence' must be a number")
|
|
56
|
+
if not 0 <= min_confidence <= 1:
|
|
57
|
+
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
58
|
+
|
|
59
|
+
# Get image dimensions
|
|
60
|
+
h, w = image.shape[:2]
|
|
61
|
+
|
|
62
|
+
# Initialize detector if needed
|
|
63
|
+
if face_mesh_obj is None:
|
|
64
|
+
face_mesh_obj = mp_face_mesh.FaceMesh(
|
|
65
|
+
min_detection_confidence=min_confidence,
|
|
66
|
+
refine_landmarks=True,
|
|
67
|
+
static_image_mode=True
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
# Get face landmarks
|
|
71
|
+
mesh_result = analyze_face_mesh(
|
|
72
|
+
image=image,
|
|
73
|
+
max_faces=1,
|
|
74
|
+
min_confidence=min_confidence,
|
|
75
|
+
landmarks_idx=RIGHT_EYE_INDICES,
|
|
76
|
+
face_mesh_obj=face_mesh_obj
|
|
77
|
+
)
|
|
78
|
+
landmarks = mesh_result.data
|
|
79
|
+
|
|
80
|
+
# Handle no detections
|
|
81
|
+
if not landmarks:
|
|
82
|
+
return Result(
|
|
83
|
+
image=None,
|
|
84
|
+
data=None,
|
|
85
|
+
meta={
|
|
86
|
+
"source": image,
|
|
87
|
+
"operation": "analyze_eye_status",
|
|
88
|
+
"min_confidence": min_confidence,
|
|
89
|
+
"threshold": threshold,
|
|
90
|
+
"error": "No face landmarks detected"
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Map landmarks by index
|
|
95
|
+
eye_points = {lm[1]: lm for lm in landmarks[0]}
|
|
96
|
+
|
|
97
|
+
try:
|
|
98
|
+
# Extract key points (scale to image dimensions)
|
|
99
|
+
top_y = eye_points[386][3] * h # Top of eye
|
|
100
|
+
bottom_y = eye_points[374][3] * h # Bottom of eye
|
|
101
|
+
left_x = eye_points[263][2] * w # Left corner
|
|
102
|
+
right_x = eye_points[362][2] * w # Right corner
|
|
103
|
+
except KeyError as e:
|
|
104
|
+
return Result(
|
|
105
|
+
image=None,
|
|
106
|
+
data=None,
|
|
107
|
+
meta={
|
|
108
|
+
"source": image,
|
|
109
|
+
"operation": "analyze_eye_status",
|
|
110
|
+
"min_confidence": min_confidence,
|
|
111
|
+
"threshold": threshold,
|
|
112
|
+
"error": f"Missing landmark: {e}"
|
|
113
|
+
}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
# Calculate Eye Aspect Ratio (EAR)
|
|
117
|
+
vertical_dist = abs(bottom_y - top_y)
|
|
118
|
+
horizontal_dist = abs(right_x - left_x)
|
|
119
|
+
|
|
120
|
+
# Determine eye state
|
|
121
|
+
is_open = False
|
|
122
|
+
if horizontal_dist > 0: # Avoid division by zero
|
|
123
|
+
ear = vertical_dist / horizontal_dist
|
|
124
|
+
is_open = ear > threshold
|
|
125
|
+
|
|
126
|
+
return Result(
|
|
127
|
+
image=None,
|
|
128
|
+
data=is_open,
|
|
129
|
+
meta={
|
|
130
|
+
"source": image,
|
|
131
|
+
"operation": "analyze_eye_status",
|
|
132
|
+
"min_confidence": min_confidence,
|
|
133
|
+
"threshold": threshold
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def analyze_eye_status_live(
|
|
139
|
+
*,
|
|
140
|
+
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
141
|
+
threshold: float = DEFAULT_THRESHOLD
|
|
142
|
+
) -> None:
|
|
143
|
+
"""Run live eye status detection using webcam feed.
|
|
144
|
+
|
|
145
|
+
Opens a window displaying webcam feed with overlaid eye status.
|
|
146
|
+
Press ESC to exit.
|
|
147
|
+
|
|
148
|
+
Args:
|
|
149
|
+
min_confidence: Detection confidence threshold.
|
|
150
|
+
Must be between 0 and 1.
|
|
151
|
+
Default: 0.7
|
|
152
|
+
threshold: EAR threshold for open vs closed.
|
|
153
|
+
Eye considered open if EAR > threshold.
|
|
154
|
+
Default: 0.2
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
TypeError: If min_confidence is not a number
|
|
158
|
+
ValueError: If min_confidence not in [0,1]
|
|
159
|
+
RuntimeError: If webcam cannot be accessed
|
|
160
|
+
"""
|
|
161
|
+
# Validate inputs
|
|
162
|
+
if not isinstance(min_confidence, (int, float)):
|
|
163
|
+
raise TypeError("'min_confidence' must be a number")
|
|
164
|
+
if not 0 <= min_confidence <= 1:
|
|
165
|
+
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
166
|
+
|
|
167
|
+
# Initialize webcam
|
|
168
|
+
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
169
|
+
if not cap.isOpened():
|
|
170
|
+
raise RuntimeError("Failed to access webcam")
|
|
171
|
+
|
|
172
|
+
# Initialize face mesh detector for video
|
|
173
|
+
face_mesh = mp_face_mesh.FaceMesh(
|
|
174
|
+
max_num_faces=1,
|
|
175
|
+
min_detection_confidence=min_confidence,
|
|
176
|
+
refine_landmarks=True,
|
|
177
|
+
static_image_mode=False # Optimize for video
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
try:
|
|
181
|
+
while True:
|
|
182
|
+
# Read frame
|
|
183
|
+
ret, frame = cap.read()
|
|
184
|
+
if not ret:
|
|
185
|
+
print("Warning: Failed to read frame, skipping...")
|
|
186
|
+
continue
|
|
187
|
+
|
|
188
|
+
# Process frame
|
|
189
|
+
try:
|
|
190
|
+
img = Image.from_array(frame)
|
|
191
|
+
result = analyze_eye_status(
|
|
192
|
+
image=img,
|
|
193
|
+
min_confidence=min_confidence,
|
|
194
|
+
face_mesh_obj=face_mesh,
|
|
195
|
+
threshold=threshold
|
|
196
|
+
)
|
|
197
|
+
status = "Open" if result.data else "Closed"
|
|
198
|
+
except (TypeError, ValueError):
|
|
199
|
+
status = "No face"
|
|
200
|
+
|
|
201
|
+
# Draw results
|
|
202
|
+
color = (0, 255, 0) if status == "Open" else (0, 0, 255)
|
|
203
|
+
cv2.putText(
|
|
204
|
+
frame,
|
|
205
|
+
f"Eye: {status}",
|
|
206
|
+
(10, 40),
|
|
207
|
+
cv2.FONT_HERSHEY_SIMPLEX,
|
|
208
|
+
0.7,
|
|
209
|
+
color,
|
|
210
|
+
2
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
# Display frame
|
|
214
|
+
cv2.imshow("ImagePRO - Eye Status", frame)
|
|
215
|
+
|
|
216
|
+
# Check for ESC key
|
|
217
|
+
if cv2.waitKey(5) & 0xFF == 27:
|
|
218
|
+
break
|
|
219
|
+
|
|
220
|
+
finally:
|
|
221
|
+
cap.release()
|
|
222
|
+
cv2.destroyAllWindows()
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
if __name__ == "__main__":
|
|
226
|
+
analyze_eye_status_live()
|