sudo-paras-shah commited on
Commit
e916c8e
·
1 Parent(s): 87ffbc7

Remove async limitations

Browse files

Add capability for mobilenet model

src/classification.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import numpy as np
3
+ import tensorflow as tf
4
+ from huggingface_hub import hf_hub_download
5
+ from nets import get_model_from_name
6
+ from utils.utils import cvtColor, get_classes, letterbox_image, preprocess_input
7
+ import tempfile
8
+
9
+ class Classification:
10
+ def __init__(self, model_choice):
11
+ self.model_choice = model_choice
12
+ self.classes_path = "src/model_data/cls_classes.txt"
13
+ self.input_shape = (224, 224)
14
+ self.alpha = 0.25
15
+
16
+ # Download the model from Hugging Face
17
+ cache_dir = os.path.join(tempfile.gettempdir(), "hf_cache")
18
+ os.makedirs(cache_dir, exist_ok=True)
19
+ self.model_path = hf_hub_download(
20
+ repo_id="sudo-paras-shah/micro-expression-casme2",
21
+ filename="ep097.weights.h5" if self.model_choice is "mobilenet" else "ep089.weights.h5",
22
+ cache_dir=cache_dir
23
+ )
24
+
25
+ # Load class names
26
+ self.class_names, self.num_classes = get_classes(self.classes_path)
27
+
28
+ # Load model
29
+ self.load_model()
30
+
31
+ def load_model(self):
32
+ if self.model_choice == "mobilenet":
33
+ self.model = get_model_from_name[self.model_choice](
34
+ input_shape=[self.input_shape[0], self.input_shape[1], 3],
35
+ classes=self.num_classes,
36
+ alpha=self.alpha
37
+ )
38
+ else:
39
+ self.model = get_model_from_name[self.model_choice](
40
+ input_shape=[self.input_shape[0], self.input_shape[1], 3],
41
+ classes=self.num_classes
42
+ )
43
+
44
+ self.model.load_weights(self.model_path)
45
+ print("Model loaded from", self.model_path)
46
+ print("Classes:", self.class_names)
47
+
48
+ def detect_image(self, image):
49
+ image = cvtColor(image)
50
+ image = letterbox_image(image, [self.input_shape[1], self.input_shape[0]])
51
+ image = np.array(image, dtype=np.float32)
52
+ image = preprocess_input(image)
53
+ image = np.expand_dims(image, axis=0)
54
+
55
+ preds = self.model.predict(image)[0]
56
+ class_index = np.argmax(preds)
57
+ class_name = self.class_names[class_index]
58
+ probability = preds[class_index]
59
+
60
+ return class_name, probability
src/nets/Loss.py CHANGED
@@ -1,19 +1,8 @@
1
  import tensorflow as tf
 
2
  from keras import backend as K
3
 
4
  def multi_category_focal_loss2(gamma=2., alpha=1):
5
- """
6
- focal loss for multi category of multi label problem
7
- 适用于多分类或多标签问题的focal loss
8
- alpha控制真值y_true为1/0时的权重
9
- 1的权重为alpha, 0的权重为1-alpha
10
- 当你的模型欠拟合,学习存在困难时,可以尝试适用本函数作为loss
11
- 当模型过于激进(无论何时总是倾向于预测出1),尝试将alpha调小
12
- 当模型过于惰性(无论何时总是倾向于预测出0,或是某一个固定的常数,说明没有学到有效特征)
13
- 尝试将alpha调大,鼓励模型进行预测出1。
14
- Usage:
15
- model.compile(loss=[multi_category_focal_loss2(alpha=0.25, gamma=2)], metrics=["accuracy"], optimizer=adam)
16
- """
17
  epsilon = 1.e-7
18
  gamma = float(gamma)
19
  alpha = tf.constant(alpha, dtype=tf.float32)
@@ -33,18 +22,8 @@ def multi_category_focal_loss2(gamma=2., alpha=1):
33
  return multi_category_focal_loss2_fixed
34
 
35
  def multi_category_focal_loss1(alpha, gamma=2.0):
36
- """
37
- focal loss for multi category of multi label problem
38
- 适用于多分类或多标签问题的focal loss
39
- alpha用于指定不同类别/标签的权重,数组大小需要与类别个数一致
40
- 当你的数据集不同类别/标签之间存在偏斜,可以尝试适用本函数作为loss
41
- Usage:
42
- model.compile(loss=[multi_category_focal_loss1(alpha=[1,2,3,2], gamma=2)], metrics=["accuracy"], optimizer=adam)
43
- """
44
  epsilon = 1.e-7
