ImagePRO-Python 0.5.2__py3-none-any.whl → 1.0.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 +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/__init__.py +3 -2
- ImagePRO/utils/image.py +118 -0
- ImagePRO/utils/result.py +146 -0
- {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.1.dist-info}/METADATA +2 -1
- imagepro_python-1.0.1.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.1.dist-info}/WHEEL +0 -0
- {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.1.dist-info}/licenses/LICENSE +0 -0
- {imagepro_python-0.5.2.dist-info → imagepro_python-1.0.1.dist-info}/top_level.txt +0 -0
|
@@ -1,182 +1,222 @@
|
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
from utils.io_handler import IOHandler
|
|
12
|
-
from human_analysis.face_analysis.face_mesh_analysis import analyze_face_mesh
|
|
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
|
|
13
9
|
|
|
14
10
|
# Constants
|
|
15
11
|
mp_face_mesh = mp.solutions.face_mesh
|
|
16
12
|
DEFAULT_MIN_CONFIDENCE = 0.7
|
|
17
13
|
DEFAULT_THRESHOLD = 0.2
|
|
18
|
-
RIGHT_EYE_INDICES = [386, 374, 263, 362] # MediaPipe
|
|
14
|
+
RIGHT_EYE_INDICES = [386, 374, 263, 362] # MediaPipe right eye landmarks
|
|
19
15
|
|
|
20
16
|
|
|
21
17
|
def analyze_eye_status(
|
|
18
|
+
image: Image,
|
|
19
|
+
*,
|
|
22
20
|
min_confidence: float = DEFAULT_MIN_CONFIDENCE,
|
|
23
|
-
src_image_path: str | None = None,
|
|
24
|
-
src_np_image=None,
|
|
25
|
-
face_mesh_obj=None,
|
|
26
21
|
threshold: float = DEFAULT_THRESHOLD,
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
Analyze right
|
|
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
|
-
From ``IOHandler.load_image`` on invalid argument types.
|
|
56
|
-
FileNotFoundError
|
|
57
|
-
From ``IOHandler.load_image`` when ``src_image_path`` does not exist.
|
|
58
|
-
|
|
59
|
-
Notes
|
|
60
|
-
-----
|
|
61
|
-
* Uses MediaPipe indices for the right eye:
|
|
62
|
-
386 (upper lid), 374 (lower lid), 263 (outer corner), 362 (inner corner).
|
|
63
|
-
* EAR is computed as ``vertical / horizontal`` on pixel coordinates.
|
|
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]
|
|
64
50
|
"""
|
|
65
|
-
if not isinstance(
|
|
66
|
-
raise
|
|
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")
|
|
67
58
|
|
|
68
|
-
|
|
69
|
-
h, w =
|
|
59
|
+
# Get image dimensions
|
|
60
|
+
h, w = image.shape[:2]
|
|
70
61
|
|
|
62
|
+
# Initialize detector if needed
|
|
71
63
|
if face_mesh_obj is None:
|
|
72
64
|
face_mesh_obj = mp_face_mesh.FaceMesh(
|
|
73
65
|
min_detection_confidence=min_confidence,
|
|
74
66
|
refine_landmarks=True,
|
|
75
|
-
static_image_mode=True
|
|
67
|
+
static_image_mode=True
|
|
76
68
|
)
|
|
77
69
|
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
_, landmarks = analyze_face_mesh(
|
|
70
|
+
# Get face landmarks
|
|
71
|
+
mesh_result = analyze_face_mesh(
|
|
72
|
+
image=image,
|
|
82
73
|
max_faces=1,
|
|
83
74
|
min_confidence=min_confidence,
|
|
84
|
-
landmarks_idx=
|
|
85
|
-
|
|
86
|
-
face_mesh_obj=face_mesh_obj,
|
|
75
|
+
landmarks_idx=RIGHT_EYE_INDICES,
|
|
76
|
+
face_mesh_obj=face_mesh_obj
|
|
87
77
|
)
|
|
78
|
+
landmarks = mesh_result.data
|
|
88
79
|
|
|
80
|
+
# Handle no detections
|
|
89
81
|
if not landmarks:
|
|
90
|
-
|
|
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
|
+
)
|
|
91
93
|
|
|
94
|
+
# Map landmarks by index
|
|
92
95
|
eye_points = {lm[1]: lm for lm in landmarks[0]}
|
|
93
96
|
|
|
94
97
|
try:
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
|
99
103
|
except KeyError as e:
|
|
100
|
-
|
|
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
|
+
)
|
|
101
115
|
|
|
116
|
+
# Calculate Eye Aspect Ratio (EAR)
|
|
102
117
|
vertical_dist = abs(bottom_y - top_y)
|
|
103
118
|
horizontal_dist = abs(right_x - left_x)
|
|
104
119
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
+
)
|
|
110
136
|
|
|
111
137
|
|
|
112
|
-
def analyze_eye_status_live(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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
|
|
134
160
|
"""
|
|
135
|
-
|
|
136
|
-
|
|
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")
|
|
137
166
|
|
|
167
|
+
# Initialize webcam
|
|
138
168
|
cap = cv2.VideoCapture(0, cv2.CAP_DSHOW)
|
|
139
169
|
if not cap.isOpened():
|
|
140
|
-
raise RuntimeError("
|
|
170
|
+
raise RuntimeError("Failed to access webcam")
|
|
141
171
|
|
|
172
|
+
# Initialize face mesh detector for video
|
|
142
173
|
face_mesh = mp_face_mesh.FaceMesh(
|
|
143
174
|
max_num_faces=1,
|
|
144
175
|
min_detection_confidence=min_confidence,
|
|
145
176
|
refine_landmarks=True,
|
|
146
|
-
static_image_mode=False
|
|
177
|
+
static_image_mode=False # Optimize for video
|
|
147
178
|
)
|
|
148
179
|
|
|
149
180
|
try:
|
|
150
181
|
while True:
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
182
|
+
# Read frame
|
|
183
|
+
ret, frame = cap.read()
|
|
184
|
+
if not ret:
|
|
185
|
+
print("Warning: Failed to read frame, skipping...")
|
|
154
186
|
continue
|
|
155
187
|
|
|
188
|
+
# Process frame
|
|
156
189
|
try:
|
|
157
|
-
|
|
190
|
+
img = Image.from_array(frame)
|
|
191
|
+
result = analyze_eye_status(
|
|
192
|
+
image=img,
|
|
158
193
|
min_confidence=min_confidence,
|
|
159
|
-
src_np_image=frame,
|
|
160
194
|
face_mesh_obj=face_mesh,
|
|
161
|
-
threshold=threshold
|
|
195
|
+
threshold=threshold
|
|
162
196
|
)
|
|
163
|
-
status = "Open" if
|
|
164
|
-
except ValueError:
|
|
197
|
+
status = "Open" if result.data else "Closed"
|
|
198
|
+
except (TypeError, ValueError):
|
|
165
199
|
status = "No face"
|
|
166
200
|
|
|
201
|
+
# Draw results
|
|
202
|
+
color = (0, 255, 0) if status == "Open" else (0, 0, 255)
|
|
167
203
|
cv2.putText(
|
|
168
204
|
frame,
|
|
169
205
|
f"Eye: {status}",
|
|
170
206
|
(10, 40),
|
|
171
207
|
cv2.FONT_HERSHEY_SIMPLEX,
|
|
172
208
|
0.7,
|
|
173
|
-
|
|
174
|
-
2
|
|
209
|
+
color,
|
|
210
|
+
2
|
|
175
211
|
)
|
|
176
212
|
|
|
177
|
-
|
|
213
|
+
# Display frame
|
|
214
|
+
cv2.imshow("ImagePRO - Eye Status", frame)
|
|
215
|
+
|
|
216
|
+
# Check for ESC key
|
|
178
217
|
if cv2.waitKey(5) & 0xFF == 27:
|
|
179
218
|
break
|
|
219
|
+
|
|
180
220
|
finally:
|
|
181
221
|
cap.release()
|
|
182
222
|
cv2.destroyAllWindows()
|
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
|
|
2
5
|
import cv2
|
|
3
6
|
import numpy as np
|
|
7
|
+
from insightface.app import FaceAnalysis
|
|
8
|
+
|
|
9
|
+
from ImagePRO.utils.image import Image
|
|
10
|
+
from ImagePRO.utils.result import Result
|
|
4
11
|
|
|
5
12
|
# Constants
|
|
6
13
|
DEFAULT_SIMILARITY_THRESHOLD = 0.5
|
|
@@ -8,59 +15,115 @@ DEFAULT_MODEL_NAME = "buffalo_l"
|
|
|
8
15
|
DEFAULT_PROVIDER = "CPUExecutionProvider"
|
|
9
16
|
|
|
10
17
|
|
|
11
|
-
def compare_faces(
|
|
12
|
-
|
|
13
|
-
|
|
18
|
+
def compare_faces(
|
|
19
|
+
image_1: Image,
|
|
20
|
+
image_2: Image,
|
|
21
|
+
*,
|
|
22
|
+
app: FaceAnalysis | None = None
|
|
23
|
+
) -> Result:
|
|
24
|
+
"""Compare two face images to determine if they are the same person.
|
|
14
25
|
|
|
15
|
-
|
|
16
|
-
|
|
26
|
+
Uses InsightFace's FaceAnalysis model to extract facial embeddings
|
|
27
|
+
and compares them using cosine similarity.
|
|
17
28
|
|
|
18
29
|
Args:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
30
|
+
image_1: First image to compare.
|
|
31
|
+
Must contain a clearly visible face.
|
|
32
|
+
image_2: Second image to compare.
|
|
33
|
+
Must contain a clearly visible face.
|
|
34
|
+
app: Pre-initialized FaceAnalysis model.
|
|
35
|
+
If None, creates new instance.
|
|
36
|
+
Default: None
|
|
23
37
|
|
|
24
38
|
Returns:
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
39
|
+
Result object with comparison results:
|
|
40
|
+
- image: None
|
|
41
|
+
- data: True if same person, False if different
|
|
42
|
+
None if face detection fails
|
|
43
|
+
- meta: Operation info and similarity score
|
|
44
|
+
Error details if detection fails
|
|
45
|
+
|
|
46
|
+
Raises:
|
|
47
|
+
TypeError: If either image is not an Image instance
|
|
48
|
+
FileNotFoundError: If image data cannot be loaded
|
|
29
49
|
"""
|
|
30
|
-
#
|
|
50
|
+
# Validate inputs
|
|
51
|
+
if not isinstance(image_1, Image):
|
|
52
|
+
raise TypeError("'image_1' must be an Image instance")
|
|
53
|
+
if not isinstance(image_2, Image):
|
|
54
|
+
raise TypeError("'image_2' must be an Image instance")
|
|
55
|
+
|
|
56
|
+
# Initialize model if needed
|
|
31
57
|
if app is None:
|
|
32
58
|
app = FaceAnalysis(
|
|
33
59
|
name=DEFAULT_MODEL_NAME,
|
|
34
|
-
providers=[DEFAULT_PROVIDER]
|
|
60
|
+
providers=[DEFAULT_PROVIDER]
|
|
35
61
|
)
|
|
36
|
-
app.prepare(ctx_id=0) #
|
|
62
|
+
app.prepare(ctx_id=0) # Use CPU
|
|
63
|
+
|
|
64
|
+
def save_temp_image(image: Image, temp_name: str) -> str:
|
|
65
|
+
"""Save image data to temporary file if needed."""
|
|
66
|
+
if image.source_type == 'path':
|
|
67
|
+
return image.path
|
|
68
|
+
temp_path = os.path.join(os.getcwd(), temp_name)
|
|
69
|
+
cv2.imwrite(temp_path, image._data)
|
|
70
|
+
return temp_path
|
|
37
71
|
|
|
38
|
-
|
|
39
|
-
|
|
72
|
+
def load_rgb_image(path: str) -> np.ndarray | None:
|
|
73
|
+
"""Load image file and convert to RGB."""
|
|
40
74
|
img = cv2.imread(str(path))
|
|
41
75
|
return cv2.cvtColor(img, cv2.COLOR_BGR2RGB) if img is not None else None
|
|
42
76
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
77
|
+
try:
|
|
78
|
+
# Save images to disk if needed
|
|
79
|
+
path_1 = save_temp_image(image_1._data, 'tmp1.jpg')
|
|
80
|
+
path_2 = save_temp_image(image_2._data, 'tmp2.jpg')
|
|
46
81
|
|
|
47
|
-
|
|
48
|
-
|
|
82
|
+
# Load and preprocess images
|
|
83
|
+
img1 = load_rgb_image(path_1)
|
|
84
|
+
img2 = load_rgb_image(path_2)
|
|
49
85
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
faces2 = app.get(img2)
|
|
86
|
+
if img1 is None or img2 is None:
|
|
87
|
+
raise FileNotFoundError("Failed to load one or both images")
|
|
53
88
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
89
|
+
# Detect faces
|
|
90
|
+
faces1 = app.get(img1)
|
|
91
|
+
faces2 = app.get(img2)
|
|
57
92
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
93
|
+
# Validate detections
|
|
94
|
+
if not faces1 or not faces2:
|
|
95
|
+
return Result(
|
|
96
|
+
image=None,
|
|
97
|
+
data=None,
|
|
98
|
+
meta={
|
|
99
|
+
"source": (image_1, image_2),
|
|
100
|
+
"operation": "compare_faces",
|
|
101
|
+
"error": "No face detected in one or both images"
|
|
102
|
+
}
|
|
103
|
+
)
|
|
61
104
|
|
|
62
|
-
|
|
63
|
-
|
|
105
|
+
# Extract embeddings
|
|
106
|
+
emb1 = faces1[0].embedding
|
|
107
|
+
emb2 = faces2[0].embedding
|
|
108
|
+
|
|
109
|
+
# Calculate similarity
|
|
110
|
+
similarity = np.dot(emb1, emb2) / (np.linalg.norm(emb1) * np.linalg.norm(emb2))
|
|
111
|
+
is_match = similarity > DEFAULT_SIMILARITY_THRESHOLD
|
|
112
|
+
|
|
113
|
+
return Result(
|
|
114
|
+
image=None,
|
|
115
|
+
data=is_match,
|
|
116
|
+
meta={
|
|
117
|
+
"source": (image_1, image_2),
|
|
118
|
+
"operation": "compare_faces",
|
|
119
|
+
"similarity": float(similarity),
|
|
120
|
+
"threshold": DEFAULT_SIMILARITY_THRESHOLD
|
|
121
|
+
}
|
|
122
|
+
)
|
|
64
123
|
|
|
65
|
-
|
|
66
|
-
|
|
124
|
+
finally:
|
|
125
|
+
# Cleanup temporary files
|
|
126
|
+
if image_1.source_type != 'path' and os.path.exists('tmp1.jpg'):
|
|
127
|
+
os.remove('tmp1.jpg')
|
|
128
|
+
if image_2.source_type != 'path' and os.path.exists('tmp2.jpg'):
|
|
129
|
+
os.remove('tmp2.jpg')
|