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,21 +1,15 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
3
|
import random
|
|
4
|
+
import time
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
|
|
6
7
|
import cv2
|
|
7
8
|
import mediapipe as mp
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
from human_analysis.face_analysis.face_detection import detect_faces
|
|
14
|
-
from blur import apply_median_blur
|
|
15
|
-
from sharpen import apply_laplacian_sharpening
|
|
16
|
-
from rotate import rotate_image_custom
|
|
17
|
-
from grayscale import convert_to_grayscale
|
|
18
|
-
from resize import resize_image
|
|
10
|
+
from ImagePRO.human_analysis.face_analysis.face_detection import detect_faces
|
|
11
|
+
from ImagePRO.pre_processing import blur, grayscale, rotate, sharpen, resize
|
|
12
|
+
from ImagePRO.utils.image import Image
|
|
19
13
|
|
|
20
14
|
# Constants
|
|
21
15
|
DEFAULT_NUM_IMAGES = 200
|
|
@@ -29,6 +23,7 @@ DEFAULT_FACE_ID = "unknown"
|
|
|
29
23
|
def capture_bulk_pictures(
|
|
30
24
|
folder_path: str | Path,
|
|
31
25
|
face_id: str | int = DEFAULT_FACE_ID,
|
|
26
|
+
*,
|
|
32
27
|
num_images: int = DEFAULT_NUM_IMAGES,
|
|
33
28
|
start_index: int = DEFAULT_START_INDEX,
|
|
34
29
|
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
@@ -38,133 +33,150 @@ def capture_bulk_pictures(
|
|
|
38
33
|
apply_sharpen: bool = False,
|
|
39
34
|
apply_rotate: bool = False,
|
|
40
35
|
apply_resize: tuple = False,
|
|
41
|
-
delay: float = DEFAULT_DELAY
|
|
36
|
+
delay: float = DEFAULT_DELAY
|
|
42
37
|
) -> None:
|
|
43
|
-
"""
|
|
44
|
-
Capture frames from webcam and save cropped face images, with optional preprocessing.
|
|
38
|
+
"""Generate a dataset by capturing faces from webcam.
|
|
45
39
|
|
|
46
|
-
|
|
40
|
+
Captures frames and saves preprocessed face images.
|
|
41
|
+
Processing pipeline (if enabled):
|
|
47
42
|
median blur → laplacian sharpen → grayscale → resize → random rotate
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
If arguments are invalid.
|
|
80
|
-
FileExistsError
|
|
81
|
-
If the destination folder already exists.
|
|
82
|
-
RuntimeError
|
|
83
|
-
If the webcam cannot be opened.
|
|
44
|
+
Args:
|
|
45
|
+
folder_path: Base directory for dataset.
|
|
46
|
+
face_id: Subject identifier, creates folder "<base_dir>/<face_id>".
|
|
47
|
+
Default: "unknown"
|
|
48
|
+
num_images: Number of images to capture.
|
|
49
|
+
Default: 200
|
|
50
|
+
start_index: Starting number for filenames.
|
|
51
|
+
Default: 0
|
|
52
|
+
min_confidence: Face detection confidence threshold.
|
|
53
|
+
Default: 0.7
|
|
54
|
+
camera_index: OpenCV camera device index.
|
|
55
|
+
Default: 0
|
|
56
|
+
apply_blur: Apply median blur (size=3).
|
|
57
|
+
Default: False
|
|
58
|
+
apply_grayscale: Convert to single-channel.
|
|
59
|
+
Default: False
|
|
60
|
+
apply_sharpen: Apply Laplacian (coef=1.0).
|
|
61
|
+
Default: False
|
|
62
|
+
apply_rotate: Random rotation [-45°,45°].
|
|
63
|
+
Default: False
|
|
64
|
+
apply_resize: Optional (width,height).
|
|
65
|
+
Default: False (no resize)
|
|
66
|
+
delay: Time between captures (seconds).
|
|
67
|
+
Default: 0.1
|
|
68
|
+
|
|
69
|
+
Raises:
|
|
70
|
+
TypeError: If input types are invalid
|
|
71
|
+
ValueError: If numeric values are out of range
|
|
72
|
+
FileExistsError: If output folder exists
|
|
73
|
+
RuntimeError: If camera access fails
|
|
84
74
|
"""
|
|
75
|
+
# Validate numeric parameters
|
|
85
76
|
if not isinstance(num_images, int) or num_images <= 0:
|
|
86
|
-
raise ValueError("'num_images' must be a positive integer
|
|
77
|
+
raise ValueError("'num_images' must be a positive integer")
|
|
78
|
+
|
|
87
79
|
if not isinstance(start_index, int) or start_index < 0:
|
|
88
|
-
raise ValueError("'start_index' must be a non-negative integer
|
|
80
|
+
raise ValueError("'start_index' must be a non-negative integer")
|
|
81
|
+
|
|
89
82
|
if not isinstance(min_confidence, (int, float)) or not (0 <= min_confidence <= 1):
|
|
90
|
-
raise ValueError("'min_confidence' must be between 0 and 1
|
|
83
|
+
raise ValueError("'min_confidence' must be between 0 and 1")
|
|
84
|
+
|
|
91
85
|
if not isinstance(delay, (int, float)) or delay < 0:
|
|
92
|
-
raise ValueError("'delay' must be a non-negative number
|
|
86
|
+
raise ValueError("'delay' must be a non-negative number")
|
|
93
87
|
|
|
88
|
+
# Setup output directory
|
|
94
89
|
base_dir = Path(folder_path)
|
|
95
90
|
face_folder = base_dir / str(face_id)
|
|
96
91
|
|
|
97
|
-
# Create destination folder (fail if exists to avoid accidental overwrite)
|
|
98
92
|
try:
|
|
99
93
|
face_folder.mkdir(parents=True, exist_ok=False)
|
|
100
94
|
except FileExistsError as e:
|
|
101
|
-
raise FileExistsError(f"
|
|
95
|
+
raise FileExistsError(f"Output folder already exists: {face_folder}") from e
|
|
102
96
|
|
|
103
|
-
#
|
|
97
|
+
# Initialize camera and face detector
|
|
104
98
|
cap = cv2.VideoCapture(camera_index)
|
|
105
99
|
if not cap.isOpened():
|
|
106
|
-
raise RuntimeError(f"
|
|
100
|
+
raise RuntimeError(f"Cannot access camera (index={camera_index})")
|
|
107
101
|
|
|
108
|
-
# Initialize FaceMesh for live stream (tracking mode)
|
|
109
102
|
face_mesh = mp.solutions.face_mesh.FaceMesh(
|
|
110
103
|
max_num_faces=1,
|
|
111
104
|
min_detection_confidence=min_confidence,
|
|
112
105
|
refine_landmarks=True,
|
|
113
|
-
static_image_mode=False
|
|
106
|
+
static_image_mode=False
|
|
114
107
|
)
|
|
115
108
|
|
|
116
|
-
|
|
109
|
+
saved_count = 0
|
|
117
110
|
try:
|
|
118
|
-
while
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
111
|
+
while saved_count < num_images:
|
|
112
|
+
# Capture frame
|
|
113
|
+
success, frame = cap.read()
|
|
114
|
+
if not success:
|
|
115
|
+
print("Failed to capture frame, skipping...")
|
|
122
116
|
continue
|
|
123
117
|
|
|
124
|
-
#
|
|
125
|
-
|
|
118
|
+
# Apply preprocessing pipeline
|
|
119
|
+
processed = frame
|
|
126
120
|
|
|
127
121
|
if apply_blur:
|
|
128
|
-
#
|
|
129
|
-
|
|
122
|
+
# Reduce noise while keeping facial features
|
|
123
|
+
processed = blur.apply_average_blur(
|
|
124
|
+
image=Image.from_array(processed),
|
|
125
|
+
filter_size=3
|
|
126
|
+
).image
|
|
130
127
|
|
|
131
128
|
if apply_sharpen:
|
|
132
|
-
#
|
|
133
|
-
|
|
129
|
+
# Enhance edges with gentle sharpening
|
|
130
|
+
processed = sharpen.apply_laplacian_sharpening(
|
|
131
|
+
image=Image.from_array(processed),
|
|
132
|
+
coefficient=1.0
|
|
133
|
+
).image
|
|
134
134
|
|
|
135
135
|
if apply_grayscale:
|
|
136
|
-
|
|
136
|
+
processed = grayscale.convert_to_grayscale(
|
|
137
|
+
image=Image.from_array(processed)
|
|
138
|
+
).image
|
|
137
139
|
|
|
138
140
|
if apply_resize is not False:
|
|
139
|
-
|
|
141
|
+
processed = resize.resize_image(
|
|
142
|
+
image=Image.from_array(processed),
|
|
143
|
+
new_size=apply_resize
|
|
144
|
+
).image
|
|
140
145
|
|
|
141
146
|
if apply_rotate:
|
|
147
|
+
# Apply random rotation with scaling
|
|
142
148
|
angle = float(random.randint(-45, 45))
|
|
143
149
|
scale = random.choice([1.0, 1.1, 1.2, 1.3])
|
|
144
|
-
|
|
150
|
+
processed = rotate.rotate_image_custom(
|
|
151
|
+
image=Image.from_array(processed),
|
|
152
|
+
angle=angle,
|
|
153
|
+
scale=scale
|
|
154
|
+
).image
|
|
145
155
|
|
|
146
|
-
#
|
|
147
|
-
filename = f"{start_index +
|
|
148
|
-
|
|
156
|
+
# Save processed face image
|
|
157
|
+
filename = f"{start_index + saved_count:04d}.jpg"
|
|
158
|
+
output_path = face_folder / filename
|
|
149
159
|
|
|
150
160
|
try:
|
|
151
|
-
|
|
152
|
-
|
|
161
|
+
result = detect_faces(
|
|
162
|
+
image=Image.from_array(processed),
|
|
153
163
|
max_faces=1,
|
|
154
164
|
min_confidence=min_confidence,
|
|
155
|
-
|
|
156
|
-
output_image_path=str(out_path),
|
|
157
|
-
face_mesh_obj=face_mesh,
|
|
165
|
+
face_mesh_obj=face_mesh
|
|
158
166
|
)
|
|
159
|
-
|
|
167
|
+
|
|
168
|
+
result.save_as_img(str(output_path))
|
|
169
|
+
saved_count += 1
|
|
160
170
|
|
|
161
171
|
if delay > 0:
|
|
162
172
|
time.sleep(delay)
|
|
163
173
|
|
|
164
174
|
except ValueError:
|
|
165
|
-
#
|
|
175
|
+
# Skip frames with no detected faces
|
|
166
176
|
continue
|
|
177
|
+
|
|
167
178
|
finally:
|
|
179
|
+
# Clean up resources
|
|
168
180
|
cap.release()
|
|
169
181
|
cv2.destroyAllWindows()
|
|
170
182
|
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
import cv2
|
|
4
|
-
import numpy as np
|
|
1
|
+
from __future__ import annotations
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
parent_dir = Path(__file__).resolve().parent.parent
|
|
8
|
-
sys.path.append(str(parent_dir))
|
|
3
|
+
import cv2
|
|
9
4
|
|
|
10
|
-
from utils.
|
|
5
|
+
from ImagePRO.utils.image import Image
|
|
6
|
+
from ImagePRO.utils.result import Result
|
|
11
7
|
|
|
12
8
|
|
|
13
|
-
def convert_to_grayscale(
|
|
14
|
-
src_image_path: str | None = None,
|
|
15
|
-
src_np_image=None,
|
|
16
|
-
output_image_path: str | None = None
|
|
17
|
-
) -> np.ndarray:
|
|
9
|
+
def convert_to_grayscale(image: Image) -> Result:
|
|
18
10
|
"""
|
|
19
11
|
Convert an image to grayscale.
|
|
20
12
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
Raises
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
If input types are invalid.
|
|
39
|
-
FileNotFoundError
|
|
40
|
-
If image path does not exist.
|
|
41
|
-
IOError
|
|
42
|
-
If saving the image fails.
|
|
13
|
+
Converts BGR or RGB image to single-channel grayscale using OpenCV.
|
|
14
|
+
For BGR images (default), uses standard ITU-R BT.601 conversion.
|
|
15
|
+
For RGB images, automatically handles colorspace conversion.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
image (Image):
|
|
19
|
+
Input image to convert. Must be BGR or RGB format.
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
Result: Result object with converted grayscale image.
|
|
23
|
+
- image (np.ndarray): Single-channel grayscale image
|
|
24
|
+
- data (None): No additional data
|
|
25
|
+
- meta (dict): Contains source object and operation info
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
TypeError: If image is not an Image instance
|
|
29
|
+
ValueError: If image colorspace is already GRAY
|
|
43
30
|
"""
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
if
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
31
|
+
if not isinstance(image, Image):
|
|
32
|
+
raise TypeError("'image' must be an Image instance.")
|
|
33
|
+
|
|
34
|
+
if image.colorspace == "GRAY":
|
|
35
|
+
raise ValueError("Image is already in grayscale format.")
|
|
36
|
+
|
|
37
|
+
# Convert based on source colorspace
|
|
38
|
+
if image.colorspace == "RGB":
|
|
39
|
+
grayscale = cv2.cvtColor(image._data, cv2.COLOR_RGB2GRAY)
|
|
40
|
+
else: # BGR is default
|
|
41
|
+
grayscale = cv2.cvtColor(image._data, cv2.COLOR_BGR2GRAY)
|
|
42
|
+
|
|
43
|
+
return Result(
|
|
44
|
+
image=grayscale,
|
|
45
|
+
data=None,
|
|
46
|
+
meta={
|
|
47
|
+
"source": image,
|
|
48
|
+
"operation": "convert_to_grayscale"
|
|
49
|
+
}
|
|
50
|
+
)
|
|
@@ -1,62 +1,54 @@
|
|
|
1
|
-
import
|
|
2
|
-
from pathlib import Path
|
|
3
|
-
import cv2
|
|
4
|
-
import numpy as np
|
|
1
|
+
from __future__ import annotations
|
|
5
2
|
|
|
6
|
-
|
|
7
|
-
parent_dir = Path(__file__).resolve().parent.parent
|
|
8
|
-
sys.path.append(str(parent_dir))
|
|
3
|
+
import cv2
|
|
9
4
|
|
|
10
|
-
from utils.
|
|
5
|
+
from ImagePRO.utils.image import Image
|
|
6
|
+
from ImagePRO.utils.result import Result
|
|
11
7
|
|
|
12
8
|
|
|
13
9
|
def resize_image(
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
Raises
|
|
39
|
-
------
|
|
40
|
-
ValueError
|
|
41
|
-
If new_size is invalid.
|
|
42
|
-
TypeError
|
|
43
|
-
If input types are invalid.
|
|
44
|
-
FileNotFoundError
|
|
45
|
-
If image path does not exist.
|
|
46
|
-
IOError
|
|
47
|
-
If saving the image fails.
|
|
10
|
+
image: Image,
|
|
11
|
+
*,
|
|
12
|
+
new_size: tuple[int, int]
|
|
13
|
+
) -> Result:
|
|
14
|
+
"""Resize an image to specified dimensions.
|
|
15
|
+
|
|
16
|
+
Changes image size while maintaining aspect ratio.
|
|
17
|
+
Uses bilinear interpolation for smooth resizing.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
image: Input image to resize.
|
|
21
|
+
new_size: Target size as (width, height) in pixels.
|
|
22
|
+
Both dimensions must be positive integers.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Result object with resized image and metadata:
|
|
26
|
+
- image: Resized image array
|
|
27
|
+
- data: None
|
|
28
|
+
- meta: Operation info and new size used
|
|
29
|
+
|
|
30
|
+
Raises:
|
|
31
|
+
TypeError: If image is not an Image instance
|
|
32
|
+
ValueError: If new_size is not valid (tuple of 2 positive ints)
|
|
48
33
|
"""
|
|
34
|
+
if not isinstance(image, Image):
|
|
35
|
+
raise TypeError("'image' must be an Image instance")
|
|
36
|
+
|
|
49
37
|
if (
|
|
50
38
|
not isinstance(new_size, tuple)
|
|
51
39
|
or len(new_size) != 2
|
|
52
40
|
or not all(isinstance(dim, int) and dim > 0 for dim in new_size)
|
|
53
41
|
):
|
|
54
|
-
raise ValueError("'new_size' must be a tuple of two positive integers
|
|
42
|
+
raise ValueError("'new_size' must be a tuple of two positive integers")
|
|
55
43
|
|
|
56
|
-
|
|
57
|
-
resized = cv2.resize(
|
|
58
|
-
|
|
59
|
-
if output_image_path:
|
|
60
|
-
print(IOHandler.save_image(resized, output_image_path))
|
|
44
|
+
# Resize using bilinear interpolation
|
|
45
|
+
resized = cv2.resize(image._data, new_size, interpolation=cv2.INTER_LINEAR)
|
|
61
46
|
|
|
62
|
-
return
|
|
47
|
+
return Result(
|
|
48
|
+
image=resized,
|
|
49
|
+
meta={
|
|
50
|
+
"source": image,
|
|
51
|
+
"operation": "resize_image",
|
|
52
|
+
"new_size": new_size
|
|
53
|
+
}
|
|
54
|
+
)
|