45
  alpha = tf.constant(alpha, dtype=tf.float32)
46
- #alpha = tf.constant([[1],[1],[1],[1],[1]], dtype=tf.float32)
47
- #alpha = tf.constant_initializer(alpha)
48
  gamma = float(gamma)
49
  def multi_category_focal_loss1_fixed(y_true, y_pred):
50
  y_true = tf.cast(y_true, tf.float32)
@@ -59,34 +38,17 @@ def multi_category_focal_loss1(alpha, gamma=2.0):
59
 
60
 
61
  def Cross_entropy_loss(y_true, y_pred):
62
- '''
63
- :param y_true: ont-hot encoding ,shape is [batch_size,nums_classes]
64
- :param y_pred: shape is [batch_size,nums_classes],each example defined as probability for per class
65
- :return:shape is [batch_size,], a list include cross_entropy for per example
66
- '''
67
  y_pred = K.clip(y_pred, K.epsilon(), 1.0 - K.epsilon())
68
  crossEntropyLoss = -y_true * tf.log(y_pred)
69
 
70
  return tf.reduce_sum(crossEntropyLoss, -1)
71
 
72
- # focal loss with multi label
73
  def focal_loss(classes_num, gamma=2., alpha=.25, e=0.1):
74
- # classes_num contains sample number of each classes
75
  def focal_loss_fixed(target_tensor, prediction_tensor):
76
- '''
77
- prediction_tensor is the output tensor with shape [None, 100], where 100 is the number of classes
78
- target_tensor is the label tensor, same shape as predcition_tensor
79
- '''
80
- import tensorflow as tf
81
- from tensorflow.python.ops import array_ops
82
- from keras import backend as K
83
-
84
- #1# get focal loss with no balanced weight which presented in paper function (4)
85
  zeros = array_ops.zeros_like(prediction_tensor, dtype=prediction_tensor.dtype)
86
  one_minus_p = array_ops.where(tf.greater(target_tensor,zeros), target_tensor - prediction_tensor, zeros)
87
  FT = -1 * (one_minus_p ** gamma) * tf.log(tf.clip_by_value(prediction_tensor, 1e-8, 1.0))
88
 
89
- #2# get balanced weight alpha
90
  classes_weight = array_ops.zeros_like(prediction_tensor, dtype=prediction_tensor.dtype)
91
 
92
  total_num = float(sum(classes_num))
@@ -98,12 +60,9 @@ def focal_loss(classes_num, gamma=2., alpha=.25, e=0.1):
98
 
99
  alpha = array_ops.where(tf.greater(target_tensor, zeros), classes_weight, zeros)
100
 
101
- #3# get balanced focal loss
102
  balanced_fl = alpha * FT
103
  balanced_fl = tf.reduce_mean(balanced_fl)
104
 
105
- #4# add other op to prevent overfit
106
- # reference : https://spaces.ac.cn/archives/4493
107
  nb_classes = len(classes_num)
108
  fianal_loss = (1-e) * balanced_fl + e * K.categorical_crossentropy(K.ones_like(prediction_tensor)/nb_classes, prediction_tensor)
109
 
 
1
  import tensorflow as tf
2
+ from tensorflow.python.ops import array_ops
3
  from keras import backend as K
4
 
5
  def multi_category_focal_loss2(gamma=2., alpha=1):
 
 
 
 
 
 
 
 
 
 
 
 
6
  epsilon = 1.e-7
7
  gamma = float(gamma)
8
  alpha = tf.constant(alpha, dtype=tf.float32)
 
22
  return multi_category_focal_loss2_fixed
23
 
24
  def multi_category_focal_loss1(alpha, gamma=2.0):
 
 
 
 
 
 
 
 
25
  epsilon = 1.e-7
26
  alpha = tf.constant(alpha, dtype=tf.float32)
 
 
27
  gamma = float(gamma)
28
  def multi_category_focal_loss1_fixed(y_true, y_pred):
29
  y_true = tf.cast(y_true, tf.float32)
 
38
 
39
 
40
  def Cross_entropy_loss(y_true, y_pred):
 
 
 
 
 
