| import cv2 |
| import gradio as gr |
| from ultralytics import YOLO |
|
|
| print("Đang tải mô hình Pose...") |
| pose_model = YOLO('yolov8n-pose.pt') |
|
|
| def analyze_posture(frame, do_calibrate, baseline_data): |
| if frame is None: |
| return None, "Đang chờ Camera...", do_calibrate, baseline_data |
| |
| frame_bgr = cv2.cvtColor(frame, cv2.COLOR_RGB2BGR) |
| results = pose_model(frame_bgr, verbose=False, conf=0.5) |
| |
| annotated_frame = frame_bgr.copy() |
| status_text = "Đang theo dõi..." |
| |
| for r in results: |
| annotated_frame = r.plot() |
| |
| if r.keypoints is not None and len(r.keypoints.data) > 0: |
| kpts = r.keypoints.data[0] |
| |
| if len(kpts) >= 7: |
| nose = kpts[0][:2].tolist() |
| left_shoulder = kpts[5][:2].tolist() |
| right_shoulder = kpts[6][:2].tolist() |
| |
| if nose[0] != 0 and left_shoulder[0] != 0 and right_shoulder[0] != 0: |
| shoulder_mid_y = (left_shoulder[1] + right_shoulder[1]) / 2 |
| current_neck_dist = shoulder_mid_y - nose[1] |
| current_sh_diff = abs(left_shoulder[1] - right_shoulder[1]) |
| |
| if current_neck_dist <= 0: |
| continue |
| |
| |
| if do_calibrate: |
| baseline_data = { |
| "neck_dist": current_neck_dist, |
| "sh_diff": current_sh_diff |
| } |
| do_calibrate = False |
| status_text = "Đã lưu tư thế chuẩn! Bắt đầu giám sát ✅" |
| break |
| |
| |
| if baseline_data is not None: |
| base_neck = baseline_data["neck_dist"] |
| base_sh_diff = baseline_data["sh_diff"] |
| |
| status_text = "Tư thế TỐT ✅" |
| |
| |
| if current_neck_dist < 0.75 * base_neck: |
| status_text = "⚠️ CẢNH BÁO: Đang rướn cổ / Gù lưng!" |
| |
| |
| |
| tilt_threshold = 0.20 * base_neck |
| if abs(current_sh_diff - base_sh_diff) > tilt_threshold: |
| status_text = "⚠️ CẢNH BÁO: Ngồi lệch vai!" |
| else: |
| status_text = "⏳ Vui lòng ngồi thẳng và bấm 'Hiệu chỉnh tư thế chuẩn'." |
|
|
| annotated_rgb = cv2.cvtColor(annotated_frame, cv2.COLOR_BGR2RGB) |
| return annotated_rgb, status_text, do_calibrate, baseline_data |
|
|
| |
| |
| |
| with gr.Blocks(title="SmartErgo - Dynamic Pose Tracking") as demo: |
| gr.Markdown("## 🎥 Giám Sát Tư Thế (Có Hiệu Chỉnh Động)") |
| |
| with gr.Row(): |
| with gr.Column(scale=2): |
| live_input = gr.Image(sources=["webcam"], streaming=True, label="Camera của bạn") |
| |
| with gr.Column(scale=1): |
| live_output = gr.Image(label="AI Tracking") |
| status_output = gr.Textbox(label="Trạng thái", text_align="center", value="Chưa hiệu chỉnh") |
| |
| gr.Markdown("**Hướng dẫn:** Ngồi thẳng lưng, mắt nhìn ngang màn hình rồi bấm nút bên dưới.") |
| calibrate_btn = gr.Button("🎯 Hiệu chỉnh tư thế chuẩn", variant="primary") |
| |
| |
| do_calibrate = gr.State(False) |
| baseline_data = gr.State(None) |
| |
| |
| calibrate_btn.click(fn=lambda: True, outputs=[do_calibrate]) |
| |
| |
| live_input.stream( |
| fn=analyze_posture, |
| inputs=[live_input, do_calibrate, baseline_data], |
| outputs=[live_output, status_output, do_calibrate, baseline_data], |
| stream_every=0.1 |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |