htrnguyen commited on
Commit
caa082c
·
1 Parent(s): 5d4c695

Fix vertical video support & improve biomechanics analysis logic

Browse files
.gitattributes CHANGED
@@ -1,2 +1,3 @@
1
  models/*.pth.tar filter=lfs diff=lfs merge=lfs -text
2
- *.TTF filter=lfs diff=lfs merge=lfs -text
 
 
1
  models/*.pth.tar filter=lfs diff=lfs merge=lfs -text
2
+ models_v1/*.pth.tar filter=lfs diff=lfs merge=lfs -text
3
+ *.TTF filter=lfs diff=lfs merge=lfs -text
.gitignore CHANGED
@@ -71,3 +71,6 @@ ngrok.exe
71
  !demo/
72
  !demo/*.mp4
73
  !demo/*.json
 
 
 
 
71
  !demo/
72
  !demo/*.mp4
73
  !demo/*.json
74
+
75
+ # documents
76
+ !docs/
SCORING_METHODOLOGY.md DELETED
@@ -1,50 +0,0 @@
1
- # Thuyết minh: Phương pháp Đánh giá Golf AI (Thang điểm 10)
2
-
3
- Hệ thống của chúng tôi đánh giá cú swing dựa trên sự kết hợp giữa **Dữ liệu Hình ảnh (AI)** và **Hình học Tư thế (Pose Geometry)**.
4
-
5
- ## 1. Cơ chế Chấm điểm
6
-
7
- - **Điểm cơ sở**: Mỗi giai đoạn bắt đầu với **10.0 điểm**.
8
- - **Điểm trừ (Penalties)**: Tùy theo mức độ nghiêm trọng của lỗi, hệ thống sẽ trừ từ 0.5 đến 2.5 điểm mỗi lỗi.
9
- - **Trọng số (Weights)**: Điểm tổng kết không phải là trung bình cộng đơn thuần mà được tính theo tầm quan trọng của từng pha.
10
-
11
- | Giai đoạn | Trọng số | Tầm quan trọng |
12
- | :----------- | :------- | :--------------------------------------- |
13
- | Address | 10% | Nền tảng ban đầu |
14
- | Top | 20% | Đỉnh cao của năng lượng |
15
- | Impact | 35% | **Quan trọng nhất** (Điểm tiếp xúc bóng) |
16
- | Finish | 10% | Thể hiện sự thăng bằng |
17
- | Các pha khác | 25% | Độ mượt mà của quá trình chuyển động |
18
-
19
- ## 2. Tiêu chí Chẩn đoán Chi tiết
20
-
21
- ### Pha Address (Tư thế chuẩn bị)
22
-
23
- - **Độ rộng chân (Stance Width)**: Được đo so với độ rộng vai.
24
- - _Chuẩn_: Tỉ lệ từ 0.8 đến 1.4.
25
- - _Lỗi_: Quá rộng (mất linh hoạt) hoặc quá hẹp (mất thăng bằng).
26
-
27
- ### Pha Top (Đỉnh Backswing)
28
-
29
- - **Tay dẫn (Lead Arm - Tay trái)**: Phải thẳng để tạo cung swing lớn nhất.
30
- - _Chuẩn_: Góc tay >= 150 độ.
31
- - _Lỗi_: "Chicken Wing" (Tay bị gập) làm giảm lực đánh.
32
-
33
- ### Pha Impact (Tiếp bóng)
34
-
35
- - **Tay dẫn lúc va chạm**: Đây là khoảnh khắc truyền lực.
36
- - _Chuẩn_: Góc tay >= 160 độ (Gần như thẳng tuyệt đối).
37
- - _Lỗi_: Tay bị nhấc hoặc gập sớm làm mất lực và dễ đánh trượt.
38
-
39
- ### Pha Finish (Kết thúc)
40
-
41
- - **Thăng bằng (Balance Control)**: Kiểm tra vị trí hông so với chân trụ.
42
- - _Chuẩn_: Trọng tâm dồn hoàn toàn về chân trái, kết thúc vững chãi.
43
- - _Lỗi_: Ngả người ra sau hoặc đổ về phía trước.
44
-
45
- ## 3. Phân loại Trình độ
46
-
47
- - **9.0 - 10.0**: Pro / Low Handicap (Kỹ thuật tiệm cận chuyên nghiệp).
48
- - **7.5 - 8.9**: Mid Handicap (Kỹ thuật tốt, cần tinh chỉnh chi tiết).
49
- - **5.5 - 7.4**: High Handicap (Cần tập trung vào các bài tập cơ bản).
50
- - **Dưới 5.5**: Beginner (Cần sự hướng dẫn trực tiếp từ huấn luyện viên).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
analyze.py CHANGED
@@ -32,23 +32,19 @@ class GolfDiagnosticEngine:
32
  r_hip = landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value]
33
 
34
  shoulder_width = abs(l_shoulder.x - r_shoulder.x)
35
-
36
- # Tính chiều cao thân người (Torso Height) để chuẩn hóa
37
  mid_shoulder_y = (l_shoulder.y + r_shoulder.y) / 2
38
  mid_hip_y = (l_hip.y + r_hip.y) / 2
39
  torso_height = abs(mid_shoulder_y - mid_hip_y)
40
 
41
  if torso_height == 0: return "Unknown"
42
 
43
- # Tỷ lệ chiều rộng vai / chiều cao thân
44
  ratio = shoulder_width / torso_height
45
 
46
- # Heuristic: Nếu tỷ lệ > 0.5 (vai rộng bằng nửa lưng) -> Face-on
47
- if ratio > 0.4: # Hạ threshold xuống chút cho an toàn
48
- return "Face-on (Trực diện)"
49
  else:
50
- return "Down-the-Line (Dọc)"
51
-
52
 
53
  def calculate_angle(self, a, b, c):
54
  """Tính góc (độ) giữa 3 điểm a, b, c (b là đỉnh)"""
@@ -74,101 +70,158 @@ class GolfDiagnosticEngine:
74
  """Vẽ landmarks tinh tế và chuyên nghiệp hơn"""
75
  annotated_image = image.copy()
76
 
77
- # Cấu hình phong cách vẽ tinh tế (mỏng hơn, nhỏ hơn)
78
- landmark_style = self.mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=1, circle_radius=1) # Neon Green thon gọn
79
- connection_style = self.mp_drawing.DrawingSpec(color=(255, 255, 255), thickness=1) # Đường nối trắng mỏng
80
 
81
- # Vẽ khung xương
82
  self.mp_drawing.draw_landmarks(
83
  annotated_image, landmarks_proto, self.mp_pose.POSE_CONNECTIONS,
84
  landmark_style,
85
  connection_style
86
  )
87
-
88
  return annotated_image
89
 
90
- def analyze_phase(self, phase_name, landmarks):
91
  if not landmarks:
92
  return {"score": 0, "comments": ["Không tìm thấy tư thế"], "data": {}}
93
 
94
  analysis = {"score": 10.0, "comments": [], "data": {}, "raw_landmarks": []}
95
 
96
- # Trích xuất tọa độ thô của toàn bộ khớp xương để kỹ sư khác sử dụng
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
  for i, lm in enumerate(landmarks):
98
  analysis["raw_landmarks"].append({
99
- "id": i,
100
- "name": self.mp_pose.PoseLandmark(i).name,
101
- "x": round(lm.x, 4),
102
- "y": round(lm.y, 4),
103
- "z": round(lm.z, 4),
104
- "visibility": round(lm.visibility, 4)
105
  })
106
 
107
- # Lấy các điểm mốc quan trọng để tính toán logic nội bộ
108
- l_shoulder = [landmarks[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].x, landmarks[self.mp_pose.PoseLandmark.LEFT_SHOULDER.value].y]
109
- r_shoulder = [landmarks[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].x, landmarks[self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value].y]
110
- l_elbow = [landmarks[self.mp_pose.PoseLandmark.LEFT_ELBOW.value].x, landmarks[self.mp_pose.PoseLandmark.LEFT_ELBOW.value].y]
111
- r_elbow = [landmarks[self.mp_pose.PoseLandmark.RIGHT_ELBOW.value].x, landmarks[self.mp_pose.PoseLandmark.RIGHT_ELBOW.value].y]
112
- l_wrist = [landmarks[self.mp_pose.PoseLandmark.LEFT_WRIST.value].x, landmarks[self.mp_pose.PoseLandmark.LEFT_WRIST.value].y]
113
- r_wrist = [landmarks[self.mp_pose.PoseLandmark.RIGHT_WRIST.value].x, landmarks[self.mp_pose.PoseLandmark.RIGHT_WRIST.value].y]
114
- l_hip = [landmarks[self.mp_pose.PoseLandmark.LEFT_HIP.value].x, landmarks[self.mp_pose.PoseLandmark.LEFT_HIP.value].y]
115
- r_hip = [landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value].x, landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value].y]
116
- l_ankle = [landmarks[self.mp_pose.PoseLandmark.LEFT_ANKLE.value].x, landmarks[self.mp_pose.PoseLandmark.LEFT_ANKLE.value].y]
117
- r_ankle = [landmarks[self.mp_pose.PoseLandmark.RIGHT_ANKLE.value].x, landmarks[self.mp_pose.PoseLandmark.RIGHT_ANKLE.value].y]
118
-
119
- # Phân tích cụ thể từng pha (Thang điểm 10)
120
- # Sử dụng endswith để khớp với tên có hoặc không có số thứ tự
 
 
 
 
 
 
 
 
 
 
121
  if phase_name.endswith('Address'):
122
- # 1. Độ rộng chân (so với vai)
123
- shoulder_width = np.abs(l_shoulder[0] - r_shoulder[0])
124
- stance_width = np.abs(l_ankle[0] - r_ankle[0])
125
- ratio = stance_width / shoulder_width if shoulder_width > 0 else 0
126
- analysis["data"]["stance_ratio"] = ratio
127
- if ratio < 0.8:
128
- analysis["score"] -= 1.0
129
- analysis["comments"].append("Tư thế đứng quá hẹp")
130
- elif ratio > 1.4:
 
 
 
 
 
 
 
 
 
 
 
131
  analysis["score"] -= 1.0
132
- analysis["comments"].append(" thế đứng quá rộng")
 
 
 
 
133
 
134
  elif phase_name.endswith('Top'):
135
- # 2. Độ thẳng tay trái (Lead arm - giả sử golfer thuận tay phải)
136
  lead_arm_angle = self.calculate_angle(l_shoulder, l_elbow, l_wrist)
137
  analysis["data"]["lead_arm_angle"] = lead_arm_angle
138
- if lead_arm_angle < 150:
 
139
  analysis["score"] -= 2.0
140
- analysis["comments"].append("Tay trái bị cong quá nhiều (Chicken Wing)")
141
-
142
- # 3. Góc xoay vai (đơn giản hóa)
143
- shoulder_tilt = np.abs(l_shoulder[1] - r_shoulder[1])
144
- analysis["data"]["shoulder_tilt"] = shoulder_tilt
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
145
 
146
  elif phase_name.endswith('Impact'):
147
- # 4. Độ thẳng tay trái lúc Impact
148
- impact_arm_angle = self.calculate_angle(l_shoulder, l_elbow, l_wrist)
149
- if impact_arm_angle < 160:
150
- analysis["score"] -= 2.5
151
- analysis["comments"].append("Tay trái không thẳng, mất lực")
152
-
153
- # 5. Hip rotation (Lead hip vs Back hip)
154
- hip_openness = l_hip[0] - r_hip[0]
155
- analysis["data"]["hip_openness"] = hip_openness
156
 
157
- elif phase_name.endswith('Finish'):
158
- # 6. Thăng bằng (Trọng tâm dồn về chân trái)
159
- # Kiểm tra thăng bằng đơn giản bằng vị trí hông so với chân lead
160
- if l_hip[0] > l_ankle[0] + 0.1 or l_hip[0] < l_ankle[0] - 0.1:
161
- analysis["score"] -= 1.5
162
- analysis["comments"].append("Kết thúc thiếu thăng bằng")
163
 
 
 
 
 
 
 
 
 
164
  if not analysis["comments"]:
165
- analysis["comments"].append("Đạt chuẩn")
166
 
167
- analysis["score"] = round(max(0, analysis["score"]), 1)
168
  return analysis
169
 
170
  def process_video_results(self, output_dir, return_dict=False):
171
- # Lấy video_id từ tên thư mục output
172
  video_id = os.path.basename(output_dir)
173
  extraction_dir = os.path.join(output_dir, 'phases')
174
 
@@ -177,8 +230,18 @@ class GolfDiagnosticEngine:
177
  total_score = 0
178
  valid_phases = 0
179
 
180
- # Mảng lưu landmarks Address để detect view
181
  address_landmarks = None
 
 
 
 
 
 
 
 
 
 
 
182
 
183
  for label in self.labels:
184
  img_path = os.path.join(extraction_dir, label, f"{video_id}.jpg")
@@ -188,11 +251,7 @@ class GolfDiagnosticEngine:
188
  image = cv2.imread(img_path)
189
  landmarks_list, _ = self.get_landmarks(image)
190
 
191
- # Lưu landmarks của Address
192
- if label == '1_Address' and landmarks_list:
193
- address_landmarks = landmarks_list
194
-
195
- phase_analysis = self.analyze_phase(label, landmarks_list)
196
 
197
  report["phases"][label] = phase_analysis
198
  total_score += phase_analysis["score"]
@@ -201,15 +260,9 @@ class GolfDiagnosticEngine:
201
  if valid_phases > 0:
202
  report["overall_score"] = round(total_score / valid_phases, 1)
203
 
204
- # Detect View Angle một lần duy nhất
205
- if address_landmarks:
206
- report["view_angle"] = self.detect_view_angle(address_landmarks)
207
-
208
- # Return dict hoặc ghi file (legacy)
209
  if return_dict:
210
  return report
211
  else:
212
- # Lưu báo cáo vào thư mục output của video
213
  report_path = os.path.join(output_dir, 'report.json')
214
  with open(report_path, 'w', encoding='utf-8') as f:
215
  json.dump(report, f, indent=4, ensure_ascii=False)
 
32
  r_hip = landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value]
33
 
34
  shoulder_width = abs(l_shoulder.x - r_shoulder.x)
 
 
35
  mid_shoulder_y = (l_shoulder.y + r_shoulder.y) / 2
36
  mid_hip_y = (l_hip.y + r_hip.y) / 2
37
  torso_height = abs(mid_shoulder_y - mid_hip_y)
38
 
39
  if torso_height == 0: return "Unknown"
40
 
 
41
  ratio = shoulder_width / torso_height
42
 
43
+ # Nếu tỷ lệ > 0.35 -> Face-on.
44
+ if ratio > 0.35:
45
+ return "Face-on"
46
  else:
47
+ return "Down-the-Line"
 
48
 
49
  def calculate_angle(self, a, b, c):
50
  """Tính góc (độ) giữa 3 điểm a, b, c (b là đỉnh)"""
 
70
  """Vẽ landmarks tinh tế và chuyên nghiệp hơn"""
71
  annotated_image = image.copy()
72
 
73
+ landmark_style = self.mp_drawing.DrawingSpec(color=(0, 255, 0), thickness=1, circle_radius=1)
74
+ connection_style = self.mp_drawing.DrawingSpec(color=(255, 255, 255), thickness=1)
 
75
 
 
76
  self.mp_drawing.draw_landmarks(
77
  annotated_image, landmarks_proto, self.mp_pose.POSE_CONNECTIONS,
78
  landmark_style,
79
  connection_style
80
  )
 
81
  return annotated_image
82
 
83
+ def analyze_phase(self, phase_name, landmarks, address_landmarks=None, view_angle="Unknown"):
84
  if not landmarks:
85
  return {"score": 0, "comments": ["Không tìm thấy tư thế"], "data": {}}
86
 
87
  analysis = {"score": 10.0, "comments": [], "data": {}, "raw_landmarks": []}
88
 
89
+ # Helper lấy tọa độ (x, y)
90
+ def get_xy(idx):
91
+ return [landmarks[idx].x, landmarks[idx].y]
92
+
93
+ l_shoulder = get_xy(self.mp_pose.PoseLandmark.LEFT_SHOULDER.value)
94
+ r_shoulder = get_xy(self.mp_pose.PoseLandmark.RIGHT_SHOULDER.value)
95
+ l_elbow = get_xy(self.mp_pose.PoseLandmark.LEFT_ELBOW.value)
96
+ r_elbow = get_xy(self.mp_pose.PoseLandmark.RIGHT_ELBOW.value)
97
+ l_wrist = get_xy(self.mp_pose.PoseLandmark.LEFT_WRIST.value)
98
+ r_wrist = get_xy(self.mp_pose.PoseLandmark.RIGHT_WRIST.value)
99
+ l_hip = get_xy(self.mp_pose.PoseLandmark.LEFT_HIP.value)
100
+ r_hip = get_xy(self.mp_pose.PoseLandmark.RIGHT_HIP.value)
101
+ l_knee = get_xy(self.mp_pose.PoseLandmark.LEFT_KNEE.value)
102
+ r_knee = get_xy(self.mp_pose.PoseLandmark.RIGHT_KNEE.value)
103
+ l_ankle = get_xy(self.mp_pose.PoseLandmark.LEFT_ANKLE.value)
104
+ r_ankle = get_xy(self.mp_pose.PoseLandmark.RIGHT_ANKLE.value)
105
+ nose = get_xy(self.mp_pose.PoseLandmark.NOSE.value)
106
+
107
+ # Trích xuất landmarks thô
108
  for i, lm in enumerate(landmarks):
109
  analysis["raw_landmarks"].append({
110
+ "id": i, "name": self.mp_pose.PoseLandmark(i).name,
111
+ "x": round(lm.x, 4), "y": round(lm.y, 4), "z": round(lm.z, 4), "visibility": round(lm.visibility, 4)
 
 
 
 
112
  })
113
 
114
+ # --- CHUẨN HÓA TỌA ĐỘ (Normalization) ---
115
+ current_ankle_center_x = (l_ankle[0] + r_ankle[0]) / 2
116
+
117
+ addr_ankle_center_x = 0
118
+ addr_nose_x_rel = 0
119
+ addr_hip_x_rel = 0
120
+
121
+ if address_landmarks:
122
+ al_ankle_l = address_landmarks[self.mp_pose.PoseLandmark.LEFT_ANKLE.value].x
123
+ al_ankle_r = address_landmarks[self.mp_pose.PoseLandmark.RIGHT_ANKLE.value].x
124
+ addr_ankle_center_x = (al_ankle_l + al_ankle_r) / 2
125
+
126
+ al_nose = address_landmarks[self.mp_pose.PoseLandmark.NOSE.value].x
127
+ al_hip = (address_landmarks[self.mp_pose.PoseLandmark.LEFT_HIP.value].x +
128
+ address_landmarks[self.mp_pose.PoseLandmark.RIGHT_HIP.value].x) / 2
129
+
130
+ addr_nose_x_rel = al_nose - addr_ankle_center_x
131
+ addr_hip_x_rel = al_hip - addr_ankle_center_x
132
+
133
+ curr_nose_x_rel = nose[0] - current_ankle_center_x
134
+ curr_hip_x_rel = ((l_hip[0] + r_hip[0])/2) - current_ankle_center_x
135
+
136
+ # --- LOGIC ---
137
+
138
  if phase_name.endswith('Address'):
139
+ # 1. Stance Width (CHỈ CHECK FACE-ON)
140
+ if view_angle == "Face-on":
141
+ shoulder_width = np.abs(l_shoulder[0] - r_shoulder[0])
142
+ stance_width = np.abs(l_ankle[0] - r_ankle[0])
143
+ if shoulder_width > 0:
144
+ ratio = stance_width / shoulder_width
145
+ analysis["data"]["stance_ratio"] = ratio
146
+ if ratio < 0.9:
147
+ analysis["score"] -= 1.0
148
+ analysis["comments"].append("Tư thế đứng quá hẹp (Narrow Stance)")
149
+ elif ratio > 1.5:
150
+ analysis["score"] -= 1.0
151
+ analysis["comments"].append("Tư thế đứng quá rộng (Wide Stance)")
152
+
153
+ # 2. Knee Flex
154
+ avg_knee_angle = (self.calculate_angle(l_hip, l_knee, l_ankle) +
155
+ self.calculate_angle(r_hip, r_knee, r_ankle)) / 2
156
+ analysis["data"]["knee_angle"] = avg_knee_angle
157
+ if avg_knee_angle > 175:
158
+ # DTL thấy rõ hơn, Face-on cũng thấy được
159
  analysis["score"] -= 1.0
160
+ analysis["comments"].append("Đầu gối quá thẳng (Locking Knees)")
161
+ elif avg_knee_angle < 135:
162
+ if view_angle == "Face-on":
163
+ analysis["score"] -= 1.0
164
+ analysis["comments"].append("Đầu gối khuỵu quá mức")
165
 
166
  elif phase_name.endswith('Top'):
167
+ # 3. Chicken Wing
168
  lead_arm_angle = self.calculate_angle(l_shoulder, l_elbow, l_wrist)
169
  analysis["data"]["lead_arm_angle"] = lead_arm_angle
170
+ threshold = 140 if view_angle == "Face-on" else 130
171
+ if lead_arm_angle < threshold:
172
  analysis["score"] -= 2.0
173
+ analysis["comments"].append("Tay trái bị gập (Chicken Wing)")
174
+
175
+ # 4. Sway / Slide (CHỈ FACE-ON)
176
+ if address_landmarks and view_angle == "Face-on":
177
+ sway = curr_nose_x_rel - addr_nose_x_rel
178
+ analysis["data"]["head_sway"] = sway
179
+ if abs(sway) > 0.08:
180
+ analysis["score"] -= 1.5
181
+ analysis["comments"].append("Đầu di chuy��n quá nhiều (Sway)")
182
+
183
+ slide = curr_hip_x_rel - addr_hip_x_rel
184
+ if slide > 0.08:
185
+ analysis["score"] -= 1.0
186
+ analysis["comments"].append("Hông trượt sang phải (Hip Slide)")
187
+
188
+ # 5. Reverse Spine Angle (Face-on only)
189
+ if view_angle == "Face-on":
190
+ if curr_nose_x_rel < curr_hip_x_rel - 0.05:
191
+ analysis["score"] -= 2.0
192
+ analysis["comments"].append("Lỗi trục nghiêng ngược (Reverse Spine Angle)")
193
 
194
  elif phase_name.endswith('Impact'):
195
+ # 6. Weight Shift
196
+ if address_landmarks and view_angle == "Face-on":
197
+ shift = curr_hip_x_rel - addr_hip_x_rel
198
+ analysis["data"]["weight_shift"] = shift
199
+
200
+ if shift > -0.01:
201
+ analysis["score"] -= 2.0
202
+ analysis["comments"].append("Thiếu chuyển trọng tâm sang trái")
 
203
 
204
+ # 7. Scooping
205
+ impact_angle = self.calculate_angle(l_shoulder, l_elbow, l_wrist)
206
+ if impact_angle < 155:
207
+ analysis["score"] -= 2.0
208
+ analysis["comments"].append("Tay trái gập sớm (Scooping/Chicken Wing)")
 
209
 
210
+ elif phase_name.endswith('Finish'):
211
+ # 8. Balance
212
+ if view_angle == "Face-on":
213
+ hip_vs_ankle = l_hip[0] - l_ankle[0]
214
+ if abs(hip_vs_ankle) > 0.15:
215
+ analysis["score"] -= 1.0
216
+ analysis["comments"].append("Kết thúc chưa vững (Off Balance)")
217
+
218
  if not analysis["comments"]:
219
+ analysis["comments"].append(" thế tốt")
220
 
221
+ analysis["score"] = round(max(0, min(10, analysis["score"])), 1)
222
  return analysis
223
 
224
  def process_video_results(self, output_dir, return_dict=False):
 
225
  video_id = os.path.basename(output_dir)
226
  extraction_dir = os.path.join(output_dir, 'phases')
227
 
 
230
  total_score = 0
231
  valid_phases = 0
232
 
 
233
  address_landmarks = None
234
+ address_path = os.path.join(extraction_dir, '1_Address', f"{video_id}.jpg")
235
+
236
+ view_angle = "Unknown"
237
+ if os.path.exists(address_path):
238
+ img = cv2.imread(address_path)
239
+ lm_list, _ = self.get_landmarks(img)
240
+ if lm_list:
241
+ address_landmarks = lm_list
242
+ view_angle = self.detect_view_angle(address_landmarks)
243
+
244
+ report["view_angle"] = view_angle
245
 
246
  for label in self.labels:
247
  img_path = os.path.join(extraction_dir, label, f"{video_id}.jpg")
 
251
  image = cv2.imread(img_path)
252
  landmarks_list, _ = self.get_landmarks(image)
253
 
254
+ phase_analysis = self.analyze_phase(label, landmarks_list, address_landmarks, view_angle)
 
 
 
 
255
 
256
  report["phases"][label] = phase_analysis
257
  total_score += phase_analysis["score"]
 
260
  if valid_phases > 0:
261
  report["overall_score"] = round(total_score / valid_phases, 1)
262
 
 
 
 
 
 
263
  if return_dict:
264
  return report
265
  else:
 
266
  report_path = os.path.join(output_dir, 'report.json')
267
  with open(report_path, 'w', encoding='utf-8') as f:
268
  json.dump(report, f, indent=4, ensure_ascii=False)
coach.py CHANGED
@@ -24,11 +24,26 @@ class GolfCoachEngine:
24
 
25
  # Thư viện bài tập gợi ý
26
  self.drill_library = {
27
- "Tư thế đứng quá rộng": "Bài tập đứng trên hai đầu gối hoặc khép chân để cảm nhận sự xoay trục.",
28
- "Tư thế đứng quá hẹp": "Bài tập bước rộng chân xoay hông tại chỗ để tạo nền tảng vững chắc.",
29
- "Tay trái bị cong quá nhiều ở đỉnh swing (Chicken Wing)": "Kẹp một chiếc khăn hoặc quả bóng tập giữa hai cánh tay ở nách để giữ sự kết nối.",
30
- "Tay trái không thẳng lúc tiếp bóng, mất lực": "Bài tập Half-swing (swing nửa vòng) tập trung vào việc giữ tay lead thẳng và xoay hông.",
31
- "Kết thúc không vững, trọng tâm chưa dồn hết về chân trái": "Bài tập Hold Finish - giữ nguyên thế kết thúc trong 3 giây cho đến khi thăng bằng hoàn toàn."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  }
33
 
34
  def generate_report(self, diagnostic_json_path):
 
24
 
25
  # Thư viện bài tập gợi ý
26
  self.drill_library = {
27
+ # Stance & Address
28
+ "Tư thế đứng quá rộng (Wide Stance)": "Thu hẹp khoảng cách chân bằng chiều rộng vai để xoay hông dễ dàng hơn.",
29
+ " thế đứng quá hẹp (Narrow Stance)": "Mở rộng chân thêm chút (bằng vai) để tạo nền tảng vững chắc.",
30
+ "Đầu gối quá thẳng, thiếu sự linh hoạt (Locking Knees)": "Thả lỏng đầu gối, làm động tác như chuẩn bị ngồi xuống ghế cao.",
31
+ "Đầu gối khuỵu quá mức (Sitting too much)": "Đứng cao hơn chút, chỉ khuỵu nhẹ đầu gối để dồn trọng tâm lên ức bàn chân.",
32
+
33
+ # Backswing & Top
34
+ "Tay trái bị gập (Chicken Wing) - Mất bán kính swing": "Tập kẹp khăn dưới nách khi swing để giữ tay sát thân.",
35
+ "Đầu di chuyển quá nhiều (Head Sway) - Gây mất ổn định": "Nhờ bạn giữ đầu hoặc nhìn vào gương, tập xoay vai mà đầu vẫn giữ yên.",
36
+ "Hông trượt sang phải (Hip Slide) thay vì xoay": "Tập bài xoay hông trong xô nước (Barrel Turn) - tưởng tượng đứng trong thùng phuy và chỉ xoay, không trượt.",
37
+ "Lỗi trục nghiêng ngược (Reverse Spine Angle) - Dễ gây đau lưng": "Đảm bảo vai trái hạ thấp hơn vai phải khi lên đỉnh, mắt vẫn nhìn vào phía sau bóng.",
38
+
39
+ # Impact
40
+ "Tay trái gập khi tiếp bóng (Scooping/Chicken Wing)": "Bài tập Impact Bag: Tập đánh vào túi đệm và dừng lại ở Impact để cảm nhận tay thẳng.",
41
+ "Trọng tâm dồn về chân sau (Hanging Back) - Mất lực": "Bài tập bước chân (Step Drill): Bước chân trái lên khi downswing để ép trọng tâm chuyển sang trái.",
42
+ "Thiếu chuyển trọng tâm sang chân trái": "Đặt bóng dưới lòng bàn chân phải, khi swing xuống phải nhấc gót phải lên khỏi bóng.",
43
+ "Đầu lao về trước quá sớm (Lunging)": "Cố gắng cảm giác như 'đánh đầu' về phía sau khi gậy tiếp xúc bóng.",
44
+
45
+ # Finish
46
+ "Kết thúc không dồn hết trọng tâm sang trái": "Tập giữ tư thế kết thúc trong 3 giây, nhấc hoàn toàn chân phải lên chỉ tì bằng mũi giày."
47
  }
48
 
49
  def generate_report(self, diagnostic_json_path):
docs/final_golf_swing.py ADDED
@@ -0,0 +1,730 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Generated from: final_golf_swing.ipynb
2
+ # Converted at: 2026-01-01T14:06:29.903Z
3
+ # Next step (optional): refactor into modules & generate tests with RunCell
4
+ # Quick start: pip install runcell
5
+
6
+ # ### 📦 Installation Summary
7
+ #
8
+ # This code installs essential Python libraries for video processing, pose detection, progress tracking, and data handling:
9
+ #
10
+ # - `moviepy==1.0.3`: Used for video editing (cutting, merging, adding audio).
11
+ # - `mediapipe==0.10.21`: Provides pose and hand detection (great for analyzing movement like golf swings).
12
+ # - `tqdm==4.67.1`: Adds progress bars to loops and downloads.
13
+ # - `dataclasses-json==0.6.7`: Allows easy serialization/deserialization of Python dataclasses to/from JSON.
14
+ # - `backoff==2.2.1`: Helps with retry logic for functions that might fail (e.g., API calls).
15
+ # - `supervision==0.26.0`: Useful for drawing overlays on video frames (bounding boxes, labels).
16
+ # - `yt-dlp`: Downloads videos from YouTube and other platforms (quietly installed with `-q`).
17
+ #
18
+ # Note: `--no-deps` skips installing dependencies for the first command to avoid version conflicts.
19
+ #
20
+
21
+
22
+ !pip install --no-deps moviepy==1.0.3 mediapipe==0.10.21 tqdm==4.67.1
23
+ !pip install dataclasses-json==0.6.7
24
+ !pip install backoff==2.2.1
25
+ !pip install supervision==0.26.0
26
+ !!pip install -q yt-dlp
27
+
28
+ # ### 🎬 YouTube Shorts Downloader (with `yt-dlp`)
29
+ #
30
+ # This code uses `yt-dlp` to download high-quality YouTube Shorts as `.mp4` files:
31
+ #
32
+ # - `-f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4"`: Selects the best available video stream in MP4 format and merges it with the best audio in M4A. Falls back to a single MP4 if needed.
33
+ # - `-o "filename.mp4"`: Specifies the output filename for the downloaded video.
34
+ # - URLs point to YouTube Shorts featuring golfers Ludvig Åberg and Max Homa.
35
+ #
36
+ # 📥 Output files:
37
+ # - `ludvig_aberg_driver.mp4`
38
+ # - `max_homa_iron.mp4`
39
+ #
40
+
41
+
42
+ !yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4" -o "ludvig_aberg_driver.mp4" "https://www.youtube.com/shorts/9nhFobbdJxQ"
43
+ !yt-dlp -f "bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4" -o "max_homa_iron.mp4" "https://www.youtube.com/shorts/aB__zg42i9g"
44
+
45
+
46
+ # ### 🧠 Core Imports Overview
47
+ #
48
+ # This block imports essential libraries for processing, visualization, and media handling:
49
+ #
50
+ # - **System & Utilities**:
51
+ # - `gc`, `time`, `subprocess`: For garbage collection, timing operations, and running shell commands.
52
+ #
53
+ # - **Numerical & Signal Processing**:
54
+ # - `numpy`: Core library for numerical computations.
55
+ # - `uniform_filter1d` (from `scipy.ndimage`): Smooths 1D signals (e.g., joint positions over frames).
56
+ #
57
+ # - **Visualization**:
58
+ # - `matplotlib.pyplot`: For plotting graphs and visuals.
59
+ # - `FigureCanvas`: Renders plots into images.
60
+ # - `mpl_use`: Sets a Colab-safe backend for rendering plots.
61
+ #
62
+ # - **Media Processing**:
63
+ # - `cv2`: OpenCV library for reading, processing, and displaying video frames.
64
+ # - `mediapipe`: Used for pose estimation and landmark detection.
65
+ #
66
+ # - **Colab Display**:
67
+ # - `IPython.display`: For showing videos inline using `Video()` and `display()` functions.
68
+ #
69
+
70
+
71
+ # Core
72
+ import gc
73
+ import time
74
+ import subprocess
75
+
76
+ # Numerical and signal processing
77
+ import numpy as np
78
+ from scipy.ndimage import uniform_filter1d
79
+
80
+ # Visualization
81
+ import matplotlib.pyplot as plt
82
+ from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
83
+ from matplotlib import use as mpl_use # Needed for Colab-safe rendering
84
+
85
+ # Media processing
86
+ import cv2
87
+ import mediapipe as mp
88
+
89
+ # Colab display tools
90
+ from IPython.display import Video, display
91
+
92
+
93
+ # ### 🔇 Add Silent Audio to Videos Using FFmpeg
94
+ #
95
+ # This code prepares four golf swing videos (two input clips and two YouTube references) by adding a silent audio track to each. This ensures compatibility with video players that require an audio stream.
96
+ #
97
+ # - **Input Videos**:
98
+ # - `video_iron`, `video_driver`: Your own recorded swing clips.
99
+ # - `iron_reference`, `driver_reference`: Reference swings from YouTube (e.g., Max Homa, Ludvig Åberg).
100
+ #
101
+ # - **Output Paths**:
102
+ # - For each video, a `_fixed.MP4` version is created with silent audio.
103
+ #
104
+ # - **FFmpeg Command**:
105
+ # - `-i <input>`: Input video file.
106
+ # - `-f lavfi -i anullsrc`: Adds silent stereo audio at 44.1kHz.
107
+ # - `-shortest`: Ends the audio when video ends.
108
+ # - `-c:v libx264`: Encodes video using H.264.
109
+ # - `-c:a aac -b:a 128k`: Encodes audio using AAC at 128 kbps.
110
+ # - `-movflags +faststart`: Optimizes video for fast playback start.
111
+ #
112
+ # 📁 Outputs:
113
+ # - `*_fixed.MP4` files for all input and reference videos with embedded silent audio.
114
+ #
115
+
116
+
117
+ # Input video path and define output path for audio-fixed video
118
+
119
+ iron_reference = "max_homa_iron.mp4"
120
+ driver_reference = "ludvig_aberg_driver"
121
+
122
+ video_iron = "/content/IMG_1022.MP4" # original input video # IRON
123
+ video_iron_fixed = video_iron.rsplit(".", 1)[0] + "_fixed.MP4"
124
+
125
+ video_driver = "/content/IMG_0758.MP4" # alternate input # DRIVER
126
+ video_driver_fixed = video_driver.rsplit(".", 1)[0] + "_fixed.MP4"
127
+
128
+ iron_reference = "/content/max_homa_iron.mp4"
129
+ iron_reference_fixed = iron_reference.rsplit(".", 1)[0] + "_fixed.MP4"
130
+
131
+ driver_reference = "/content/ludvig_aberg_driver.mp4"
132
+ driver_reference_fixed = driver_reference.rsplit(".", 1)[0] + "_fixed.MP4"
133
+
134
+
135
+ # 1. Preserve original video and add silent audio track to a copy
136
+ ffmpeg_cmd = f"""
137
+ ffmpeg -y -i "{video_iron}" -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \
138
+ -shortest -c:v libx264 -c:a aac -b:a 128k -movflags +faststart "{video_iron_fixed}"
139
+ """
140
+ subprocess.run(ffmpeg_cmd, shell=True)
141
+
142
+ # 1. Preserve original video and add silent audio track to a copy
143
+ ffmpeg_cmd = f"""
144
+ ffmpeg -y -i "{video_driver}" -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \
145
+ -shortest -c:v libx264 -c:a aac -b:a 128k -movflags +faststart "{video_driver_fixed}"
146
+ """
147
+ subprocess.run(ffmpeg_cmd, shell=True)
148
+
149
+ # 1. Preserve original video and add silent audio track to a copy
150
+ ffmpeg_cmd = f"""
151
+ ffmpeg -y -i "{iron_reference}" -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \
152
+ -shortest -c:v libx264 -c:a aac -b:a 128k -movflags +faststart "{iron_reference_fixed}"
153
+ """
154
+ subprocess.run(ffmpeg_cmd, shell=True)
155
+
156
+ # 1. Preserve original video and add silent audio track to a copy
157
+ ffmpeg_cmd = f"""
158
+ ffmpeg -y -i "{driver_reference}" -f lavfi -i anullsrc=channel_layout=stereo:sample_rate=44100 \
159
+ -shortest -c:v libx264 -c:a aac -b:a 128k -movflags +faststart "{driver_reference_fixed}"
160
+ """
161
+ subprocess.run(ffmpeg_cmd, shell=True)
162
+
163
+ # Preview the audio-fixed video (same content as original, with an added audio stream)
164
+ Video(video_iron_fixed, embed=True, width=600, height=800)
165
+
166
+ # Preview the audio-fixed video (same content as original, with an added audio stream)
167
+ Video(video_driver_fixed, embed=True, width=600, height=800)
168
+
169
+ # Preview the audio-fixed video (same content as original, with an added audio stream)
170
+ Video(iron_reference_fixed, embed=True, width=600, height=800)
171
+
172
+ # Preview the audio-fixed video (same content as original, with an added audio stream)
173
+ Video(driver_reference_fixed, embed=True, width=600, height=800)
174
+
175
+ # ### 🏌️‍♂️ Pose Feature Extraction (Wrist Y-Coordinate)
176
+ #
177
+ # This function `extract_pose_features_debug(video_path)` analyzes a video frame-by-frame to extract the **vertical (Y) position of visible wrists** using MediaPipe Pose:
178
+ #
179
+ # - **Video Setup**:
180
+ # - Loads the video and retrieves its frame count and resolution.
181
+ # - Initializes MediaPipe Pose in video (non-static) mode.
182
+ #
183
+ # - **Frame Loop**:
184
+ # - For each frame:
185
+ # - Converts BGR to RGB for pose processing.
186
+ # - Detects body landmarks using MediaPipe.
187
+ # - If pose is found:
188
+ # - Checks visibility of **left and right wrists**.
189
+ # - Computes average Y-position (vertical) of visible wrists (scaled to video height).
190
+ # - If no pose or wrist visibility is low, logs a warning and sets `wrist_y` as `None`.
191
+ #
192
+ # - **Return**:
193
+ # - Returns a list of dictionaries like `{"frame_idx": 42, "wrist_y": 138.5}` for each frame.
194
+ #
195
+ # 👀 Use this to analyze hand motion over time, useful for swing segmentation or performance analysis.
196
+ #
197
+
198
+
199
+ def extract_pose_features_debug(video_path):
200
+ cap = cv2.VideoCapture(video_path)
201
+ original_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
202
+ original_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
203
+ num_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
204
+ print(f"🎞️ Total frames: {num_frames}, Resolution: {original_width}x{original_height}")
205
+
206
+ used_height = original_height # No rotation, use original height
207
+
208
+ mp_pose = mp.solutions.pose
209
+ pose = mp_pose.Pose(static_image_mode=False)
210
+ frame_data = []
211
+
212
+ for i in range(num_frames):
213
+ ret, frame = cap.read()
214
+ if not ret:
215
+ print(f"❌ Failed to read frame {i}")
216
+ break
217
+
218
+ rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
219
+ results = pose.process(rgb)
220
+ data = {"frame_idx": i}
221
+
222
+ if results.pose_landmarks:
223
+ # print(f"✅ Pose detected on frame {i}")
224
+ lm = results.pose_landmarks.landmark
225
+ lw = lm[mp_pose.PoseLandmark.LEFT_WRIST]
226
+ rw = lm[mp_pose.PoseLandmark.RIGHT_WRIST]
227
+ visible_wrists = [p for p in (lw, rw) if p.visibility > 0.4]
228
+ if visible_wrists:
229
+ avg_y = np.mean([p.y for p in visible_wrists])
230
+ data["wrist_y"] = avg_y * used_height
231
+ # print(f"👋 Frame {i}: Wrist Y = {data['wrist_y']:.2f}")
232
+ else:
233
+ print(f"⚠️ Frame {i}: Wrists not visible")
234
+ data["wrist_y"] = None
235
+ else:
236
+ print(f"❌ Frame {i}: No pose detected")
237
+ data["wrist_y"] = None
238
+
239
+ frame_data.append(data)
240
+
241
+ cap.release()
242
+ pose.close()
243
+ return frame_data
244
+
245
+
246
+
247
+
248
+ # ### 📈 Run Pose Extraction on All Videos
249
+ #
250
+ # This block runs the `extract_pose_features_debug()` function on all four prepared videos (with silent audio):
251
+ #
252
+ # - `video_iron_fixed`: Your iron swing clip
253
+ # - `video_driver_fixed`: Your driver swing clip
254
+ # - `iron_reference_fixed`: Max Homa’s reference swing
255
+ # - `driver_reference_fixed`: Ludvig Åberg’s reference swing
256
+ #
257
+ # Each call extracts the **Y-coordinate of visible wrists** frame-by-frame and stores the results in:
258
+ #
259
+ # - `frame_data_iron`
260
+ # - `frame_data_driver`
261
+ # - `frame_data_iron_reference`
262
+ # - `frame_data_driver_reference`
263
+ #
264
+ # 📦 These variables now hold pose-based wrist motion data for later analysis or swing comparison.
265
+ #
266
+
267
+
268
+ frame_data_iron = extract_pose_features_debug(video_iron_fixed)
269
+ frame_data_driver = extract_pose_features_debug(video_driver_fixed)
270
+ frame_data_iron_reference = extract_pose_features_debug(iron_reference_fixed)
271
+ frame_data_driver_reference = extract_pose_features_debug(driver_reference_fixed)
272
+
273
+ #
274
+
275
+
276
+ # ### 🏌️‍♀️ Golf Swing Phase Detection via Wrist Y-Motion
277
+ #
278
+ # This function `detect_swing_phases()` takes wrist Y-coordinate data and dynamically identifies key golf swing phases using smoothed motion patterns and gradient analysis:
279
+ #
280
+ # #### 🔍 Key Steps:
281
+ # - **Smoothing & Velocity**:
282
+ # - Applies a moving average to wrist Y (`smoothing_window`), then calculates velocity and its magnitude.
283
+ #
284
+ # - **Swing Start & End**:
285
+ # - Detects when motion exceeds a dynamic threshold (`threshold_percentile`) to mark swing boundaries.
286
+ #
287
+ # - **Address Detection**:
288
+ # - Scans backwards from swing start to find a stable ("flat") wrist phase, indicating the pre-swing address.
289
+ #
290
+ # - **Top of Backswing**:
291
+ # - Finds the lowest wrist Y point before the swing starts descending (indicating the top of the backswing).
292
+ #
293
+ # - **Impact Detection**:
294
+ # - After the top, identifies a stable high point in wrist Y as impact, using tolerance around local max.
295
+ #
296
+ # - **Phase Segmentation**:
297
+ # - Returns six labeled segments: `"Address"`, `"Backswing"`, `"Top"`, `"Downswing"`, `"Impact"`, `"Follow Through"`.
298
+ #
299
+ # #### 📊 Visualization:
300
+ # - Plots wrist trajectory with Y flipped (so upward motion looks natural).
301
+ # - Highlights each swing phase using colored spans and labels.
302
+ #
303
+ # #### 📤 Returns:
304
+ # - `phase_ranges`: Dictionary of start/end frames for each phase.
305
+ # - `swing_start`, `swing_end`: Frame indices marking full swing.
306
+ # - `wrist_y`, `smoothed`: Raw and smoothed wrist Y arrays for further analysis.
307
+ #
308
+ # ✅ This unified logic handles both pros and amateurs, dynamically adjusting to wrist movement stability and swing patterns.
309
+ #
310
+
311
+
312
+
313
+
314
+ def detect_swing_phases(frame_data, smoothing_window=5, precheck_window=30, threshold_percentile=90):
315
+ """
316
+ Analyze wrist Y trajectory and determine key golf swing phase ranges.
317
+ Unified logic. Dynamically determines Address using flatness in wrist_y.
318
+ """
319
+ # Convert to numpy and handle None
320
+ wrist_y = np.array([d["wrist_y"] if d["wrist_y"] is not None else np.nan for d in frame_data], dtype=np.float32)
321
+ smoothed = uniform_filter1d(wrist_y, size=smoothing_window, mode='nearest')
322
+ velocity = np.gradient(smoothed)
323
+ velocity_mag = np.abs(velocity)
324
+
325
+ # Motion threshold and swing start
326
+ early_motion = np.nanmean(velocity_mag[:precheck_window])
327
+ motion_std = np.nanstd(velocity_mag[:precheck_window])
328
+ buffer_start = precheck_window if (early_motion < 0.001 and motion_std < 0.001) else 0
329
+ threshold = np.nanpercentile(velocity_mag[buffer_start:], threshold_percentile)
330
+ motion_indices = np.where(velocity_mag > threshold)[0]
331
+ motion_indices = motion_indices[motion_indices > buffer_start]
332
+
333
+ if len(motion_indices) > 0:
334
+ swing_start = int(motion_indices[0])
335
+ peak_idx = motion_indices[np.argmax(velocity_mag[motion_indices])]
336
+ target_y = smoothed[swing_start]
337
+ post_peak_range = smoothed[peak_idx+1:]
338
+ swing_end = peak_idx + 1 + int(np.nanargmin(np.abs(post_peak_range - target_y))) if len(post_peak_range) > 0 else len(wrist_y) - 1
339
+ else:
340
+ swing_start = 0
341
+ swing_end = len(wrist_y) - 1
342
+
343
+ # === Dynamic Address detection ===
344
+ flat_std_thresh = 1.0
345
+ min_flat_frames = 10
346
+ address_start = 0
347
+ for i in range(swing_start - min_flat_frames, 0, -1):
348
+ window = smoothed[i:swing_start]
349
+ if np.count_nonzero(~np.isnan(window)) < min_flat_frames:
350
+ continue
351
+ if np.nanstd(window) < flat_std_thresh:
352
+ address_start = i
353
+ else:
354
+ break
355
+ address_end = swing_start
356
+
357
+ # === Top of backswing ===
358
+ full_swing_segment = smoothed[swing_start:swing_end]
359
+ diff = np.diff(full_swing_segment)
360
+ rising_indices = np.where(diff > 0)[0]
361
+ top_candidate_max = swing_start + rising_indices[0] if len(rising_indices) > 0 else min(swing_start + 20, len(smoothed) - 1)
362
+ top_search_end = min(top_candidate_max, swing_end)
363
+ top_idx = swing_start + int(np.nanargmin(smoothed[swing_start:top_search_end])) if top_search_end > swing_start else swing_start
364
+ top_range = (max(0, top_idx - 5), min(len(wrist_y) - 1, top_idx + 5))
365
+
366
+ # === Impact ===
367
+ post_top = smoothed[top_range[1]:swing_end]
368
+ if len(post_top) == 0 or np.all(np.isnan(post_top)):
369
+ impact_range = (top_range[1], top_range[1] + 1)
370
+ else:
371
+ local_max_idx = int(np.nanargmax(post_top))
372
+ max_val = post_top[local_max_idx]
373
+ local_min = np.nanmin(post_top)
374
+ local_max = np.nanmax(post_top)
375
+ dynamic_tol = 0.05 * (local_max - local_min)
376
+ left = local_max_idx
377
+ right = local_max_idx
378
+ while left > 0 and abs(post_top[left - 1] - max_val) < dynamic_tol:
379
+ left -= 1
380
+ while right < len(post_top) - 1 and abs(post_top[right + 1] - max_val) < dynamic_tol:
381
+ right += 1
382
+ impact_range = (top_range[1] + left, top_range[1] + right)
383
+
384
+ # === Phase dictionary ===
385
+ phase_ranges = {
386
+ "Address": (address_start, address_end),
387
+ "Backswing": (swing_start, top_range[0]),
388
+ "Top": top_range,
389
+ "Downswing": (top_range[1], impact_range[0]),
390
+ "Impact": impact_range,
391
+ "Follow Through": (impact_range[1], swing_end)
392
+ }
393
+
394
+ # === Plotting with gaps and flipped Y ===
395
+ x = np.array([d['frame_idx'] for d in frame_data])
396
+ y = np.array([d['wrist_y'] if d['wrist_y'] is not None else np.nan for d in frame_data], dtype=np.float32)
397
+ valid_y = y[~np.isnan(y)]
398
+ y_flipped = np.nan if len(valid_y) == 0 else np.max(valid_y) - y + np.min(valid_y)
399
+
400
+ plt.figure(figsize=(12, 6))
401
+ plt.plot(x, y_flipped, label='Wrist Y (flipped)', color='black')
402
+ colors = ['gray', 'blue', 'orange', 'green', 'red', 'purple']
403
+ for i, (phase, (start, end)) in enumerate(phase_ranges.items()):
404
+ plt.axvspan(start, end, alpha=0.3, color=colors[i % len(colors)], label=phase)
405
+ plt.xlabel("Frame Index")
406
+ plt.ylabel("Wrist Y (flipped)")
407
+ plt.title("Wrist Trajectory with Dynamic Phase Detection")
408
+ plt.legend(loc='upper left')
409
+ plt.grid(True)
410
+ plt.tight_layout()
411
+ plt.show()
412
+
413
+ return phase_ranges, swing_start, swing_end, wrist_y, smoothed
414
+
415
+
416
+ phase_ranges_iron, swing_start_iron, swing_end_iron, wrist_y_iron, smoothed_iron = detect_swing_phases(frame_data_iron)
417
+ print("🏌️‍♂️ Detected Phase Ranges:", phase_ranges_iron)
418
+
419
+ phase_ranges_driver, swing_start_driver, swing_end_driver, wrist_y_driver, smoothed_driver = detect_swing_phases(frame_data_driver)
420
+ print("🏌️‍♂️ Detected Phase Ranges:", phase_ranges_driver)
421
+
422
+ phase_ranges_iron_reference, swing_start, swing_end, wrist_y, smoothed = detect_swing_phases(frame_data_iron_reference)
423
+ print("🏌️‍♂️ Detected Phase Ranges:", phase_ranges_iron_reference)
424
+
425
+ phase_ranges_driver_reference, swing_start, swing_end, wrist_y, smoothed = detect_swing_phases(frame_data_driver_reference)
426
+ print("🏌️‍♂️ Detected Phase Ranges:", phase_ranges_driver_reference)
427
+
428
+ # ### 🧑‍🎓📹 Pro vs Student Swing Comparison by Phase (with Pose Overlay)
429
+ #
430
+ # The `compare_swing_phases()` function visually compares each swing phase between a **student video** and a **pro reference video**, displaying side-by-side frames (optionally with pose landmarks).
431
+ #
432
+ # #### 📦 Inputs:
433
+ # - `student_video_path`, `pro_video_path`: Paths to fixed videos (with silent audio).
434
+ # - `student_phases`, `pro_phases`: Dicts of frame ranges per phase (from `detect_swing_phases()`).
435
+ # - `show_pose`: If `True`, overlays pose landmarks using MediaPipe.
436
+ #
437
+ # #### 🧠 Core Logic:
438
+ # - For each swing phase (e.g., `"Address"`, `"Impact"`):
439
+ # - Finds the middle frame of that phase for both student and pro.
440
+ # - Extracts and optionally annotates frames using MediaPipe Pose.
441
+ # - Converts BGR → RGB for display compatibility.
442
+ #
443
+ # #### 🎨 Output:
444
+ # - A multi-row Matplotlib figure with:
445
+ # - Left: Student frame for each phase.
446
+ # - Right: Pro frame for the same phase.
447
+ # - Optional pose overlay to help analyze alignment, posture, and joint positioning.
448
+ #
449
+ # #### 🧹 Cleanup:
450
+ # - Releases video capture resources and closes the MediaPipe model.
451
+ #
452
+ # ✅ Use this for **visual feedback and coaching**, making swing differences easy to spot phase-by-phase.
453
+ #
454
+
455
+
456
+
457
+
458
+ def compare_swing_phases(
459
+ student_video_path, student_phases,
460
+ pro_video_path, pro_phases,
461
+ show_pose=True
462
+ ):
463
+ mp_pose = mp.solutions.pose
464
+ pose = mp_pose.Pose(static_image_mode=True)
465
+ mp_drawing = mp.solutions.drawing_utils
466
+ mp_style = mp.solutions.drawing_styles
467
+
468
+ def get_frame(cap, frame_idx, apply_pose=False):
469
+ cap.set(cv2.CAP_PROP_POS_FRAMES, frame_idx)
470
+ ret, frame = cap.read()
471
+ if not ret:
472
+ raise ValueError(f"❌ Failed to read frame {frame_idx}")
473
+ if apply_pose:
474
+ rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
475
+ results = pose.process(rgb)
476
+ if results.pose_landmarks:
477
+ mp_drawing.draw_landmarks(
478
+ frame,
479
+ results.pose_landmarks,
480
+ mp_pose.POSE_CONNECTIONS,
481
+ landmark_drawing_spec=mp_style.get_default_pose_landmarks_style()
482
+ )
483
+ return frame
484
+
485
+ student_cap = cv2.VideoCapture(student_video_path)
486
+ pro_cap = cv2.VideoCapture(pro_video_path)
487
+
488
+ phase_names = list(student_phases.keys())
489
+ num_phases = len(phase_names)
490
+
491
+ plt.figure(figsize=(16, 6 * num_phases))
492
+
493
+ for i, phase in enumerate(phase_names):
494
+ # === Frame indices ===
495
+ s_start, s_end = student_phases[phase]
496
+ p_start, p_end = pro_phases[phase]
497
+
498
+ s_middle = int((s_start + s_end) // 2)
499
+ p_middle = int((p_start + p_end) // 2)
500
+
501
+ # === Load frames ===
502
+ student_frame = get_frame(student_cap, s_middle, apply_pose=show_pose)
503
+ pro_frame = get_frame(pro_cap, p_middle, apply_pose=show_pose)
504
+
505
+ student_rgb = cv2.cvtColor(student_frame, cv2.COLOR_BGR2RGB)
506
+ pro_rgb = cv2.cvtColor(pro_frame, cv2.COLOR_BGR2RGB)
507
+
508
+ # === Plot ===
509
+ plt.subplot(num_phases, 2, 2*i+1)
510
+ plt.imshow(student_rgb)
511
+ plt.title(f"Student - {phase}")
512
+ plt.axis("off")
513
+
514
+ plt.subplot(num_phases, 2, 2*i+2)
515
+ plt.imshow(pro_rgb)
516
+ plt.title(f"Pro - {phase}")
517
+ plt.axis("off")
518
+
519
+ plt.tight_layout()
520
+ plt.show()
521
+
522
+ student_cap.release()
523
+ pro_cap.release()
524
+ pose.close()
525
+
526
+
527
+ compare_swing_phases(
528
+ student_video_path=video_iron_fixed,
529
+ student_phases=phase_ranges_iron,
530
+ pro_video_path=iron_reference_fixed,
531
+ pro_phases=phase_ranges_iron_reference,
532
+ show_pose=True # Set to False to skip pose overlay
533
+ )
534
+
535
+
536
+ compare_swing_phases(
537
+ student_video_path=video_driver_fixed,
538
+ student_phases=phase_ranges_driver,
539
+ pro_video_path=driver_reference_fixed,
540
+ pro_phases=phase_ranges_driver_reference,
541
+ show_pose=True # Set to False to skip pose overlay
542
+ )
543
+
544
+
545
+ # ### 🎥 Generate Annotated Debug Video with Swing Overlay and Slow Motion
546
+ #
547
+ # This function `generate_debug_video_with_overlay()` creates a side-by-side **video+plot debug visualization**, showing real-time wrist motion trajectory alongside the actual video frames.
548
+ #
549
+ # ---
550
+ #
551
+ # #### 🔁 Step-by-Step Workflow:
552
+ #
553
+ # - **Frame-by-Frame Rendering**:
554
+ # - Reads each frame from the input video.
555
+ # - Overlays a Matplotlib plot of smoothed wrist Y-position (`smoothed`), marks current frame, swing start/end, and displays swing phase label (via `get_swing_phase_label()`).
556
+ #
557
+ # - **Combined Output**:
558
+ # - Horizontally stacks video frame + debug plot.
559
+ # - Writes to an MP4 video using OpenCV (`cv2.VideoWriter`).
560
+ #
561
+ # - **Plot Details**:
562
+ # - Flipped Y-axis for intuitive visualization.
563
+ # - Clear phase zones (`Address`, `Backswing`, etc.) shown with labels and color spans.
564
+ #
565
+ # ---
566
+ #
567
+ # #### 🐢 Slow-Mo Re-encoding:
568
+ # - After video is saved, re-encodes it using **FFmpeg** to:
569
+ # - Insert a slow-motion segment during the swing window (`swing_start` → `swing_end`).
570
+ # - Concatenate trimmed clips before, during (slowed), and after the swing.
571
+ # - Save to a more compatible format (`*_playable.mp4`).
572
+ #
573
+ # ---
574
+ #
575
+ # #### 📤 Returns:
576
+ # - A Colab-embedded video (`IPython.display.Video`) showing:
577
+ # - Original clip + live tracking plot.
578
+ # - Frame-wise swing phase.
579
+ # - Smooth debug overlay and swing window.
580
+ #
581
+ # 🎯 Perfect for **visual diagnostics**, **student feedback**, and **explaining biomechanics** with frame-accurate debug context.
582
+ #
583
+
584
+
585
+
586
+ # Get swing phase label for a given frame index
587
+ def get_swing_phase_label(frame_idx, phase_ranges):
588
+ for label, (start, end) in phase_ranges.items():
589
+ if start <= frame_idx <= end:
590
+ return label
591
+ return None
592
+
593
+ def generate_debug_video_with_overlay(
594
+ video_filename: str,
595
+ output_path: str,
596
+ wrist_y: list,
597
+ smoothed: list,
598
+ phase_ranges: dict,
599
+ swing_start: int,
600
+ swing_end: int
601
+ ):
602
+ import gc
603
+ import cv2
604
+ import numpy as np
605
+ import matplotlib.pyplot as plt
606
+ from matplotlib.backends.backend_agg import FigureCanvasAgg as FigureCanvas
607
+ from matplotlib import use as mpl_use
608
+ mpl_use("Agg") # ✅ For headless backend in Colab
609
+
610
+ cap = cv2.VideoCapture(video_filename)
611
+ width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
612
+ height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
613
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
614
+ fps = cap.get(cv2.CAP_PROP_FPS)
615
+
616
+ print(f"📹 Frame resolution: {width}x{height}, FPS: {fps:.2f}, Total frames: {total_frames}")
617
+
618
+ plot_width = int(width * 2.0)
619
+ output_size = (width + plot_width, height)
620
+
621
+ # ✅ Use mp4v codec for writing temporary mp4
622
+ fourcc = cv2.VideoWriter_fourcc(*'mp4v')
623
+ out = cv2.VideoWriter(output_path, fourcc, fps, output_size)
624
+
625
+ fig, ax = plt.subplots(figsize=(plot_width / 100.0, height / 100.0), dpi=100)
626
+ canvas = FigureCanvas(fig)
627
+
628
+ cap.set(cv2.CAP_PROP_POS_FRAMES, 0)
629
+ frame_idx = 0
630
+ print("🔄 Rendering video with overlaid debug plots...")
631
+
632
+ while cap.isOpened():
633
+ ret, frame = cap.read()
634
+ if not ret or frame is None:
635
+ break
636
+
637
+ if frame_idx >= len(wrist_y):
638
+ print(f"📌 Reached end of trajectory data at frame {frame_idx}.")
639
+ break
640
+
641
+ try:
642
+ ax.clear()
643
+ ax.plot(smoothed, label="Smoothed Wrist Y", linestyle='--', color='blue')
644
+ ax.axvline(frame_idx, color='black', linestyle='-', label="Current Frame")
645
+
646
+ # ✅ Phase label helper (you must define or import this)
647
+ phase_label = get_swing_phase_label(frame_idx, phase_ranges)
648
+ if phase_label:
649
+ ax.text(0.02, 0.95, phase_label, transform=ax.transAxes,
650
+ fontsize=18, color='black', fontweight='bold',
651
+ verticalalignment='top', horizontalalignment='left',
652
+ bbox=dict(facecolor='white', alpha=0.7, boxstyle='round'))
653
+
654
+ ax.axvspan(swing_start, swing_end, color='green', alpha=0.2, label="Swing Window")
655
+
656
+ ax.set_title(f"Debug Plot (Frame {frame_idx})", fontsize=20)
657
+ ax.set_xlabel("Frame", fontsize=16)
658
+ ax.set_ylabel("Wrist Y Position", fontsize=16)
659
+ ax.set_xlim(0, len(wrist_y))
660
+ ax.set_ylim(np.nanmax(wrist_y) + 10, np.nanmin(wrist_y) - 10)
661
+ ax.legend(fontsize=14)
662
+ fig.tight_layout()
663
+ canvas.draw()
664
+
665
+ plot_img = np.frombuffer(canvas.buffer_rgba(), dtype=np.uint8).reshape(
666
+ canvas.get_width_height()[1], canvas.get_width_height()[0], 4
667
+ )[..., :3]
668
+
669
+ plot_img = cv2.resize(plot_img, (plot_width, height))
670
+ frame = cv2.resize(frame, (width, height))
671
+ combined_frame = np.hstack((frame, plot_img))
672
+ out.write(combined_frame)
673
+
674
+ if frame_idx % 50 == 0:
675
+ print(f"✅ Processed frame {frame_idx}/{total_frames}")
676
+
677
+ frame_idx += 1
678
+ gc.collect()
679
+
680
+ except Exception as e:
681
+ print(f"❌ Error at frame {frame_idx}: {e}")
682
+ break
683
+
684
+ cap.release()
685
+ out.release()
686
+ print(f"✅ Debug video saved as {output_path}")
687
+
688
+ # === Re-encode with FFmpeg ===
689
+ reencoded_path = output_path.replace(".mp4", "_playable.mp4")
690
+
691
+ # Convert swing frame indices to seconds
692
+ start_time = swing_start / fps
693
+ end_time = swing_end / fps
694
+ slow_factor = 2.0 # Adjust this factor as needed
695
+
696
+ # FFmpeg slow-motion filter logic
697
+ filter_complex = (
698
+ f"[0:v]trim=0:{start_time:.3f},setpts=PTS-STARTPTS[v1];"
699
+ f"[0:v]trim={start_time:.3f}:{end_time:.3f},setpts={slow_factor}*(PTS-STARTPTS)[v2];"
700
+ f"[0:v]trim={end_time:.3f},setpts=PTS-STARTPTS[v3];"
701
+ f"[v1][v2][v3]concat=n=3:v=1:a=0[outv]"
702
+ )
703
+
704
+ cmd = [
705
+ "ffmpeg", "-y", "-i", output_path,
706
+ "-filter_complex", filter_complex,
707
+ "-map", "[outv]",
708
+ "-vcodec", "libx264", "-crf", "23", "-preset", "veryfast",
709
+ "-pix_fmt", "yuv420p",
710
+ reencoded_path
711
+ ]
712
+
713
+ print("🎬 Re-encoding with slow-motion for Colab compatibility...")
714
+ subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
715
+
716
+
717
+ # Optional: give disk a moment to flush
718
+ time.sleep(1)
719
+
720
+ return Video(reencoded_path, embed=True, width=960)
721
+
722
+
723
+ debug_video = generate_debug_video_with_overlay(video_iron_fixed, "iron_debug.mp4", wrist_y_iron, smoothed_iron, phase_ranges_iron, swing_start_iron, swing_end_iron)
724
+
725
+ debug_video
726
+
727
+
728
+ debug_video = generate_debug_video_with_overlay(video_driver_fixed, "iron_debug.mp4", wrist_y_driver, smoothed_driver, phase_ranges_driver, swing_start_driver, swing_end_driver)
729
+
730
+ debug_video
extract.py CHANGED
@@ -33,18 +33,15 @@ def run_ai_extraction(video_path, slow_factor=1.0, output_dir=None, skip_slow_vi
33
  video_name = os.path.basename(video_path)
34
  video_prefix = os.path.splitext(video_name)[0]
35
 
36
- # Nếu không có output_dir, dùng mặc định
37
  if output_dir is None:
38
  output_dir = os.path.join('output', video_prefix)
39
 
40
  model_path = 'models/swingnet_1800.pth.tar'
41
  phases_dir = os.path.join(output_dir, 'phases')
42
 
43
- # Thêm số thứ tự để đảm bảo sắp xếp đúng trong thư mục
44
  labels = ['1_Address', '2_Toe-up', '3_Mid-Backswing', '4_Top',
45
  '5_Mid-Downswing', '6_Impact', '7_Mid-Follow-Through', '8_Finish']
46
 
47
- # Chỉ tạo thư mục phases nếu cần lưu ảnh
48
  if not skip_phase_images:
49
  for label in labels:
50
  os.makedirs(os.path.join(phases_dir, label), exist_ok=True)
@@ -74,7 +71,7 @@ def run_ai_extraction(video_path, slow_factor=1.0, output_dir=None, skip_slow_vi
74
  print("Không tìm thấy frame nào.")
75
  return
76
 
77
- # Frame Interpolation (Nội suy tuyến tính)
78
  if slow_factor < 1.0:
79
  if not production:
80
  print(f"Đang nội suy frame (Slow-mo {slow_factor}x)...")
@@ -115,13 +112,20 @@ def run_ai_extraction(video_path, slow_factor=1.0, output_dir=None, skip_slow_vi
115
  else:
116
  full_res_frames = raw_frames
117
 
118
- # Tiền xử lý cho AI
119
  if not production:
120
  print("Đang tiền xử lý cho AI...")
121
  images = []
122
  input_size = 160
123
  for img in tqdm(full_res_frames, disable=production):
124
  h, w = img.shape[:2]
 
 
 
 
 
 
 
 
125
  ratio = input_size / max(h, w)
126
  new_size = (int(w * ratio), int(h * ratio))
127
  resized = cv2.resize(img, new_size)
@@ -145,15 +149,11 @@ def run_ai_extraction(video_path, slow_factor=1.0, output_dir=None, skip_slow_vi
145
  logits_concat = np.concatenate(all_logits, axis=0)
146
  probs = F.softmax(torch.tensor(logits_concat), dim=1).numpy()
147
 
148
- # Làm mượt kết quả để giảm nhiễu (đặc biệt cho DTL)
149
  probs = smooth_probs(probs, window_size=5)
150
 
151
- # Áp dụng thuật toán Anchor-based Bidirectional Search
152
- # 1. Tìm sự kiện "Neo" (Anchor) đáng tin cậy nhất (thường là Impact hoặc Top)
153
- # Chỉ xét 8 lớp sự kiện đầu tiên, bỏ qua lớp số 9 (No Event/Background)
154
  probs_events = probs[:, :8]
155
- max_probs = np.max(probs_events, axis=0) # Max prob của từng class sự kiện
156
- anchor_class = np.argmax(max_probs) # Class có độ tự tin cao nhất trong 8 sự kiện
157
  anchor_frame = np.argmax(probs_events[:, anchor_class])
158
 
159
  if not production:
@@ -162,11 +162,9 @@ def run_ai_extraction(video_path, slow_factor=1.0, output_dir=None, skip_slow_vi
162
  events = np.zeros(8, dtype=int)
163
  events[anchor_class] = anchor_frame
164
 
165
- # 2. Đi lùi: Tìm các pha trước Anchor
166
  current_limit = anchor_frame
167
  for i in range(anchor_class - 1, -1, -1):
168
  if current_limit > 0:
169
- # Tìm max trong vùng cho phép [0, current_limit]
170
  segment = probs[0:current_limit, i]
171
  if len(segment) > 0:
172
  events[i] = np.argmax(segment)
@@ -176,12 +174,11 @@ def run_ai_extraction(video_path, slow_factor=1.0, output_dir=None, skip_slow_vi
176
  events[i] = 0
177
  current_limit = events[i]
178
 
179
- # 3. Đi tiến: Tìm các pha sau Anchor
180
  current_limit = anchor_frame
181
  total_frames = probs.shape[0]
182
  for i in range(anchor_class + 1, 8):
183
  if current_limit < total_frames - 1:
184
- # Tìm max trong vùng cho phép [current_limit + 1, end]
185
  # Bắt buộc tiến ít nhất 1 frame
186
  segment = probs[current_limit + 1:, i]
187
  if len(segment) > 0:
@@ -195,11 +192,9 @@ def run_ai_extraction(video_path, slow_factor=1.0, output_dir=None, skip_slow_vi
195
  if not production:
196
  print(f"Detected Events (Frames): {events}")
197
 
198
- # Lưu thông tin frame index để visual_report sử dụng
199
  event_metadata = {}
200
  for i, frame_idx in enumerate(events):
201
  if frame_idx < len(full_res_frames):
202
- # Chỉ lưu ảnh nếu không skip
203
  if not skip_phase_images:
204
  cv2.imwrite(os.path.join(phases_dir, labels[i], f"{video_prefix}.jpg"), full_res_frames[frame_idx])
205
  event_metadata[labels[i]] = int(frame_idx)
 
33
  video_name = os.path.basename(video_path)
34
  video_prefix = os.path.splitext(video_name)[0]
35
 
 
36
  if output_dir is None:
37
  output_dir = os.path.join('output', video_prefix)
38
 
39
  model_path = 'models/swingnet_1800.pth.tar'
40
  phases_dir = os.path.join(output_dir, 'phases')
41
 
 
42
  labels = ['1_Address', '2_Toe-up', '3_Mid-Backswing', '4_Top',
43
  '5_Mid-Downswing', '6_Impact', '7_Mid-Follow-Through', '8_Finish']
44
 
 
45
  if not skip_phase_images:
46
  for label in labels:
47
  os.makedirs(os.path.join(phases_dir, label), exist_ok=True)
 
71
  print("Không tìm thấy frame nào.")
72
  return
73
 
74
+ # Frame Interpolation
75
  if slow_factor < 1.0:
76
  if not production:
77
  print(f"Đang nội suy frame (Slow-mo {slow_factor}x)...")
 
112
  else:
113
  full_res_frames = raw_frames
114
 
 
115
  if not production:
116
  print("Đang tiền xử lý cho AI...")
117
  images = []
118
  input_size = 160
119
  for img in tqdm(full_res_frames, disable=production):
120
  h, w = img.shape[:2]
121
+ if h > w:
122
+ center_y = h // 2
123
+ half_w = w // 2
124
+ start_y = max(0, center_y - half_w)
125
+ end_y = min(h, center_y + half_w)
126
+ img = img[start_y:end_y, :]
127
+ h, w = img.shape[:2]
128
+
129
  ratio = input_size / max(h, w)
130
  new_size = (int(w * ratio), int(h * ratio))
131
  resized = cv2.resize(img, new_size)
 
149
  logits_concat = np.concatenate(all_logits, axis=0)
150
  probs = F.softmax(torch.tensor(logits_concat), dim=1).numpy()
151
 
 
152
  probs = smooth_probs(probs, window_size=5)
153
 
 
 
 
154
  probs_events = probs[:, :8]
155
+ max_probs = np.max(probs_events, axis=0)
156
+ anchor_class = np.argmax(max_probs)
157
  anchor_frame = np.argmax(probs_events[:, anchor_class])
158
 
159
  if not production:
 
162
  events = np.zeros(8, dtype=int)
163
  events[anchor_class] = anchor_frame
164
 
 
165
  current_limit = anchor_frame
166
  for i in range(anchor_class - 1, -1, -1):
167
  if current_limit > 0:
 
168
  segment = probs[0:current_limit, i]
169
  if len(segment) > 0:
170
  events[i] = np.argmax(segment)
 
174
  events[i] = 0
175
  current_limit = events[i]
176
 
 
177
  current_limit = anchor_frame
178
  total_frames = probs.shape[0]
179
  for i in range(anchor_class + 1, 8):
180
  if current_limit < total_frames - 1:
181
+ segment = probs[current_limit + 1:, i]
182
  # Bắt buộc tiến ít nhất 1 frame
183
  segment = probs[current_limit + 1:, i]
184
  if len(segment) > 0:
 
192
  if not production:
193
  print(f"Detected Events (Frames): {events}")
194
 
 
195
  event_metadata = {}
196
  for i, frame_idx in enumerate(events):
197
  if frame_idx < len(full_res_frames):
 
198
  if not skip_phase_images:
199
  cv2.imwrite(os.path.join(phases_dir, labels[i], f"{video_prefix}.jpg"), full_res_frames[frame_idx])
200
  event_metadata[labels[i]] = int(frame_idx)
hf_api_result.json → golf_analysis.json RENAMED
@@ -1,300 +1,300 @@
1
  {
2
  "status": "success",
3
- "job_id": "b5a70272-d520-4eb4-bc91-7e92d1d1ec12",
4
  "metadata": {
5
  "event_frames": {
6
- "1_Address": 10,
7
- "2_Toe-up": 34,
8
- "3_Mid-Backswing": 43,
9
- "4_Top": 47,
10
- "5_Mid-Downswing": 60,
11
- "6_Impact": 62,
12
- "7_Mid-Follow-Through": 66,
13
- "8_Finish": 85
14
  },
15
- "slow_factor": 1.0,
16
- "fps": 29.97
17
  },
18
  "analysis": {
19
- "video_id": "b5a70272-d520-4eb4-bc91-7e92d1d1ec12",
20
  "phases": {
21
  "1_Address": {
22
- "score": 9.0,
23
  "comments": [
24
- " thế đứng quá rộng"
25
  ],
26
  "data": {
27
- "stance_ratio": 1.5782203786848197
28
  },
29
  "raw_landmarks": [
30
  {
31
  "id": 0,
32
  "name": "NOSE",
33
- "x": 0.4693,
34
- "y": 0.2687,
35
- "z": -0.4986,
36
- "visibility": 0.9995
37
  },
38
  {
39
  "id": 1,
40
  "name": "LEFT_EYE_INNER",
41
- "x": 0.4711,
42
- "y": 0.2531,
43
- "z": -0.4942,
44
- "visibility": 0.9981
45
  },
46
  {
47
  "id": 2,
48
  "name": "LEFT_EYE",
49
- "x": 0.4738,
50
- "y": 0.2497,
51
- "z": -0.4942,
52
- "visibility": 0.9983
53
  },
54
  {
55
  "id": 3,
56
  "name": "LEFT_EYE_OUTER",
57
- "x": 0.4764,
58
- "y": 0.2454,
59
- "z": -0.4943,
60
- "visibility": 0.9985
61
  },
62
  {
63
  "id": 4,
64
  "name": "RIGHT_EYE_INNER",
65
- "x": 0.4634,
66
- "y": 0.2575,
67
- "z": -0.4906,
68
- "visibility": 0.9981
69
  },
70
  {
71
  "id": 5,
72
  "name": "RIGHT_EYE",
73
- "x": 0.4607,
74
- "y": 0.2575,
75
- "z": -0.4907,
76
- "visibility": 0.9981
77
  },
78
  {
79
  "id": 6,
80
  "name": "RIGHT_EYE_OUTER",
81
- "x": 0.458,
82
- "y": 0.2572,
83
- "z": -0.4908,
84
- "visibility": 0.9982
85
  },
86
  {
87
  "id": 7,
88
  "name": "LEFT_EAR",
89
- "x": 0.4796,
90
- "y": 0.2343,
91
- "z": -0.3949,
92
- "visibility": 0.9967
93
  },
94
  {
95
  "id": 8,
96
  "name": "RIGHT_EAR",
97
- "x": 0.4553,
98
- "y": 0.2518,
99
- "z": -0.3741,
100
- "visibility": 0.9957
101
  },
102
  {
103
  "id": 9,
104
  "name": "MOUTH_LEFT",
105
- "x": 0.4758,
106
- "y": 0.274,
107
- "z": -0.4548,
108
- "visibility": 0.9991
109
  },
110
  {
111
  "id": 10,
112
  "name": "MOUTH_RIGHT",
113
- "x": 0.4663,
114
- "y": 0.279,
115
- "z": -0.4491,
116
- "visibility": 0.9987
117
  },
118
  {
119
  "id": 11,
120
  "name": "LEFT_SHOULDER",
121
- "x": 0.519,
122
- "y": 0.2811,
123
- "z": -0.2811,
124
- "visibility": 0.9999
125
  },
126
  {
127
  "id": 12,
128
  "name": "RIGHT_SHOULDER",
129
- "x": 0.4423,
130
- "y": 0.3185,
131
- "z": -0.2545,
132
- "visibility": 0.9999
133
  },
134
  {
135
  "id": 13,
136
  "name": "LEFT_ELBOW",
137
- "x": 0.5202,
138
- "y": 0.3966,
139
- "z": -0.2576,
140
- "visibility": 0.9708
141
  },
142
  {
143
  "id": 14,
144
  "name": "RIGHT_ELBOW",
145
- "x": 0.4681,
146
- "y": 0.4256,
147
- "z": -0.2122,
148
- "visibility": 0.9224
149
  },
150
  {
151
  "id": 15,
152
  "name": "LEFT_WRIST",
153
- "x": 0.5041,
154
- "y": 0.5106,
155
- "z": -0.3461,
156
- "visibility": 0.9246
157
  },
158
  {
159
  "id": 16,
160
  "name": "RIGHT_WRIST",
161
- "x": 0.4844,
162
- "y": 0.5218,
163
- "z": -0.2876,
164
- "visibility": 0.8555
165
  },
166
  {
167
  "id": 17,
168
  "name": "LEFT_PINKY",
169
- "x": 0.5041,
170
- "y": 0.5434,
171
- "z": -0.3784,
172
- "visibility": 0.8635
173
  },
174
  {
175
  "id": 18,
176
  "name": "RIGHT_PINKY",
177
- "x": 0.4867,
178
- "y": 0.5554,
179
- "z": -0.3171,
180
- "visibility": 0.7781
181
  },
182
  {
183
  "id": 19,
184
  "name": "LEFT_INDEX",
185
- "x": 0.5,
186
- "y": 0.5484,
187
- "z": -0.4054,
188
- "visibility": 0.8708
189
  },
190
  {
191
  "id": 20,
192
  "name": "RIGHT_INDEX",
193
- "x": 0.4878,
194
- "y": 0.5578,
195
- "z": -0.347,
196
- "visibility": 0.7853
197
  },
198
  {
199
  "id": 21,
200
  "name": "LEFT_THUMB",
201
- "x": 0.4998,
202
- "y": 0.5346,
203
- "z": -0.3575,
204
- "visibility": 0.8129
205
  },
206
  {
207
  "id": 22,
208
  "name": "RIGHT_THUMB",
209
- "x": 0.4882,
210
- "y": 0.5451,
211
- "z": -0.3002,
212
- "visibility": 0.7348
213
  },
214
  {
215
  "id": 23,
216
  "name": "LEFT_HIP",
217
- "x": 0.5221,
218
- "y": 0.4922,
219
- "z": -0.0104,
220
- "visibility": 0.9997
221
  },
222
  {
223
  "id": 24,
224
  "name": "RIGHT_HIP",
225
- "x": 0.4712,
226
- "y": 0.4939,
227
- "z": 0.0104,
228
- "visibility": 0.9997
229
  },
230
  {
231
  "id": 25,
232
  "name": "LEFT_KNEE",
233
- "x": 0.5345,
234
- "y": 0.6575,
235
- "z": -0.0671,
236
- "visibility": 0.9943
237
  },
238
  {
239
  "id": 26,
240
  "name": "RIGHT_KNEE",
241
- "x": 0.4553,
242
- "y": 0.662,
243
- "z": -0.0487,
244
- "visibility": 0.9941
245
  },
246
  {
247
  "id": 27,
248
  "name": "LEFT_ANKLE",
249
- "x": 0.5542,
250
- "y": 0.8225,
251
- "z": 0.0342,
252
- "visibility": 0.9961
253
  },
254
  {
255
  "id": 28,
256
  "name": "RIGHT_ANKLE",
257
- "x": 0.4332,
258
- "y": 0.817,
259
- "z": 0.0642,
260
- "visibility": 0.9956
261
  },
262
  {
263
  "id": 29,
264
  "name": "LEFT_HEEL",
265
- "x": 0.5536,
266
- "y": 0.8439,
267
- "z": 0.0359,
268
- "visibility": 0.8988
269
  },
270
  {
271
  "id": 30,
272
  "name": "RIGHT_HEEL",
273
- "x": 0.4355,
274
- "y": 0.8382,
275
- "z": 0.0686,
276
- "visibility": 0.9048
277
  },
278
  {
279
  "id": 31,
280
  "name": "LEFT_FOOT_INDEX",
281
- "x": 0.556,
282
- "y": 0.864,
283
- "z": -0.1057,
284
- "visibility": 0.9901
285
  },
286
  {
287
  "id": 32,
288
  "name": "RIGHT_FOOT_INDEX",
289
- "x": 0.4192,
290
- "y": 0.8605,
291
- "z": -0.0577,
292
- "visibility": 0.9912
293
  }
294
  ]
295
  },
296
  "2_Toe-up": {
297
- "score": 10.0,
298
  "comments": [
299
  "Đạt chuẩn"
300
  ],
@@ -303,271 +303,271 @@
303
  {
304
  "id": 0,
305
  "name": "NOSE",
306
- "x": 0.4615,
307
- "y": 0.2694,
308
- "z": -0.3643,
309
- "visibility": 0.9996
310
  },
311
  {
312
  "id": 1,
313
  "name": "LEFT_EYE_INNER",
314
- "x": 0.4649,
315
- "y": 0.2545,
316
- "z": -0.3613,
317
- "visibility": 0.9991
318
  },
319
  {
320
  "id": 2,
321
  "name": "LEFT_EYE",
322
- "x": 0.4671,
323
- "y": 0.2534,
324
- "z": -0.3614,
325
- "visibility": 0.9991
326
  },
327
  {
328
  "id": 3,
329
  "name": "LEFT_EYE_OUTER",
330
- "x": 0.4694,
331
- "y": 0.2518,
332
- "z": -0.3614,
333
- "visibility": 0.999
334
  },
335
  {
336
  "id": 4,
337
  "name": "RIGHT_EYE_INNER",
338
- "x": 0.4585,
339
- "y": 0.2527,
340
- "z": -0.3497,
341
- "visibility": 0.9992
342
  },
343
  {
344
  "id": 5,
345
  "name": "RIGHT_EYE",
346
- "x": 0.4563,
347
- "y": 0.2502,
348
- "z": -0.3498,
349
- "visibility": 0.9991
350
  },
351
  {
352
  "id": 6,
353
  "name": "RIGHT_EYE_OUTER",
354
- "x": 0.4542,
355
- "y": 0.2471,
356
- "z": -0.3499,
357
- "visibility": 0.9991
358
  },
359
  {
360
  "id": 7,
361
  "name": "LEFT_EAR",
362
- "x": 0.4725,
363
- "y": 0.2462,
364
- "z": -0.2839,
365
- "visibility": 0.9984
366
  },
367
  {
368
  "id": 8,
369
  "name": "RIGHT_EAR",
370
- "x": 0.4519,
371
- "y": 0.2427,
372
- "z": -0.2313,
373
- "visibility": 0.9983
374
  },
375
  {
376
  "id": 9,
377
  "name": "MOUTH_LEFT",
378
- "x": 0.4659,
379
- "y": 0.279,
380
- "z": -0.3305,
381
- "visibility": 0.9988
382
  },
383
  {
384
  "id": 10,
385
  "name": "MOUTH_RIGHT",
386
- "x": 0.4583,
387
- "y": 0.277,
388
- "z": -0.3157,
389
- "visibility": 0.999
390
  },
391
  {
392
  "id": 11,
393
  "name": "LEFT_SHOULDER",
394
- "x": 0.4873,
395
- "y": 0.3177,
396
- "z": -0.2594,
397
- "visibility": 0.9997
398
  },
399
  {
400
  "id": 12,
401
  "name": "RIGHT_SHOULDER",
402
- "x": 0.4443,
403
- "y": 0.3034,
404
- "z": -0.0692,
405
- "visibility": 0.9997
406
  },
407
  {
408
  "id": 13,
409
  "name": "LEFT_ELBOW",
410
- "x": 0.4416,
411
- "y": 0.4263,
412
- "z": -0.271,
413
- "visibility": 0.939
414
  },
415
  {
416
  "id": 14,
417
  "name": "RIGHT_ELBOW",
418
- "x": 0.4315,
419
- "y": 0.398,
420
- "z": 0.0229,
421
- "visibility": 0.3964
422
  },
423
  {
424
  "id": 15,
425
  "name": "LEFT_WRIST",
426
- "x": 0.3966,
427
- "y": 0.4919,
428
- "z": -0.3078,
429
- "visibility": 0.8444
430
  },
431
  {
432
  "id": 16,
433
  "name": "RIGHT_WRIST",
434
- "x": 0.3999,
435
- "y": 0.482,
436
- "z": -0.0168,
437
- "visibility": 0.4553
438
  },
439
  {
440
  "id": 17,
441
  "name": "LEFT_PINKY",
442
- "x": 0.3862,
443
- "y": 0.5127,
444
- "z": -0.3414,
445
- "visibility": 0.7786
446
  },
447
  {
448
  "id": 18,
449
  "name": "RIGHT_PINKY",
450
- "x": 0.3893,
451
- "y": 0.4994,
452
- "z": -0.0239,
453
- "visibility": 0.425
454
  },
455
  {
456
  "id": 19,
457
  "name": "LEFT_INDEX",
458
- "x": 0.3865,
459
- "y": 0.5082,
460
- "z": -0.3523,
461
- "visibility": 0.7826
462
  },
463
  {
464
  "id": 20,
465
  "name": "RIGHT_INDEX",
466
- "x": 0.3898,
467
- "y": 0.4986,
468
- "z": -0.062,
469
- "visibility": 0.4507
470
  },
471
  {
472
  "id": 21,
473
  "name": "LEFT_THUMB",
474
- "x": 0.3908,
475
- "y": 0.5027,
476
- "z": -0.3124,
477
- "visibility": 0.6719
478
  },
479
  {
480
  "id": 22,
481
  "name": "RIGHT_THUMB",
482
- "x": 0.3958,
483
- "y": 0.4903,
484
- "z": -0.033,
485
- "visibility": 0.4289
486
  },
487
  {
488
  "id": 23,
489
  "name": "LEFT_HIP",
490
- "x": 0.5109,
491
- "y": 0.5169,
492
- "z": -0.0473,
493
- "visibility": 0.9992
494
  },
495
  {
496
  "id": 24,
497
  "name": "RIGHT_HIP",
498
- "x": 0.4728,
499
- "y": 0.5196,
500
- "z": 0.0473,
501
- "visibility": 0.9994
502
  },
503
  {
504
  "id": 25,
505
  "name": "LEFT_KNEE",
506
- "x": 0.5263,
507
- "y": 0.6759,
508
- "z": -0.0456,
509
- "visibility": 0.9959
510
  },
511
  {
512
  "id": 26,
513
  "name": "RIGHT_KNEE",
514
- "x": 0.4539,
515
- "y": 0.6735,
516
- "z": 0.0255,
517
- "visibility": 0.9939
518
  },
519
  {
520
  "id": 27,
521
  "name": "LEFT_ANKLE",
522
- "x": 0.5533,
523
- "y": 0.8264,
524
- "z": 0.0672,
525
- "visibility": 0.9983
526
  },
527
  {
528
  "id": 28,
529
  "name": "RIGHT_ANKLE",
530
- "x": 0.4328,
531
- "y": 0.824,
532
- "z": 0.1448,
533
- "visibility": 0.997
534
  },
535
  {
536
  "id": 29,
537
  "name": "LEFT_HEEL",
538
- "x": 0.5541,
539
- "y": 0.8476,
540
- "z": 0.07,
541
- "visibility": 0.946
542
  },
543
  {
544
  "id": 30,
545
  "name": "RIGHT_HEEL",
546
- "x": 0.4358,
547
- "y": 0.8449,
548
- "z": 0.1498,
549
- "visibility": 0.9621
550
  },
551
  {
552
  "id": 31,
553
  "name": "LEFT_FOOT_INDEX",
554
- "x": 0.5532,
555
- "y": 0.8665,
556
- "z": -0.0542,
557
- "visibility": 0.9961
558
  },
559
  {
560
  "id": 32,
561
  "name": "RIGHT_FOOT_INDEX",
562
- "x": 0.4181,
563
- "y": 0.8561,
564
- "z": 0.0462,
565
- "visibility": 0.9943
566
  }
567
  ]
568
  },
569
  "3_Mid-Backswing": {
570
- "score": 10.0,
571
  "comments": [
572
  "Đạt chuẩn"
573
  ],
@@ -576,547 +576,547 @@
576
  {
577
  "id": 0,
578
  "name": "NOSE",
579
- "x": 0.4395,
580
- "y": 0.2612,
581
- "z": -0.0376,
582
- "visibility": 0.9997
583
  },
584
  {
585
  "id": 1,
586
  "name": "LEFT_EYE_INNER",
587
- "x": 0.4412,
588
- "y": 0.2534,
589
- "z": -0.0596,
590
- "visibility": 0.9997
591
  },
592
  {
593
  "id": 2,
594
  "name": "LEFT_EYE",
595
- "x": 0.441,
596
- "y": 0.2546,
597
- "z": -0.0596,
598
- "visibility": 0.9996
599
  },
600
  {
601
  "id": 3,
602
  "name": "LEFT_EYE_OUTER",
603
- "x": 0.4406,
604
- "y": 0.2557,
605
- "z": -0.0595,
606
- "visibility": 0.9997
607
  },
608
  {
609
  "id": 4,
610
  "name": "RIGHT_EYE_INNER",
611
- "x": 0.4425,
612
- "y": 0.2471,
613
- "z": -0.0362,
614
- "visibility": 0.9996
615
  },
616
  {
617
  "id": 5,
618
  "name": "RIGHT_EYE",
619
- "x": 0.4435,
620
- "y": 0.2442,
621
- "z": -0.0363,
622
- "visibility": 0.9995
623
  },
624
  {
625
  "id": 6,
626
  "name": "RIGHT_EYE_OUTER",
627
- "x": 0.4443,
628
- "y": 0.2413,
629
- "z": -0.0364,
630
- "visibility": 0.9997
631
  },
632
  {
633
  "id": 7,
634
  "name": "LEFT_EAR",
635
- "x": 0.4453,
636
- "y": 0.2548,
637
- "z": -0.1064,
638
- "visibility": 0.9996
639
  },
640
  {
641
  "id": 8,
642
  "name": "RIGHT_EAR",
643
- "x": 0.45,
644
- "y": 0.2391,
645
- "z": -0.0033,
646
- "visibility": 0.9996
647
  },
648
  {
649
  "id": 9,
650
  "name": "MOUTH_LEFT",
651
- "x": 0.4411,
652
- "y": 0.2715,
653
- "z": -0.0492,
654
- "visibility": 0.9993
655
  },
656
  {
657
  "id": 10,
658
  "name": "MOUTH_RIGHT",
659
- "x": 0.4424,
660
- "y": 0.2672,
661
- "z": -0.0197,
662
- "visibility": 0.9996
663
  },
664
  {
665
  "id": 11,
666
  "name": "LEFT_SHOULDER",
667
- "x": 0.4503,
668
- "y": 0.3184,
669
- "z": -0.1584,
670
- "visibility": 0.9992
671
  },
672
  {
673
  "id": 12,
674
  "name": "RIGHT_SHOULDER",
675
- "x": 0.473,
676
- "y": 0.282,
677
- "z": 0.0964,
678
- "visibility": 0.9995
679
  },
680
  {
681
  "id": 13,
682
  "name": "LEFT_ELBOW",
683
- "x": 0.407,
684
- "y": 0.3367,
685
- "z": -0.1282,
686
- "visibility": 0.9579
687
  },
688
  {
689
  "id": 14,
690
  "name": "RIGHT_ELBOW",
691
- "x": 0.449,
692
- "y": 0.2905,
693
- "z": 0.2568,
694
- "visibility": 0.1393
695
  },
696
  {
697
  "id": 15,
698
  "name": "LEFT_WRIST",
699
- "x": 0.3805,
700
- "y": 0.3108,
701
- "z": 0.013,
702
- "visibility": 0.8708
703
  },
704
  {
705
  "id": 16,
706
  "name": "RIGHT_WRIST",
707
- "x": 0.4049,
708
- "y": 0.2941,
709
- "z": 0.3561,
710
- "visibility": 0.2907
711
  },
712
  {
713
  "id": 17,
714
  "name": "LEFT_PINKY",
715
- "x": 0.3714,
716
- "y": 0.3041,
717
- "z": 0.0095,
718
- "visibility": 0.8563
719
  },
720
  {
721
  "id": 18,
722
  "name": "RIGHT_PINKY",
723
- "x": 0.3882,
724
- "y": 0.2845,
725
- "z": 0.3757,
726
- "visibility": 0.3335
727
  },
728
  {
729
  "id": 19,
730
  "name": "LEFT_INDEX",
731
- "x": 0.3775,
732
- "y": 0.2931,
733
- "z": 0.0,
734
- "visibility": 0.8585
735
  },
736
  {
737
  "id": 20,
738
  "name": "RIGHT_INDEX",
739
- "x": 0.3897,
740
- "y": 0.2806,
741
- "z": 0.353,
742
- "visibility": 0.3693
743
  },
744
  {
745
  "id": 21,
746
  "name": "LEFT_THUMB",
747
- "x": 0.3815,
748
- "y": 0.2987,
749
- "z": 0.0132,
750
- "visibility": 0.8191
751
  },
752
  {
753
  "id": 22,
754
  "name": "RIGHT_THUMB",
755
- "x": 0.3978,
756
- "y": 0.2885,
757
- "z": 0.3484,
758
- "visibility": 0.3998
759
  },
760
  {
761
  "id": 23,
762
  "name": "LEFT_HIP",
763
- "x": 0.4943,
764
- "y": 0.519,
765
- "z": -0.0825,
766
- "visibility": 0.9994
767
  },
768
  {
769
  "id": 24,
770
  "name": "RIGHT_HIP",
771
- "x": 0.4953,
772
- "y": 0.5123,
773
- "z": 0.0825,
774
- "visibility": 0.9996
775
  },
776
  {
777
  "id": 25,
778
  "name": "LEFT_KNEE",
779
- "x": 0.4823,
780
- "y": 0.6855,
781
- "z": -0.0866,
782
- "visibility": 0.9842
783
  },
784
  {
785
  "id": 26,
786
  "name": "RIGHT_KNEE",
787
- "x": 0.4845,
788
- "y": 0.6785,
789
- "z": 0.0497,
790
- "visibility": 0.9176
791
  },
792
  {
793
  "id": 27,
794
  "name": "LEFT_ANKLE",
795
- "x": 0.4936,
796
- "y": 0.8396,
797
- "z": -0.0635,
798
- "visibility": 0.9939
799
  },
800
  {
801
  "id": 28,
802
  "name": "RIGHT_ANKLE",
803
- "x": 0.49,
804
- "y": 0.8355,
805
- "z": 0.0701,
806
- "visibility": 0.977
807
  },
808
  {
809
  "id": 29,
810
  "name": "LEFT_HEEL",
811
- "x": 0.5017,
812
- "y": 0.8601,
813
- "z": -0.0627,
814
- "visibility": 0.9753
815
  },
816
  {
817
  "id": 30,
818
  "name": "RIGHT_HEEL",
819
- "x": 0.4988,
820
- "y": 0.8611,
821
- "z": 0.0692,
822
- "visibility": 0.9483
823
  },
824
  {
825
  "id": 31,
826
  "name": "LEFT_FOOT_INDEX",
827
- "x": 0.4702,
828
- "y": 0.893,
829
- "z": -0.1193,
830
- "visibility": 0.9905
831
  },
832
  {
833
  "id": 32,
834
  "name": "RIGHT_FOOT_INDEX",
835
- "x": 0.4308,
836
- "y": 0.849,
837
- "z": 0.0171,
838
- "visibility": 0.9671
839
  }
840
  ]
841
  },
842
  "4_Top": {
843
- "score": 8.0,
844
  "comments": [
845
- "Tay trái bị cong quá nhiều (Chicken Wing)"
846
  ],
847
  "data": {
848
- "lead_arm_angle": 128.008196921695,
849
- "shoulder_tilt": 0.026631832122802734
850
  },
851
  "raw_landmarks": [
852
  {
853
  "id": 0,
854
  "name": "NOSE",
855
- "x": 0.4377,
856
- "y": 0.273,
857
- "z": 0.0396,
858
- "visibility": 0.9989
859
  },
860
  {
861
  "id": 1,
862
  "name": "LEFT_EYE_INNER",
863
- "x": 0.4374,
864
- "y": 0.2616,
865
- "z": 0.0153,
866
- "visibility": 0.9991
867
  },
868
  {
869
  "id": 2,
870
  "name": "LEFT_EYE",
871
- "x": 0.4369,
872
- "y": 0.2623,
873
- "z": 0.0153,
874
- "visibility": 0.9991
875
  },
876
  {
877
  "id": 3,
878
  "name": "LEFT_EYE_OUTER",
879
- "x": 0.4362,
880
- "y": 0.2627,
881
- "z": 0.0154,
882
- "visibility": 0.9992
883
  },
884
  {
885
  "id": 4,
886
  "name": "RIGHT_EYE_INNER",
887
- "x": 0.4395,
888
- "y": 0.256,
889
- "z": 0.0335,
890
- "visibility": 0.999
891
  },
892
  {
893
  "id": 5,
894
  "name": "RIGHT_EYE",
895
- "x": 0.4406,
896
- "y": 0.2536,
897
- "z": 0.0335,
898
- "visibility": 0.9987
899
  },
900
  {
901
  "id": 6,
902
  "name": "RIGHT_EYE_OUTER",
903
- "x": 0.4417,
904
- "y": 0.2512,
905
- "z": 0.0334,
906
- "visibility": 0.9991
907
  },
908
  {
909
  "id": 7,
910
  "name": "LEFT_EAR",
911
- "x": 0.4409,
912
- "y": 0.2627,
913
- "z": -0.0555,
914
- "visibility": 0.9991
915
  },
916
  {
917
  "id": 8,
918
  "name": "RIGHT_EAR",
919
- "x": 0.4493,
920
- "y": 0.251,
921
- "z": 0.0254,
922
- "visibility": 0.9988
923
  },
924
  {
925
  "id": 9,
926
  "name": "MOUTH_LEFT",
927
- "x": 0.4396,
928
- "y": 0.2837,
929
- "z": 0.0187,
930
- "visibility": 0.998
931
  },
932
  {
933
  "id": 10,
934
  "name": "MOUTH_RIGHT",
935
- "x": 0.4412,
936
- "y": 0.2789,
937
- "z": 0.0419,
938
- "visibility": 0.9982
939
  },
940
  {
941
  "id": 11,
942
  "name": "LEFT_SHOULDER",
943
- "x": 0.4487,
944
- "y": 0.3226,
945
- "z": -0.1081,
946
- "visibility": 0.9982
947
  },
948
  {
949
  "id": 12,
950
  "name": "RIGHT_SHOULDER",
951
- "x": 0.474,
952
- "y": 0.2959,
953
- "z": 0.0763,
954
- "visibility": 0.9967
955
  },
956
  {
957
  "id": 13,
958
  "name": "LEFT_ELBOW",
959
- "x": 0.4135,
960
- "y": 0.2908,
961
- "z": -0.0808,
962
- "visibility": 0.8984
963
  },
964
  {
965
  "id": 14,
966
  "name": "RIGHT_ELBOW",
967
- "x": 0.4505,
968
- "y": 0.2472,
969
- "z": 0.2204,
970
- "visibility": 0.1767
971
  },
972
  {
973
  "id": 15,
974
  "name": "LEFT_WRIST",
975
- "x": 0.418,
976
- "y": 0.229,
977
- "z": 0.0448,
978
- "visibility": 0.5895
979
  },
980
  {
981
  "id": 16,
982
  "name": "RIGHT_WRIST",
983
- "x": 0.4258,
984
- "y": 0.2151,
985
- "z": 0.3397,
986
- "visibility": 0.197
987
  },
988
  {
989
  "id": 17,
990
  "name": "LEFT_PINKY",
991
- "x": 0.4191,
992
- "y": 0.2129,
993
- "z": 0.0455,
994
- "visibility": 0.5811
995
  },
996
  {
997
  "id": 18,
998
  "name": "RIGHT_PINKY",
999
- "x": 0.419,
1000
- "y": 0.2096,
1001
- "z": 0.3577,
1002
- "visibility": 0.2344
1003
  },
1004
  {
1005
  "id": 19,
1006
  "name": "LEFT_INDEX",
1007
- "x": 0.4216,
1008
- "y": 0.2104,
1009
- "z": 0.0357,
1010
- "visibility": 0.5897
1011
  },
1012
  {
1013
  "id": 20,
1014
  "name": "RIGHT_INDEX",
1015
- "x": 0.4249,
1016
- "y": 0.2046,
1017
- "z": 0.3477,
1018
- "visibility": 0.2564
1019
  },
1020
  {
1021
  "id": 21,
1022
  "name": "LEFT_THUMB",
1023
- "x": 0.4237,
1024
- "y": 0.221,
1025
- "z": 0.0442,
1026
- "visibility": 0.5717
1027
  },
1028
  {
1029
  "id": 22,
1030
  "name": "RIGHT_THUMB",
1031
- "x": 0.4261,
1032
- "y": 0.2085,
1033
- "z": 0.3379,
1034
- "visibility": 0.2747
1035
  },
1036
  {
1037
  "id": 23,
1038
  "name": "LEFT_HIP",
1039
- "x": 0.4841,
1040
- "y": 0.5276,
1041
- "z": -0.0766,
1042
- "visibility": 0.9995
1043
  },
1044
  {
1045
  "id": 24,
1046
  "name": "RIGHT_HIP",
1047
- "x": 0.5014,
1048
- "y": 0.5151,
1049
- "z": 0.0764,
1050
- "visibility": 0.9995
1051
  },
1052
  {
1053
  "id": 25,
1054
  "name": "LEFT_KNEE",
1055
- "x": 0.4533,
1056
- "y": 0.6775,
1057
- "z": -0.0693,
1058
- "visibility": 0.9711
1059
  },
1060
  {
1061
  "id": 26,
1062
  "name": "RIGHT_KNEE",
1063
- "x": 0.5148,
1064
- "y": 0.6834,
1065
- "z": 0.0549,
1066
- "visibility": 0.7988
1067
  },
1068
  {
1069
  "id": 27,
1070
  "name": "LEFT_ANKLE",
1071
- "x": 0.4323,
1072
- "y": 0.8331,
1073
- "z": -0.0563,
1074
- "visibility": 0.9895
1075
  },
1076
  {
1077
  "id": 28,
1078
  "name": "RIGHT_ANKLE",
1079
- "x": 0.5534,
1080
- "y": 0.8374,
1081
- "z": 0.0564,
1082
- "visibility": 0.9413
1083
  },
1084
  {
1085
  "id": 29,
1086
  "name": "LEFT_HEEL",
1087
- "x": 0.4384,
1088
- "y": 0.8567,
1089
- "z": -0.0556,
1090
- "visibility": 0.97
1091
  },
1092
  {
1093
  "id": 30,
1094
  "name": "RIGHT_HEEL",
1095
- "x": 0.5601,
1096
- "y": 0.861,
1097
- "z": 0.0546,
1098
- "visibility": 0.9171
1099
  },
1100
  {
1101
  "id": 31,
1102
  "name": "LEFT_FOOT_INDEX",
1103
- "x": 0.4115,
1104
- "y": 0.8627,
1105
- "z": -0.1026,
1106
  "visibility": 0.9763
1107
  },
1108
  {
1109
  "id": 32,
1110
  "name": "RIGHT_FOOT_INDEX",
1111
- "x": 0.5424,
1112
- "y": 0.8736,
1113
- "z": 0.011,
1114
- "visibility": 0.8912
1115
  }
1116
  ]
1117
  },
1118
  "5_Mid-Downswing": {
1119
- "score": 10.0,
1120
  "comments": [
1121
  "Đạt chuẩn"
1122
  ],
@@ -1125,546 +1125,546 @@
1125
  {
1126
  "id": 0,
1127
  "name": "NOSE",
1128
- "x": 0.4701,
1129
- "y": 0.3058,
1130
- "z": -0.2845,
1131
- "visibility": 0.9972
1132
  },
1133
  {
1134
  "id": 1,
1135
  "name": "LEFT_EYE_INNER",
1136
- "x": 0.4694,
1137
- "y": 0.2917,
1138
- "z": -0.285,
1139
- "visibility": 0.9936
1140
  },
1141
  {
1142
  "id": 2,
1143
  "name": "LEFT_EYE",
1144
- "x": 0.4713,
1145
- "y": 0.2874,
1146
- "z": -0.285,
1147
- "visibility": 0.9928
1148
  },
1149
  {
1150
  "id": 3,
1151
  "name": "LEFT_EYE_OUTER",
1152
- "x": 0.4733,
1153
- "y": 0.282,
1154
- "z": -0.2851,
1155
- "visibility": 0.994
1156
  },
1157
  {
1158
  "id": 4,
1159
  "name": "RIGHT_EYE_INNER",
1160
- "x": 0.464,
1161
- "y": 0.2966,
1162
- "z": -0.2751,
1163
- "visibility": 0.9917
1164
  },
1165
  {
1166
  "id": 5,
1167
  "name": "RIGHT_EYE",
1168
- "x": 0.4623,
1169
- "y": 0.2961,
1170
- "z": -0.2752,
1171
- "visibility": 0.9921
1172
  },
1173
  {
1174
  "id": 6,
1175
  "name": "RIGHT_EYE_OUTER",
1176
- "x": 0.4604,
1177
- "y": 0.2956,
1178
- "z": -0.2753,
1179
- "visibility": 0.9941
1180
  },
1181
  {
1182
  "id": 7,
1183
  "name": "LEFT_EAR",
1184
- "x": 0.4772,
1185
- "y": 0.2632,
1186
- "z": -0.2147,
1187
- "visibility": 0.9914
1188
  },
1189
  {
1190
  "id": 8,
1191
  "name": "RIGHT_EAR",
1192
- "x": 0.4601,
1193
- "y": 0.2862,
1194
- "z": -0.1666,
1195
- "visibility": 0.9867
1196
  },
1197
  {
1198
  "id": 9,
1199
  "name": "MOUTH_LEFT",
1200
- "x": 0.4764,
1201
- "y": 0.3054,
1202
- "z": -0.2523,
1203
- "visibility": 0.9965
1204
  },
1205
  {
1206
  "id": 10,
1207
  "name": "MOUTH_RIGHT",
1208
- "x": 0.4706,
1209
- "y": 0.31,
1210
- "z": -0.2385,
1211
- "visibility": 0.9939
1212
  },
1213
  {
1214
  "id": 11,
1215
  "name": "LEFT_SHOULDER",
1216
- "x": 0.5206,
1217
- "y": 0.2872,
1218
- "z": -0.1376,
1219
- "visibility": 0.9998
1220
  },
1221
  {
1222
  "id": 12,
1223
  "name": "RIGHT_SHOULDER",
1224
- "x": 0.4586,
1225
- "y": 0.3407,
1226
- "z": -0.0559,
1227
- "visibility": 0.9996
1228
  },
1229
  {
1230
  "id": 13,
1231
  "name": "LEFT_ELBOW",
1232
- "x": 0.5451,
1233
- "y": 0.3815,
1234
- "z": -0.108,
1235
- "visibility": 0.7577
1236
  },
1237
  {
1238
  "id": 14,
1239
  "name": "RIGHT_ELBOW",
1240
- "x": 0.458,
1241
- "y": 0.4435,
1242
- "z": -0.0407,
1243
- "visibility": 0.7024
1244
  },
1245
  {
1246
  "id": 15,
1247
  "name": "LEFT_WRIST",
1248
- "x": 0.5467,
1249
- "y": 0.4515,
1250
- "z": -0.1764,
1251
- "visibility": 0.5113
1252
  },
1253
  {
1254
  "id": 16,
1255
  "name": "RIGHT_WRIST",
1256
- "x": 0.4317,
1257
- "y": 0.4956,
1258
- "z": -0.1847,
1259
- "visibility": 0.7124
1260
  },
1261
  {
1262
  "id": 17,
1263
  "name": "LEFT_PINKY",
1264
- "x": 0.5501,
1265
- "y": 0.469,
1266
- "z": -0.1975,
1267
- "visibility": 0.4453
1268
  },
1269
  {
1270
  "id": 18,
1271
  "name": "RIGHT_PINKY",
1272
- "x": 0.4226,
1273
- "y": 0.5117,
1274
- "z": -0.2105,
1275
- "visibility": 0.6542
1276
  },
1277
  {
1278
  "id": 19,
1279
  "name": "LEFT_INDEX",
1280
- "x": 0.5497,
1281
- "y": 0.4668,
1282
- "z": -0.2221,
1283
- "visibility": 0.4724
1284
  },
1285
  {
1286
  "id": 20,
1287
  "name": "RIGHT_INDEX",
1288
- "x": 0.4196,
1289
- "y": 0.5075,
1290
- "z": -0.2419,
1291
- "visibility": 0.6862
1292
  },
1293
  {
1294
  "id": 21,
1295
  "name": "LEFT_THUMB",
1296
- "x": 0.5468,
1297
- "y": 0.4604,
1298
- "z": -0.1874,
1299
- "visibility": 0.4321
1300
  },
1301
  {
1302
  "id": 22,
1303
  "name": "RIGHT_THUMB",
1304
- "x": 0.425,
1305
- "y": 0.5002,
1306
- "z": -0.2018,
1307
- "visibility": 0.6791
1308
  },
1309
  {
1310
  "id": 23,
1311
  "name": "LEFT_HIP",
1312
- "x": 0.5429,
1313
- "y": 0.4997,
1314
- "z": -0.0288,
1315
- "visibility": 0.9988
1316
  },
1317
  {
1318
  "id": 24,
1319
  "name": "RIGHT_HIP",
1320
- "x": 0.5023,
1321
- "y": 0.5051,
1322
- "z": 0.0288,
1323
- "visibility": 0.9986
1324
  },
1325
  {
1326
  "id": 25,
1327
  "name": "LEFT_KNEE",
1328
- "x": 0.5564,
1329
- "y": 0.6632,
1330
- "z": -0.0689,
1331
- "visibility": 0.9781
1332
  },
1333
  {
1334
  "id": 26,
1335
  "name": "RIGHT_KNEE",
1336
- "x": 0.4778,
1337
- "y": 0.6677,
1338
- "z": 0.003,
1339
- "visibility": 0.9781
1340
  },
1341
  {
1342
  "id": 27,
1343
  "name": "LEFT_ANKLE",
1344
- "x": 0.5603,
1345
- "y": 0.832,
1346
- "z": 0.0267,
1347
- "visibility": 0.9836
1348
  },
1349
  {
1350
  "id": 28,
1351
  "name": "RIGHT_ANKLE",
1352
- "x": 0.4349,
1353
- "y": 0.8154,
1354
- "z": 0.0971,
1355
- "visibility": 0.9785
1356
  },
1357
  {
1358
  "id": 29,
1359
  "name": "LEFT_HEEL",
1360
- "x": 0.559,
1361
- "y": 0.8543,
1362
- "z": 0.0292,
1363
- "visibility": 0.739
1364
  },
1365
  {
1366
  "id": 30,
1367
  "name": "RIGHT_HEEL",
1368
- "x": 0.4325,
1369
- "y": 0.8365,
1370
- "z": 0.1014,
1371
- "visibility": 0.8427
1372
  },
1373
  {
1374
  "id": 31,
1375
  "name": "LEFT_FOOT_INDEX",
1376
- "x": 0.5564,
1377
- "y": 0.8757,
1378
- "z": -0.0918,
1379
- "visibility": 0.9648
1380
  },
1381
  {
1382
  "id": 32,
1383
  "name": "RIGHT_FOOT_INDEX",
1384
- "x": 0.4208,
1385
- "y": 0.8594,
1386
- "z": 0.0042,
1387
- "visibility": 0.9674
1388
  }
1389
  ]
1390
  },
1391
  "6_Impact": {
1392
- "score": 10.0,
1393
  "comments": [
1394
  "Đạt chuẩn"
1395
  ],
1396
  "data": {
1397
- "hip_openness": 0.04398834705352783
1398
  },
1399
  "raw_landmarks": [
1400
  {
1401
  "id": 0,
1402
  "name": "NOSE",
1403
- "x": 0.4685,
1404
- "y": 0.3077,
1405
- "z": -0.4283,
1406
- "visibility": 0.9996
1407
  },
1408
  {
1409
  "id": 1,
1410
  "name": "LEFT_EYE_INNER",
1411
- "x": 0.4673,
1412
- "y": 0.2909,
1413
- "z": -0.4276,
1414
- "visibility": 0.9986
1415
  },
1416
  {
1417
  "id": 2,
1418
  "name": "LEFT_EYE",
1419
- "x": 0.4693,
1420
- "y": 0.286,
1421
- "z": -0.4276,
1422
- "visibility": 0.9986
1423
  },
1424
  {
1425
  "id": 3,
1426
  "name": "LEFT_EYE_OUTER",
1427
- "x": 0.4713,
1428
- "y": 0.28,
1429
- "z": -0.4276,
1430
- "visibility": 0.9988
1431
  },
1432
  {
1433
  "id": 4,
1434
  "name": "RIGHT_EYE_INNER",
1435
- "x": 0.4614,
1436
- "y": 0.2976,
1437
- "z": -0.4208,
1438
- "visibility": 0.9985
1439
  },
1440
  {
1441
  "id": 5,
1442
  "name": "RIGHT_EYE",
1443
- "x": 0.4592,
1444
- "y": 0.2976,
1445
- "z": -0.4209,
1446
- "visibility": 0.9986
1447
  },
1448
  {
1449
  "id": 6,
1450
  "name": "RIGHT_EYE_OUTER",
1451
- "x": 0.4569,
1452
- "y": 0.2976,
1453
- "z": -0.421,
1454
- "visibility": 0.9987
1455
  },
1456
  {
1457
  "id": 7,
1458
  "name": "LEFT_EAR",
1459
- "x": 0.4752,
1460
- "y": 0.2652,
1461
- "z": -0.341,
1462
- "visibility": 0.9975
1463
  },
1464
  {
1465
  "id": 8,
1466
  "name": "RIGHT_EAR",
1467
- "x": 0.4564,
1468
- "y": 0.2927,
1469
- "z": -0.307,
1470
- "visibility": 0.9974
1471
  },
1472
  {
1473
  "id": 9,
1474
  "name": "MOUTH_LEFT",
1475
- "x": 0.4754,
1476
- "y": 0.3058,
1477
- "z": -0.3887,
1478
- "visibility": 0.9993
1479
  },
1480
  {
1481
  "id": 10,
1482
  "name": "MOUTH_RIGHT",
1483
- "x": 0.4688,
1484
- "y": 0.3141,
1485
- "z": -0.3791,
1486
- "visibility": 0.999
1487
  },
1488
  {
1489
  "id": 11,
1490
  "name": "LEFT_SHOULDER",
1491
- "x": 0.5191,
1492
- "y": 0.2773,
1493
- "z": -0.2287,
1494
- "visibility": 1.0
1495
  },
1496
  {
1497
  "id": 12,
1498
  "name": "RIGHT_SHOULDER",
1499
- "x": 0.4538,
1500
- "y": 0.3462,
1501
- "z": -0.1736,
1502
- "visibility": 0.9999
1503
  },
1504
  {
1505
  "id": 13,
1506
  "name": "LEFT_ELBOW",
1507
- "x": 0.5359,
1508
- "y": 0.3889,
1509
- "z": -0.1762,
1510
- "visibility": 0.9654
1511
  },
1512
  {
1513
  "id": 14,
1514
  "name": "RIGHT_ELBOW",
1515
- "x": 0.4617,
1516
- "y": 0.4377,
1517
- "z": -0.1067,
1518
- "visibility": 0.9093
1519
  },
1520
  {
1521
  "id": 15,
1522
  "name": "LEFT_WRIST",
1523
- "x": 0.5412,
1524
- "y": 0.4898,
1525
- "z": -0.2409,
1526
- "visibility": 0.9343
1527
  },
1528
  {
1529
  "id": 16,
1530
  "name": "RIGHT_WRIST",
1531
- "x": 0.4685,
1532
- "y": 0.5421,
1533
- "z": -0.1825,
1534
- "visibility": 0.8386
1535
  },
1536
  {
1537
  "id": 17,
1538
  "name": "LEFT_PINKY",
1539
- "x": 0.5431,
1540
- "y": 0.5178,
1541
- "z": -0.2633,
1542
- "visibility": 0.8823
1543
  },
1544
  {
1545
  "id": 18,
1546
  "name": "RIGHT_PINKY",
1547
- "x": 0.4692,
1548
- "y": 0.5714,
1549
- "z": -0.2021,
1550
- "visibility": 0.7526
1551
  },
1552
  {
1553
  "id": 19,
1554
  "name": "LEFT_INDEX",
1555
- "x": 0.5377,
1556
- "y": 0.5181,
1557
- "z": -0.2924,
1558
- "visibility": 0.8925
1559
  },
1560
  {
1561
  "id": 20,
1562
  "name": "RIGHT_INDEX",
1563
- "x": 0.47,
1564
- "y": 0.5698,
1565
- "z": -0.2359,
1566
- "visibility": 0.7668
1567
  },
1568
  {
1569
  "id": 21,
1570
  "name": "LEFT_THUMB",
1571
- "x": 0.5361,
1572
- "y": 0.5125,
1573
- "z": -0.2524,
1574
- "visibility": 0.8578
1575
  },
1576
  {
1577
  "id": 22,
1578
  "name": "RIGHT_THUMB",
1579
- "x": 0.4725,
1580
- "y": 0.5572,
1581
- "z": -0.1971,
1582
- "visibility": 0.7461
1583
  },
1584
  {
1585
  "id": 23,
1586
  "name": "LEFT_HIP",
1587
- "x": 0.5461,
1588
- "y": 0.4905,
1589
- "z": -0.0145,
1590
- "visibility": 0.9997
1591
  },
1592
  {
1593
  "id": 24,
1594
  "name": "RIGHT_HIP",
1595
- "x": 0.5021,
1596
- "y": 0.5003,
1597
- "z": 0.0145,
1598
- "visibility": 0.9997
1599
  },
1600
  {
1601
  "id": 25,
1602
  "name": "LEFT_KNEE",
1603
- "x": 0.5589,
1604
- "y": 0.6649,
1605
- "z": -0.067,
1606
- "visibility": 0.9848
1607
  },
1608
  {
1609
  "id": 26,
1610
  "name": "RIGHT_KNEE",
1611
- "x": 0.4847,
1612
- "y": 0.659,
1613
- "z": -0.0441,
1614
- "visibility": 0.9916
1615
  },
1616
  {
1617
  "id": 27,
1618
  "name": "LEFT_ANKLE",
1619
- "x": 0.5639,
1620
- "y": 0.8328,
1621
- "z": 0.0228,
1622
- "visibility": 0.9884
1623
  },
1624
  {
1625
  "id": 28,
1626
  "name": "RIGHT_ANKLE",
1627
- "x": 0.4317,
1628
- "y": 0.8147,
1629
- "z": 0.0358,
1630
- "visibility": 0.9916
1631
  },
1632
  {
1633
  "id": 29,
1634
  "name": "LEFT_HEEL",
1635
- "x": 0.5601,
1636
- "y": 0.8554,
1637
- "z": 0.0241,
1638
- "visibility": 0.8555
1639
  },
1640
  {
1641
  "id": 30,
1642
  "name": "RIGHT_HEEL",
1643
- "x": 0.4298,
1644
- "y": 0.8332,
1645
- "z": 0.0381,
1646
- "visibility": 0.9083
1647
  },
1648
  {
1649
  "id": 31,
1650
  "name": "LEFT_FOOT_INDEX",
1651
- "x": 0.5595,
1652
- "y": 0.8747,
1653
- "z": -0.1053,
1654
- "visibility": 0.9785
1655
  },
1656
  {
1657
  "id": 32,
1658
  "name": "RIGHT_FOOT_INDEX",
1659
- "x": 0.4183,
1660
- "y": 0.864,
1661
- "z": -0.0732,
1662
- "visibility": 0.9877
1663
  }
1664
  ]
1665
  },
1666
  "7_Mid-Follow-Through": {
1667
- "score": 10.0,
1668
  "comments": [
1669
  "Đạt chuẩn"
1670
  ],
@@ -1673,271 +1673,271 @@
1673
  {
1674
  "id": 0,
1675
  "name": "NOSE",
1676
- "x": 0.4653,
1677
- "y": 0.2711,
1678
- "z": -0.2687,
1679
- "visibility": 0.9954
1680
  },
1681
  {
1682
  "id": 1,
1683
  "name": "LEFT_EYE_INNER",
1684
- "x": 0.4575,
1685
- "y": 0.2685,
1686
- "z": -0.2592,
1687
- "visibility": 0.9903
1688
  },
1689
  {
1690
  "id": 2,
1691
  "name": "LEFT_EYE",
1692
- "x": 0.4578,
1693
- "y": 0.2645,
1694
- "z": -0.2592,
1695
- "visibility": 0.9894
1696
  },
1697
  {
1698
  "id": 3,
1699
  "name": "LEFT_EYE_OUTER",
1700
- "x": 0.4578,
1701
- "y": 0.26,
1702
- "z": -0.2592,
1703
- "visibility": 0.9915
1704
  },
1705
  {
1706
  "id": 4,
1707
  "name": "RIGHT_EYE_INNER",
1708
- "x": 0.455,
1709
- "y": 0.2805,
1710
- "z": -0.2648,
1711
- "visibility": 0.9906
1712
  },
1713
  {
1714
  "id": 5,
1715
  "name": "RIGHT_EYE",
1716
- "x": 0.454,
1717
- "y": 0.2836,
1718
- "z": -0.2648,
1719
- "visibility": 0.992
1720
  },
1721
  {
1722
  "id": 6,
1723
  "name": "RIGHT_EYE_OUTER",
1724
- "x": 0.4527,
1725
- "y": 0.286,
1726
- "z": -0.2649,
1727
- "visibility": 0.9916
1728
  },
1729
  {
1730
  "id": 7,
1731
  "name": "LEFT_EAR",
1732
- "x": 0.4598,
1733
- "y": 0.2571,
1734
- "z": -0.1646,
1735
- "visibility": 0.9906
1736
  },
1737
  {
1738
  "id": 8,
1739
  "name": "RIGHT_EAR",
1740
- "x": 0.4539,
1741
- "y": 0.2907,
1742
- "z": -0.1869,
1743
- "visibility": 0.9887
1744
  },
1745
  {
1746
  "id": 9,
1747
  "name": "MOUTH_LEFT",
1748
- "x": 0.473,
1749
- "y": 0.2671,
1750
- "z": -0.2286,
1751
- "visibility": 0.9946
1752
  },
1753
  {
1754
  "id": 10,
1755
  "name": "MOUTH_RIGHT",
1756
- "x": 0.4699,
1757
- "y": 0.2814,
1758
- "z": -0.2351,
1759
- "visibility": 0.9867
1760
  },
1761
  {
1762
  "id": 11,
1763
  "name": "LEFT_SHOULDER",
1764
- "x": 0.5002,
1765
- "y": 0.2547,
1766
- "z": -0.0581,
1767
- "visibility": 0.9999
1768
  },
1769
  {
1770
  "id": 12,
1771
  "name": "RIGHT_SHOULDER",
1772
- "x": 0.4655,
1773
- "y": 0.3412,
1774
- "z": -0.1783,
1775
- "visibility": 0.9975
1776
  },
1777
  {
1778
  "id": 13,
1779
  "name": "LEFT_ELBOW",
1780
- "x": 0.5625,
1781
- "y": 0.2635,
1782
- "z": -0.0341,
1783
- "visibility": 0.9719
1784
  },
1785
  {
1786
  "id": 14,
1787
  "name": "RIGHT_ELBOW",
1788
- "x": 0.5021,
1789
- "y": 0.3684,
1790
- "z": -0.3109,
1791
- "visibility": 0.9509
1792
  },
1793
  {
1794
  "id": 15,
1795
  "name": "LEFT_WRIST",
1796
- "x": 0.6201,
1797
- "y": 0.2476,
1798
- "z": -0.0903,
1799
- "visibility": 0.9895
1800
  },
1801
  {
1802
  "id": 16,
1803
  "name": "RIGHT_WRIST",
1804
- "x": 0.5358,
1805
- "y": 0.3262,
1806
- "z": -0.4596,
1807
- "visibility": 0.9463
1808
  },
1809
  {
1810
  "id": 17,
1811
  "name": "LEFT_PINKY",
1812
- "x": 0.6372,
1813
- "y": 0.2413,
1814
- "z": -0.1003,
1815
- "visibility": 0.9762
1816
  },
1817
  {
1818
  "id": 18,
1819
  "name": "RIGHT_PINKY",
1820
- "x": 0.5449,
1821
- "y": 0.32,
1822
- "z": -0.5017,
1823
- "visibility": 0.8863
1824
  },
1825
  {
1826
  "id": 19,
1827
  "name": "LEFT_INDEX",
1828
- "x": 0.6373,
1829
- "y": 0.2449,
1830
- "z": -0.1322,
1831
- "visibility": 0.9802
1832
  },
1833
  {
1834
  "id": 20,
1835
  "name": "RIGHT_INDEX",
1836
- "x": 0.5416,
1837
- "y": 0.3121,
1838
- "z": -0.4984,
1839
- "visibility": 0.8836
1840
  },
1841
  {
1842
  "id": 21,
1843
  "name": "LEFT_THUMB",
1844
- "x": 0.6315,
1845
- "y": 0.25,
1846
- "z": -0.1058,
1847
- "visibility": 0.9782
1848
  },
1849
  {
1850
  "id": 22,
1851
  "name": "RIGHT_THUMB",
1852
- "x": 0.5396,
1853
- "y": 0.3156,
1854
- "z": -0.4628,
1855
- "visibility": 0.8383
1856
  },
1857
  {
1858
  "id": 23,
1859
  "name": "LEFT_HIP",
1860
- "x": 0.5494,
1861
- "y": 0.4928,
1862
- "z": 0.0143,
1863
- "visibility": 0.9997
1864
  },
1865
  {
1866
  "id": 24,
1867
  "name": "RIGHT_HIP",
1868
- "x": 0.5073,
1869
- "y": 0.5026,
1870
- "z": -0.0142,
1871
- "visibility": 0.9988
1872
  },
1873
  {
1874
  "id": 25,
1875
  "name": "LEFT_KNEE",
1876
- "x": 0.5535,
1877
- "y": 0.6737,
1878
- "z": -0.035,
1879
- "visibility": 0.9886
1880
  },
1881
  {
1882
  "id": 26,
1883
  "name": "RIGHT_KNEE",
1884
- "x": 0.4754,
1885
- "y": 0.6604,
1886
- "z": -0.0364,
1887
- "visibility": 0.9898
1888
  },
1889
  {
1890
  "id": 27,
1891
  "name": "LEFT_ANKLE",
1892
- "x": 0.5599,
1893
- "y": 0.8326,
1894
- "z": 0.0869,
1895
- "visibility": 0.988
1896
  },
1897
  {
1898
  "id": 28,
1899
  "name": "RIGHT_ANKLE",
1900
- "x": 0.4258,
1901
- "y": 0.8127,
1902
- "z": 0.0594,
1903
- "visibility": 0.9867
1904
  },
1905
  {
1906
  "id": 29,
1907
  "name": "LEFT_HEEL",
1908
- "x": 0.5578,
1909
- "y": 0.8545,
1910
- "z": 0.0928,
1911
- "visibility": 0.8836
1912
  },
1913
  {
1914
  "id": 30,
1915
  "name": "RIGHT_HEEL",
1916
- "x": 0.423,
1917
- "y": 0.8307,
1918
- "z": 0.0657,
1919
- "visibility": 0.8884
1920
  },
1921
  {
1922
  "id": 31,
1923
  "name": "LEFT_FOOT_INDEX",
1924
- "x": 0.5535,
1925
- "y": 0.8734,
1926
- "z": -0.0145,
1927
- "visibility": 0.9762
1928
  },
1929
  {
1930
  "id": 32,
1931
  "name": "RIGHT_FOOT_INDEX",
1932
- "x": 0.4157,
1933
- "y": 0.8632,
1934
- "z": -0.0266,
1935
- "visibility": 0.9796
1936
  }
1937
  ]
1938
  },
1939
  "8_Finish": {
1940
- "score": 10.0,
1941
  "comments": [
1942
  "Đạt chuẩn"
1943
  ],
@@ -1946,290 +1946,288 @@
1946
  {
1947
  "id": 0,
1948
  "name": "NOSE",
1949
- "x": 0.5444,
1950
- "y": 0.1864,
1951
- "z": 0.0103,
1952
  "visibility": 0.9998
1953
  },
1954
  {
1955
  "id": 1,
1956
  "name": "LEFT_EYE_INNER",
1957
- "x": 0.5369,
1958
- "y": 0.1782,
1959
- "z": 0.0174,
1960
- "visibility": 0.9999
1961
  },
1962
  {
1963
  "id": 2,
1964
  "name": "LEFT_EYE",
1965
- "x": 0.5356,
1966
- "y": 0.1783,
1967
- "z": 0.0174,
1968
- "visibility": 0.9998
1969
  },
1970
  {
1971
  "id": 3,
1972
  "name": "LEFT_EYE_OUTER",
1973
- "x": 0.5342,
1974
- "y": 0.1788,
1975
- "z": 0.0174,
1976
- "visibility": 0.9999
1977
  },
1978
  {
1979
  "id": 4,
1980
  "name": "RIGHT_EYE_INNER",
1981
- "x": 0.5374,
1982
- "y": 0.1795,
1983
- "z": -0.0109,
1984
  "visibility": 0.9999
1985
  },
1986
  {
1987
  "id": 5,
1988
  "name": "RIGHT_EYE",
1989
- "x": 0.5366,
1990
- "y": 0.1803,
1991
- "z": -0.011,
1992
  "visibility": 0.9999
1993
  },
1994
  {
1995
  "id": 6,
1996
  "name": "RIGHT_EYE_OUTER",
1997
- "x": 0.5359,
1998
- "y": 0.1815,
1999
- "z": -0.0111,
2000
  "visibility": 0.9999
2001
  },
2002
  {
2003
  "id": 7,
2004
  "name": "LEFT_EAR",
2005
- "x": 0.5249,
2006
- "y": 0.1927,
2007
- "z": 0.0703,
2008
- "visibility": 0.9999
2009
  },
2010
  {
2011
  "id": 8,
2012
  "name": "RIGHT_EAR",
2013
- "x": 0.5293,
2014
- "y": 0.1955,
2015
- "z": -0.0616,
2016
- "visibility": 0.9999
2017
  },
2018
  {
2019
  "id": 9,
2020
  "name": "MOUTH_LEFT",
2021
- "x": 0.5433,
2022
- "y": 0.201,
2023
- "z": 0.0336,
2024
- "visibility": 0.9994
2025
  },
2026
  {
2027
  "id": 10,
2028
  "name": "MOUTH_RIGHT",
2029
- "x": 0.5434,
2030
- "y": 0.2022,
2031
- "z": -0.0052,
2032
- "visibility": 0.9991
2033
  },
2034
  {
2035
  "id": 11,
2036
  "name": "LEFT_SHOULDER",
2037
- "x": 0.5154,
2038
- "y": 0.2669,
2039
- "z": 0.1662,
2040
- "visibility": 0.9972
2041
  },
2042
  {
2043
  "id": 12,
2044
  "name": "RIGHT_SHOULDER",
2045
- "x": 0.5387,
2046
- "y": 0.2652,
2047
- "z": -0.138,
2048
- "visibility": 0.9986
2049
  },
2050
  {
2051
  "id": 13,
2052
  "name": "LEFT_ELBOW",
2053
- "x": 0.4762,
2054
- "y": 0.2477,
2055
- "z": 0.4054,
2056
- "visibility": 0.2628
2057
  },
2058
  {
2059
  "id": 14,
2060
  "name": "RIGHT_ELBOW",
2061
- "x": 0.5182,
2062
- "y": 0.2615,
2063
- "z": -0.3503,
2064
- "visibility": 0.6322
2065
  },
2066
  {
2067
  "id": 15,
2068
  "name": "LEFT_WRIST",
2069
- "x": 0.4718,
2070
- "y": 0.222,
2071
- "z": 0.6823,
2072
- "visibility": 0.2828
2073
  },
2074
  {
2075
  "id": 16,
2076
  "name": "RIGHT_WRIST",
2077
- "x": 0.4974,
2078
- "y": 0.2212,
2079
- "z": -0.5212,
2080
- "visibility": 0.6981
2081
  },
2082
  {
2083
  "id": 17,
2084
  "name": "LEFT_PINKY",
2085
- "x": 0.4676,
2086
- "y": 0.2087,
2087
- "z": 0.7367,
2088
- "visibility": 0.2921
2089
  },
2090
  {
2091
  "id": 18,
2092
  "name": "RIGHT_PINKY",
2093
- "x": 0.4874,
2094
- "y": 0.2071,
2095
- "z": -0.5597,
2096
- "visibility": 0.7076
2097
  },
2098
  {
2099
  "id": 19,
2100
  "name": "LEFT_INDEX",
2101
- "x": 0.4685,
2102
- "y": 0.2051,
2103
- "z": 0.719,
2104
- "visibility": 0.3088
2105
  },
2106
  {
2107
  "id": 20,
2108
  "name": "RIGHT_INDEX",
2109
- "x": 0.4856,
2110
- "y": 0.2067,
2111
- "z": -0.5491,
2112
- "visibility": 0.7119
2113
  },
2114
  {
2115
  "id": 21,
2116
  "name": "LEFT_THUMB",
2117
- "x": 0.4739,
2118
- "y": 0.2107,
2119
- "z": 0.6821,
2120
- "visibility": 0.3023
2121
  },
2122
  {
2123
  "id": 22,
2124
  "name": "RIGHT_THUMB",
2125
- "x": 0.4888,
2126
- "y": 0.2135,
2127
- "z": -0.5213,
2128
- "visibility": 0.609
2129
  },
2130
  {
2131
  "id": 23,
2132
  "name": "LEFT_HIP",
2133
- "x": 0.5461,
2134
- "y": 0.4946,
2135
- "z": 0.0989,
2136
- "visibility": 0.9999
2137
  },
2138
  {
2139
  "id": 24,
2140
  "name": "RIGHT_HIP",
2141
- "x": 0.5367,
2142
- "y": 0.4911,
2143
- "z": -0.0992,
2144
- "visibility": 0.9997
2145
  },
2146
  {
2147
  "id": 25,
2148
  "name": "LEFT_KNEE",
2149
- "x": 0.5482,
2150
- "y": 0.6593,
2151
- "z": 0.0665,
2152
- "visibility": 0.8605
2153
  },
2154
  {
2155
  "id": 26,
2156
  "name": "RIGHT_KNEE",
2157
- "x": 0.5035,
2158
- "y": 0.6681,
2159
- "z": -0.0946,
2160
- "visibility": 0.9784
2161
  },
2162
  {
2163
  "id": 27,
2164
  "name": "LEFT_ANKLE",
2165
- "x": 0.5592,
2166
- "y": 0.8336,
2167
- "z": 0.0999,
2168
- "visibility": 0.9579
2169
  },
2170
  {
2171
  "id": 28,
2172
  "name": "RIGHT_ANKLE",
2173
- "x": 0.4356,
2174
- "y": 0.7968,
2175
- "z": -0.058,
2176
- "visibility": 0.9937
2177
  },
2178
  {
2179
  "id": 29,
2180
  "name": "LEFT_HEEL",
2181
- "x": 0.5543,
2182
- "y": 0.8662,
2183
- "z": 0.0984,
2184
- "visibility": 0.9319
2185
  },
2186
  {
2187
  "id": 30,
2188
  "name": "RIGHT_HEEL",
2189
- "x": 0.4222,
2190
- "y": 0.7981,
2191
- "z": -0.0556,
2192
- "visibility": 0.9716
2193
  },
2194
  {
2195
  "id": 31,
2196
  "name": "LEFT_FOOT_INDEX",
2197
- "x": 0.5765,
2198
- "y": 0.8588,
2199
- "z": 0.0432,
2200
- "visibility": 0.9515
2201
  },
2202
  {
2203
  "id": 32,
2204
  "name": "RIGHT_FOOT_INDEX",
2205
- "x": 0.4341,
2206
- "y": 0.8619,
2207
- "z": -0.1121,
2208
- "visibility": 0.9891
2209
  }
2210
  ]
2211
  }
2212
  },
2213
- "overall_score": 9.6,
2214
- "view_angle": "Down-the-Line (Dọc)"
2215
  },
2216
  "coaching": {
2217
- "video_id": "b5a70272-d520-4eb4-bc91-7e92d1d1ec12",
2218
- "final_score": 9.5,
2219
  "skill_level": "Professional / Low Handicap",
2220
  "key_faults": [
2221
- "[1_Address] thế đứng quá rộng",
2222
  "[2_Toe-up] Đạt chuẩn",
2223
  "[3_Mid-Backswing] Đạt chuẩn",
2224
- "[4_Top] Tay trái bị cong quá nhiều (Chicken Wing)",
2225
  "[5_Mid-Downswing] Đạt chuẩn",
2226
  "[6_Impact] Đạt chuẩn",
2227
  "[7_Mid-Follow-Through] Đạt chuẩn",
2228
  "[8_Finish] Đạt chuẩn"
2229
  ],
2230
- "recommended_drills": [
2231
- "Bài tập đứng trên hai đầu gối hoặc khép chân để cảm nhận sự xoay trục."
2232
- ],
2233
- "summary": "Điểm kỹ thuật của bạn đạt 9.5/10. Kỹ thuật của bạn khá tốt, chỉ cần tinh chỉnh một vài chi tiết nhỏ. Lỗi ưu tiên cần sửa: Tư thế đứng quá rộng."
2234
  }
2235
  }
 
1
  {
2
  "status": "success",
3
+ "job_id": "d907f221-0b2f-44cd-9af8-cb4921bb13bb",
4
  "metadata": {
5
  "event_frames": {
6
+ "1_Address": 0,
7
+ "2_Toe-up": 0,
8
+ "3_Mid-Backswing": 0,
9
+ "4_Top": 0,
10
+ "5_Mid-Downswing": 0,
11
+ "6_Impact": 0,
12
+ "7_Mid-Follow-Through": 34,
13
+ "8_Finish": 98
14
  },
15
+ "slow_factor": 1,
16
+ "fps": 30
17
  },
18
  "analysis": {
19
+ "video_id": "d907f221-0b2f-44cd-9af8-cb4921bb13bb",
20
  "phases": {
21
  "1_Address": {
22
+ "score": 10,
23
  "comments": [
24
+ "Đạt chuẩn"
25
  ],
26
  "data": {
27
+ "stance_ratio": 1.3912659454869785
28
  },
29
  "raw_landmarks": [
30
  {
31
  "id": 0,
32
  "name": "NOSE",
33
+ "x": 0.5452,
34
+ "y": 0.5194,
35
+ "z": -0.5827,
36
+ "visibility": 1
37
  },
38
  {
39
  "id": 1,
40
  "name": "LEFT_EYE_INNER",
41
+ "x": 0.5509,
42
+ "y": 0.5123,
43
+ "z": -0.5868,
44
+ "visibility": 1
45
  },
46
  {
47
  "id": 2,
48
  "name": "LEFT_EYE",
49
+ "x": 0.5553,
50
+ "y": 0.5111,
51
+ "z": -0.5867,
52
+ "visibility": 0.9999
53
  },
54
  {
55
  "id": 3,
56
  "name": "LEFT_EYE_OUTER",
57
+ "x": 0.5596,
58
+ "y": 0.5099,
59
+ "z": -0.5867,
60
+ "visibility": 0.9999
61
  },
62
  {
63
  "id": 4,
64
  "name": "RIGHT_EYE_INNER",
65
+ "x": 0.5385,
66
+ "y": 0.5137,
67
+ "z": -0.5845,
68
+ "visibility": 1
69
  },
70
  {
71
  "id": 5,
72
  "name": "RIGHT_EYE",
73
+ "x": 0.5341,
74
+ "y": 0.5134,
75
+ "z": -0.5847,
76
+ "visibility": 1
77
  },
78
  {
79
  "id": 6,
80
  "name": "RIGHT_EYE_OUTER",
81
+ "x": 0.5296,
82
+ "y": 0.513,
83
+ "z": -0.5849,
84
+ "visibility": 1
85
  },
86
  {
87
  "id": 7,
88
  "name": "LEFT_EAR",
89
+ "x": 0.5626,
90
+ "y": 0.5054,
91
+ "z": -0.493,
92
+ "visibility": 0.9999
93
  },
94
  {
95
  "id": 8,
96
  "name": "RIGHT_EAR",
97
+ "x": 0.5201,
98
+ "y": 0.508,
99
+ "z": -0.4836,
100
+ "visibility": 1
101
  },
102
  {
103
  "id": 9,
104
  "name": "MOUTH_LEFT",
105
+ "x": 0.552,
106
+ "y": 0.521,
107
+ "z": -0.5366,
108
+ "visibility": 1
109
  },
110
  {
111
  "id": 10,
112
  "name": "MOUTH_RIGHT",
113
+ "x": 0.5369,
114
+ "y": 0.5218,
115
+ "z": -0.5344,
116
+ "visibility": 1
117
  },
118
  {
119
  "id": 11,
120
  "name": "LEFT_SHOULDER",
121
+ "x": 0.602,
122
+ "y": 0.5248,
123
+ "z": -0.3651,
124
+ "visibility": 1
125
  },
126
  {
127
  "id": 12,
128
  "name": "RIGHT_SHOULDER",
129
+ "x": 0.4712,
130
+ "y": 0.5315,
131
+ "z": -0.3288,
132
+ "visibility": 1
133
  },
134
  {
135
  "id": 13,
136
  "name": "LEFT_ELBOW",
137
+ "x": 0.584,
138
+ "y": 0.5799,
139
+ "z": -0.3018,
140
+ "visibility": 0.9806
141
  },
142
  {
143
  "id": 14,
144
  "name": "RIGHT_ELBOW",
145
+ "x": 0.4933,
146
+ "y": 0.5926,
147
+ "z": -0.2602,
148
+ "visibility": 0.9898
149
  },
150
  {
151
  "id": 15,
152
  "name": "LEFT_WRIST",
153
+ "x": 0.5475,
154
+ "y": 0.6287,
155
+ "z": -0.3638,
156
+ "visibility": 0.968
157
  },
158
  {
159
  "id": 16,
160
  "name": "RIGHT_WRIST",
161
+ "x": 0.5229,
162
+ "y": 0.6379,
163
+ "z": -0.3363,
164
+ "visibility": 0.98
165
  },
166
  {
167
  "id": 17,
168
  "name": "LEFT_PINKY",
169
+ "x": 0.5475,
170
+ "y": 0.6448,
171
+ "z": -0.3947,
172
+ "visibility": 0.9426
173
  },
174
  {
175
  "id": 18,
176
  "name": "RIGHT_PINKY",
177
+ "x": 0.529,
178
+ "y": 0.6524,
179
+ "z": -0.3639,
180
+ "visibility": 0.9546
181
  },
182
  {
183
  "id": 19,
184
  "name": "LEFT_INDEX",
185
+ "x": 0.5399,
186
+ "y": 0.6458,
187
+ "z": -0.4222,
188
+ "visibility": 0.9451
189
  },
190
  {
191
  "id": 20,
192
  "name": "RIGHT_INDEX",
193
+ "x": 0.5351,
194
+ "y": 0.6513,
195
+ "z": -0.3988,
196
+ "visibility": 0.958
197
  },
198
  {
199
  "id": 21,
200
  "name": "LEFT_THUMB",
201
+ "x": 0.5387,
202
+ "y": 0.6405,
203
+ "z": -0.3737,
204
+ "visibility": 0.9237
205
  },
206
  {
207
  "id": 22,
208
  "name": "RIGHT_THUMB",
209
+ "x": 0.5338,
210
+ "y": 0.646,
211
+ "z": -0.3491,
212
+ "visibility": 0.9522
213
  },
214
  {
215
  "id": 23,
216
  "name": "LEFT_HIP",
217
+ "x": 0.5697,
218
+ "y": 0.5977,
219
+ "z": -0.0158,
220
+ "visibility": 1
221
  },
222
  {
223
  "id": 24,
224
  "name": "RIGHT_HIP",
225
+ "x": 0.5028,
226
+ "y": 0.5995,
227
+ "z": 0.0155,
228
+ "visibility": 1
229
  },
230
  {
231
  "id": 25,
232
  "name": "LEFT_KNEE",
233
+ "x": 0.5888,
234
+ "y": 0.6708,
235
+ "z": -0.0782,
236
+ "visibility": 0.9769
237
  },
238
  {
239
  "id": 26,
240
  "name": "RIGHT_KNEE",
241
+ "x": 0.4641,
242
+ "y": 0.6745,
243
+ "z": -0.0431,
244
+ "visibility": 0.9759
245
  },
246
  {
247
  "id": 27,
248
  "name": "LEFT_ANKLE",
249
+ "x": 0.6158,
250
+ "y": 0.7507,
251
+ "z": -0.0037,
252
+ "visibility": 0.9864
253
  },
254
  {
255
  "id": 28,
256
  "name": "RIGHT_ANKLE",
257
+ "x": 0.4338,
258
+ "y": 0.7489,
259
+ "z": 0.0473,
260
+ "visibility": 0.9817
261
  },
262
  {
263
  "id": 29,
264
  "name": "LEFT_HEEL",
265
+ "x": 0.6155,
266
+ "y": 0.7593,
267
+ "z": -0.0068,
268
+ "visibility": 0.8259
269
  },
270
  {
271
  "id": 30,
272
  "name": "RIGHT_HEEL",
273
+ "x": 0.4388,
274
+ "y": 0.7566,
275
+ "z": 0.0478,
276
+ "visibility": 0.8404
277
  },
278
  {
279
  "id": 31,
280
  "name": "LEFT_FOOT_INDEX",
281
+ "x": 0.6305,
282
+ "y": 0.7788,
283
+ "z": -0.1656,
284
+ "visibility": 0.9763
285
  },
286
  {
287
  "id": 32,
288
  "name": "RIGHT_FOOT_INDEX",
289
+ "x": 0.4196,
290
+ "y": 0.7767,
291
+ "z": -0.0952,
292
+ "visibility": 0.978
293
  }
294
  ]
295
  },
296
  "2_Toe-up": {
297
+ "score": 10,
298
  "comments": [
299
  "Đạt chuẩn"
300
  ],
 
303
  {
304
  "id": 0,
305
  "name": "NOSE",
306
+ "x": 0.5452,
307
+ "y": 0.5194,
308
+ "z": -0.5827,
309
+ "visibility": 1
310
  },
311
  {
312
  "id": 1,
313
  "name": "LEFT_EYE_INNER",
314
+ "x": 0.5509,
315
+ "y": 0.5123,
316
+ "z": -0.5868,
317
+ "visibility": 1
318
  },
319
  {
320
  "id": 2,
321
  "name": "LEFT_EYE",
322
+ "x": 0.5553,
323
+ "y": 0.5111,
324
+ "z": -0.5867,
325
+ "visibility": 0.9999
326
  },
327
  {
328
  "id": 3,
329
  "name": "LEFT_EYE_OUTER",
330
+ "x": 0.5596,
331
+ "y": 0.5099,
332
+ "z": -0.5867,
333
+ "visibility": 0.9999
334
  },
335
  {
336
  "id": 4,
337
  "name": "RIGHT_EYE_INNER",
338
+ "x": 0.5385,
339
+ "y": 0.5137,
340
+ "z": -0.5845,
341
+ "visibility": 1
342
  },
343
  {
344
  "id": 5,
345
  "name": "RIGHT_EYE",
346
+ "x": 0.5341,
347
+ "y": 0.5134,
348
+ "z": -0.5847,
349
+ "visibility": 1
350
  },
351
  {
352
  "id": 6,
353
  "name": "RIGHT_EYE_OUTER",
354
+ "x": 0.5296,
355
+ "y": 0.513,
356
+ "z": -0.5849,
357
+ "visibility": 1
358
  },
359
  {
360
  "id": 7,
361
  "name": "LEFT_EAR",
362
+ "x": 0.5626,
363
+ "y": 0.5054,
364
+ "z": -0.493,
365
+ "visibility": 0.9999
366
  },
367
  {
368
  "id": 8,
369
  "name": "RIGHT_EAR",
370
+ "x": 0.5201,
371
+ "y": 0.508,
372
+ "z": -0.4836,
373
+ "visibility": 1
374
  },
375
  {
376
  "id": 9,
377
  "name": "MOUTH_LEFT",
378
+ "x": 0.552,
379
+ "y": 0.521,
380
+ "z": -0.5366,
381
+ "visibility": 1
382
  },
383
  {
384
  "id": 10,
385
  "name": "MOUTH_RIGHT",
386
+ "x": 0.5369,
387
+ "y": 0.5218,
388
+ "z": -0.5344,
389
+ "visibility": 1
390
  },
391
  {
392
  "id": 11,
393
  "name": "LEFT_SHOULDER",
394
+ "x": 0.602,
395
+ "y": 0.5248,
396
+ "z": -0.3651,
397
+ "visibility": 1
398
  },
399
  {
400
  "id": 12,
401
  "name": "RIGHT_SHOULDER",
402
+ "x": 0.4712,
403
+ "y": 0.5315,
404
+ "z": -0.3288,
405
+ "visibility": 1
406
  },
407
  {
408
  "id": 13,
409
  "name": "LEFT_ELBOW",
410
+ "x": 0.584,
411
+ "y": 0.5799,
412
+ "z": -0.3018,
413
+ "visibility": 0.9806
414
  },
415
  {
416
  "id": 14,
417
  "name": "RIGHT_ELBOW",
418
+ "x": 0.4933,
419
+ "y": 0.5926,
420
+ "z": -0.2602,
421
+ "visibility": 0.9898
422
  },
423
  {
424
  "id": 15,
425
  "name": "LEFT_WRIST",
426
+ "x": 0.5475,
427
+ "y": 0.6287,
428
+ "z": -0.3638,
429
+ "visibility": 0.968
430
  },
431
  {
432
  "id": 16,
433
  "name": "RIGHT_WRIST",
434
+ "x": 0.5229,
435
+ "y": 0.6379,
436
+ "z": -0.3363,
437
+ "visibility": 0.98
438
  },
439
  {
440
  "id": 17,
441
  "name": "LEFT_PINKY",
442
+ "x": 0.5475,
443
+ "y": 0.6448,
444
+ "z": -0.3947,
445
+ "visibility": 0.9426
446
  },
447
  {
448
  "id": 18,
449
  "name": "RIGHT_PINKY",
450
+ "x": 0.529,
451
+ "y": 0.6524,
452
+ "z": -0.3639,
453
+ "visibility": 0.9546
454
  },
455
  {
456
  "id": 19,
457
  "name": "LEFT_INDEX",
458
+ "x": 0.5399,
459
+ "y": 0.6458,
460
+ "z": -0.4222,
461
+ "visibility": 0.9451
462
  },
463
  {
464
  "id": 20,
465
  "name": "RIGHT_INDEX",
466
+ "x": 0.5351,
467
+ "y": 0.6513,
468
+ "z": -0.3988,
469
+ "visibility": 0.958
470
  },
471
  {
472
  "id": 21,
473
  "name": "LEFT_THUMB",
474
+ "x": 0.5387,
475
+ "y": 0.6405,
476
+ "z": -0.3737,
477
+ "visibility": 0.9237
478
  },
479
  {
480
  "id": 22,
481
  "name": "RIGHT_THUMB",
482
+ "x": 0.5338,
483
+ "y": 0.646,
484
+ "z": -0.3491,
485
+ "visibility": 0.9522
486
  },
487
  {
488
  "id": 23,
489
  "name": "LEFT_HIP",
490
+ "x": 0.5697,
491
+ "y": 0.5977,
492
+ "z": -0.0158,
493
+ "visibility": 1
494
  },
495
  {
496
  "id": 24,
497
  "name": "RIGHT_HIP",
498
+ "x": 0.5028,
499
+ "y": 0.5995,
500
+ "z": 0.0155,
501
+ "visibility": 1
502
  },
503
  {
504
  "id": 25,
505
  "name": "LEFT_KNEE",
506
+ "x": 0.5888,
507
+ "y": 0.6708,
508
+ "z": -0.0782,
509
+ "visibility": 0.9769
510
  },
511
  {
512
  "id": 26,
513
  "name": "RIGHT_KNEE",
514
+ "x": 0.4641,
515
+ "y": 0.6745,
516
+ "z": -0.0431,
517
+ "visibility": 0.9759
518
  },
519
  {
520
  "id": 27,
521
  "name": "LEFT_ANKLE",
522
+ "x": 0.6158,
523
+ "y": 0.7507,
524
+ "z": -0.0037,
525
+ "visibility": 0.9864
526
  },
527
  {
528
  "id": 28,
529
  "name": "RIGHT_ANKLE",
530
+ "x": 0.4338,
531
+ "y": 0.7489,
532
+ "z": 0.0473,
533
+ "visibility": 0.9817
534
  },
535
  {
536
  "id": 29,
537
  "name": "LEFT_HEEL",
538
+ "x": 0.6155,
539
+ "y": 0.7593,
540
+ "z": -0.0068,
541
+ "visibility": 0.8259
542
  },
543
  {
544
  "id": 30,
545
  "name": "RIGHT_HEEL",
546
+ "x": 0.4388,
547
+ "y": 0.7566,
548
+ "z": 0.0478,
549
+ "visibility": 0.8404
550
  },
551
  {
552
  "id": 31,
553
  "name": "LEFT_FOOT_INDEX",
554
+ "x": 0.6305,
555
+ "y": 0.7788,
556
+ "z": -0.1656,
557
+ "visibility": 0.9763
558
  },
559
  {
560
  "id": 32,
561
  "name": "RIGHT_FOOT_INDEX",
562
+ "x": 0.4196,
563
+ "y": 0.7767,
564
+ "z": -0.0952,
565
+ "visibility": 0.978
566
  }
567
  ]
568
  },
569
  "3_Mid-Backswing": {
570
+ "score": 10,
571
  "comments": [
572
  "Đạt chuẩn"
573
  ],
 
576
  {
577
  "id": 0,
578
  "name": "NOSE",
579
+ "x": 0.5452,
580
+ "y": 0.5194,
581
+ "z": -0.5827,
582
+ "visibility": 1
583
  },
584
  {
585
  "id": 1,
586
  "name": "LEFT_EYE_INNER",
587
+ "x": 0.5509,
588
+ "y": 0.5123,
589
+ "z": -0.5868,
590
+ "visibility": 1
591
  },
592
  {
593
  "id": 2,
594
  "name": "LEFT_EYE",
595
+ "x": 0.5553,
596
+ "y": 0.5111,
597
+ "z": -0.5867,
598
+ "visibility": 0.9999
599
  },
600
  {
601
  "id": 3,
602
  "name": "LEFT_EYE_OUTER",
603
+ "x": 0.5596,
604
+ "y": 0.5099,
605
+ "z": -0.5867,
606
+ "visibility": 0.9999
607
  },
608
  {
609
  "id": 4,
610
  "name": "RIGHT_EYE_INNER",
611
+ "x": 0.5385,
612
+ "y": 0.5137,
613
+ "z": -0.5845,
614
+ "visibility": 1
615
  },
616
  {
617
  "id": 5,
618
  "name": "RIGHT_EYE",
619
+ "x": 0.5341,
620
+ "y": 0.5134,
621
+ "z": -0.5847,
622
+ "visibility": 1
623
  },
624
  {
625
  "id": 6,
626
  "name": "RIGHT_EYE_OUTER",
627
+ "x": 0.5296,
628
+ "y": 0.513,
629
+ "z": -0.5849,
630
+ "visibility": 1
631
  },
632
  {
633
  "id": 7,
634
  "name": "LEFT_EAR",
635
+ "x": 0.5626,
636
+ "y": 0.5054,
637
+ "z": -0.493,
638
+ "visibility": 0.9999
639
  },
640
  {
641
  "id": 8,
642
  "name": "RIGHT_EAR",
643
+ "x": 0.5201,
644
+ "y": 0.508,
645
+ "z": -0.4836,
646
+ "visibility": 1
647
  },
648
  {
649
  "id": 9,
650
  "name": "MOUTH_LEFT",
651
+ "x": 0.552,
652
+ "y": 0.521,
653
+ "z": -0.5366,
654
+ "visibility": 1
655
  },
656
  {
657
  "id": 10,
658
  "name": "MOUTH_RIGHT",
659
+ "x": 0.5369,
660
+ "y": 0.5218,
661
+ "z": -0.5344,
662
+ "visibility": 1
663
  },
664
  {
665
  "id": 11,
666
  "name": "LEFT_SHOULDER",
667
+ "x": 0.602,
668
+ "y": 0.5248,
669
+ "z": -0.3651,
670
+ "visibility": 1
671
  },
672
  {
673
  "id": 12,
674
  "name": "RIGHT_SHOULDER",
675
+ "x": 0.4712,
676
+ "y": 0.5315,
677
+ "z": -0.3288,
678
+ "visibility": 1
679
  },
680
  {
681
  "id": 13,
682
  "name": "LEFT_ELBOW",
683
+ "x": 0.584,
684
+ "y": 0.5799,
685
+ "z": -0.3018,
686
+ "visibility": 0.9806
687
  },
688
  {
689
  "id": 14,
690
  "name": "RIGHT_ELBOW",
691
+ "x": 0.4933,
692
+ "y": 0.5926,
693
+ "z": -0.2602,
694
+ "visibility": 0.9898
695
  },
696
  {
697
  "id": 15,
698
  "name": "LEFT_WRIST",
699
+ "x": 0.5475,
700
+ "y": 0.6287,
701
+ "z": -0.3638,
702
+ "visibility": 0.968
703
  },
704
  {
705
  "id": 16,
706
  "name": "RIGHT_WRIST",
707
+ "x": 0.5229,
708
+ "y": 0.6379,
709
+ "z": -0.3363,
710
+ "visibility": 0.98
711
  },
712
  {
713
  "id": 17,
714
  "name": "LEFT_PINKY",
715
+ "x": 0.5475,
716
+ "y": 0.6448,
717
+ "z": -0.3947,
718
+ "visibility": 0.9426
719
  },
720
  {
721
  "id": 18,
722
  "name": "RIGHT_PINKY",
723
+ "x": 0.529,
724
+ "y": 0.6524,
725
+ "z": -0.3639,
726
+ "visibility": 0.9546
727
  },
728
  {
729
  "id": 19,
730
  "name": "LEFT_INDEX",
731
+ "x": 0.5399,
732
+ "y": 0.6458,
733
+ "z": -0.4222,
734
+ "visibility": 0.9451
735
  },
736
  {
737
  "id": 20,
738
  "name": "RIGHT_INDEX",
739
+ "x": 0.5351,
740
+ "y": 0.6513,
741
+ "z": -0.3988,
742
+ "visibility": 0.958
743
  },
744
  {
745
  "id": 21,
746
  "name": "LEFT_THUMB",
747
+ "x": 0.5387,
748
+ "y": 0.6405,
749
+ "z": -0.3737,
750
+ "visibility": 0.9237
751
  },
752
  {
753
  "id": 22,
754
  "name": "RIGHT_THUMB",
755
+ "x": 0.5338,
756
+ "y": 0.646,
757
+ "z": -0.3491,
758
+ "visibility": 0.9522
759
  },
760
  {
761
  "id": 23,
762
  "name": "LEFT_HIP",
763
+ "x": 0.5697,
764
+ "y": 0.5977,
765
+ "z": -0.0158,
766
+ "visibility": 1
767
  },
768
  {
769
  "id": 24,
770
  "name": "RIGHT_HIP",
771
+ "x": 0.5028,
772
+ "y": 0.5995,
773
+ "z": 0.0155,
774
+ "visibility": 1
775
  },
776
  {
777
  "id": 25,
778
  "name": "LEFT_KNEE",
779
+ "x": 0.5888,
780
+ "y": 0.6708,
781
+ "z": -0.0782,
782
+ "visibility": 0.9769
783
  },
784
  {
785
  "id": 26,
786
  "name": "RIGHT_KNEE",
787
+ "x": 0.4641,
788
+ "y": 0.6745,
789
+ "z": -0.0431,
790
+ "visibility": 0.9759
791
  },
792
  {
793
  "id": 27,
794
  "name": "LEFT_ANKLE",
795
+ "x": 0.6158,
796
+ "y": 0.7507,
797
+ "z": -0.0037,
798
+ "visibility": 0.9864
799
  },
800
  {
801
  "id": 28,
802
  "name": "RIGHT_ANKLE",
803
+ "x": 0.4338,
804
+ "y": 0.7489,
805
+ "z": 0.0473,
806
+ "visibility": 0.9817
807
  },
808
  {
809
  "id": 29,
810
  "name": "LEFT_HEEL",
811
+ "x": 0.6155,
812
+ "y": 0.7593,
813
+ "z": -0.0068,
814
+ "visibility": 0.8259
815
  },
816
  {
817
  "id": 30,
818
  "name": "RIGHT_HEEL",
819
+ "x": 0.4388,
820
+ "y": 0.7566,
821
+ "z": 0.0478,
822
+ "visibility": 0.8404
823
  },
824
  {
825
  "id": 31,
826
  "name": "LEFT_FOOT_INDEX",
827
+ "x": 0.6305,
828
+ "y": 0.7788,
829
+ "z": -0.1656,
830
+ "visibility": 0.9763
831
  },
832
  {
833
  "id": 32,
834
  "name": "RIGHT_FOOT_INDEX",
835
+ "x": 0.4196,
836
+ "y": 0.7767,
837
+ "z": -0.0952,
838
+ "visibility": 0.978
839
  }
840
  ]
841
  },
842
  "4_Top": {
843
+ "score": 10,
844
  "comments": [
845
+ "Đạt chuẩn"
846
  ],
847
  "data": {
848
+ "lead_arm_angle": 161.36934184577666,
849
+ "shoulder_tilt": 0.006613552570343018
850
  },
851
  "raw_landmarks": [
852
  {
853
  "id": 0,
854
  "name": "NOSE",
855
+ "x": 0.5452,
856
+ "y": 0.5194,
857
+ "z": -0.5827,
858
+ "visibility": 1
859
  },
860
  {
861
  "id": 1,
862
  "name": "LEFT_EYE_INNER",
863
+ "x": 0.5509,
864
+ "y": 0.5123,
865
+ "z": -0.5868,
866
+ "visibility": 1
867
  },
868
  {
869
  "id": 2,
870
  "name": "LEFT_EYE",
871
+ "x": 0.5553,
872
+ "y": 0.5111,
873
+ "z": -0.5867,
874
+ "visibility": 0.9999
875
  },
876
  {
877
  "id": 3,
878
  "name": "LEFT_EYE_OUTER",
879
+ "x": 0.5596,
880
+ "y": 0.5099,
881
+ "z": -0.5867,
882
+ "visibility": 0.9999
883
  },
884
  {
885
  "id": 4,
886
  "name": "RIGHT_EYE_INNER",
887
+ "x": 0.5385,
888
+ "y": 0.5137,
889
+ "z": -0.5845,
890
+ "visibility": 1
891
  },
892
  {
893
  "id": 5,
894
  "name": "RIGHT_EYE",
895
+ "x": 0.5341,
896
+ "y": 0.5134,
897
+ "z": -0.5847,
898
+ "visibility": 1
899
  },
900
  {
901
  "id": 6,
902
  "name": "RIGHT_EYE_OUTER",
903
+ "x": 0.5296,
904
+ "y": 0.513,
905
+ "z": -0.5849,
906
+ "visibility": 1
907
  },
908
  {
909
  "id": 7,
910
  "name": "LEFT_EAR",
911
+ "x": 0.5626,
912
+ "y": 0.5054,
913
+ "z": -0.493,
914
+ "visibility": 0.9999
915
  },
916
  {
917
  "id": 8,
918
  "name": "RIGHT_EAR",
919
+ "x": 0.5201,
920
+ "y": 0.508,
921
+ "z": -0.4836,
922
+ "visibility": 1
923
  },
924
  {
925
  "id": 9,
926
  "name": "MOUTH_LEFT",
927
+ "x": 0.552,
928
+ "y": 0.521,
929
+ "z": -0.5366,
930
+ "visibility": 1
931
  },
932
  {
933
  "id": 10,
934
  "name": "MOUTH_RIGHT",
935
+ "x": 0.5369,
936
+ "y": 0.5218,
937
+ "z": -0.5344,
938
+ "visibility": 1
939
  },
940
  {
941
  "id": 11,
942
  "name": "LEFT_SHOULDER",
943
+ "x": 0.602,
944
+ "y": 0.5248,
945
+ "z": -0.3651,
946
+ "visibility": 1
947
  },
948
  {
949
  "id": 12,
950
  "name": "RIGHT_SHOULDER",
951
+ "x": 0.4712,
952
+ "y": 0.5315,
953
+ "z": -0.3288,
954
+ "visibility": 1
955
  },
956
  {
957
  "id": 13,
958
  "name": "LEFT_ELBOW",
959
+ "x": 0.584,
960
+ "y": 0.5799,
961
+ "z": -0.3018,
962
+ "visibility": 0.9806
963
  },
964
  {
965
  "id": 14,
966
  "name": "RIGHT_ELBOW",
967
+ "x": 0.4933,
968
+ "y": 0.5926,
969
+ "z": -0.2602,
970
+ "visibility": 0.9898
971
  },
972
  {
973
  "id": 15,
974
  "name": "LEFT_WRIST",
975
+ "x": 0.5475,
976
+ "y": 0.6287,
977
+ "z": -0.3638,
978
+ "visibility": 0.968
979
  },
980
  {
981
  "id": 16,
982
  "name": "RIGHT_WRIST",
983
+ "x": 0.5229,
984
+ "y": 0.6379,
985
+ "z": -0.3363,
986
+ "visibility": 0.98
987
  },
988
  {
989
  "id": 17,
990
  "name": "LEFT_PINKY",
991
+ "x": 0.5475,
992
+ "y": 0.6448,
993
+ "z": -0.3947,
994
+ "visibility": 0.9426
995
  },
996
  {
997
  "id": 18,
998
  "name": "RIGHT_PINKY",
999
+ "x": 0.529,
1000
+ "y": 0.6524,
1001
+ "z": -0.3639,
1002
+ "visibility": 0.9546
1003
  },
1004
  {
1005
  "id": 19,
1006
  "name": "LEFT_INDEX",
1007
+ "x": 0.5399,
1008
+ "y": 0.6458,
1009
+ "z": -0.4222,
1010
+ "visibility": 0.9451
1011
  },
1012
  {
1013
  "id": 20,
1014
  "name": "RIGHT_INDEX",
1015
+ "x": 0.5351,
1016
+ "y": 0.6513,
1017
+ "z": -0.3988,
1018
+ "visibility": 0.958
1019
  },
1020
  {
1021
  "id": 21,
1022
  "name": "LEFT_THUMB",
1023
+ "x": 0.5387,
1024
+ "y": 0.6405,
1025
+ "z": -0.3737,
1026
+ "visibility": 0.9237
1027
  },
1028
  {
1029
  "id": 22,
1030
  "name": "RIGHT_THUMB",
1031
+ "x": 0.5338,
1032
+ "y": 0.646,
1033
+ "z": -0.3491,
1034
+ "visibility": 0.9522
1035
  },
1036
  {
1037
  "id": 23,
1038
  "name": "LEFT_HIP",
1039
+ "x": 0.5697,
1040
+ "y": 0.5977,
1041
+ "z": -0.0158,
1042
+ "visibility": 1
1043
  },
1044
  {
1045
  "id": 24,
1046
  "name": "RIGHT_HIP",
1047
+ "x": 0.5028,
1048
+ "y": 0.5995,
1049
+ "z": 0.0155,
1050
+ "visibility": 1
1051
  },
1052
  {
1053
  "id": 25,
1054
  "name": "LEFT_KNEE",
1055
+ "x": 0.5888,
1056
+ "y": 0.6708,
1057
+ "z": -0.0782,
1058
+ "visibility": 0.9769
1059
  },
1060
  {
1061
  "id": 26,
1062
  "name": "RIGHT_KNEE",
1063
+ "x": 0.4641,
1064
+ "y": 0.6745,
1065
+ "z": -0.0431,
1066
+ "visibility": 0.9759
1067
  },
1068
  {
1069
  "id": 27,
1070
  "name": "LEFT_ANKLE",
1071
+ "x": 0.6158,
1072
+ "y": 0.7507,
1073
+ "z": -0.0037,
1074
+ "visibility": 0.9864
1075
  },
1076
  {
1077
  "id": 28,
1078
  "name": "RIGHT_ANKLE",
1079
+ "x": 0.4338,
1080
+ "y": 0.7489,
1081
+ "z": 0.0473,
1082
+ "visibility": 0.9817
1083
  },
1084
  {
1085
  "id": 29,
1086
  "name": "LEFT_HEEL",
1087
+ "x": 0.6155,
1088
+ "y": 0.7593,
1089
+ "z": -0.0068,
1090
+ "visibility": 0.8259
1091
  },
1092
  {
1093
  "id": 30,
1094
  "name": "RIGHT_HEEL",
1095
+ "x": 0.4388,
1096
+ "y": 0.7566,
1097
+ "z": 0.0478,
1098
+ "visibility": 0.8404
1099
  },
1100
  {
1101
  "id": 31,
1102
  "name": "LEFT_FOOT_INDEX",
1103
+ "x": 0.6305,
1104
+ "y": 0.7788,
1105
+ "z": -0.1656,
1106
  "visibility": 0.9763
1107
  },
1108
  {
1109
  "id": 32,
1110
  "name": "RIGHT_FOOT_INDEX",
1111
+ "x": 0.4196,
1112
+ "y": 0.7767,
1113
+ "z": -0.0952,
1114
+ "visibility": 0.978
1115
  }
1116
  ]
1117
  },
1118
  "5_Mid-Downswing": {
1119
+ "score": 10,
1120
  "comments": [
1121
  "Đạt chuẩn"
1122
  ],
 
1125
  {
1126
  "id": 0,
1127
  "name": "NOSE",
1128
+ "x": 0.5452,
1129
+ "y": 0.5194,
1130
+ "z": -0.5827,
1131
+ "visibility": 1
1132
  },
1133
  {
1134
  "id": 1,
1135
  "name": "LEFT_EYE_INNER",
1136
+ "x": 0.5509,
1137
+ "y": 0.5123,
1138
+ "z": -0.5868,
1139
+ "visibility": 1
1140
  },
1141
  {
1142
  "id": 2,
1143
  "name": "LEFT_EYE",
1144
+ "x": 0.5553,
1145
+ "y": 0.5111,
1146
+ "z": -0.5867,
1147
+ "visibility": 0.9999
1148
  },
1149
  {
1150
  "id": 3,
1151
  "name": "LEFT_EYE_OUTER",
1152
+ "x": 0.5596,
1153
+ "y": 0.5099,
1154
+ "z": -0.5867,
1155
+ "visibility": 0.9999
1156
  },
1157
  {
1158
  "id": 4,
1159
  "name": "RIGHT_EYE_INNER",
1160
+ "x": 0.5385,
1161
+ "y": 0.5137,
1162
+ "z": -0.5845,
1163
+ "visibility": 1
1164
  },
1165
  {
1166
  "id": 5,
1167
  "name": "RIGHT_EYE",
1168
+ "x": 0.5341,
1169
+ "y": 0.5134,
1170
+ "z": -0.5847,
1171
+ "visibility": 1
1172
  },
1173
  {
1174
  "id": 6,
1175
  "name": "RIGHT_EYE_OUTER",
1176
+ "x": 0.5296,
1177
+ "y": 0.513,
1178
+ "z": -0.5849,
1179
+ "visibility": 1
1180
  },
1181
  {
1182
  "id": 7,
1183
  "name": "LEFT_EAR",
1184
+ "x": 0.5626,
1185
+ "y": 0.5054,
1186
+ "z": -0.493,
1187
+ "visibility": 0.9999
1188
  },
1189
  {
1190
  "id": 8,
1191
  "name": "RIGHT_EAR",
1192
+ "x": 0.5201,
1193
+ "y": 0.508,
1194
+ "z": -0.4836,
1195
+ "visibility": 1
1196
  },
1197
  {
1198
  "id": 9,
1199
  "name": "MOUTH_LEFT",
1200
+ "x": 0.552,
1201
+ "y": 0.521,
1202
+ "z": -0.5366,
1203
+ "visibility": 1
1204
  },
1205
  {
1206
  "id": 10,
1207
  "name": "MOUTH_RIGHT",
1208
+ "x": 0.5369,
1209
+ "y": 0.5218,
1210
+ "z": -0.5344,
1211
+ "visibility": 1
1212
  },
1213
  {
1214
  "id": 11,
1215
  "name": "LEFT_SHOULDER",
1216
+ "x": 0.602,
1217
+ "y": 0.5248,
1218
+ "z": -0.3651,
1219
+ "visibility": 1
1220
  },
1221
  {
1222
  "id": 12,
1223
  "name": "RIGHT_SHOULDER",
1224
+ "x": 0.4712,
1225
+ "y": 0.5315,
1226
+ "z": -0.3288,
1227
+ "visibility": 1
1228
  },
1229
  {
1230
  "id": 13,
1231
  "name": "LEFT_ELBOW",
1232
+ "x": 0.584,
1233
+ "y": 0.5799,
1234
+ "z": -0.3018,
1235
+ "visibility": 0.9806
1236
  },
1237
  {
1238
  "id": 14,
1239
  "name": "RIGHT_ELBOW",
1240
+ "x": 0.4933,
1241
+ "y": 0.5926,
1242
+ "z": -0.2602,
1243
+ "visibility": 0.9898
1244
  },
1245
  {
1246
  "id": 15,
1247
  "name": "LEFT_WRIST",
1248
+ "x": 0.5475,
1249
+ "y": 0.6287,
1250
+ "z": -0.3638,
1251
+ "visibility": 0.968
1252
  },
1253
  {
1254
  "id": 16,
1255
  "name": "RIGHT_WRIST",
1256
+ "x": 0.5229,
1257
+ "y": 0.6379,
1258
+ "z": -0.3363,
1259
+ "visibility": 0.98
1260
  },
1261
  {
1262
  "id": 17,
1263
  "name": "LEFT_PINKY",
1264
+ "x": 0.5475,
1265
+ "y": 0.6448,
1266
+ "z": -0.3947,
1267
+ "visibility": 0.9426
1268
  },
1269
  {
1270
  "id": 18,
1271
  "name": "RIGHT_PINKY",
1272
+ "x": 0.529,
1273
+ "y": 0.6524,
1274
+ "z": -0.3639,
1275
+ "visibility": 0.9546
1276
  },
1277
  {
1278
  "id": 19,
1279
  "name": "LEFT_INDEX",
1280
+ "x": 0.5399,
1281
+ "y": 0.6458,
1282
+ "z": -0.4222,
1283
+ "visibility": 0.9451
1284
  },
1285
  {
1286
  "id": 20,
1287
  "name": "RIGHT_INDEX",
1288
+ "x": 0.5351,
1289
+ "y": 0.6513,
1290
+ "z": -0.3988,
1291
+ "visibility": 0.958
1292
  },
1293
  {
1294
  "id": 21,
1295
  "name": "LEFT_THUMB",
1296
+ "x": 0.5387,
1297
+ "y": 0.6405,
1298
+ "z": -0.3737,
1299
+ "visibility": 0.9237
1300
  },
1301
  {
1302
  "id": 22,
1303
  "name": "RIGHT_THUMB",
1304
+ "x": 0.5338,
1305
+ "y": 0.646,
1306
+ "z": -0.3491,
1307
+ "visibility": 0.9522
1308
  },
1309
  {
1310
  "id": 23,
1311
  "name": "LEFT_HIP",
1312
+ "x": 0.5697,
1313
+ "y": 0.5977,
1314
+ "z": -0.0158,
1315
+ "visibility": 1
1316
  },
1317
  {
1318
  "id": 24,
1319
  "name": "RIGHT_HIP",
1320
+ "x": 0.5028,
1321
+ "y": 0.5995,
1322
+ "z": 0.0155,
1323
+ "visibility": 1
1324
  },
1325
  {
1326
  "id": 25,
1327
  "name": "LEFT_KNEE",
1328
+ "x": 0.5888,
1329
+ "y": 0.6708,
1330
+ "z": -0.0782,
1331
+ "visibility": 0.9769
1332
  },
1333
  {
1334
  "id": 26,
1335
  "name": "RIGHT_KNEE",
1336
+ "x": 0.4641,
1337
+ "y": 0.6745,
1338
+ "z": -0.0431,
1339
+ "visibility": 0.9759
1340
  },
1341
  {
1342
  "id": 27,
1343
  "name": "LEFT_ANKLE",
1344
+ "x": 0.6158,
1345
+ "y": 0.7507,
1346
+ "z": -0.0037,
1347
+ "visibility": 0.9864
1348
  },
1349
  {
1350
  "id": 28,
1351
  "name": "RIGHT_ANKLE",
1352
+ "x": 0.4338,
1353
+ "y": 0.7489,
1354
+ "z": 0.0473,
1355
+ "visibility": 0.9817
1356
  },
1357
  {
1358
  "id": 29,
1359
  "name": "LEFT_HEEL",
1360
+ "x": 0.6155,
1361
+ "y": 0.7593,
1362
+ "z": -0.0068,
1363
+ "visibility": 0.8259
1364
  },
1365
  {
1366
  "id": 30,
1367
  "name": "RIGHT_HEEL",
1368
+ "x": 0.4388,
1369
+ "y": 0.7566,
1370
+ "z": 0.0478,
1371
+ "visibility": 0.8404
1372
  },
1373
  {
1374
  "id": 31,
1375
  "name": "LEFT_FOOT_INDEX",
1376
+ "x": 0.6305,
1377
+ "y": 0.7788,
1378
+ "z": -0.1656,
1379
+ "visibility": 0.9763
1380
  },
1381
  {
1382
  "id": 32,
1383
  "name": "RIGHT_FOOT_INDEX",
1384
+ "x": 0.4196,
1385
+ "y": 0.7767,
1386
+ "z": -0.0952,
1387
+ "visibility": 0.978
1388
  }
1389
  ]
1390
  },
1391
  "6_Impact": {
1392
+ "score": 10,
1393
  "comments": [
1394
  "Đạt chuẩn"
1395
  ],
1396
  "data": {
1397
+ "hip_openness": 0.06694477796554565
1398
  },
1399
  "raw_landmarks": [
1400
  {
1401
  "id": 0,
1402
  "name": "NOSE",
1403
+ "x": 0.5452,
1404
+ "y": 0.5194,
1405
+ "z": -0.5827,
1406
+ "visibility": 1
1407
  },
1408
  {
1409
  "id": 1,
1410
  "name": "LEFT_EYE_INNER",
1411
+ "x": 0.5509,
1412
+ "y": 0.5123,
1413
+ "z": -0.5868,
1414
+ "visibility": 1
1415
  },
1416
  {
1417
  "id": 2,
1418
  "name": "LEFT_EYE",
1419
+ "x": 0.5553,
1420
+ "y": 0.5111,
1421
+ "z": -0.5867,
1422
+ "visibility": 0.9999
1423
  },
1424
  {
1425
  "id": 3,
1426
  "name": "LEFT_EYE_OUTER",
1427
+ "x": 0.5596,
1428
+ "y": 0.5099,
1429
+ "z": -0.5867,
1430
+ "visibility": 0.9999
1431
  },
1432
  {
1433
  "id": 4,
1434
  "name": "RIGHT_EYE_INNER",
1435
+ "x": 0.5385,
1436
+ "y": 0.5137,
1437
+ "z": -0.5845,
1438
+ "visibility": 1
1439
  },
1440
  {
1441
  "id": 5,
1442
  "name": "RIGHT_EYE",
1443
+ "x": 0.5341,
1444
+ "y": 0.5134,
1445
+ "z": -0.5847,
1446
+ "visibility": 1
1447
  },
1448
  {
1449
  "id": 6,
1450
  "name": "RIGHT_EYE_OUTER",
1451
+ "x": 0.5296,
1452
+ "y": 0.513,
1453
+ "z": -0.5849,
1454
+ "visibility": 1
1455
  },
1456
  {
1457
  "id": 7,
1458
  "name": "LEFT_EAR",
1459
+ "x": 0.5626,
1460
+ "y": 0.5054,
1461
+ "z": -0.493,
1462
+ "visibility": 0.9999
1463
  },
1464
  {
1465
  "id": 8,
1466
  "name": "RIGHT_EAR",
1467
+ "x": 0.5201,
1468
+ "y": 0.508,
1469
+ "z": -0.4836,
1470
+ "visibility": 1
1471
  },
1472
  {
1473
  "id": 9,
1474
  "name": "MOUTH_LEFT",
1475
+ "x": 0.552,
1476
+ "y": 0.521,
1477
+ "z": -0.5366,
1478
+ "visibility": 1
1479
  },
1480
  {
1481
  "id": 10,
1482
  "name": "MOUTH_RIGHT",
1483
+ "x": 0.5369,
1484
+ "y": 0.5218,
1485
+ "z": -0.5344,
1486
+ "visibility": 1
1487
  },
1488
  {
1489
  "id": 11,
1490
  "name": "LEFT_SHOULDER",
1491
+ "x": 0.602,
1492
+ "y": 0.5248,
1493
+ "z": -0.3651,
1494
+ "visibility": 1
1495
  },
1496
  {
1497
  "id": 12,
1498
  "name": "RIGHT_SHOULDER",
1499
+ "x": 0.4712,
1500
+ "y": 0.5315,
1501
+ "z": -0.3288,
1502
+ "visibility": 1
1503
  },
1504
  {
1505
  "id": 13,
1506
  "name": "LEFT_ELBOW",
1507
+ "x": 0.584,
1508
+ "y": 0.5799,
1509
+ "z": -0.3018,
1510
+ "visibility": 0.9806
1511
  },
1512
  {
1513
  "id": 14,
1514
  "name": "RIGHT_ELBOW",
1515
+ "x": 0.4933,
1516
+ "y": 0.5926,
1517
+ "z": -0.2602,
1518
+ "visibility": 0.9898
1519
  },
1520
  {
1521
  "id": 15,
1522
  "name": "LEFT_WRIST",
1523
+ "x": 0.5475,
1524
+ "y": 0.6287,
1525
+ "z": -0.3638,
1526
+ "visibility": 0.968
1527
  },
1528
  {
1529
  "id": 16,
1530
  "name": "RIGHT_WRIST",
1531
+ "x": 0.5229,
1532
+ "y": 0.6379,
1533
+ "z": -0.3363,
1534
+ "visibility": 0.98
1535
  },
1536
  {
1537
  "id": 17,
1538
  "name": "LEFT_PINKY",
1539
+ "x": 0.5475,
1540
+ "y": 0.6448,
1541
+ "z": -0.3947,
1542
+ "visibility": 0.9426
1543
  },
1544
  {
1545
  "id": 18,
1546
  "name": "RIGHT_PINKY",
1547
+ "x": 0.529,
1548
+ "y": 0.6524,
1549
+ "z": -0.3639,
1550
+ "visibility": 0.9546
1551
  },
1552
  {
1553
  "id": 19,
1554
  "name": "LEFT_INDEX",
1555
+ "x": 0.5399,
1556
+ "y": 0.6458,
1557
+ "z": -0.4222,
1558
+ "visibility": 0.9451
1559
  },
1560
  {
1561
  "id": 20,
1562
  "name": "RIGHT_INDEX",
1563
+ "x": 0.5351,
1564
+ "y": 0.6513,
1565
+ "z": -0.3988,
1566
+ "visibility": 0.958
1567
  },
1568
  {
1569
  "id": 21,
1570
  "name": "LEFT_THUMB",
1571
+ "x": 0.5387,
1572
+ "y": 0.6405,
1573
+ "z": -0.3737,
1574
+ "visibility": 0.9237
1575
  },
1576
  {
1577
  "id": 22,
1578
  "name": "RIGHT_THUMB",
1579
+ "x": 0.5338,
1580
+ "y": 0.646,
1581
+ "z": -0.3491,
1582
+ "visibility": 0.9522
1583
  },
1584
  {
1585
  "id": 23,
1586
  "name": "LEFT_HIP",
1587
+ "x": 0.5697,
1588
+ "y": 0.5977,
1589
+ "z": -0.0158,
1590
+ "visibility": 1
1591
  },
1592
  {
1593
  "id": 24,
1594
  "name": "RIGHT_HIP",
1595
+ "x": 0.5028,
1596
+ "y": 0.5995,
1597
+ "z": 0.0155,
1598
+ "visibility": 1
1599
  },
1600
  {
1601
  "id": 25,
1602
  "name": "LEFT_KNEE",
1603
+ "x": 0.5888,
1604
+ "y": 0.6708,
1605
+ "z": -0.0782,
1606
+ "visibility": 0.9769
1607
  },
1608
  {
1609
  "id": 26,
1610
  "name": "RIGHT_KNEE",
1611
+ "x": 0.4641,
1612
+ "y": 0.6745,
1613
+ "z": -0.0431,
1614
+ "visibility": 0.9759
1615
  },
1616
  {
1617
  "id": 27,
1618
  "name": "LEFT_ANKLE",
1619
+ "x": 0.6158,
1620
+ "y": 0.7507,
1621
+ "z": -0.0037,
1622
+ "visibility": 0.9864
1623
  },
1624
  {
1625
  "id": 28,
1626
  "name": "RIGHT_ANKLE",
1627
+ "x": 0.4338,
1628
+ "y": 0.7489,
1629
+ "z": 0.0473,
1630
+ "visibility": 0.9817
1631
  },
1632
  {
1633
  "id": 29,
1634
  "name": "LEFT_HEEL",
1635
+ "x": 0.6155,
1636
+ "y": 0.7593,
1637
+ "z": -0.0068,
1638
+ "visibility": 0.8259
1639
  },
1640
  {
1641
  "id": 30,
1642
  "name": "RIGHT_HEEL",
1643
+ "x": 0.4388,
1644
+ "y": 0.7566,
1645
+ "z": 0.0478,
1646
+ "visibility": 0.8404
1647
  },
1648
  {
1649
  "id": 31,
1650
  "name": "LEFT_FOOT_INDEX",
1651
+ "x": 0.6305,
1652
+ "y": 0.7788,
1653
+ "z": -0.1656,
1654
+ "visibility": 0.9763
1655
  },
1656
  {
1657
  "id": 32,
1658
  "name": "RIGHT_FOOT_INDEX",
1659
+ "x": 0.4196,
1660
+ "y": 0.7767,
1661
+ "z": -0.0952,
1662
+ "visibility": 0.978
1663
  }
1664
  ]
1665
  },
1666
  "7_Mid-Follow-Through": {
1667
+ "score": 10,
1668
  "comments": [
1669
  "Đạt chuẩn"
1670
  ],
 
1673
  {
1674
  "id": 0,
1675
  "name": "NOSE",
1676
+ "x": 0.5185,
1677
+ "y": 0.5218,
1678
+ "z": -0.7834,
1679
+ "visibility": 1
1680
  },
1681
  {
1682
  "id": 1,
1683
  "name": "LEFT_EYE_INNER",
1684
+ "x": 0.5254,
1685
+ "y": 0.5169,
1686
+ "z": -0.7878,
1687
+ "visibility": 0.9999
1688
  },
1689
  {
1690
  "id": 2,
1691
  "name": "LEFT_EYE",
1692
+ "x": 0.5293,
1693
+ "y": 0.5165,
1694
+ "z": -0.7876,
1695
+ "visibility": 0.9999
1696
  },
1697
  {
1698
  "id": 3,
1699
  "name": "LEFT_EYE_OUTER",
1700
+ "x": 0.5333,
1701
+ "y": 0.5159,
1702
+ "z": -0.7876,
1703
+ "visibility": 0.9999
1704
  },
1705
  {
1706
  "id": 4,
1707
  "name": "RIGHT_EYE_INNER",
1708
+ "x": 0.5153,
1709
+ "y": 0.5161,
1710
+ "z": -0.7822,
1711
+ "visibility": 0.9999
1712
  },
1713
  {
1714
  "id": 5,
1715
  "name": "RIGHT_EYE",
1716
+ "x": 0.5121,
1717
+ "y": 0.5152,
1718
+ "z": -0.7824,
1719
+ "visibility": 0.9999
1720
  },
1721
  {
1722
  "id": 6,
1723
  "name": "RIGHT_EYE_OUTER",
1724
+ "x": 0.5089,
1725
+ "y": 0.5142,
1726
+ "z": -0.7825,
1727
+ "visibility": 0.9999
1728
  },
1729
  {
1730
  "id": 7,
1731
  "name": "LEFT_EAR",
1732
+ "x": 0.5396,
1733
+ "y": 0.5104,
1734
+ "z": -0.6658,
1735
+ "visibility": 0.9999
1736
  },
1737
  {
1738
  "id": 8,
1739
  "name": "RIGHT_EAR",
1740
+ "x": 0.504,
1741
+ "y": 0.5087,
1742
+ "z": -0.6424,
1743
+ "visibility": 0.9999
1744
  },
1745
  {
1746
  "id": 9,
1747
  "name": "MOUTH_LEFT",
1748
+ "x": 0.5245,
1749
+ "y": 0.5241,
1750
+ "z": -0.7244,
1751
+ "visibility": 1
1752
  },
1753
  {
1754
  "id": 10,
1755
  "name": "MOUTH_RIGHT",
1756
+ "x": 0.5115,
1757
+ "y": 0.5228,
1758
+ "z": -0.7182,
1759
+ "visibility": 1
1760
  },
1761
  {
1762
  "id": 11,
1763
  "name": "LEFT_SHOULDER",
1764
+ "x": 0.5781,
1765
+ "y": 0.5309,
1766
+ "z": -0.509,
1767
+ "visibility": 1
1768
  },
1769
  {
1770
  "id": 12,
1771
  "name": "RIGHT_SHOULDER",
1772
+ "x": 0.4549,
1773
+ "y": 0.5223,
1774
+ "z": -0.3961,
1775
+ "visibility": 1
1776
  },
1777
  {
1778
  "id": 13,
1779
  "name": "LEFT_ELBOW",
1780
+ "x": 0.5468,
1781
+ "y": 0.5848,
1782
+ "z": -0.4442,
1783
+ "visibility": 0.9891
1784
  },
1785
  {
1786
  "id": 14,
1787
  "name": "RIGHT_ELBOW",
1788
+ "x": 0.4525,
1789
+ "y": 0.5773,
1790
+ "z": -0.2462,
1791
+ "visibility": 0.981
1792
  },
1793
  {
1794
  "id": 15,
1795
  "name": "LEFT_WRIST",
1796
+ "x": 0.4879,
1797
+ "y": 0.6283,
1798
+ "z": -0.5367,
1799
+ "visibility": 0.9831
1800
  },
1801
  {
1802
  "id": 16,
1803
  "name": "RIGHT_WRIST",
1804
+ "x": 0.4552,
1805
+ "y": 0.6304,
1806
+ "z": -0.325,
1807
+ "visibility": 0.9735
1808
  },
1809
  {
1810
  "id": 17,
1811
  "name": "LEFT_PINKY",
1812
+ "x": 0.4762,
1813
+ "y": 0.6445,
1814
+ "z": -0.5803,
1815
+ "visibility": 0.9634
1816
  },
1817
  {
1818
  "id": 18,
1819
  "name": "RIGHT_PINKY",
1820
+ "x": 0.4492,
1821
+ "y": 0.6466,
1822
+ "z": -0.3501,
1823
+ "visibility": 0.9438
1824
  },
1825
  {
1826
  "id": 19,
1827
  "name": "LEFT_INDEX",
1828
+ "x": 0.4699,
1829
+ "y": 0.6432,
1830
+ "z": -0.613,
1831
+ "visibility": 0.9661
1832
  },
1833
  {
1834
  "id": 20,
1835
  "name": "RIGHT_INDEX",
1836
+ "x": 0.4545,
1837
+ "y": 0.646,
1838
+ "z": -0.4028,
1839
+ "visibility": 0.9496
1840
  },
1841
  {
1842
  "id": 21,
1843
  "name": "LEFT_THUMB",
1844
+ "x": 0.4734,
1845
+ "y": 0.6373,
1846
+ "z": -0.548,
1847
+ "visibility": 0.9436
1848
  },
1849
  {
1850
  "id": 22,
1851
  "name": "RIGHT_THUMB",
1852
+ "x": 0.4594,
1853
+ "y": 0.6403,
1854
+ "z": -0.3459,
1855
+ "visibility": 0.9429
1856
  },
1857
  {
1858
  "id": 23,
1859
  "name": "LEFT_HIP",
1860
+ "x": 0.5592,
1861
+ "y": 0.5928,
1862
+ "z": -0.0226,
1863
+ "visibility": 1
1864
  },
1865
  {
1866
  "id": 24,
1867
  "name": "RIGHT_HIP",
1868
+ "x": 0.4926,
1869
+ "y": 0.5922,
1870
+ "z": 0.0225,
1871
+ "visibility": 1
1872
  },
1873
  {
1874
  "id": 25,
1875
  "name": "LEFT_KNEE",
1876
+ "x": 0.5813,
1877
+ "y": 0.6704,
1878
+ "z": -0.1068,
1879
+ "visibility": 0.99
1880
  },
1881
  {
1882
  "id": 26,
1883
  "name": "RIGHT_KNEE",
1884
+ "x": 0.4566,
1885
+ "y": 0.6723,
1886
+ "z": -0.0681,
1887
+ "visibility": 0.9901
1888
  },
1889
  {
1890
  "id": 27,
1891
  "name": "LEFT_ANKLE",
1892
+ "x": 0.6169,
1893
+ "y": 0.7513,
1894
+ "z": 0.0031,
1895
+ "visibility": 0.996
1896
  },
1897
  {
1898
  "id": 28,
1899
  "name": "RIGHT_ANKLE",
1900
+ "x": 0.4363,
1901
+ "y": 0.7491,
1902
+ "z": 0.0811,
1903
+ "visibility": 0.9901
1904
  },
1905
  {
1906
  "id": 29,
1907
  "name": "LEFT_HEEL",
1908
+ "x": 0.6177,
1909
+ "y": 0.7608,
1910
+ "z": 0.0015,
1911
+ "visibility": 0.9118
1912
  },
1913
  {
1914
  "id": 30,
1915
  "name": "RIGHT_HEEL",
1916
+ "x": 0.4425,
1917
+ "y": 0.7565,
1918
+ "z": 0.0854,
1919
+ "visibility": 0.8768
1920
  },
1921
  {
1922
  "id": 31,
1923
  "name": "LEFT_FOOT_INDEX",
1924
+ "x": 0.6328,
1925
+ "y": 0.7787,
1926
+ "z": -0.1983,
1927
+ "visibility": 0.9912
1928
  },
1929
  {
1930
  "id": 32,
1931
  "name": "RIGHT_FOOT_INDEX",
1932
+ "x": 0.4208,
1933
+ "y": 0.7775,
1934
+ "z": -0.0891,
1935
+ "visibility": 0.9855
1936
  }
1937
  ]
1938
  },
1939
  "8_Finish": {
1940
+ "score": 10,
1941
  "comments": [
1942
  "Đạt chuẩn"
1943
  ],
 
1946
  {
1947
  "id": 0,
1948
  "name": "NOSE",
1949
+ "x": 0.6068,
1950
+ "y": 0.4777,
1951
+ "z": -0.1523,
1952
  "visibility": 0.9998
1953
  },
1954
  {
1955
  "id": 1,
1956
  "name": "LEFT_EYE_INNER",
1957
+ "x": 0.5992,
1958
+ "y": 0.4706,
1959
+ "z": -0.1289,
1960
+ "visibility": 0.9997
1961
  },
1962
  {
1963
  "id": 2,
1964
  "name": "LEFT_EYE",
1965
+ "x": 0.5978,
1966
+ "y": 0.4703,
1967
+ "z": -0.1289,
1968
+ "visibility": 0.9996
1969
  },
1970
  {
1971
  "id": 3,
1972
  "name": "LEFT_EYE_OUTER",
1973
+ "x": 0.5965,
1974
+ "y": 0.4699,
1975
+ "z": -0.1289,
1976
+ "visibility": 0.9996
1977
  },
1978
  {
1979
  "id": 4,
1980
  "name": "RIGHT_EYE_INNER",
1981
+ "x": 0.5997,
1982
+ "y": 0.4707,
1983
+ "z": -0.1741,
1984
  "visibility": 0.9999
1985
  },
1986
  {
1987
  "id": 5,
1988
  "name": "RIGHT_EYE",
1989
+ "x": 0.5986,
1990
+ "y": 0.4704,
1991
+ "z": -0.1742,
1992
  "visibility": 0.9999
1993
  },
1994
  {
1995
  "id": 6,
1996
  "name": "RIGHT_EYE_OUTER",
1997
+ "x": 0.5975,
1998
+ "y": 0.4703,
1999
+ "z": -0.1743,
2000
  "visibility": 0.9999
2001
  },
2002
  {
2003
  "id": 7,
2004
  "name": "LEFT_EAR",
2005
+ "x": 0.5836,
2006
+ "y": 0.4712,
2007
+ "z": 0.0026,
2008
+ "visibility": 0.9993
2009
  },
2010
  {
2011
  "id": 8,
2012
  "name": "RIGHT_EAR",
2013
+ "x": 0.5842,
2014
+ "y": 0.4726,
2015
+ "z": -0.2109,
2016
+ "visibility": 0.9998
2017
  },
2018
  {
2019
  "id": 9,
2020
  "name": "MOUTH_LEFT",
2021
+ "x": 0.6044,
2022
+ "y": 0.4839,
2023
+ "z": -0.0992,
2024
+ "visibility": 0.9989
2025
  },
2026
  {
2027
  "id": 10,
2028
  "name": "MOUTH_RIGHT",
2029
+ "x": 0.6038,
2030
+ "y": 0.4836,
2031
+ "z": -0.1613,
2032
+ "visibility": 0.9995
2033
  },
2034
  {
2035
  "id": 11,
2036
  "name": "LEFT_SHOULDER",
2037
+ "x": 0.5753,
2038
+ "y": 0.5054,
2039
+ "z": 0.1657,
2040
+ "visibility": 0.9995
2041
  },
2042
  {
2043
  "id": 12,
2044
  "name": "RIGHT_SHOULDER",
2045
+ "x": 0.5237,
2046
+ "y": 0.5001,
2047
+ "z": -0.2612,
2048
+ "visibility": 0.9998
2049
  },
2050
  {
2051
  "id": 13,
2052
  "name": "LEFT_ELBOW",
2053
+ "x": 0.5994,
2054
+ "y": 0.5328,
2055
+ "z": 0.4623,
2056
+ "visibility": 0.3608
2057
  },
2058
  {
2059
  "id": 14,
2060
  "name": "RIGHT_ELBOW",
2061
+ "x": 0.4355,
2062
+ "y": 0.4992,
2063
+ "z": -0.5334,
2064
+ "visibility": 0.9872
2065
  },
2066
  {
2067
  "id": 15,
2068
  "name": "LEFT_WRIST",
2069
+ "x": 0.6319,
2070
+ "y": 0.5231,
2071
+ "z": 0.6471,
2072
+ "visibility": 0.7541
2073
  },
2074
  {
2075
  "id": 16,
2076
  "name": "RIGHT_WRIST",
2077
+ "x": 0.4696,
2078
+ "y": 0.4648,
2079
+ "z": -0.731,
2080
+ "visibility": 0.9858
2081
  },
2082
  {
2083
  "id": 17,
2084
  "name": "LEFT_PINKY",
2085
+ "x": 0.6377,
2086
+ "y": 0.5218,
2087
+ "z": 0.7003,
2088
+ "visibility": 0.6758
2089
  },
2090
  {
2091
  "id": 18,
2092
  "name": "RIGHT_PINKY",
2093
+ "x": 0.4823,
2094
+ "y": 0.4618,
2095
+ "z": -0.7776,
2096
+ "visibility": 0.9699
2097
  },
2098
  {
2099
  "id": 19,
2100
  "name": "LEFT_INDEX",
2101
+ "x": 0.6416,
2102
+ "y": 0.5167,
2103
+ "z": 0.6675,
2104
+ "visibility": 0.6994
2105
  },
2106
  {
2107
  "id": 20,
2108
  "name": "RIGHT_INDEX",
2109
+ "x": 0.4931,
2110
+ "y": 0.4624,
2111
+ "z": -0.7546,
2112
+ "visibility": 0.9677
2113
  },
2114
  {
2115
  "id": 21,
2116
  "name": "LEFT_THUMB",
2117
+ "x": 0.6412,
2118
+ "y": 0.518,
2119
+ "z": 0.6395,
2120
+ "visibility": 0.6996
2121
  },
2122
  {
2123
  "id": 22,
2124
  "name": "RIGHT_THUMB",
2125
+ "x": 0.4919,
2126
+ "y": 0.4654,
2127
+ "z": -0.7241,
2128
+ "visibility": 0.9442
2129
  },
2130
  {
2131
  "id": 23,
2132
  "name": "LEFT_HIP",
2133
+ "x": 0.5815,
2134
+ "y": 0.6089,
2135
+ "z": 0.1371,
2136
+ "visibility": 0.9998
2137
  },
2138
  {
2139
  "id": 24,
2140
  "name": "RIGHT_HIP",
2141
+ "x": 0.5528,
2142
+ "y": 0.6094,
2143
+ "z": -0.1373,
2144
+ "visibility": 0.9999
2145
  },
2146
  {
2147
  "id": 25,
2148
  "name": "LEFT_KNEE",
2149
+ "x": 0.6245,
2150
+ "y": 0.6746,
2151
+ "z": 0.2873,
2152
+ "visibility": 0.8154
2153
  },
2154
  {
2155
  "id": 26,
2156
  "name": "RIGHT_KNEE",
2157
+ "x": 0.5476,
2158
+ "y": 0.6807,
2159
+ "z": -0.192,
2160
+ "visibility": 0.988
2161
  },
2162
  {
2163
  "id": 27,
2164
  "name": "LEFT_ANKLE",
2165
+ "x": 0.6165,
2166
+ "y": 0.7466,
2167
+ "z": 0.4796,
2168
+ "visibility": 0.9565
2169
  },
2170
  {
2171
  "id": 28,
2172
  "name": "RIGHT_ANKLE",
2173
+ "x": 0.4303,
2174
+ "y": 0.7406,
2175
+ "z": -0.1284,
2176
+ "visibility": 0.9948
2177
  },
2178
  {
2179
  "id": 29,
2180
  "name": "LEFT_HEEL",
2181
+ "x": 0.6072,
2182
+ "y": 0.7583,
2183
+ "z": 0.4922,
2184
+ "visibility": 0.927
2185
  },
2186
  {
2187
  "id": 30,
2188
  "name": "RIGHT_HEEL",
2189
+ "x": 0.4063,
2190
+ "y": 0.7377,
2191
+ "z": -0.1253,
2192
+ "visibility": 0.9657
2193
  },
2194
  {
2195
  "id": 31,
2196
  "name": "LEFT_FOOT_INDEX",
2197
+ "x": 0.6557,
2198
+ "y": 0.7717,
2199
+ "z": 0.4193,
2200
+ "visibility": 0.947
2201
  },
2202
  {
2203
  "id": 32,
2204
  "name": "RIGHT_FOOT_INDEX",
2205
+ "x": 0.428,
2206
+ "y": 0.7812,
2207
+ "z": -0.2448,
2208
+ "visibility": 0.99
2209
  }
2210
  ]
2211
  }
2212
  },
2213
+ "overall_score": 10,
2214
+ "view_angle": "Face-on (Trực diện)"
2215
  },
2216
  "coaching": {
2217
+ "video_id": "d907f221-0b2f-44cd-9af8-cb4921bb13bb",
2218
+ "final_score": 10,
2219
  "skill_level": "Professional / Low Handicap",
2220
  "key_faults": [
2221
+ "[1_Address] Đạt chuẩn",
2222
  "[2_Toe-up] Đạt chuẩn",
2223
  "[3_Mid-Backswing] Đạt chuẩn",
2224
+ "[4_Top] Đạt chuẩn",
2225
  "[5_Mid-Downswing] Đạt chuẩn",
2226
  "[6_Impact] Đạt chuẩn",
2227
  "[7_Mid-Follow-Through] Đạt chuẩn",
2228
  "[8_Finish] Đạt chuẩn"
2229
  ],
2230
+ "recommended_drills": [],
2231
+ "summary": "Cú swing của bạn đạt 10.0/10. Kỹ thuật rất chuẩn, hãy tiếp tục duy trì!"
 
 
2232
  }
2233
  }
models_v1/mobilenet_v2.pth.tar ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:ecbe2b568c8602549fa9e1d5833c63848f490a48d92e5d224d1eb2063e152cf8
3
+ size 14205652
models_v1/swingnet_1800.pth.tar ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6331e303a9e86d0c19f183899f958bf2a71cf5a7070d46899e25e1ac877b23d4
3
+ size 63280059
src/eval.py CHANGED
@@ -60,7 +60,7 @@ if __name__ == '__main__':
60
  bidirectional=True,
61
  dropout=False)
62
 
63
- save_dict = torch.load('models/swingnet_1800.pth.tar')
64
  model.load_state_dict(save_dict['model_state_dict'])
65
  model.cuda()
66
  model.eval()
 
60
  bidirectional=True,
61
  dropout=False)
62
 
63
+ save_dict = torch.load('models_v1/swingnet_1800.pth.tar')
64
  model.load_state_dict(save_dict['model_state_dict'])
65
  model.cuda()
66
  model.eval()
src/model.py CHANGED
@@ -18,7 +18,7 @@ class EventDetector(nn.Module):
18
  # Get the directory of the current script (src/)
19
  current_script_directory = os.path.dirname(os.path.abspath(__file__))
20
  # Path to models folder (outside src/)
21
- relative_directory = '../models/mobilenet_v2.pth.tar'
22
 
23
  # Construct the absolute path
24
  model_dir = os.path.normpath(os.path.join(current_script_directory, relative_directory))
 
18
  # Get the directory of the current script (src/)
19
  current_script_directory = os.path.dirname(os.path.abspath(__file__))
20
  # Path to models folder (outside src/)
21
+ relative_directory = '../models_v1/mobilenet_v2.pth.tar'
22
 
23
  # Construct the absolute path
24
  model_dir = os.path.normpath(os.path.join(current_script_directory, relative_directory))