41
  y_pred = K.clip(y_pred, K.epsilon(), 1.0 - K.epsilon())
42
  crossEntropyLoss = -y_true * tf.log(y_pred)
43
 
44
  return tf.reduce_sum(crossEntropyLoss, -1)
45
 
 
46
  def focal_loss(classes_num, gamma=2., alpha=.25, e=0.1):
 
47
  def focal_loss_fixed(target_tensor, prediction_tensor):
 
 
 
 
 
 
 
 
 
48
  zeros = array_ops.zeros_like(prediction_tensor, dtype=prediction_tensor.dtype)
49
  one_minus_p = array_ops.where(tf.greater(target_tensor,zeros), target_tensor - prediction_tensor, zeros)
50
  FT = -1 * (one_minus_p ** gamma) * tf.log(tf.clip_by_value(prediction_tensor, 1e-8, 1.0))
51
 
 
52
  classes_weight = array_ops.zeros_like(prediction_tensor, dtype=prediction_tensor.dtype)
53
 
54
  total_num = float(sum(classes_num))
 
60
 
61
  alpha = array_ops.where(tf.greater(target_tensor, zeros), classes_weight, zeros)
62
 
 
63
  balanced_fl = alpha * FT
64
  balanced_fl = tf.reduce_mean(balanced_fl)
65
 
 
 
66
  nb_classes = len(classes_num)
67
  fianal_loss = (1-e) * balanced_fl + e * K.categorical_crossentropy(K.ones_like(prediction_tensor)/nb_classes, prediction_tensor)
68
 
src/nets/resnet50.py CHANGED
@@ -12,17 +12,14 @@ def identity_block(input_tensor, kernel_size, filters, stage, block):
12
  conv_name_base = 'res' + str(stage) + block + '_branch'
13
  bn_name_base = 'bn' + str(stage) + block + '_branch'
14
 
15
- # 减少通道数
16
  x = Conv2D(filters1, (1, 1), name=conv_name_base + '2a')(input_tensor)
17
  x = BatchNormalization(name=bn_name_base + '2a')(x)
18
  x = Activation('relu')(x)
19
 
20
- # 3x3卷积
21
  x = Conv2D(filters2, kernel_size,padding='same', name=conv_name_base + '2b')(x)
22
  x = BatchNormalization(name=bn_name_base + '2b')(x)
23
  x = Activation('relu')(x)
24
 
25
- # 上升通道数
26
  x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x)
27
  x = BatchNormalization(name=bn_name_base + '2c')(x)
28
 
@@ -37,21 +34,17 @@ def conv_block(input_tensor, kernel_size, filters, stage, block, strides=(2, 2))
37
  conv_name_base = 'res' + str(stage) + block + '_branch'
38
  bn_name_base = 'bn' + str(stage) + block + '_branch'
39
 
40
- # 减少通道数
41
  x = Conv2D(filters1, (1, 1), strides=strides, name=conv_name_base + '2a')(input_tensor)
42
  x = BatchNormalization(name=bn_name_base + '2a')(x)
43
  x = Activation('relu')(x)
44
 
45
- # 3x3卷积
46
  x = Conv2D(filters2, kernel_size, padding='same', name=conv_name_base + '2b')(x)
47
  x = BatchNormalization(name=bn_name_base + '2b')(x)
48
  x = Activation('relu')(x)
49
 
50
- # 上升通道数
51
  x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x)
52
  x = BatchNormalization(name=bn_name_base + '2c')(x)
53
 
54
- # 残差边
55
  shortcut = Conv2D(filters3, (1, 1), strides=strides,
56
  name=conv_name_base + '1')(input_tensor)
57
  shortcut = BatchNormalization(name=bn_name_base + '1')(shortcut)
@@ -101,7 +94,6 @@ def ResNet50(input_shape=[224,224,3], classes=1000):
101
  # 1,1,2048
102
  x = AveragePooling2D((7, 7), name='avg_pool')(x)
103
 
104
- # 进行预测
105
  # 2048
106
  x = Flatten()(x)
107
 
 
12
  conv_name_base = 'res' + str(stage) + block + '_branch'
13
  bn_name_base = 'bn' + str(stage) + block + '_branch'
14
 
 
15
  x = Conv2D(filters1, (1, 1), name=conv_name_base + '2a')(input_tensor)
16
  x = BatchNormalization(name=bn_name_base + '2a')(x)
