sadimanna commited on
Commit
01f6b50
·
1 Parent(s): 5df4e52

updated files

Browse files
README.md CHANGED
@@ -1,2 +1,14 @@
1
- # face-emotion-recognition
 
 
 
 
 
 
 
 
 
 
 
 
2
  Setting up a Hugging Face Spaces Demo using Streamlit
 
1
+ ---
2
+ title: Face Emotion Recognition
3
+ emoji: 🏃
4
+ colorFrom: purple
5
+ colorTo: red
6
+ sdk: streamlit
7
+ sdk_version: 1.26.0
8
+ app_file: app.py
9
+ pinned: false
10
+ license: bsd-3-clause-clear
11
+ ---
12
+
13
+ # MobileNet-SSD Object Detection Demo with Web App using Hugging Face Spaces
14
  Setting up a Hugging Face Spaces Demo using Streamlit
app.py ADDED
@@ -0,0 +1,344 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Emotion Detection:
3
+ Model from: https://github.com/onnx/models/blob/main/vision/body_analysis/emotion_ferplus/model/emotion-ferplus-8.onnx
4
+ Model name: emotion-ferplus-8.onnx
5
+ """
6
+
7
+ import logging
8
+ import queue
9
+ from pathlib import Path
10
+ from typing import List, NamedTuple
11
+
12
+ import cv2
13
+ import numpy as np
14
+ import time
15
+ import os
16
+
17
+ from cv2 import dnn
18
+ from math import ceil
19
+
20
+ import av
21
+ import streamlit as st
22
+ from streamlit_webrtc import WebRtcMode, webrtc_streamer
23
+
24
+ from sample_utils.download import download_file
25
+ from sample_utils.turn import get_ice_servers
26
+
27
+ HERE = Path(__file__).parent
28
+ ROOT = HERE.parent
29
+
30
+ logger = logging.getLogger(__name__)
31
+
32
+ ONNX_MODEL_URL = "https://github.com/spmallick/learnopencv/raw/master/Facial-Emotion-Recognition/emotion-ferplus-8.onnx"
33
+ ONNX_MODEL_LOCAL_PATH = ROOT / "./emotion-ferplus-8.onnx"
34
+ CAFFE_MODEL_URL = "https://github.com/spmallick/learnopencv/raw/master/Facial-Emotion-Recognition/RFB-320/RFB-320.caffemodel" # noqa: E501
35
+ CAFFE_MODEL_LOCAL_PATH = ROOT / "./RFB-320/RFB-320.caffemodel"
36
+ PROTOTXT_URL = "https://github.com/spmallick/learnopencv/raw/master/Facial-Emotion-Recognition/RFB-320/RFB-320.prototxt" # noqa: E501
37
+ PROTOTXT_LOCAL_PATH = ROOT / "./RFB-320/RFB-320.prototxt.txt"
38
+
39
+ download_file(CAFFE_MODEL_URL, CAFFE_MODEL_LOCAL_PATH) #, expected_size=23147564)
40
+ download_file(ONNX_MODEL_URL, ONNX_MODEL_LOCAL_PATH) #, expected_size=23147564)
41
+ download_file(PROTOTXT_URL, PROTOTXT_LOCAL_PATH) #, expected_size=29353)
42
+
43
+ # Session-specific caching
44
+ onnx_cache_key = "emotion_dnn"
45
+ caffe_cache_key = "face_detection_dnn"
46
+ if caffe_cache_key in st.session_state and onnx_cache_key in st.session_state:
47
+ model = st.session_state[onnx_cache_key]
48
+ net = st.session_state[caffe_cache_key]
49
+ else:
50
+ # Read ONNX model
51
+ model = 'onnx_model.onnx'
52
+ model = cv2.dnn.readNetFromONNX('emotion-ferplus-8.onnx')
53
+ st.session_state[onnx_cache_key] = model
54
+ # Read the Caffe face detector.
55
+ net = cv2.dnn.readNetFromCaffe(str(PROTOTXT_LOCAL_PATH), str(CAFFE_MODEL_LOCAL_PATH))
56
+ st.session_state[caffe_cache_key] = net
57
+
58
+
59
+ ########################################
60
+ image_mean = np.array([127, 127, 127])
61
+ image_std = 128.0
62
+ iou_threshold = 0.3
63
+ center_variance = 0.1
64
+ size_variance = 0.2
65
+ min_boxes = [
66
+ [10.0, 16.0, 24.0],
67
+ [32.0, 48.0],
68
+ [64.0, 96.0],
69
+ [128.0, 192.0, 256.0]
70
+ ]
71
+ strides = [8.0, 16.0, 32.0, 64.0]
72
+ threshold = 0.5
73
+
74
+ emotion_dict = {
75
+ 0: 'neutral',
76
+ 1: 'happiness',
77
+ 2: 'surprise',
78
+ 3: 'sadness',
79
+ 4: 'anger',
80
+ 5: 'disgust',
81
+ 6: 'fear'
82
+ }
83
+ ########################################
84
+
85
+ def define_img_size(image_size):
86
+ shrinkage_list = []
87
+ feature_map_w_h_list = []
88
+ for size in image_size:
89
+ feature_map = [int(ceil(size / stride)) for stride in strides]
90
+ feature_map_w_h_list.append(feature_map)
91
+
92
+ for i in range(0, len(image_size)):
93
+ shrinkage_list.append(strides)
94
+ priors = generate_priors(
95
+ feature_map_w_h_list, shrinkage_list, image_size, min_boxes
96
+ )
97
+ return priors
98
+
99
+
100
+ def generate_priors(
101
+ feature_map_list, shrinkage_list, image_size, min_boxes
102
+ ):
103
+ priors = []
104
+ for index in range(0, len(feature_map_list[0])):
105
+ scale_w = image_size[0] / shrinkage_list[0][index]
106
+ scale_h = image_size[1] / shrinkage_list[1][index]
107
+ for j in range(0, feature_map_list[1][index]):
108
+ for i in range(0, feature_map_list[0][index]):
109
+ x_center = (i + 0.5) / scale_w
110
+ y_center = (j + 0.5) / scale_h
111
+
112
+ for min_box in min_boxes[index]:
113
+ w = min_box / image_size[0]
114
+ h = min_box / image_size[1]
115
+ priors.append([
116
+ x_center,
117
+ y_center,
118
+ w,
119
+ h
120
+ ])
121
+ print("priors nums:{}".format(len(priors)))
122
+ return np.clip(priors, 0.0, 1.0)
123
+
124
+
125
+ def hard_nms(box_scores, iou_threshold, top_k=-1, candidate_size=200):
126
+ scores = box_scores[:, -1]
127
+ boxes = box_scores[:, :-1]
128
+ picked = []
129
+ indexes = np.argsort(scores)
130
+ indexes = indexes[-candidate_size:]
131
+ while len(indexes) > 0:
132
+ current = indexes[-1]
133
+ picked.append(current)
134
+ if 0 < top_k == len(picked) or len(indexes) == 1:
135
+ break
136
+ current_box = boxes[current, :]
137
+ indexes = indexes[:-1]
138
+ rest_boxes = boxes[indexes, :]
139
+ iou = iou_of(
140
+ rest_boxes,
141
+ np.expand_dims(current_box, axis=0),
142
+ )
143
+ indexes = indexes[iou <= iou_threshold]
144
+ return box_scores[picked, :]
145
+
146
+
147
+ def area_of(left_top, right_bottom):
148
+ hw = np.clip(right_bottom - left_top, 0.0, None)
149
+ return hw[..., 0] * hw[..., 1]
150
+
151
+
152
+ def iou_of(boxes0, boxes1, eps=1e-5):
153
+ overlap_left_top = np.maximum(boxes0[..., :2], boxes1[..., :2])
154
+ overlap_right_bottom = np.minimum(boxes0[..., 2:], boxes1[..., 2:])
155
+
156
+ overlap_area = area_of(overlap_left_top, overlap_right_bottom)
157
+ area0 = area_of(boxes0[..., :2], boxes0[..., 2:])
158
+ area1 = area_of(boxes1[..., :2], boxes1[..., 2:])
159
+ return overlap_area / (area0 + area1 - overlap_area + eps)
160
+
161
+
162
+ def predict(
163
+ width,
164
+ height,
165
+ confidences,
166
+ boxes,
167
+ prob_threshold,
168
+ iou_threshold=0.3,
169
+ top_k=-1
170
+ ):
171
+ boxes = boxes[0]
172
+ confidences = confidences[0]
173
+ picked_box_probs = []
174
+ picked_labels = []
175
+ for class_index in range(1, confidences.shape[1]):
176
+ probs = confidences[:, class_index]
177
+ mask = probs > prob_threshold
178
+ probs = probs[mask]
179
+ if probs.shape[0] == 0:
180
+ continue
181
+ subset_boxes = boxes[mask, :]
182
+ box_probs = np.concatenate(
183
+ [subset_boxes, probs.reshape(-1, 1)], axis=1
184
+ )
185
+ box_probs = hard_nms(box_probs,
186
+ iou_threshold=iou_threshold,
187
+ top_k=top_k,
188
+ )
189
+ picked_box_probs.append(box_probs)
190
+ picked_labels.extend([class_index] * box_probs.shape[0])
191
+ if not picked_box_probs:
192
+ return np.array([]), np.array([]), np.array([])
193
+ picked_box_probs = np.concatenate(picked_box_probs)
194
+ picked_box_probs[:, 0] *= width
195
+ picked_box_probs[:, 1] *= height
196
+ picked_box_probs[:, 2] *= width
197
+ picked_box_probs[:, 3] *= height
198
+ return (
199
+ picked_box_probs[:, :4].astype(np.int32),
200
+ np.array(picked_labels),
201
+ picked_box_probs[:, 4]
202
+ )
203
+
204
+
205
+ def convert_locations_to_boxes(locations, priors, center_variance,
206
+ size_variance):
207
+ if len(priors.shape) + 1 == len(locations.shape):
208
+ priors = np.expand_dims(priors, 0)
209
+ return np.concatenate([
210
+ locations[..., :2] * center_variance * priors[..., 2:] + priors[..., :2],
211
+ np.exp(locations[..., 2:] * size_variance) * priors[..., 2:]
212
+ ], axis=len(locations.shape) - 1)
213
+
214
+
215
+ def center_form_to_corner_form(locations):
216
+ return np.concatenate(
217
+ [locations[..., :2] - locations[..., 2:] / 2,
218
+ locations[..., :2] + locations[..., 2:] / 2],
219
+ len(locations.shape) - 1
220
+ )
221
+
222
+
223
+ # def FER_live_cam():
224
+ def video_frame_callback(frame: av.VideoFrame) -> av.VideoFrame:
225
+ frame = frame.to_ndarray(format="bgr24")
226
+
227
+ # cap = cv2.VideoCapture('video3.mp4')
228
+ # cap = cv2.VideoCapture(0)
229
+
230
+ # frame_width = int(cap.get(3))
231
+ # frame_height = int(cap.get(4))
232
+ # size = (frame_width, frame_height)
233
+ # result = cv2.VideoWriter('infer2-test.avi',
234
+ # cv2.VideoWriter_fourcc(*'MJPG'),
235
+ # 10, size)
236
+ # Read ONNX model
237
+ # model = 'onnx_model.onnx'
238
+ # model = cv2.dnn.readNetFromONNX('emotion-ferplus-8.onnx')
239
+
240
+ # # Read the Caffe face detector.
241
+ # model_path = 'RFB-320/RFB-320.caffemodel'
242
+ # proto_path = 'RFB-320/RFB-320.prototxt'
243
+ # net = dnn.readNetFromCaffe(proto_path, model_path)
244
+
245
+ input_size = [320, 240]
246
+ width = input_size[0]
247
+ height = input_size[1]
248
+ priors = define_img_size(input_size)
249
+
250
+ # while cap.isOpened():
251
+ # ret, frame = cap.read()
252
+ # if ret:
253
+ img_ori = frame
254
+ #print("frame size: ", frame.shape)
255
+ rect = cv2.resize(img_ori, (width, height))
256
+ rect = cv2.cvtColor(rect, cv2.COLOR_BGR2RGB)
257
+ net.setInput(dnn.blobFromImage(rect, 1 / image_std, (width, height), 127))
258
+ start_time = time.time()
259
+ boxes, scores = net.forward(["boxes", "scores"])
260
+ boxes = np.expand_dims(np.reshape(boxes, (-1, 4)), axis=0)
261
+ scores = np.expand_dims(np.reshape(scores, (-1, 2)), axis=0)
262
+ boxes = convert_locations_to_boxes(
263
+ boxes, priors, center_variance, size_variance
264
+ )
265
+ boxes = center_form_to_corner_form(boxes)
266
+ boxes, labels, probs = predict(
267
+ img_ori.shape[1],
268
+ img_ori.shape[0],
269
+ scores,
270
+ boxes,
271
+ threshold
272
+ )
273
+ gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
274
+ for (x1, y1, x2, y2) in boxes:
275
+ w = x2 - x1
276
+ h = y2 - y1
277
+ cv2.rectangle(frame, (x1,y1), (x2, y2), (255,0,0), 2)
278
+ resize_frame = cv2.resize(
279
+ gray[y1:y1 + h, x1:x1 + w], (64, 64)
280
+ )
281
+ resize_frame = resize_frame.reshape(1, 1, 64, 64)
282
+ model.setInput(resize_frame)
283
+ output = model.forward()
284
+ end_time = time.time()
285
+ fps = 1 / (end_time - start_time)
286
+ print(f"FPS: {fps:.1f}")
287
+ pred = emotion_dict[list(output[0]).index(max(output[0]))]
288
+ cv2.rectangle(
289
+ img_ori,
290
+ (x1, y1),
291
+ (x2, y2),
292
+ (215, 5, 247),
293
+ 2,
294
+ lineType=cv2.LINE_AA
295
+ )
296
+ cv2.putText(
297
+ frame,
298
+ pred,
299
+ (x1, y1-10),
300
+ cv2.FONT_HERSHEY_SIMPLEX,
301
+ 0.8,
302
+ (215, 5, 247),
303
+ 2,
304
+ lineType=cv2.LINE_AA
305
+ )
306
+
307
+ # result.write(frame)
308
+
309
+ # cv2.imshow('frame', frame)
310
+ # if cv2.waitKey(1) & 0xFF == ord('q'):
311
+ # break
312
+ # else:
313
+ # break
314
+
315
+ # cap.release()
316
+ # result.release()
317
+ # cv2.destroyAllWindows()
318
+ return av.VideoFrame.from_ndarray(frame, format="bgr24")
319
+
320
+ if __name__ == "__main__":
321
+ # FER_live_cam()
322
+ webrtc_ctx = webrtc_streamer(
323
+ key="object-detection",
324
+ mode=WebRtcMode.SENDRECV,
325
+ rtc_configuration={"iceServers": get_ice_servers()},
326
+ video_frame_callback=video_frame_callback,
327
+ media_stream_constraints={"video": True, "audio": False},
328
+ async_processing=True,
329
+ )
330
+
331
+ if st.checkbox("Show the detected labels", value=True):
332
+ if webrtc_ctx.state.playing:
333
+ labels_placeholder = st.empty()
334
+ # NOTE: The video transformation with object detection and
335
+ # this loop displaying the result labels are running
336
+ # in different threads asynchronously.
337
+ # Then the rendered video frames and the labels displayed here
338
+ # are not strictly synchronized.
339
+
340
+ st.markdown(
341
+ "This demo uses a model and code from "
342
+ "https://github.com/spmallick/learnopncv/Facial-Emotion-Recognition. "
343
+ "Many thanks to the project."
344
+ )
packages.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ ffmpeg
requirements.txt ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ tqdm
2
+ numpy
3
+ torch
4
+ torchvision
5
+ Pillow
6
+ opencv-python-headless==4.8.0.76
7
+ streamlit_webrtc==0.47.0
8
+ pydub==0.25.1
9
+ twilio~=8.5.0
10
+ matplotlib
runtime.txt ADDED
@@ -0,0 +1 @@
 
 
1
+ python-3.8.7
sample_utils/__init__.py ADDED
File without changes
sample_utils/download.py ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import urllib.request
2
+ from pathlib import Path
3
+
4
+ import streamlit as st
5
+
6
+
7
+ # This code is based on https://github.com/streamlit/demo-self-driving/blob/230245391f2dda0cb464008195a470751c01770b/streamlit_app.py#L48 # noqa: E501
8
+ def download_file(url, download_to: Path, expected_size=None):
9
+ # Don't download the file twice.
10
+ # (If possible, verify the download using the file length.)
11
+ if download_to.exists():
12
+ if expected_size:
13
+ if download_to.stat().st_size == expected_size:
14
+ return
15
+ else:
16
+ st.info(f"{url} is already downloaded.")
17
+ if not st.button("Download again?"):
18
+ return
19
+
20
+ download_to.parent.mkdir(parents=True, exist_ok=True)
21
+
22
+ # These are handles to two visual elements to animate.
23
+ weights_warning, progress_bar = None, None
24
+ try:
25
+ weights_warning = st.warning("Downloading %s..." % url)
26
+ progress_bar = st.progress(0)
27
+ with open(download_to, "wb") as output_file:
28
+ with urllib.request.urlopen(url) as response:
29
+ length = int(response.info()["Content-Length"])
30
+ counter = 0.0
31
+ MEGABYTES = 2.0 ** 20.0
32
+ while True:
33
+ data = response.read(8192)
34
+ if not data:
35
+ break
36
+ counter += len(data)
37
+ output_file.write(data)
38
+
39
+ # We perform animation by overwriting the elements.
40
+ weights_warning.warning(
41
+ "Downloading %s... (%6.2f/%6.2f MB)"
42
+ % (url, counter / MEGABYTES, length / MEGABYTES)
43
+ )
44
+ progress_bar.progress(min(counter / length, 1.0))
45
+ # Finally, we remove these visual elements by calling .empty().
46
+ finally:
47
+ if weights_warning is not None:
48
+ weights_warning.empty()
49
+ if progress_bar is not None:
50
+ progress_bar.empty()
sample_utils/turn.py ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ import os
3
+
4
+ import streamlit as st
5
+ from twilio.base.exceptions import TwilioRestException
6
+ from twilio.rest import Client
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ def get_ice_servers():
12
+ """Use Twilio's TURN server because Streamlit Community Cloud has changed
13
+ its infrastructure and WebRTC connection cannot be established without TURN server now. # noqa: E501
14
+ We considered Open Relay Project (https://www.metered.ca/tools/openrelay/) too,
15
+ but it is not stable and hardly works as some people reported like https://github.com/aiortc/aiortc/issues/832#issuecomment-1482420656 # noqa: E501
16
+ See https://github.com/whitphx/streamlit-webrtc/issues/1213
17
+ """
18
+
19
+ # Ref: https://www.twilio.com/docs/stun-turn/api
20
+ try:
21
+ account_sid = os.environ["TWILIO_ACCOUNT_SID"]
22
+ auth_token = os.environ["TWILIO_AUTH_TOKEN"]
23
+ except KeyError:
24
+ logger.warning(
25
+ "Twilio credentials are not set. Fallback to a free STUN server from Google." # noqa: E501
26
+ )
27
+ return [{"urls": ["stun:stun.l.google.com:19302"]}]
28
+
29
+ client = Client(account_sid, auth_token)
30
+
31
+ try:
32
+ token = client.tokens.create()
33
+ except TwilioRestException as e:
34
+ st.warning(
35
+ f"Error occurred while accessing Twilio API. Fallback to a free STUN server from Google. ({e})" # noqa: E501
36
+ )
37
+ return [{"urls": ["stun:stun.l.google.com:19302"]}]
38
+
39
+ return token.ice_servers