jonyondlin commited on
Commit
8d199d1
·
1 Parent(s): d78e920

Add Gradio app and requirements

Browse files
Files changed (3) hide show
  1. README.md +2 -13
  2. app.py +249 -0
  3. requirements.txt +5 -0
README.md CHANGED
@@ -1,14 +1,3 @@
1
- ---
2
- title: Online Teaching
3
- emoji: 🐨
4
- colorFrom: green
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.6.0
8
- app_file: app.py
9
- pinned: false
10
- license: apache-2.0
11
- short_description: A face recognition demo for online teaching analysis
12
- ---
13
 
14
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
+ # My online teaching App
 
 
 
 
 
 
 
 
 
 
 
2
 
3
+ This is a face expression recognition demo for online teaching analysis.
app.py ADDED
@@ -0,0 +1,249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import cv2
3
+ import numpy as np
4
+ from collections import defaultdict
5
+ import matplotlib.pyplot as plt
6
+ from rmn import RMN
7
+ import gradio as gr
8
+
9
+ def process_video(video_path, share_screen_mode):
10
+ # 初始化目录
11
+ output_dir = 'output'
12
+ if not os.path.exists(output_dir):
13
+ os.makedirs(output_dir)
14
+
15
+ # 初始化表情检测模型
16
+ print("Initializing emotion detection model...")
17
+ m = RMN()
18
+
19
+ # 打开视频文件
20
+ cap = cv2.VideoCapture(video_path)
21
+ fps = cap.get(cv2.CAP_PROP_FPS)
22
+ frame_interval = int(fps * 1) # 每秒处理一帧
23
+ total_frames = int(cap.get(cv2.CAP_PROP_FRAME_COUNT))
24
+ frame_width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
25
+ frame_height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
26
+
27
+ print(f"Total frames: {total_frames}, FPS: {fps}")
28
+
29
+ # 创建视频写入器
30
+ output_video_path = os.path.join(output_dir, 'output_video.avi')
31
+ fourcc = cv2.VideoWriter_fourcc(*'XVID')
32
+ out = cv2.VideoWriter(output_video_path, fourcc, fps, (frame_width, frame_height))
33
+
34
+ current_frame = 0
35
+
36
+ # 面部ID和表情数据
37
+ face_ids = []
38
+ max_face_id = 0
39
+ face_emotions = defaultdict(list)
40
+ max_faces = 0
41
+ initial_faces = []
42
+ last_detections = {}
43
+
44
+ print("Starting video processing...")
45
+
46
+ while True:
47
+ ret, frame = cap.read()
48
+ if not ret:
49
+ print("Finished processing video.")
50
+ break
51
+
52
+ if share_screen_mode:
53
+ # 裁剪右侧1/5区域
54
+ x_start = int(frame_width * 4 / 5)
55
+ frame_to_process = frame[:, x_start:]
56
+ else:
57
+ frame_to_process = frame.copy()
58
+ x_start = 0 # 无偏移
59
+
60
+ if current_frame % frame_interval == 0:
61
+ print(f"Processing frame {current_frame}...")
62
+ # 检测面部
63
+ detections = m.detect_faces(frame_to_process)
64
+ print(f"Detected {len(detections)} faces.")
65
+ # 更新最大面部计数
66
+ if len(detections) > max_faces:
67
+ max_faces = len(detections)
68
+
69
+ for det in detections:
70
+ xmin = det['xmin']
71
+ ymin = det['ymin']
72
+ xmax = det['xmax']
73
+ ymax = det['ymax']
74
+ matched_id = None
75
+ max_iou = 0
76
+
77
+ # 与现有面部进行比较
78
+ for face in initial_faces:
79
+ ixmin, iymin, ixmax, iymax = face['bbox']
80
+ # 计算IoU
81
+ xx1 = max(xmin, ixmin)
82
+ yy1 = max(ymin, iymin)
83
+ xx2 = min(xmax, ixmax)
84
+ yy2 = min(ymax, iymax)
85
+ inter_area = max(0, xx2 - xx1) * max(0, yy2 - yy1)
86
+ area1 = (xmax - xmin) * (ymax - ymin)
87
+ area2 = (ixmax - ixmin) * (iymax - iymin)
88
+ iou = inter_area / float(area1 + area2 - inter_area + 1e-5)
89
+ if iou > 0.3 and iou > max_iou:
90
+ matched_id = face['id']
91
+ max_iou = iou
92
+
93
+ if matched_id is None:
94
+ if len(initial_faces) < max_faces:
95
+ # 创建新的面部ID
96
+ matched_id = max_face_id
97
+ max_face_id += 1
98
+ initial_faces.append({'id': matched_id, 'bbox': (xmin, ymin, xmax, ymax)})
99
+ else:
100
+ # 基于距离匹配
101
+ cx = (xmin + xmax) / 2
102
+ cy = (ymin + ymax) / 2
103
+ min_dist = float('inf')
104
+ for face in initial_faces:
105
+ fx = (face['bbox'][0] + face['bbox'][2]) / 2
106
+ fy = (face['bbox'][1] + face['bbox'][3]) / 2
107
+ dist = np.sqrt((cx - fx) ** 2 + (cy - fy) ** 2)
108
+ if dist < min_dist:
109
+ min_dist = dist
110
+ matched_id = face['id']
111
+
112
+ # 更新面部边界框
113
+ for face in initial_faces:
114
+ if face['id'] == matched_id:
115
+ face['bbox'] = (xmin, ymin, xmax, ymax)
116
+ break
117
+
118
+ # 获取表情标签
119
+ face_img = frame_to_process[ymin:ymax, xmin:xmax]
120
+ if face_img.size == 0:
121
+ continue
122
+ emo_label, _, _ = m.detect_emotion_for_single_face_image(face_img)
123
+ if emo_label not in ['neutral', 'happy']:
124
+ emo_label = 'confused'
125
+
126
+ # 记录表情
127
+ face_emotions[matched_id].append((current_frame / fps, emo_label))
128
+ print(f"Face {matched_id} emotion: {emo_label}")
129
+
130
+ # 更新最后的检测结果,调整坐标到原始帧
131
+ xmin_global = xmin + x_start
132
+ xmax_global = xmax + x_start
133
+ last_detections[matched_id] = (xmin_global, ymin, xmax_global, ymax, emo_label)
134
+
135
+ # 在原始帧上绘制最后的检测结果
136
+ for face_id, (xmin, ymin, xmax, ymax, emo_label) in last_detections.items():
137
+ cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), (0, 255, 0), 2)
138
+ cv2.putText(frame, f"ID:{face_id} {emo_label}", (xmin, ymin + 20),
139
+ cv2.FONT_HERSHEY_SIMPLEX, 0.9, (255, 0, 0), 2)
140
+
141
+ # 将处理后的帧写入输出视频
142
+ out.write(frame)
143
+ current_frame += 1
144
+
145
+ cap.release()
146
+ out.release()
147
+
148
+ print("Finished processing video.")
149
+
150
+ # 返回输出视频路径和面部表情数据
151
+ return output_video_path, face_emotions
152
+
153
+ def generate_graphs(selected_ids, face_emotions):
154
+ # 将selected_ids从字符串转换为整数
155
+ selected_ids = [int(face_id) for face_id in selected_ids]
156
+ selected_face_emotions = {face_id: emotions for face_id, emotions in face_emotions.items() if face_id in selected_ids}
157
+
158
+ output_dir = 'output'
159
+ emotion_labels = ['confused', 'neutral', 'happy']
160
+
161
+ # 生成表情变化图
162
+ plt.figure(figsize=(15, 10))
163
+ for i, (face_id, emotions) in enumerate(selected_face_emotions.items(), 1):
164
+ times = [t for t, _ in emotions]
165
+ labels = [emotion_labels.index(emo) for _, emo in emotions]
166
+ plt.subplot(len(selected_face_emotions), 1, i)
167
+ plt.plot(times, labels, marker='o')
168
+ plt.title(f"Emotion changes for face {face_id}")
169
+ plt.xlabel('Time (s)')
170
+ plt.ylabel('Emotion')
171
+ plt.yticks([0, 1, 2], emotion_labels)
172
+ plt.tight_layout()
173
+ graph_path = os.path.join(output_dir, "selected_faces_emotions.png")
174
+ plt.savefig(graph_path)
175
+ plt.close()
176
+ print("Saved emotion change graph for selected faces.")
177
+
178
+ # 生成表情比例图
179
+ time_points = sorted(set(t for emotions in selected_face_emotions.values() for t, _ in emotions))
180
+ emotion_counts_over_time = {t: defaultdict(int) for t in time_points}
181
+ for emotions in selected_face_emotions.values():
182
+ for t, emo in emotions:
183
+ emotion_counts_over_time[t][emo] += 1
184
+
185
+ emotion_proportions_over_time = {t: {emo: 0 for emo in emotion_labels} for t in time_points}
186
+ for t in time_points:
187
+ total_faces = sum(emotion_counts_over_time[t].values())
188
+ if total_faces > 0:
189
+ for emo in emotion_labels:
190
+ emotion_proportions_over_time[t][emo] = emotion_counts_over_time[t][emo] / total_faces
191
+
192
+ plt.figure(figsize=(15, 10))
193
+ for i, emo in enumerate(emotion_labels, 1):
194
+ proportions = [emotion_proportions_over_time[t][emo] for t in time_points]
195
+ plt.subplot(len(emotion_labels), 1, i)
196
+ plt.plot(time_points, proportions, marker='o')
197
+ plt.title(f"Proportion of {emo} over time")
198
+ plt.xlabel('Time (s)')
199
+ plt.ylabel('Proportion')
200
+ plt.ylim(0, 1)
201
+ plt.tight_layout()
202
+ emotion_proportions_path = os.path.join(output_dir, "selected_emotion_proportions_over_time.png")
203
+ plt.savefig(emotion_proportions_path)
204
+ plt.close()
205
+ print("Saved emotion proportion graph for selected faces.")
206
+
207
+ return graph_path, emotion_proportions_path
208
+
209
+ # Gradio Interface
210
+ with gr.Blocks() as demo:
211
+ gr.Markdown("# Emotion Detection in Videos")
212
+
213
+ video_input = gr.Video(label="Upload a video")
214
+
215
+ share_screen_checkbox = gr.Checkbox(label="Turn on share mode", value=False)
216
+
217
+ process_btn = gr.Button("Process Video")
218
+
219
+ video_output = gr.Video(label="Processed Video Output")
220
+
221
+ # 状态,用于保存面部表情数据
222
+ face_emotions_state = gr.State()
223
+
224
+ # 多选框,列出检测到的ID
225
+ id_checkbox_group = gr.CheckboxGroup(label="Select Face IDs")
226
+
227
+ generate_graphs_btn = gr.Button("Generate Graphs")
228
+
229
+ graph_output = gr.Image(label="Emotion Change Graph")
230
+ emotion_proportions_output = gr.Image(label="Emotion Proportions Graph")
231
+
232
+ def process_and_get_ids(video, share_screen_mode):
233
+ video_output_path, face_emotions = process_video(video, share_screen_mode)
234
+ face_ids = [str(face_id) for face_id in face_emotions.keys()]
235
+ return video_output_path, gr.update(choices=face_ids), face_emotions
236
+
237
+ process_btn.click(
238
+ fn=process_and_get_ids,
239
+ inputs=[video_input, share_screen_checkbox],
240
+ outputs=[video_output, id_checkbox_group, face_emotions_state]
241
+ )
242
+
243
+ generate_graphs_btn.click(
244
+ fn=generate_graphs,
245
+ inputs=[id_checkbox_group, face_emotions_state],
246
+ outputs=[graph_output, emotion_proportions_output]
247
+ )
248
+
249
+ demo.launch(share=True)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ opencv-python==4.10.0.84
2
+ numpy==2.1.2
3
+ matplotlib==3.9.2
4
+ gradio==1.1.1
5
+ rmn