-
Notifications
You must be signed in to change notification settings - Fork 3
/
FacialMeasurementsforGlassesFitting.py
238 lines (195 loc) · 10.8 KB
/
FacialMeasurementsforGlassesFitting.py
1
2
3
4
5
6
7
8
9
10
11
12
13
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
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
import cv2
import mediapipe as mp
import numpy as np
from sklearn.linear_model import LinearRegression
# Initialize Mediapipe Face Mesh and drawing utilities
mp_face_mesh = mp.solutions.face_mesh
mp_drawing = mp.solutions.drawing_utils
mp_drawing_styles = mp.solutions.drawing_styles
# Initialize webcam
cap = cv2.VideoCapture(0)
# Prepare the updated data
bounding_boxes = np.array([
[135, 114],
[200, 185],
[113, 104],
[185, 154],
[360, 270],
[280, 220],
[164, 145],
[245, 215],
[430, 330],
[135, 110],
[104, 90],
[95, 83],
[88, 75],
[78, 68],
[193, 160],
[297, 232]
])
distances = np.array([58, 36, 70, 45, 20, 28, 50, 30, 17, 60, 80, 90, 100, 110, 40, 25])
# Fit the linear regression model
model = LinearRegression()
model.fit(bounding_boxes, distances)
# Average face dimensions in mm
MALE_FACE_WIDTH = 147.6
FEMALE_FACE_WIDTH = 140.1
# Global variable to adjust lens size
LENS_SIZE_MULTIPLIER = 1.7
# Function to calculate distance between two points
def calculate_distance(point1, point2):
return np.linalg.norm(np.array(point1) - np.array(point2))
# Function to estimate the distance of the face from the camera
def estimate_distance(height, width):
return model.predict([[height, width]])[0]
# Function to draw dashed lines
def draw_dashed_line(img, start, end, color, thickness, dash_length=5):
x1, y1 = start
x2, y2 = end
line_length = calculate_distance(start, end)
num_dashes = int(line_length // dash_length)
for i in range(num_dashes):
start_x = int(x1 + (x2 - x1) * (i / num_dashes))
start_y = int(y1 + (y2 - y1) * (i / num_dashes))
end_x = int(x1 + (x2 - x1) * ((i + 1) / num_dashes))
end_y = int(y1 + (y2 - y1) * ((i + 1) / num_dashes))
if i % 2 == 0:
cv2.line(img, (start_x, start_y), (end_x, end_y), color, thickness)
# Set gender (can be detected or specified)
gender = 'male' # or 'female'
# Determine known face width based on gender
if gender == 'male':
KNOWN_FACE_WIDTH = MALE_FACE_WIDTH
elif gender == 'female':
KNOWN_FACE_WIDTH = FEMALE_FACE_WIDTH
else:
raise ValueError("Invalid gender specified. Please choose 'male' or 'female'.")
# Start the webcam and process frames
with mp_face_mesh.FaceMesh(max_num_faces=1, refine_landmarks=True, min_detection_confidence=0.7) as face_mesh:
while cap.isOpened():
success, frame = cap.read()
if not success:
print("Ignoring empty camera frame.")
continue
# Convert the frame to RGB
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
# Process the frame and detect face landmarks
results = face_mesh.process(frame_rgb)
# Draw face landmarks and calculate measurements
if results.multi_face_landmarks:
for face_landmarks in results.multi_face_landmarks:
mp_drawing.draw_landmarks(
image=frame,
landmark_list=face_landmarks,
connections=mp_face_mesh.FACEMESH_TESSELATION,
landmark_drawing_spec=None,
connection_drawing_spec=mp_drawing_styles.get_default_face_mesh_tesselation_style())
# Extract landmark points for calculations
landmarks = face_landmarks.landmark
# Calculate bounding box of the face
h, w, _ = frame.shape
x_min = int(min([landmark.x * w for landmark in landmarks]))
x_max = int(max([landmark.x * w for landmark in landmarks]))
y_min = int(min([landmark.y * h for landmark in landmarks]))
y_max = int(max([landmark.y * h for landmark in landmarks]))
face_width_in_frame = x_max - x_min
face_height_in_frame = y_max - y_min
# Estimate distance from camera
distance = estimate_distance(face_height_in_frame, face_width_in_frame)
print(f"Estimated distance from camera: {distance:.2f} cm")
# Adjust measurements based on distance
scale_factor = KNOWN_FACE_WIDTH / face_width_in_frame
# Calculate and print adjusted measurements
eye_width = calculate_distance((landmarks[33].x * w, landmarks[33].y * h),
(landmarks[133].x * w, landmarks[133].y * h)) * scale_factor / 10
print(f"Width of the eye: {eye_width:.2f} cm")
eye_distance = calculate_distance((landmarks[133].x * w, landmarks[133].y * h),
(landmarks[362].x * w, landmarks[362].y * h)) * scale_factor / 10
print(f"Distance between the eyes: {eye_distance:.2f} cm")
face_width = calculate_distance((landmarks[454].x * w, landmarks[454].y * h),
(landmarks[234].x * w, landmarks[234].y * h)) * scale_factor / 10
print(f"Width of the face: {face_width:.2f} cm")
# Calculate eye centers
left_eye_center = (
int((landmarks[33].x + landmarks[133].x) / 2 * w),
int((landmarks[33].y + landmarks[133].y) / 2 * h))
right_eye_center = (
int((landmarks[362].x + landmarks[263].x) / 2 * w),
int((landmarks[362].y + landmarks[263].y) / 2 * h))
# Get coordinates for the outer edge of the glasses
left_glass_leg_end = (int(landmarks[234].x * w), int(landmarks[234].y * h))
right_glass_leg_end = (int(landmarks[454].x * w), int(landmarks[454].y * h))
# Offset starting points of the eye distance lines
left_eye_start = (left_eye_center[0], left_eye_center[1] - 10)
right_eye_start = (right_eye_center[0], right_eye_center[1] - 10)
left_eye_top = (left_eye_center[0], left_eye_center[1] - 60)
right_eye_top = (right_eye_center[0], right_eye_center[1] - 60)
# Draw dashed lines for eye distance
draw_dashed_line(frame, left_eye_start, left_eye_top, (0, 0, 0), 1)
draw_dashed_line(frame, right_eye_start, right_eye_top, (0, 0, 0), 1)
# Eye distance text shifted a bit more to the left
eye_distance_text_pos = (
(left_eye_top[0] + right_eye_top[0]) // 2 - 30, (left_eye_top[1] + right_eye_top[1]) // 2)
cv2.putText(frame, f"{eye_distance:.2f} cm", eye_distance_text_pos, cv2.FONT_HERSHEY_SIMPLEX, 0.5,
(0, 0, 0), 2)
# Calculate eye centers and sizes
left_eye_width = calculate_distance((landmarks[133].x * w, landmarks[133].y * h),
(landmarks[33].x * w, landmarks[33].y * h))
right_eye_width = calculate_distance((landmarks[362].x * w, landmarks[362].y * h),
(landmarks[263].x * w, landmarks[263].y * h))
glass_radius = int(
max(left_eye_width, right_eye_width) / 2 * LENS_SIZE_MULTIPLIER) # Adjustable lens size
# Draw glasses
# Left lens
cv2.circle(frame, left_eye_center, glass_radius, (255, 0, 0), 2)
# Right lens
cv2.circle(frame, right_eye_center, glass_radius, (255, 0, 0), 2)
# Bridge
cv2.line(frame, (left_eye_center[0] + glass_radius, left_eye_center[1]),
(right_eye_center[0] - glass_radius, right_eye_center[1]), (255, 0, 0), 2)
# Left arm
cv2.line(frame, (left_eye_center[0] - glass_radius, left_eye_center[1]),
left_glass_leg_end, (255, 0, 0), 2)
# Right arm
cv2.line(frame, (right_eye_center[0] + glass_radius, right_eye_center[1]),
right_glass_leg_end, (255, 0, 0), 2)
# Draw dashed lines from the left and right sides of the inside circle of the left eye, reduced by 45%
line_reduction_factor = 0.55
left_eye_inside_left = (int(landmarks[33].x * w), int(landmarks[33].y * h))
left_eye_inside_right = (int(landmarks[133].x * w), int(landmarks[133].y * h))
left_eye_below_left = (
left_eye_inside_left[0], int(left_eye_inside_left[1] + 60 * line_reduction_factor))
left_eye_below_right = (
left_eye_inside_right[0], int(left_eye_inside_right[1] + 60 * line_reduction_factor))
draw_dashed_line(frame, left_eye_inside_left, left_eye_below_left, (0, 0, 0), 1)
draw_dashed_line(frame, left_eye_inside_right, left_eye_below_right, (0, 0, 0), 1)
# Eye width text shifted down and to the left
eye_width_text_pos = ((left_eye_below_left[0] + left_eye_below_right[0]) // 2 - 30,
(left_eye_below_left[1] + left_eye_below_right[1]) // 2 + 20)
cv2.putText(frame, f"{eye_width:.2f} cm", eye_width_text_pos, cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0),
2)
# Draw bounding box
#cv2.rectangle(frame, (x_min, y_min), (x_max, y_max), (0, 0, 255), 2)
# Draw dashed lines for face width
face_left = (int(landmarks[234].x * w), int(landmarks[234].y * h))
face_right = (int(landmarks[454].x * w), int(landmarks[454].y * h))
face_below_left = (face_left[0], int(face_left[1] + 140 * line_reduction_factor))
face_below_right = (face_right[0], int(face_right[1] + 140 * line_reduction_factor))
draw_dashed_line(frame, face_left, face_below_left, (0, 0, 0), 1)
draw_dashed_line(frame, face_right, face_below_right, (0, 0, 0), 1)
# Face width text moved a tiny bit to the left and down
face_width_text_pos = ((face_below_left[0] + face_below_right[0]) // 2 - 40,
(face_below_left[1] + face_below_right[1]) // 2 + 20)
cv2.putText(frame, f"{face_width:.2f} cm", face_width_text_pos, cv2.FONT_HERSHEY_SIMPLEX, 0.5,
(0, 0, 0), 2)
# Display the estimated distance from the camera
cv2.putText(frame, f"Distance: {distance:.2f} cm", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 0, 0),
2)
# Display the frame with landmarks, glasses, and bounding box
cv2.imshow('Face Landmarks with Glasses', frame)
# Press 'q' to quit the webcam window
if cv2.waitKey(5) & 0xFF == ord('q'):
break
# Release the webcam and close the window
cap.release()
cv2.destroyAllWindows()