File size: 4,710 Bytes
f3f8397
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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
                        
                    # BƯỚC 1: Xử lý khi người dùng bấm nút "Hiệu chỉnh"
                    if do_calibrate:
                        baseline_data = {
                            "neck_dist": current_neck_dist,
                            "sh_diff": current_sh_diff
                        }
                        do_calibrate = False # Tắt cờ hiệu chỉnh
                        status_text = "Đã lưu tư thế chuẩn! Bắt đầu giám sát ✅"
                        break # Lưu xong thì thoát vòng lặp khung hình này
                        
                    # BƯỚC 2: Cảnh báo theo Tỷ lệ (Nếu đã có baseline)
                    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 ✅"
                        
                        # Cảnh báo 1: Gù lưng / Rướn cổ (Rút ngắn > 25% so với chuẩn)
                        if current_neck_dist < 0.75 * base_neck:
                            status_text = "⚠️ CẢNH BÁO: Đang rướn cổ / Gù lưng!"
                            
                        # Cảnh báo 2: Lệch vai
                        # Mẹo: Dùng độ dài cổ (base_neck) làm thước đo. Nếu vai lệch > 20% chiều dài cổ là có vấn đề
                        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

# ==========================================
# GIAO DIỆN CÓ THÊM NÚT CALIBRATION
# ==========================================
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")
            
    # Các biến State để lưu trữ dữ liệu ngầm
    do_calibrate = gr.State(False)
    baseline_data = gr.State(None)
    
    # Khi bấm nút, bật cờ do_calibrate lên True
    calibrate_btn.click(fn=lambda: True, outputs=[do_calibrate])
    
    # Xử lý stream
    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()