17
  x = Activation('relu')(x)
18
 
 
19
  x = Conv2D(filters2, kernel_size,padding='same', name=conv_name_base + '2b')(x)
20
  x = BatchNormalization(name=bn_name_base + '2b')(x)
21
  x = Activation('relu')(x)
22
 
 
23
  x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x)
24
  x = BatchNormalization(name=bn_name_base + '2c')(x)
25
 
 
34
  conv_name_base = 'res' + str(stage) + block + '_branch'
35
  bn_name_base = 'bn' + str(stage) + block + '_branch'
36
 
 
37
  x = Conv2D(filters1, (1, 1), strides=strides, name=conv_name_base + '2a')(input_tensor)
38
  x = BatchNormalization(name=bn_name_base + '2a')(x)
39
  x = Activation('relu')(x)
40
 
 
41
  x = Conv2D(filters2, kernel_size, padding='same', name=conv_name_base + '2b')(x)
42
  x = BatchNormalization(name=bn_name_base + '2b')(x)
43
  x = Activation('relu')(x)
44
 
 
45
  x = Conv2D(filters3, (1, 1), name=conv_name_base + '2c')(x)
46
  x = BatchNormalization(name=bn_name_base + '2c')(x)
47
 
 
48
  shortcut = Conv2D(filters3, (1, 1), strides=strides,
49
  name=conv_name_base + '1')(input_tensor)
50
  shortcut = BatchNormalization(name=bn_name_base + '1')(shortcut)
 
94
  # 1,1,2048
95
  x = AveragePooling2D((7, 7), name='avg_pool')(x)
96
 
 
97
  # 2048
98
  x = Flatten()(x)
99
 
src/nets/vgg16.py CHANGED
@@ -1,15 +1,15 @@
1
  from keras.layers import Conv2D, Dense, Flatten, Input, MaxPooling2D
2
- from keras.models import Model #导入包Conv2D是卷积核 Flatten是展开 Input输入 MaxPooling2D最大卷积核
3
 
4
- def VGG16(input_shape=None, classes=1000): #def 就是开始定义VGG16的网络
5
  img_input = Input(shape=input_shape) # 224, 224, 3
6
 
7
  # Block 1
8
  # 224, 224, 3 -> 224, 224, 64
9
- x = Conv2D(64, (3, 3), #开始第一个卷积核进行特征提取,64为卷积核的个数;(3, 3)是卷积核的大小
10
- activation='relu', #relu激活函数
11
- padding='same', #padding='same' 尺寸不变
12
- name='block1_conv1')(img_input) #dui juanjihe jinxing mingming # x= 224*224*64
13
  x = Conv2D(64, (3, 3),
14
  activation='relu',
15
  padding='same',
 
1
  from keras.layers import Conv2D, Dense, Flatten, Input, MaxPooling2D
2
+ from keras.models import Model
3
 
4
+ def VGG16(input_shape=None, classes=1000):
5
  img_input = Input(shape=input_shape) # 224, 224, 3
6
 
7
  # Block 1
8
  # 224, 224, 3 -> 224, 224, 64
9
+ x = Conv2D(64, (3, 3),
10
+ activation='relu',
11
+ padding='same',
12
+ name='block1_conv1')(img_input)
13
  x = Conv2D(64, (3, 3),
14
  activation='relu',
15
  padding='same',
src/streamlit_app.py CHANGED
@@ -1,7 +1,4 @@
1
  import os
2
- import sys
3
- import asyncio
4
- import tempfile
5
  import traceback
6
 
7
  os.environ["HOME"] = "/tmp"
@@ -10,19 +7,12 @@ os.environ["TF_CPP_MIN_LOG_LEVEL"] = "3"
10
  os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
11
  os.environ["TF_FORCE_GPU_ALLOW_GROWTH"] = "true"
12
 
13
- if sys.platform.startswith('linux'):
14
- try:
15
- asyncio.get_event_loop()
16
- except RuntimeError:
17
- asyncio.set_event_loop(asyncio.new_event_loop())
18
-
19
  import cv2
20
  import numpy as np
21
  from PIL import Image
22
 
23
  import streamlit as st
24
  from streamlit_webrtc import VideoProcessorBase, webrtc_streamer, RTCConfiguration
25
- from huggingface_hub import hf_hub_download
26
  from twilio.rest import Client
27
 
28
  account_sid = os.environ.get("ACCOUNT_SID")
@@ -30,7 +20,7 @@ auth_token = os.environ.get("AUTH_TOKEN")
30
  ICE_SERVERS = [{"urls": ["stun:stun.l.google.com:19302"]}]
31
  if account_sid and auth_token:
32
  try:
33
- twilio_client = Client(account_sid, auth_token)
34
  token = twilio_client.tokens.create()
35
  try:
36
  ICE_SERVERS = [
@@ -56,86 +46,36 @@ if gpus:
56
  except Exception as e:
57
  print(e)
58
 
59
- # --- Utility functions (from utils/utils.py) ---
60
- # You must ensure these are implemented or import them if available.
61
- from nets import get_model_from_name
62
- from utils.utils import (cvtColor, get_classes, letterbox_image, preprocess_input)
63
-
64
-
65
- # --- Classification class (merged from classification.py) ---
66
- cache_dir = os.path.join(tempfile.gettempdir(), "hf_cache")
67
- os.makedirs(cache_dir, exist_ok=True)
68
-
69
- class Classification(object):
70
- _defaults = {
71
- "model_path": hf_hub_download(
72
- repo_id="sudo-paras-shah/micro-expression-casme2",
73
- filename="ep089.weights.h5",
74
- cache_dir=cache_dir
75
- ),
76
- "classes_path": 'src/model_data/cls_classes.txt',
77
- "input_shape": [224, 224],
78
- "backbone": 'vgg16',
79
- "alpha": 0.25
80
- }
81
-
82
- @classmethod
83
- def get_defaults(cls, n):
84
- if n in cls._defaults:
85
- return cls._defaults[n]
86
- else:
87
- return "Unrecognized attribute name '" + n + "'"
88
-
89
- def __init__(self, **kwargs):
90
- self.__dict__.update(self._defaults)
91
- for name, value in kwargs.items():
92
- setattr(self, name, value)
93
- self.class_names, self.num_classes = get_classes(self.classes_path)
94
- self.generate()
95
 
96
- def generate(self):
97
- model_path = os.path.expanduser(self.model_path)
98
- assert model_path.endswith('.h5'), 'Keras model or weights must be a .h5 file.'
99
- if self.backbone == "mobilenet":
100
- self.model = get_model_from_name[self.backbone](
101
- input_shape=[self.input_shape[0], self.input_shape[1], 3],
102
- classes=self.num_classes,
103
- alpha=self.alpha
104
- )
105
- else:
106
- self.model = get_model_from_name[self.backbone](
107
- input_shape=[self.input_shape[0], self.input_shape[1], 3],
108
- classes=self.num_classes
109
- )
110
- self.model.load_weights(self.model_path)
111
- print('{} model, and classes {} loaded.'.format(model_path, self.class_names))
112
 
113
- def detect_image(self, image):
114
- image = cvtColor(image)
115
- image_data = letterbox_image(image, [self.input_shape[1], self.input_shape[0]])
116
- image_data = np.expand_dims(preprocess_input(np.array(image_data, np.float32)), 0)
117
- preds = self.model.predict(image_data)[0]
118
- class_name = self.class_names[np.argmax(preds)]
119
- probability = np.max(preds)
120
- return class_name, probability
121
 
122
  # --- Main Streamlit App ---
123
  if __name__ == '__main__':
 
 
 
 
 
 
 
 
 
 
124
  @st.cache_resource
125
- def get_model():
126
- return Classification()
127
 
128
- classificator = get_model()
129
  face_cascade = cv2.CascadeClassifier(
130
  cv2.data.haarcascades + 'haarcascade_frontalface_alt.xml'
131
  )
132
 
133
- if face_cascade.empty():
134
- st.error("Failed to load Haarcascade XML. Check the path.")
135
-
136
- st.title("Real-Time Micro-Emotion Recognition")
137
- st.write("Turn on your camera and detect emotions in real-time.")
138
-
139
  def face_detect(img):
140
  try:
141
  img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
@@ -184,8 +124,7 @@ if __name__ == '__main__':
184
  img = frame.to_ndarray(format="bgr24")
185
  self.frame_count += 1
186
 
187
- # Only run detection every 5th frame, reuse previous results otherwise
188
- if self.frame_count % 2 == 0:
189
  img_disp, img_gray, faces = face_detect(img)
190
  self.last_faces = faces
191
  self.last_img_gray = img_gray
@@ -214,11 +153,8 @@ if __name__ == '__main__':
214
  current_class = emotion_class
215
 
216
  if current_class:
217
- history = st.session_state['emotion_history']
218
- history.append(current_class)
219
- if len(history) > 10:
220
- history.pop(0)
221
- if len(history) >= 3 and len(set(history[-3:])) > 1:
222
  self.rapid_change_count += 1
223
  else:
224
  self.rapid_change_count = 0
@@ -252,7 +188,7 @@ if __name__ == '__main__':
252
 
253
  return frame.from_ndarray(img_disp, format="bgr24")
254
  except Exception as e:
255
- st.error(f"Error in video processing: {e}")
256
  return frame
257
 
258
  RTC_CONFIGURATION = RTCConfiguration({"iceServers": ICE_SERVERS})
@@ -264,7 +200,7 @@ if __name__ == '__main__':
264
  media_stream_constraints={"video": True, "audio": False},
265
  )
266
 
267
- history = st.session_state['emotion_history']
268
  if len(history) >= 3 and len(set(history[-3:])) > 1:
269
  st.warning(
270
  "⚠️ Rapid changes in your detected emotional state were observed. "
 
1
  import os
 
 
 
2
  import traceback
3
 
4
  os.environ["HOME"] = "/tmp"
 
7
  os.environ["CUDA_DEVICE_ORDER"] = "PCI_BUS_ID"
8
  os.environ["TF_FORCE_GPU_ALLOW_GROWTH"] = "true"
9
 
 
 
 
 
 
 
10
  import cv2
11
  import numpy as np
12
  from PIL import Image
13
 
14
  import streamlit as st
15
  from streamlit_webrtc import VideoProcessorBase, webrtc_streamer, RTCConfiguration
 
16
  from twilio.rest import Client
17
 
18
  account_sid = os.environ.get("ACCOUNT_SID")
 
20
  ICE_SERVERS = [{"urls": ["stun:stun.l.google.com:19302"]}]
21
  if account_sid and auth_token:
22
  try:
23
+ twilio_client = Client(account_sid, auth_token, region="in1")
24
  token = twilio_client.tokens.create()
25
  try:
26
  ICE_SERVERS = [
 
46
  except Exception as e:
47
  print(e)
48
 
49
+ from collections import deque
50
+ shared_emotion_history = deque(maxlen=20)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
51
 
52
+ import logging
53
+ logging.getLogger("streamlit.runtime.scriptrunner.script_run_context").setLevel(logging.ERROR)
54
+ logger = logging.getLogger(__name__)
 
 
 
 
 
 
 
 
 
 
 
 
 
55
 
56
+ from classification import Classification
 
 
 
 
 
 
 
57
 
58
  # --- Main Streamlit App ---
59
  if __name__ == '__main__':
60
+ st.title("Personal Video Logger")
61
+ st.write("Turn on your camera and talk about anything that worries you or just about your day.")
62
+
63
+ model_choice = st.selectbox(
64
+ "Choose a model:",
65
+ options=["mobilenet", "vgg16"],
66
+ index=0,
67
+ help="Select the model used for emotion classification."
68
+ )
69
+
70
  @st.cache_resource
71
+ def get_model(model):
72
+ return Classification(model)
73
 
74
+ classificator = get_model(model_choice)
75
  face_cascade = cv2.CascadeClassifier(
76
  cv2.data.haarcascades + 'haarcascade_frontalface_alt.xml'
77
  )
78
 
 
 
 
 
 
 
79
  def face_detect(img):
80
  try:
81
  img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
 
124
  img = frame.to_ndarray(format="bgr24")
125
  self.frame_count += 1
126
 
127
+ if self.frame_count % 1 == 0:
 
128
  img_disp, img_gray, faces = face_detect(img)
129
  self.last_faces = faces
130
  self.last_img_gray = img_gray
 
153
  current_class = emotion_class
154
 
155
  if current_class:
156
+ shared_emotion_history.append(current_class)
157
+ if len(shared_emotion_history) >= 3 and len(set(list(shared_emotion_history)[-3:])) > 1:
 
 
 
158
  self.rapid_change_count += 1
159
  else:
160
  self.rapid_change_count = 0
 
188
 
189
  return frame.from_ndarray(img_disp, format="bgr24")
190
  except Exception as e:
191
+ logger.exception("Video processing error", e)
192
  return frame
193
 
194
  RTC_CONFIGURATION = RTCConfiguration({"iceServers": ICE_SERVERS})
 
200
  media_stream_constraints={"video": True, "audio": False},
201
  )
202
 
203
+ history = list(shared_emotion_history)
204
  if len(history) >= 3 and len(set(history[-3:])) > 1:
205
  st.warning(
206
  "⚠️ Rapid changes in your detected emotional state were observed. "
src/utils/callbacks.py CHANGED
@@ -1,5 +1,4 @@
1
  import os
2
-
3
  import matplotlib
4
  matplotlib.use('Agg')
5
  from matplotlib import pyplot as plt
 
1
  import os
 
2
  import matplotlib
3
  matplotlib.use('Agg')
4
  from matplotlib import pyplot as plt
src/utils/dataloader.py CHANGED
@@ -50,13 +50,8 @@ class ClsDatasets(keras.utils.Sequence):
50
  return np.random.rand()*(b-a) + a
51
 
52
  def get_random_data(self, image, input_shape, jitter=.3, hue=.1, sat=1.5, val=1.5, random=True):
53
- #------------------------------#
54
- # 读取图像并转换成RGB图像
55
- #------------------------------#
56
  image = cvtColor(image)
57
- #------------------------------#
58
- # 获得图像的高宽与目标高宽
59
- #------------------------------#
60
  iw, ih = image.size
61
  h, w = input_shape
62
 
@@ -67,9 +62,6 @@ class ClsDatasets(keras.utils.Sequence):
67
  dx = (w-nw)//2
68
  dy = (h-nh)//2
69
 
70
- #---------------------------------#
71
- # 将图像多余的部分加上灰条
72
- #---------------------------------#
73
  image = image.resize((nw,nh), Image.BICUBIC)
74
  new_image = Image.new('RGB', (w,h), (128,128,128))
75
  new_image.paste(image, (dx, dy))
@@ -77,9 +69,6 @@ class ClsDatasets(keras.utils.Sequence):
77
 
78
  return image_data
79
 
80
- #------------------------------------------#
81
- # 对图像进行缩放并且进行长和宽的扭曲
82
- #------------------------------------------#
83
  new_ar = w/h * self.rand(1-jitter,1+jitter)/self.rand(1-jitter,1+jitter)
84
  scale = self.rand(.75, 1.25)
85
  if new_ar < 1:
@@ -90,18 +79,12 @@ class ClsDatasets(keras.utils.Sequence):
90
  nh = int(nw/new_ar)
91
  image = image.resize((nw,nh), Image.BICUBIC)
92
 
93
- #------------------------------------------#
94
- # 将图像多余的部分加上灰条
95
- #------------------------------------------#
96
  dx = int(self.rand(0, w-nw))
97
  dy = int(self.rand(0, h-nh))
98
  new_image = Image.new('RGB', (w,h), (128,128,128))
99
  new_image.paste(image, (dx, dy))
100
  image = new_image
101
 
102
- #------------------------------------------#
103
- # 翻转图像
104
- #------------------------------------------#
105
  flip = self.rand()<.5
106
  if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT)
107
 
@@ -112,9 +95,6 @@ class ClsDatasets(keras.utils.Sequence):
112
  M = cv2.getRotationMatrix2D((a,b),angle,1)
113
  image = cv2.warpAffine(np.array(image), M, (w,h), borderValue=[128,128,128])
114
 
115
- #------------------------------------------#
116
- # 色域扭曲
117
- #------------------------------------------#
118
  hue = self.rand(-hue, hue)
119
  sat = self.rand(1, sat) if self.rand()<.5 else 1/self.rand(1, sat)
120
  val = self.rand(1, val) if self.rand()<.5 else 1/self.rand(1, val)
 
50
  return np.random.rand()*(b-a) + a
51
 
52
  def get_random_data(self, image, input_shape, jitter=.3, hue=.1, sat=1.5, val=1.5, random=True):
53
+ # Read and convert images to RGB
 
 
54
  image = cvtColor(image)
 
 
 
55
  iw, ih = image.size
56
  h, w = input_shape
57
 
 
62
  dx = (w-nw)//2
63
  dy = (h-nh)//2
64
 
 
 
 
65
  image = image.resize((nw,nh), Image.BICUBIC)
66
  new_image = Image.new('RGB', (w,h), (128,128,128))
67
  new_image.paste(image, (dx, dy))
 
69
 
70
  return image_data
71
 
 
 
 
72
  new_ar = w/h * self.rand(1-jitter,1+jitter)/self.rand(1-jitter,1+jitter)
73
  scale = self.rand(.75, 1.25)
74
  if new_ar < 1:
 
79
  nh = int(nw/new_ar)
80
  image = image.resize((nw,nh), Image.BICUBIC)
81
 
 
 
 
82
  dx = int(self.rand(0, w-nw))
83
  dy = int(self.rand(0, h-nh))
84
  new_image = Image.new('RGB', (w,h), (128,128,128))
85
  new_image.paste(image, (dx, dy))
86
  image = new_image
87
 
 
 
 
88
  flip = self.rand()<.5
89
  if flip: image = image.transpose(Image.FLIP_LEFT_RIGHT)
90
 
 
95
  M = cv2.getRotationMatrix2D((a,b),angle,1)
96
  image = cv2.warpAffine(np.array(image), M, (w,h), borderValue=[128,128,128])
97
 
 
 
 
98
  hue = self.rand(-hue, hue)
99
  sat = self.rand(1, sat) if self.rand()<.5 else 1/self.rand(1, sat)
100
  val = self.rand(1, val) if self.rand()<.5 else 1/self.rand(1, val)
src/utils/utils.py CHANGED
@@ -1,36 +1,26 @@
1
  import numpy as np
2
  from PIL import Image
3
 
4
- #---------------------------------------------------#
5
- # 不失真的resize
6
- #---------------------------------------------------#
7
  def letterbox_image(image, size):
8
- iw, ih = image.size
9
- w, h = size
10
 
11
- scale = min(w/iw, h/ih)
12
- nw = int(iw*scale)
13
- nh = int(ih*scale)
14
 
15
- image = image.resize((nw,nh), Image.BICUBIC)
16
- new_image = Image.new('RGB', size, (128,128,128))
17
- new_image.paste(image, ((w-nw)//2, (h-nh)//2))
18
 
19
  return new_image
20
 
21
- #---------------------------------------------------#
22
- # 获得类
23
- #---------------------------------------------------#
24
  def get_classes(classes_path):
25
  with open(classes_path, encoding='utf-8') as f:
26
  class_names = f.readlines()
27
  class_names = [c.strip() for c in class_names]
28
  return class_names, len(class_names)
29
 
30
- #---------------------------------------------------------#
31
- # 将图像转换成RGB图像,防止灰度图在预测时报错。
32
- # 代码仅仅支持RGB图像的预测,所有其它类型的图像都会转化成RGB
33
- #---------------------------------------------------------#
34
  def cvtColor(image):
35
  if len(np.shape(image)) == 3 and np.shape(image)[2] == 3:
36
  return image
@@ -38,9 +28,6 @@ def cvtColor(image):
38
  image = image.convert('RGB')
39
  return image
40
 
41
- #----------------------------------------#
42
- # 预处理训练图片
43
- #----------------------------------------#
44
  def preprocess_input(x):
45
  x /= 127.5
46
  x -= 1.
 
1
  import numpy as np
2
  from PIL import Image
3
 
 
 
 
4
  def letterbox_image(image, size):
5
+ iw, ih = image.size
6
+ w, h = size
7
 
8
+ scale = min(w / iw, h / ih)
9
+ nw = int(iw * scale)
10
+ nh = int(ih * scale)
11
 
12
+ image = image.resize((nw, nh), Image.BICUBIC)
13
+ new_image = Image.new('RGB', size, (128, 128, 128))
14
+ new_image.paste(image, ((w - nw) // 2, (h - nh) // 2))
15
 
16
  return new_image
17
 
 
 
 
18
  def get_classes(classes_path):
19
  with open(classes_path, encoding='utf-8') as f:
20
  class_names = f.readlines()
21
  class_names = [c.strip() for c in class_names]
22
  return class_names, len(class_names)
23
 
 
 
 
 
24
  def cvtColor(image):
25
  if len(np.shape(image)) == 3 and np.shape(image)[2] == 3:
26
  return image
 
28
  image = image.convert('RGB')
29
  return image
30
 
 
 
 
31
  def preprocess_input(x):
32
  x /= 127.5
33
  x -= 1.