kalpit sharma commited on
Commit ·
5b6acdd
1
Parent(s): fca7bd8
adding changes
Browse files- .gitignore +174 -0
- README.md +1 -0
- app.py +286 -0
- camera.py +34 -0
- clean_requirements.txt +93 -0
- cnn.py +52 -0
- cnn_emotion.py +31 -0
- cnn_lstm.py +44 -0
- cnn_resnet.py +51 -0
- cnn_rnn_model_from_dir.h5 +3 -0
- comparison.py +212 -0
- comparison2.py +187 -0
- emotion_detector_model.h5 +3 -0
- haarcascade_frontalface_default.xml +0 -0
- help.txt +34 -0
- index.html +39 -0
- k-nn_model.joblib +3 -0
- knn.py +35 -0
- logistic_regression_model.joblib +3 -0
- logisticregression.py +35 -0
- model.json +1 -0
- model.py +32 -0
- model.weights.h5 +3 -0
- model/clip_emotion.py +23 -0
- model_weights.h5 +3 -0
- models/clip_emotion.py +38 -0
- random_forest_model.joblib +3 -0
- randomforest.py +35 -0
- requirements.txt +14 -0
- static/index.html +39 -0
- svm.py +35 -0
- svm_model.joblib +3 -0
- templates/classification_reports.html +46 -0
- templates/cnn.html +71 -0
- templates/cnn_lstm.html +55 -0
- templates/cnn_resnet.html +71 -0
- templates/cnnkeras.html +55 -0
- templates/index.html +80 -0
- templates/knn.html +71 -0
- templates/lr.html +71 -0
- templates/rf.html +71 -0
- templates/svm.html +71 -0
- templates/vit.html +72 -0
- testing.py +5 -0
- train_basic_ml.py +102 -0
- train_cnn_rnn_fer2013.py +83 -0
.gitignore
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Byte-compiled / optimized / DLL files
|
| 2 |
+
__pycache__/
|
| 3 |
+
*.py[cod]
|
| 4 |
+
*$py.class
|
| 5 |
+
|
| 6 |
+
# C extensions
|
| 7 |
+
*.so
|
| 8 |
+
train/
|
| 9 |
+
# Distribution / packaging
|
| 10 |
+
.Python
|
| 11 |
+
build/
|
| 12 |
+
develop-eggs/
|
| 13 |
+
dist/
|
| 14 |
+
downloads/
|
| 15 |
+
eggs/
|
| 16 |
+
.eggs/
|
| 17 |
+
lib/
|
| 18 |
+
lib64/
|
| 19 |
+
parts/
|
| 20 |
+
sdist/
|
| 21 |
+
var/
|
| 22 |
+
wheels/
|
| 23 |
+
share/python-wheels/
|
| 24 |
+
*.egg-info/
|
| 25 |
+
.installed.cfg
|
| 26 |
+
*.egg
|
| 27 |
+
MANIFEST
|
| 28 |
+
|
| 29 |
+
# PyInstaller
|
| 30 |
+
# Usually these files are written by a python script from a template
|
| 31 |
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
| 32 |
+
*.manifest
|
| 33 |
+
*.spec
|
| 34 |
+
|
| 35 |
+
# Installer logs
|
| 36 |
+
pip-log.txt
|
| 37 |
+
pip-delete-this-directory.txt
|
| 38 |
+
|
| 39 |
+
# Unit test / coverage reports
|
| 40 |
+
htmlcov/
|
| 41 |
+
.tox/
|
| 42 |
+
.nox/
|
| 43 |
+
.coverage
|
| 44 |
+
.coverage.*
|
| 45 |
+
.cache
|
| 46 |
+
nosetests.xml
|
| 47 |
+
coverage.xml
|
| 48 |
+
*.cover
|
| 49 |
+
*.py,cover
|
| 50 |
+
.hypothesis/
|
| 51 |
+
.pytest_cache/
|
| 52 |
+
cover/
|
| 53 |
+
|
| 54 |
+
# Translations
|
| 55 |
+
*.mo
|
| 56 |
+
*.pot
|
| 57 |
+
|
| 58 |
+
# Django stuff:
|
| 59 |
+
*.log
|
| 60 |
+
local_settings.py
|
| 61 |
+
db.sqlite3
|
| 62 |
+
db.sqlite3-journal
|
| 63 |
+
|
| 64 |
+
# Flask stuff:
|
| 65 |
+
instance/
|
| 66 |
+
.webassets-cache
|
| 67 |
+
|
| 68 |
+
# Scrapy stuff:
|
| 69 |
+
.scrapy
|
| 70 |
+
|
| 71 |
+
# Sphinx documentation
|
| 72 |
+
docs/_build/
|
| 73 |
+
|
| 74 |
+
# PyBuilder
|
| 75 |
+
.pybuilder/
|
| 76 |
+
target/
|
| 77 |
+
|
| 78 |
+
# Jupyter Notebook
|
| 79 |
+
.ipynb_checkpoints
|
| 80 |
+
|
| 81 |
+
# IPython
|
| 82 |
+
profile_default/
|
| 83 |
+
ipython_config.py
|
| 84 |
+
|
| 85 |
+
# pyenv
|
| 86 |
+
# For a library or package, you might want to ignore these files since the code is
|
| 87 |
+
# intended to run in multiple environments; otherwise, check them in:
|
| 88 |
+
# .python-version
|
| 89 |
+
|
| 90 |
+
# pipenv
|
| 91 |
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
| 92 |
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
| 93 |
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
| 94 |
+
# install all needed dependencies.
|
| 95 |
+
#Pipfile.lock
|
| 96 |
+
|
| 97 |
+
# UV
|
| 98 |
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
| 99 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 100 |
+
# commonly ignored for libraries.
|
| 101 |
+
#uv.lock
|
| 102 |
+
|
| 103 |
+
# poetry
|
| 104 |
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
| 105 |
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
| 106 |
+
# commonly ignored for libraries.
|
| 107 |
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
| 108 |
+
#poetry.lock
|
| 109 |
+
|
| 110 |
+
# pdm
|
| 111 |
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
| 112 |
+
#pdm.lock
|
| 113 |
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
| 114 |
+
# in version control.
|
| 115 |
+
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
| 116 |
+
.pdm.toml
|
| 117 |
+
.pdm-python
|
| 118 |
+
.pdm-build/
|
| 119 |
+
|
| 120 |
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
| 121 |
+
__pypackages__/
|
| 122 |
+
|
| 123 |
+
# Celery stuff
|
| 124 |
+
celerybeat-schedule
|
| 125 |
+
celerybeat.pid
|
| 126 |
+
|
| 127 |
+
# SageMath parsed files
|
| 128 |
+
*.sage.py
|
| 129 |
+
|
| 130 |
+
# Environments
|
| 131 |
+
.env
|
| 132 |
+
.venv
|
| 133 |
+
env/
|
| 134 |
+
venv/
|
| 135 |
+
ENV/
|
| 136 |
+
env.bak/
|
| 137 |
+
venv.bak/
|
| 138 |
+
|
| 139 |
+
# Spyder project settings
|
| 140 |
+
.spyderproject
|
| 141 |
+
.spyproject
|
| 142 |
+
|
| 143 |
+
# Rope project settings
|
| 144 |
+
.ropeproject
|
| 145 |
+
|
| 146 |
+
# mkdocs documentation
|
| 147 |
+
/site
|
| 148 |
+
|
| 149 |
+
# mypy
|
| 150 |
+
.mypy_cache/
|
| 151 |
+
.dmypy.json
|
| 152 |
+
dmypy.json
|
| 153 |
+
|
| 154 |
+
# Pyre type checker
|
| 155 |
+
.pyre/
|
| 156 |
+
|
| 157 |
+
# pytype static type analyzer
|
| 158 |
+
.pytype/
|
| 159 |
+
|
| 160 |
+
# Cython debug symbols
|
| 161 |
+
cython_debug/
|
| 162 |
+
|
| 163 |
+
# PyCharm
|
| 164 |
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
| 165 |
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
| 166 |
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
| 167 |
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
| 168 |
+
#.idea/
|
| 169 |
+
|
| 170 |
+
# Ruff stuff:
|
| 171 |
+
.ruff_cache/
|
| 172 |
+
|
| 173 |
+
# PyPI configuration file
|
| 174 |
+
.pypirc
|
README.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
# face-emotion-detection
|
app.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify , render_template , Response
|
| 2 |
+
from flask_cors import CORS
|
| 3 |
+
from models.clip_emotion import detect_emotion , detect_age
|
| 4 |
+
import cv2
|
| 5 |
+
from camera import VideoCamera
|
| 6 |
+
import os
|
| 7 |
+
from cnn_emotion import detect_emotion as detect_emotion_cnn
|
| 8 |
+
# from cnn_lstm import detect_cnn_lstm_emotion
|
| 9 |
+
from cnn_resnet import detect_cnn_resnetemotion
|
| 10 |
+
|
| 11 |
+
from cnn import detect_cnn
|
| 12 |
+
from knn import detect_knn
|
| 13 |
+
from svm import detect_svm
|
| 14 |
+
from randomforest import detect_rf
|
| 15 |
+
from logisticregression import detect_lr
|
| 16 |
+
|
| 17 |
+
import logging
|
| 18 |
+
|
| 19 |
+
# Configure logging
|
| 20 |
+
logging.basicConfig(
|
| 21 |
+
level=logging.DEBUG, # Change to INFO in production
|
| 22 |
+
format='[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
|
| 23 |
+
)
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
app = Flask(__name__)
|
| 27 |
+
CORS(app)
|
| 28 |
+
|
| 29 |
+
UPLOAD_FOLDER = "temp_frames"
|
| 30 |
+
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
| 31 |
+
|
| 32 |
+
@app.route('/')
|
| 33 |
+
def home():
|
| 34 |
+
return render_template('index.html')
|
| 35 |
+
|
| 36 |
+
@app.route('/knn')
|
| 37 |
+
def index_knn():
|
| 38 |
+
return render_template('knn.html')
|
| 39 |
+
|
| 40 |
+
@app.route('/svm')
|
| 41 |
+
def index_svm():
|
| 42 |
+
return render_template('svm.html')
|
| 43 |
+
|
| 44 |
+
@app.route('/logistic_regression')
|
| 45 |
+
def index_lr():
|
| 46 |
+
return render_template('lr.html')
|
| 47 |
+
|
| 48 |
+
@app.route('/randomforest')
|
| 49 |
+
def index_rf():
|
| 50 |
+
return render_template('rf.html')
|
| 51 |
+
|
| 52 |
+
@app.route('/vit')
|
| 53 |
+
def index_vit():
|
| 54 |
+
return render_template('vit.html')
|
| 55 |
+
|
| 56 |
+
@app.route('/cnn')
|
| 57 |
+
def index_cnnonly():
|
| 58 |
+
return render_template('cnn.html')
|
| 59 |
+
|
| 60 |
+
@app.route('/cnnkeras')
|
| 61 |
+
def index_cnn():
|
| 62 |
+
return render_template('cnnkeras.html')
|
| 63 |
+
|
| 64 |
+
@app.route('/cnnlstm')
|
| 65 |
+
def index_cnnlstm():
|
| 66 |
+
return render_template('cnn_lstm.html')
|
| 67 |
+
|
| 68 |
+
@app.route('/cnn_resnet')
|
| 69 |
+
def index_cnn_resnet():
|
| 70 |
+
return render_template('cnn_resnet.html')
|
| 71 |
+
|
| 72 |
+
def gen(camera):
|
| 73 |
+
while True:
|
| 74 |
+
frame = camera.get_frame()
|
| 75 |
+
yield (b'--frame\r\n'
|
| 76 |
+
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n')
|
| 77 |
+
|
| 78 |
+
@app.route('/video_feed', methods=["POST"])
|
| 79 |
+
def video_feed():
|
| 80 |
+
# return Response(gen(VideoCamera()),
|
| 81 |
+
# mimetype='multipart/x-mixed-replace; boundary=frame')
|
| 82 |
+
|
| 83 |
+
if "frame" not in request.files:
|
| 84 |
+
return jsonify({"error": "No frame received"}), 400
|
| 85 |
+
|
| 86 |
+
file = request.files["frame"]
|
| 87 |
+
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 88 |
+
file.save(filepath)
|
| 89 |
+
try:
|
| 90 |
+
emotion, image_base64 = detect_emotion_cnn(filepath)
|
| 91 |
+
return jsonify({"emotion": emotion, "image": image_base64})
|
| 92 |
+
except Exception as e:
|
| 93 |
+
return jsonify({"error": str(e)}), 500
|
| 94 |
+
|
| 95 |
+
@app.route("/analyze", methods=["POST"])
|
| 96 |
+
def analyze():
|
| 97 |
+
if "frame" not in request.files:
|
| 98 |
+
return jsonify({"error": "No frame received"}), 400
|
| 99 |
+
|
| 100 |
+
file = request.files["frame"]
|
| 101 |
+
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 102 |
+
file.save(filepath)
|
| 103 |
+
|
| 104 |
+
try:
|
| 105 |
+
emotion, scores = detect_emotion(filepath)
|
| 106 |
+
age, age_scores = detect_age(filepath)
|
| 107 |
+
return jsonify({"emotion": emotion, "scores": scores, "age": age})
|
| 108 |
+
except Exception as e:
|
| 109 |
+
return jsonify({"error": str(e)}), 500
|
| 110 |
+
|
| 111 |
+
@app.route('/cnn_lstm_video_feed', methods=["POST"])
|
| 112 |
+
def cnn_lstm_video_feed():
|
| 113 |
+
|
| 114 |
+
if "frame" not in request.files:
|
| 115 |
+
return jsonify({"error": "No frame received"}), 400
|
| 116 |
+
|
| 117 |
+
file = request.files["frame"]
|
| 118 |
+
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 119 |
+
file.save(filepath)
|
| 120 |
+
try:
|
| 121 |
+
emotion, image_base64 = detect_emotion_cnn(filepath)
|
| 122 |
+
return jsonify({"emotion": emotion, "image": image_base64})
|
| 123 |
+
except Exception as e:
|
| 124 |
+
return jsonify({"error": str(e)}), 500
|
| 125 |
+
|
| 126 |
+
@app.route('/cnn_resnet', methods=['POST'])
|
| 127 |
+
def cnn_resnet():
|
| 128 |
+
if "frame" not in request.files:
|
| 129 |
+
logging.warning("No frame in request")
|
| 130 |
+
return jsonify({"error": "No frame received"}), 400
|
| 131 |
+
file = request.files["frame"]
|
| 132 |
+
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 133 |
+
logging.info(f"File saved to {filepath}")
|
| 134 |
+
file.save(filepath)
|
| 135 |
+
try:
|
| 136 |
+
emotion, image_base64 = detect_cnn_resnetemotion(filepath)
|
| 137 |
+
return jsonify({"emotion": emotion, "image": image_base64})
|
| 138 |
+
except Exception as e:
|
| 139 |
+
logging.error(f"Failed to save file: {e}")
|
| 140 |
+
return jsonify({"error": str(e)}), 500
|
| 141 |
+
|
| 142 |
+
@app.route('/cnn', methods=['POST'])
|
| 143 |
+
def cnn():
|
| 144 |
+
if "frame" not in request.files:
|
| 145 |
+
logging.warning("No frame in request")
|
| 146 |
+
return jsonify({"error": "No frame received"}), 400
|
| 147 |
+
file = request.files["frame"]
|
| 148 |
+
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 149 |
+
logging.info(f"File saved to {filepath}")
|
| 150 |
+
file.save(filepath)
|
| 151 |
+
try:
|
| 152 |
+
emotion, image_base64 = detect_cnn(filepath)
|
| 153 |
+
return jsonify({"emotion": emotion, "image": image_base64})
|
| 154 |
+
except Exception as e:
|
| 155 |
+
logging.error(f"Failed to save file: {e}")
|
| 156 |
+
return jsonify({"error": str(e)}), 500
|
| 157 |
+
|
| 158 |
+
@app.route('/knn', methods=['POST'])
|
| 159 |
+
def knnn():
|
| 160 |
+
if "frame" not in request.files:
|
| 161 |
+
logging.warning("No frame in request")
|
| 162 |
+
return jsonify({"error": "No frame received"}), 400
|
| 163 |
+
file = request.files["frame"]
|
| 164 |
+
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 165 |
+
logging.info(f"File saved to {filepath}")
|
| 166 |
+
file.save(filepath)
|
| 167 |
+
try:
|
| 168 |
+
emotion, image_base64 = detect_knn(filepath)
|
| 169 |
+
return jsonify({"emotion": emotion, "image": image_base64})
|
| 170 |
+
except Exception as e:
|
| 171 |
+
logging.error(f"Failed to save file: {e}")
|
| 172 |
+
return jsonify({"error": str(e)}), 500
|
| 173 |
+
|
| 174 |
+
@app.route('/svm', methods=['POST'])
|
| 175 |
+
def svmm():
|
| 176 |
+
if "frame" not in request.files:
|
| 177 |
+
logging.warning("No frame in request")
|
| 178 |
+
return jsonify({"error": "No frame received"}), 400
|
| 179 |
+
file = request.files["frame"]
|
| 180 |
+
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 181 |
+
logging.info(f"File saved to {filepath}")
|
| 182 |
+
file.save(filepath)
|
| 183 |
+
try:
|
| 184 |
+
emotion, image_base64 = detect_svm(filepath)
|
| 185 |
+
return jsonify({"emotion": emotion, "image": image_base64})
|
| 186 |
+
except Exception as e:
|
| 187 |
+
logging.error(f"Failed to save file: {e}")
|
| 188 |
+
return jsonify({"error": str(e)}), 500
|
| 189 |
+
|
| 190 |
+
@app.route('/randomforest', methods=['POST'])
|
| 191 |
+
def rff():
|
| 192 |
+
if "frame" not in request.files:
|
| 193 |
+
logging.warning("No frame in request")
|
| 194 |
+
return jsonify({"error": "No frame received"}), 400
|
| 195 |
+
file = request.files["frame"]
|
| 196 |
+
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 197 |
+
logging.info(f"File saved to {filepath}")
|
| 198 |
+
file.save(filepath)
|
| 199 |
+
try:
|
| 200 |
+
emotion, image_base64 = detect_rf(filepath)
|
| 201 |
+
return jsonify({"emotion": emotion, "image": image_base64})
|
| 202 |
+
except Exception as e:
|
| 203 |
+
logging.error(f"Failed to save file: {e}")
|
| 204 |
+
return jsonify({"error": str(e)}), 500
|
| 205 |
+
|
| 206 |
+
@app.route('/logistic_regression', methods=['POST'])
|
| 207 |
+
def lr():
|
| 208 |
+
if "frame" not in request.files:
|
| 209 |
+
logging.warning("No frame in request")
|
| 210 |
+
return jsonify({"error": "No frame received"}), 400
|
| 211 |
+
file = request.files["frame"]
|
| 212 |
+
filepath = os.path.join(UPLOAD_FOLDER, file.filename)
|
| 213 |
+
logging.info(f"File saved to {filepath}")
|
| 214 |
+
file.save(filepath)
|
| 215 |
+
try:
|
| 216 |
+
emotion, image_base64 = detect_lr(filepath)
|
| 217 |
+
return jsonify({"emotion": emotion, "image": image_base64})
|
| 218 |
+
except Exception as e:
|
| 219 |
+
logging.error(f"Failed to save file: {e}")
|
| 220 |
+
return jsonify({"error": str(e)}), 500
|
| 221 |
+
|
| 222 |
+
@app.route("/reports")
|
| 223 |
+
def show_reports():
|
| 224 |
+
svm_report = """[RESULTS] SVM Classification Report
|
| 225 |
+
precision recall f1-score support
|
| 226 |
+
angry 0.33 0.35 0.34 779
|
| 227 |
+
disgust 0.56 0.16 0.25 92
|
| 228 |
+
fear 0.33 0.25 0.29 838
|
| 229 |
+
happy 0.59 0.68 0.63 1473
|
| 230 |
+
neutral 0.42 0.44 0.43 987
|
| 231 |
+
sad 0.35 0.33 0.34 977
|
| 232 |
+
surprise 0.57 0.54 0.55 596
|
| 233 |
+
accuracy 0.45 5742
|
| 234 |
+
macro avg 0.45 0.39 0.40 5742
|
| 235 |
+
weighted avg 0.44 0.45 0.44 5742"""
|
| 236 |
+
|
| 237 |
+
rf_report = """[RESULTS] Random Forest Classification Report
|
| 238 |
+
precision recall f1-score support
|
| 239 |
+
angry 0.38 0.20 0.26 779
|
| 240 |
+
disgust 1.00 0.27 0.43 92
|
| 241 |
+
fear 0.39 0.21 0.28 838
|
| 242 |
+
happy 0.47 0.82 0.60 1473
|
| 243 |
+
neutral 0.40 0.43 0.41 987
|
| 244 |
+
sad 0.37 0.31 0.34 977
|
| 245 |
+
surprise 0.71 0.50 0.58 596
|
| 246 |
+
accuracy 0.45 5742
|
| 247 |
+
macro avg 0.53 0.39 0.41 5742
|
| 248 |
+
weighted avg 0.45 0.45 0.42 5742"""
|
| 249 |
+
|
| 250 |
+
knn_report = """[RESULTS] k-NN Classification Report
|
| 251 |
+
precision recall f1-score support
|
| 252 |
+
angry 0.34 0.35 0.35 779
|
| 253 |
+
disgust 0.39 0.36 0.38 92
|
| 254 |
+
fear 0.38 0.31 0.34 838
|
| 255 |
+
happy 0.53 0.75 0.62 1473
|
| 256 |
+
neutral 0.39 0.42 0.40 987
|
| 257 |
+
sad 0.40 0.21 0.28 977
|
| 258 |
+
surprise 0.56 0.47 0.51 596
|
| 259 |
+
accuracy 0.45 5742
|
| 260 |
+
macro avg 0.43 0.41 0.41 5742
|
| 261 |
+
weighted avg 0.44 0.45 0.43 5742"""
|
| 262 |
+
|
| 263 |
+
lr_report = """[RESULTS] Logistic Regression Classification Report
|
| 264 |
+
precision recall f1-score support
|
| 265 |
+
angry 0.33 0.31 0.32 779
|
| 266 |
+
disgust 0.56 0.15 0.24 92
|
| 267 |
+
fear 0.32 0.22 0.26 838
|
| 268 |
+
happy 0.57 0.70 0.63 1473
|
| 269 |
+
neutral 0.41 0.43 0.42 987
|
| 270 |
+
sad 0.34 0.31 0.32 977
|
| 271 |
+
surprise 0.51 0.55 0.53 596
|
| 272 |
+
accuracy 0.44 5742
|
| 273 |
+
macro avg 0.43 0.38 0.39 5742
|
| 274 |
+
weighted avg 0.43 0.44 0.43 5742"""
|
| 275 |
+
|
| 276 |
+
return render_template(
|
| 277 |
+
"classification_reports.html",
|
| 278 |
+
svm_report=svm_report,
|
| 279 |
+
rf_report=rf_report,
|
| 280 |
+
knn_report=knn_report,
|
| 281 |
+
lr_report=lr_report
|
| 282 |
+
)
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
if __name__ == "__main__":
|
| 286 |
+
app.run(host="0.0.0.0", port=7860)
|
camera.py
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import cv2
|
| 2 |
+
from model import ERModel
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
facec = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
|
| 6 |
+
model = ERModel("model.json", "model.weights.h5")
|
| 7 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 8 |
+
|
| 9 |
+
class VideoCamera(object):
|
| 10 |
+
def __init__(self):
|
| 11 |
+
self.video = cv2.VideoCapture(0)
|
| 12 |
+
# Set camera resolution (smaller size like 320x240)
|
| 13 |
+
# self.video.set(cv2.CAP_PROP_FRAME_WIDTH, 320)
|
| 14 |
+
# self.video.set(cv2.CAP_PROP_FRAME_HEIGHT, 240)
|
| 15 |
+
|
| 16 |
+
def __del__(self):
|
| 17 |
+
self.video.release()
|
| 18 |
+
|
| 19 |
+
def get_frame(self):
|
| 20 |
+
_, fr = self.video.read()
|
| 21 |
+
gray_fr = cv2.cvtColor(fr, cv2.COLOR_BGR2GRAY)
|
| 22 |
+
faces = facec.detectMultiScale(gray_fr, 1.3, 5)
|
| 23 |
+
|
| 24 |
+
for (x, y, w, h) in faces:
|
| 25 |
+
fc = gray_fr[y:y+h, x:x+w]
|
| 26 |
+
|
| 27 |
+
roi = cv2.resize(fc, (48, 48))
|
| 28 |
+
pred = model.predict_emotion(roi[np.newaxis, :, :, np.newaxis])
|
| 29 |
+
|
| 30 |
+
cv2.putText(fr, pred, (x, y), font, 1, (255, 255, 0), 2)
|
| 31 |
+
cv2.rectangle(fr,(x,y),(x+w,y+h),(255,0,0),2)
|
| 32 |
+
|
| 33 |
+
_, jpeg = cv2.imencode('.jpg', fr)
|
| 34 |
+
return jpeg.tobytes()
|
clean_requirements.txt
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
absl-py==2.2.2
|
| 2 |
+
astunparse==1.6.3
|
| 3 |
+
blinker==1.9.0
|
| 4 |
+
cachetools==5.5.2
|
| 5 |
+
certifi==2025.1.31
|
| 6 |
+
charset-normalizer==3.4.1
|
| 7 |
+
click==8.1.8
|
| 8 |
+
clip @ git+https://github.com/openai/CLIP.git@dcba3cb2e2827b402d2701e7e1c7d9fed8a20ef1
|
| 9 |
+
colorama==0.4.6
|
| 10 |
+
contourpy==1.3.1
|
| 11 |
+
cycler==0.12.1
|
| 12 |
+
filelock==3.18.0
|
| 13 |
+
Flask==3.1.0
|
| 14 |
+
flask-cors==5.0.1
|
| 15 |
+
flatbuffers==25.2.10
|
| 16 |
+
fonttools==4.57.0
|
| 17 |
+
fsspec==2025.3.2
|
| 18 |
+
ftfy==6.3.1
|
| 19 |
+
gast==0.4.0
|
| 20 |
+
google-auth==2.38.0
|
| 21 |
+
google-auth-oauthlib==1.0.0
|
| 22 |
+
google-pasta==0.2.0
|
| 23 |
+
grpcio==1.71.0
|
| 24 |
+
h5py==3.13.0
|
| 25 |
+
huggingface-hub==0.30.2
|
| 26 |
+
idna==3.10
|
| 27 |
+
imageio==2.37.0
|
| 28 |
+
itsdangerous==2.2.0
|
| 29 |
+
Jinja2==3.1.6
|
| 30 |
+
joblib==1.4.2
|
| 31 |
+
keras==2.15.0
|
| 32 |
+
kiwisolver==1.4.8
|
| 33 |
+
lazy_loader==0.4
|
| 34 |
+
libclang==18.1.1
|
| 35 |
+
Markdown==3.8
|
| 36 |
+
markdown-it-py==3.0.0
|
| 37 |
+
MarkupSafe==3.0.2
|
| 38 |
+
matplotlib==3.7.2
|
| 39 |
+
mdurl==0.1.2
|
| 40 |
+
ml-dtypes==0.2.0
|
| 41 |
+
mpmath==1.3.0
|
| 42 |
+
namex==0.0.8
|
| 43 |
+
networkx==3.4.2
|
| 44 |
+
numpy==1.26.4
|
| 45 |
+
oauthlib==3.2.2
|
| 46 |
+
opencv-python==4.11.0.86
|
| 47 |
+
opencv-python-headless==4.11.0.86
|
| 48 |
+
opt_einsum==3.4.0
|
| 49 |
+
optree==0.15.0
|
| 50 |
+
packaging==24.2
|
| 51 |
+
pandas==2.2.3
|
| 52 |
+
pillow==11.1.0
|
| 53 |
+
protobuf==4.25.6
|
| 54 |
+
pyasn1==0.6.1
|
| 55 |
+
pyasn1_modules==0.4.2
|
| 56 |
+
Pygments==2.19.1
|
| 57 |
+
pyparsing==3.0.9
|
| 58 |
+
python-dateutil==2.9.0.post0
|
| 59 |
+
pytz==2025.2
|
| 60 |
+
PyYAML==6.0.2
|
| 61 |
+
regex==2024.11.6
|
| 62 |
+
requests==2.31.0
|
| 63 |
+
requests-oauthlib==2.0.0
|
| 64 |
+
rich==14.0.0
|
| 65 |
+
rsa==4.9
|
| 66 |
+
safetensors==0.5.3
|
| 67 |
+
scikit-image==0.25.2
|
| 68 |
+
scikit-learn==1.6.1
|
| 69 |
+
scipy==1.15.2
|
| 70 |
+
seaborn==0.12.2
|
| 71 |
+
six==1.17.0
|
| 72 |
+
sympy==1.13.1
|
| 73 |
+
tensorboard==2.15.2
|
| 74 |
+
tensorboard-data-server==0.7.2
|
| 75 |
+
tensorboard-plugin-wit==1.8.1
|
| 76 |
+
tensorflow==2.15.0
|
| 77 |
+
tensorflow-estimator==2.15.0
|
| 78 |
+
tensorflow-intel==2.15.0
|
| 79 |
+
tensorflow-io-gcs-filesystem==0.31.0
|
| 80 |
+
termcolor==3.0.1
|
| 81 |
+
threadpoolctl==3.6.0
|
| 82 |
+
tifffile==2025.3.30
|
| 83 |
+
tokenizers==0.21.1
|
| 84 |
+
torch==2.1.2
|
| 85 |
+
torchvision==0.16.2
|
| 86 |
+
tqdm==4.67.1
|
| 87 |
+
transformers==4.51.2
|
| 88 |
+
typing_extensions==4.5.0
|
| 89 |
+
tzdata==2025.2
|
| 90 |
+
urllib3==2.4.0
|
| 91 |
+
wcwidth==0.2.13
|
| 92 |
+
Werkzeug==3.1.3
|
| 93 |
+
wrapt==1.14.1
|
cnn.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
import base64
|
| 4 |
+
from tensorflow.keras.models import load_model
|
| 5 |
+
from tensorflow.keras.preprocessing import image
|
| 6 |
+
import logging
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
# Load model once globally
|
| 10 |
+
model = load_model("emotion_detector_model.h5")
|
| 11 |
+
|
| 12 |
+
# Constants
|
| 13 |
+
IMG_HEIGHT = 48
|
| 14 |
+
IMG_WIDTH = 48
|
| 15 |
+
TIME_STEPS = 6
|
| 16 |
+
CHUNK_SIZE = 8 # So width 48 -> 6 chunks of 8
|
| 17 |
+
CLASS_NAMES = ['Angry', 'Disgust', 'Fear', 'Happy', 'Sad', 'Surprise', 'Neutral'] # Change based on your dataset
|
| 18 |
+
|
| 19 |
+
def preprocess_frame(frame):
|
| 20 |
+
# Convert to grayscale and resize
|
| 21 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 22 |
+
resized = cv2.resize(gray, (IMG_WIDTH, IMG_HEIGHT)) # 48x48
|
| 23 |
+
norm_img = resized / 255.0
|
| 24 |
+
|
| 25 |
+
# Split the width into 6 chunks (each 8 pixels wide)
|
| 26 |
+
chunks = [norm_img[:, i*CHUNK_SIZE:(i+1)*CHUNK_SIZE] for i in range(TIME_STEPS)]
|
| 27 |
+
sequence = np.stack([chunk[..., np.newaxis] for chunk in chunks], axis=0) # (6, 48, 8, 1)
|
| 28 |
+
|
| 29 |
+
return sequence[np.newaxis, ...] # shape: (1, 6, 48, 8, 1)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def detect_cnn(image_path):
|
| 33 |
+
# Read image
|
| 34 |
+
logging.debug(f"Reading image from {image_path}")
|
| 35 |
+
frame = cv2.imread(image_path)
|
| 36 |
+
img = image.load_img(image_path, target_size=(48, 48), color_mode='grayscale')
|
| 37 |
+
img_array = image.img_to_array(img)
|
| 38 |
+
img_array = np.expand_dims(img_array, axis=0) / 255.0 # Normalize
|
| 39 |
+
|
| 40 |
+
# ✅ Predict
|
| 41 |
+
prediction = model.predict(img_array)
|
| 42 |
+
predicted_class = np.argmax(prediction)
|
| 43 |
+
|
| 44 |
+
# ✅ Label map (check your training classes to confirm order)
|
| 45 |
+
class_labels = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 46 |
+
|
| 47 |
+
# ✅ Print result
|
| 48 |
+
# Encode image to base64 to send back
|
| 49 |
+
_, buffer = cv2.imencode('.jpg', frame)
|
| 50 |
+
frame_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 51 |
+
emotion=class_labels[predicted_class]
|
| 52 |
+
return emotion,frame_base64
|
cnn_emotion.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# cnn_emotion.py
|
| 2 |
+
import cv2
|
| 3 |
+
import numpy as np
|
| 4 |
+
import base64
|
| 5 |
+
from model import ERModel
|
| 6 |
+
|
| 7 |
+
facec = cv2.CascadeClassifier('haarcascade_frontalface_default.xml')
|
| 8 |
+
model = ERModel("model.json", "model.weights.h5")
|
| 9 |
+
font = cv2.FONT_HERSHEY_SIMPLEX
|
| 10 |
+
|
| 11 |
+
def detect_emotion(image_path):
|
| 12 |
+
fr = cv2.imread(image_path)
|
| 13 |
+
gray_fr = cv2.cvtColor(fr, cv2.COLOR_BGR2GRAY)
|
| 14 |
+
faces = facec.detectMultiScale(gray_fr, 1.3, 5)
|
| 15 |
+
|
| 16 |
+
final_pred = ""
|
| 17 |
+
for (x, y, w, h) in faces:
|
| 18 |
+
fc = gray_fr[y:y+h, x:x+w]
|
| 19 |
+
|
| 20 |
+
roi = cv2.resize(fc, (48, 48))
|
| 21 |
+
pred = model.predict_emotion(roi[np.newaxis, :, :, np.newaxis])
|
| 22 |
+
final_pred = pred
|
| 23 |
+
|
| 24 |
+
cv2.putText(fr, pred, (x, y - 10), font, 0.9, (0, 255, 0), 2)
|
| 25 |
+
cv2.rectangle(fr, (x, y), (x+w, y+h), (0, 255, 0), 2)
|
| 26 |
+
|
| 27 |
+
# Encode image to base64
|
| 28 |
+
_, buffer = cv2.imencode('.jpg', fr)
|
| 29 |
+
frame_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 30 |
+
|
| 31 |
+
return final_pred, frame_base64
|
cnn_lstm.py
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# import cv2
|
| 2 |
+
# import numpy as np
|
| 3 |
+
# from tensorflow.keras.models import load_model
|
| 4 |
+
# from tensorflow.keras.preprocessing.image import img_to_array
|
| 5 |
+
# import base64
|
| 6 |
+
|
| 7 |
+
# # Load the model once
|
| 8 |
+
# model = load_model("emotion_cnn_lstm.h5")
|
| 9 |
+
|
| 10 |
+
# # Emotion labels
|
| 11 |
+
# emotion_labels = ['Angry', 'Disgust', 'Fear', 'Happy', 'Neutral', 'Sad', 'Surprise']
|
| 12 |
+
|
| 13 |
+
# # Load Haar cascade
|
| 14 |
+
# face_classifier = cv2.CascadeClassifier(cv2.data.haarcascades + 'haarcascade_frontalface_default.xml')
|
| 15 |
+
|
| 16 |
+
# def detect_cnn_lstm_emotion(image_path):
|
| 17 |
+
# frame = cv2.imread(image_path)
|
| 18 |
+
# gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 19 |
+
|
| 20 |
+
# faces = face_classifier.detectMultiScale(gray, scaleFactor=1.3, minNeighbors=5)
|
| 21 |
+
# print(f"Detected faces: {len(faces)}")
|
| 22 |
+
|
| 23 |
+
# final_pred = "No face detected"
|
| 24 |
+
|
| 25 |
+
# for (x, y, w, h) in faces:
|
| 26 |
+
# roi_gray = gray[y:y + h, x:x + w]
|
| 27 |
+
# roi_gray = cv2.resize(roi_gray, (48, 48))
|
| 28 |
+
# roi = roi_gray.astype("float") / 255.0
|
| 29 |
+
# roi = img_to_array(roi)
|
| 30 |
+
# roi = np.expand_dims(roi, axis=0)
|
| 31 |
+
|
| 32 |
+
# prediction = model.predict(roi)[0]
|
| 33 |
+
# label = emotion_labels[np.argmax(prediction)]
|
| 34 |
+
# final_pred = label
|
| 35 |
+
|
| 36 |
+
# # Draw results on original image
|
| 37 |
+
# cv2.rectangle(frame, (x, y), (x + w, y + h), (255, 255, 255), 2)
|
| 38 |
+
# cv2.putText(frame, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 255, 0), 2)
|
| 39 |
+
|
| 40 |
+
# # Encode frame to base64
|
| 41 |
+
# _, buffer = cv2.imencode('.jpg', frame)
|
| 42 |
+
# frame_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 43 |
+
|
| 44 |
+
# return final_pred, frame_base64
|
cnn_resnet.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
import base64
|
| 4 |
+
from tensorflow.keras.models import load_model
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
# Load model once globally
|
| 9 |
+
model = load_model("cnn_rnn_model_from_dir.h5")
|
| 10 |
+
|
| 11 |
+
# Constants
|
| 12 |
+
IMG_HEIGHT = 48
|
| 13 |
+
IMG_WIDTH = 48
|
| 14 |
+
TIME_STEPS = 6
|
| 15 |
+
CHUNK_SIZE = 8 # So width 48 -> 6 chunks of 8
|
| 16 |
+
CLASS_NAMES = ['Angry', 'Disgust', 'Fear', 'Happy', 'Sad', 'Surprise', 'Neutral'] # Change based on your dataset
|
| 17 |
+
|
| 18 |
+
def preprocess_frame(frame):
|
| 19 |
+
# Convert to grayscale and resize
|
| 20 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 21 |
+
resized = cv2.resize(gray, (IMG_WIDTH, IMG_HEIGHT)) # 48x48
|
| 22 |
+
norm_img = resized / 255.0
|
| 23 |
+
|
| 24 |
+
# Split the width into 6 chunks (each 8 pixels wide)
|
| 25 |
+
chunks = [norm_img[:, i*CHUNK_SIZE:(i+1)*CHUNK_SIZE] for i in range(TIME_STEPS)]
|
| 26 |
+
sequence = np.stack([chunk[..., np.newaxis] for chunk in chunks], axis=0) # (6, 48, 8, 1)
|
| 27 |
+
|
| 28 |
+
return sequence[np.newaxis, ...] # shape: (1, 6, 48, 8, 1)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def detect_cnn_resnetemotion(image_path):
|
| 32 |
+
# Read image
|
| 33 |
+
logging.debug(f"Reading image from {image_path}")
|
| 34 |
+
frame = cv2.imread(image_path)
|
| 35 |
+
if frame is None:
|
| 36 |
+
logging.error("Failed to load image.")
|
| 37 |
+
raise ValueError("Could not read image")
|
| 38 |
+
|
| 39 |
+
# Preprocess
|
| 40 |
+
input_data = preprocess_frame(frame)
|
| 41 |
+
|
| 42 |
+
# Predict
|
| 43 |
+
prediction = model.predict(input_data)
|
| 44 |
+
predicted_class = int(np.argmax(prediction))
|
| 45 |
+
emotion = CLASS_NAMES[predicted_class] if predicted_class < len(CLASS_NAMES) else str(predicted_class)
|
| 46 |
+
|
| 47 |
+
# Encode image to base64 to send back
|
| 48 |
+
_, buffer = cv2.imencode('.jpg', frame)
|
| 49 |
+
frame_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 50 |
+
|
| 51 |
+
return emotion, frame_base64
|
cnn_rnn_model_from_dir.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:34427f14fcc055ce73aef33799c94ffd003ea3aa0baa784ca14fd540a4806a49
|
| 3 |
+
size 1904456
|
comparison.py
ADDED
|
@@ -0,0 +1,212 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import numpy as np
|
| 3 |
+
import torch
|
| 4 |
+
import cv2
|
| 5 |
+
import matplotlib.pyplot as plt
|
| 6 |
+
from sklearn.svm import SVC
|
| 7 |
+
from sklearn.metrics import classification_report, confusion_matrix
|
| 8 |
+
from sklearn.model_selection import train_test_split
|
| 9 |
+
from skimage.feature import hog
|
| 10 |
+
from tensorflow.keras.models import Sequential
|
| 11 |
+
from tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense
|
| 12 |
+
from tensorflow.keras.utils import to_categorical
|
| 13 |
+
from tensorflow.keras.preprocessing.image import load_img, img_to_array
|
| 14 |
+
from sklearn.preprocessing import LabelEncoder
|
| 15 |
+
from transformers import CLIPProcessor, CLIPModel
|
| 16 |
+
from transformers import ViTForImageClassification, ViTFeatureExtractor
|
| 17 |
+
from skimage.color import rgb2gray
|
| 18 |
+
from skimage.feature import hog
|
| 19 |
+
import numpy as np
|
| 20 |
+
from PIL import Image
|
| 21 |
+
# ----------------------
|
| 22 |
+
# 1. Load and Preprocess Custom Dataset
|
| 23 |
+
# ----------------------
|
| 24 |
+
|
| 25 |
+
def load_custom_dataset(dataset_path, image_size=(48, 48)):
|
| 26 |
+
images = []
|
| 27 |
+
labels = []
|
| 28 |
+
label_map = {}
|
| 29 |
+
label_idx = 0
|
| 30 |
+
|
| 31 |
+
# Loop through the dataset folders (each folder is an emotion class)
|
| 32 |
+
for folder_name in os.listdir(dataset_path):
|
| 33 |
+
folder_path = os.path.join(dataset_path, folder_name)
|
| 34 |
+
|
| 35 |
+
# Ignore non-directories (i.e., files)
|
| 36 |
+
if not os.path.isdir(folder_path):
|
| 37 |
+
continue
|
| 38 |
+
|
| 39 |
+
# Assign an integer label to each emotion
|
| 40 |
+
if folder_name not in label_map:
|
| 41 |
+
label_map[folder_name] = label_idx
|
| 42 |
+
label_idx += 1
|
| 43 |
+
|
| 44 |
+
# Load images and labels
|
| 45 |
+
for img_name in os.listdir(folder_path):
|
| 46 |
+
img_path = os.path.join(folder_path, img_name)
|
| 47 |
+
if img_path.endswith('.jpg') or img_path.endswith('.png'):
|
| 48 |
+
img = load_img(img_path, target_size=image_size, color_mode='grayscale')
|
| 49 |
+
img_array = img_to_array(img)
|
| 50 |
+
images.append(img_array)
|
| 51 |
+
labels.append(label_map[folder_name])
|
| 52 |
+
|
| 53 |
+
# Convert images and labels to numpy arrays
|
| 54 |
+
images = np.array(images, dtype="float32") / 255.0 # Normalize
|
| 55 |
+
labels = np.array(labels)
|
| 56 |
+
|
| 57 |
+
return images, labels, label_map
|
| 58 |
+
|
| 59 |
+
dataset_path = 'train' # Replace with the path to your dataset
|
| 60 |
+
x, y, label_map = load_custom_dataset(dataset_path)
|
| 61 |
+
|
| 62 |
+
# Print class mapping for reference
|
| 63 |
+
print("Class labels:", label_map)
|
| 64 |
+
|
| 65 |
+
# ----------------------
|
| 66 |
+
# 2. HOG + SVM (Classical ML)
|
| 67 |
+
# ----------------------
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def extract_hog_features(images):
|
| 72 |
+
# Check if the image is grayscale (2D) or RGB (3D)
|
| 73 |
+
# If grayscale (shape (H, W, 1)), squeeze it to (H, W) for processing
|
| 74 |
+
grayscale_images = [img.squeeze() if len(img.shape) == 3 else img for img in images]
|
| 75 |
+
|
| 76 |
+
# Extract HOG features from grayscale images
|
| 77 |
+
return np.array([hog(img, pixels_per_cell=(8, 8), cells_per_block=(2, 2)) for img in grayscale_images])
|
| 78 |
+
|
| 79 |
+
# Assuming your images are already in grayscale, so you can skip rgb2gray for grayscale images.
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# def extract_hog_features(images):
|
| 84 |
+
# return np.array([hog(img, pixels_per_cell=(8, 8), cells_per_block=(2, 2)) for img in images])
|
| 85 |
+
|
| 86 |
+
print("[INFO] Extracting HOG features...")
|
| 87 |
+
x_hog = extract_hog_features(x)
|
| 88 |
+
x_train_hog, x_test_hog, y_train_hog, y_test_hog = train_test_split(x_hog, y, test_size=0.2, random_state=42)
|
| 89 |
+
|
| 90 |
+
print("[INFO] Training SVM classifier...")
|
| 91 |
+
svm = SVC(kernel='linear')
|
| 92 |
+
svm.fit(x_train_hog, y_train_hog)
|
| 93 |
+
y_pred_svm = svm.predict(x_test_hog)
|
| 94 |
+
|
| 95 |
+
print("\n[RESULTS] SVM Classification Report")
|
| 96 |
+
print(classification_report(y_test_hog, y_pred_svm))
|
| 97 |
+
|
| 98 |
+
# ----------------------
|
| 99 |
+
# 3. CNN (Deep Learning)
|
| 100 |
+
# ----------------------
|
| 101 |
+
|
| 102 |
+
y_cnn = to_categorical(y, num_classes=len(label_map))
|
| 103 |
+
x_train_cnn, x_test_cnn, y_train_cnn, y_test_cnn = train_test_split(x, y_cnn, test_size=0.2, random_state=42)
|
| 104 |
+
|
| 105 |
+
print("[INFO] Building CNN model...")
|
| 106 |
+
cnn = Sequential([
|
| 107 |
+
Conv2D(32, (3,3), activation='relu', input_shape=(48, 48, 1)),
|
| 108 |
+
MaxPooling2D(2,2),
|
| 109 |
+
Conv2D(64, (3,3), activation='relu'),
|
| 110 |
+
MaxPooling2D(2,2),
|
| 111 |
+
Flatten(),
|
| 112 |
+
Dense(128, activation='relu'),
|
| 113 |
+
Dense(len(label_map), activation='softmax')
|
| 114 |
+
])
|
| 115 |
+
|
| 116 |
+
cnn.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
|
| 117 |
+
cnn.fit(x_train_cnn, y_train_cnn, epochs=3, batch_size=64, validation_split=0.1)
|
| 118 |
+
|
| 119 |
+
print("[INFO] Evaluating CNN...")
|
| 120 |
+
loss, acc = cnn.evaluate(x_test_cnn, y_test_cnn)
|
| 121 |
+
print(f"CNN Accuracy: {acc * 100:.2f}%")
|
| 122 |
+
|
| 123 |
+
# ----------------------
|
| 124 |
+
# 4. CLIP (Vision-Language) Comparison
|
| 125 |
+
# ----------------------
|
| 126 |
+
|
| 127 |
+
# Load the CLIP model and processor from Huggingface
|
| 128 |
+
clip_model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
|
| 129 |
+
clip_processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
| 130 |
+
|
| 131 |
+
# Example usage of CLIP for image processing
|
| 132 |
+
# image_input = torch.tensor(x[0]).unsqueeze(0) # Convert to tensor for CLIP input, add batch dimension
|
| 133 |
+
# text_input = ["Emotion description of the image"] # Provide a text description for comparison
|
| 134 |
+
|
| 135 |
+
# # Process the inputs with the CLIP processor
|
| 136 |
+
# inputs = clip_processor(text=text_input, images=image_input, return_tensors="pt", padding=True)
|
| 137 |
+
from PIL import Image
|
| 138 |
+
|
| 139 |
+
# Convert your NumPy grayscale image to a 3-channel RGB PIL image and resize it
|
| 140 |
+
gray_img = (x[0] * 255).astype(np.uint8).squeeze() # shape: (48, 48)
|
| 141 |
+
rgb_img = np.stack([gray_img]*3, axis=-1) # Convert to RGB shape: (48, 48, 3)
|
| 142 |
+
pil_img = Image.fromarray(rgb_img).resize((224, 224)) # Resize for CLIP input
|
| 143 |
+
|
| 144 |
+
# Prepare text input
|
| 145 |
+
text_input = ["Emotion description of the image"]
|
| 146 |
+
|
| 147 |
+
# Process using CLIPProcessor
|
| 148 |
+
inputs = clip_processor(text=text_input, images=pil_img, return_tensors="pt", padding=True)
|
| 149 |
+
# Get model predictions (outputs)
|
| 150 |
+
outputs = clip_model(**inputs)
|
| 151 |
+
|
| 152 |
+
# CLIP similarity comparison (text vs image)
|
| 153 |
+
logits_per_image = outputs.logits_per_image # similarity score
|
| 154 |
+
logits_per_text = outputs.logits_per_text # similarity score
|
| 155 |
+
|
| 156 |
+
print("\n[RESULTS] CLIP Similarity Scores")
|
| 157 |
+
print(f"Logits per image: {logits_per_image}")
|
| 158 |
+
print(f"Logits per text: {logits_per_text}")
|
| 159 |
+
|
| 160 |
+
# ----------------------
|
| 161 |
+
# 5. Vision Transformer (ViT) Comparison
|
| 162 |
+
# ----------------------
|
| 163 |
+
|
| 164 |
+
# Load ViT model and feature extractor for image classification
|
| 165 |
+
vit_model = ViTForImageClassification.from_pretrained("google/vit-base-patch16-224-in21k")
|
| 166 |
+
vit_feature_extractor = ViTFeatureExtractor.from_pretrained("google/vit-base-patch16-224-in21k")
|
| 167 |
+
|
| 168 |
+
# Process image and get predictions
|
| 169 |
+
inputs_vit = vit_feature_extractor(x[0], return_tensors="pt")
|
| 170 |
+
outputs_vit = vit_model(**inputs_vit)
|
| 171 |
+
|
| 172 |
+
print("\n[RESULTS] ViT Classification Scores")
|
| 173 |
+
print(f"ViT logits: {outputs_vit.logits}")
|
| 174 |
+
|
| 175 |
+
# ----------------------
|
| 176 |
+
# 6. Comparison Results Visualization
|
| 177 |
+
# ----------------------
|
| 178 |
+
|
| 179 |
+
# Prepare to visualize the comparisons
|
| 180 |
+
plt.figure(figsize=(12, 6))
|
| 181 |
+
|
| 182 |
+
# Plot Sample Image
|
| 183 |
+
plt.subplot(131)
|
| 184 |
+
plt.title("Sample Image")
|
| 185 |
+
plt.imshow(x[0], cmap='gray')
|
| 186 |
+
|
| 187 |
+
# Plot HOG Feature Visualization
|
| 188 |
+
plt.subplot(132)
|
| 189 |
+
plt.title("HOG Feature Visualization")
|
| 190 |
+
hog_img = hog(x[0], visualize=True)[1]
|
| 191 |
+
plt.imshow(hog_img, cmap='gray')
|
| 192 |
+
|
| 193 |
+
# Placeholder for CNN's evaluation
|
| 194 |
+
plt.subplot(133)
|
| 195 |
+
plt.title("CNN Evaluation")
|
| 196 |
+
cnn_img = cnn.predict(np.expand_dims(x[0], axis=0))
|
| 197 |
+
plt.imshow(cnn_img[0], cmap='gray')
|
| 198 |
+
|
| 199 |
+
plt.show()
|
| 200 |
+
|
| 201 |
+
# ----------------------
|
| 202 |
+
# 7. Final Model Comparison Summary
|
| 203 |
+
# ----------------------
|
| 204 |
+
|
| 205 |
+
# You can compare the results side by side in a table or any custom visualization
|
| 206 |
+
print("\n[COMPARISON SUMMARY]")
|
| 207 |
+
print(f"SVM (HOG) Accuracy: {svm.score(x_test_hog, y_test_hog) * 100:.2f}%")
|
| 208 |
+
print(f"CNN Accuracy: {acc * 100:.2f}%")
|
| 209 |
+
|
| 210 |
+
# CLIP and ViT don't have a direct "accuracy" metric, but you can report similarity scores for CLIP or logits for ViT.
|
| 211 |
+
print(f"CLIP Similarity Score: {logits_per_image}")
|
| 212 |
+
print(f"ViT Logits: {outputs_vit.logits}")
|
comparison2.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# import os
|
| 2 |
+
# import joblib
|
| 3 |
+
# import json
|
| 4 |
+
# import numpy as np
|
| 5 |
+
# import tensorflow as tf
|
| 6 |
+
# from tqdm import tqdm
|
| 7 |
+
# from skimage.io import imread
|
| 8 |
+
# from skimage.transform import resize
|
| 9 |
+
# from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix
|
| 10 |
+
# from tensorflow.keras.models import model_from_json
|
| 11 |
+
# from transformers import CLIPProcessor, CLIPModel
|
| 12 |
+
# from torchvision import transforms
|
| 13 |
+
# from PIL import Image
|
| 14 |
+
# from sklearn.model_selection import train_test_split
|
| 15 |
+
|
| 16 |
+
# # ========== Constants ==========
|
| 17 |
+
# IMG_SIZE = 48
|
| 18 |
+
# DATASET_PATH = "train"
|
| 19 |
+
# EMOTIONS = ['angry', 'disgust', 'fear', 'happy', 'sad', 'surprise', 'neutral']
|
| 20 |
+
# MODEL_PATH = '' # Update with the correct path to your model files
|
| 21 |
+
|
| 22 |
+
# # ========== Feature Extraction ==========
|
| 23 |
+
# def extract_hog(img):
|
| 24 |
+
# from skimage.feature import hog
|
| 25 |
+
# return hog(img, pixels_per_cell=(8, 8), cells_per_block=(2, 2), feature_vector=True)
|
| 26 |
+
|
| 27 |
+
# def extract_lbp(img):
|
| 28 |
+
# from skimage.feature import local_binary_pattern
|
| 29 |
+
# lbp = local_binary_pattern(img, P=8, R=1, method="uniform")
|
| 30 |
+
# (hist, _) = np.histogram(lbp.ravel(), bins=np.arange(0, 10), range=(0, 9))
|
| 31 |
+
# hist = hist.astype("float")
|
| 32 |
+
# hist /= (hist.sum() + 1e-7)
|
| 33 |
+
# return hist
|
| 34 |
+
|
| 35 |
+
# def extract_gabor(img):
|
| 36 |
+
# import cv2
|
| 37 |
+
# filters = []
|
| 38 |
+
# ksize = 31
|
| 39 |
+
# for theta in np.arange(0, np.pi, np.pi / 4):
|
| 40 |
+
# kernel = cv2.getGaborKernel((ksize, ksize), 4.0, theta, 10.0, 0.5, 0, ktype=cv2.CV_32F)
|
| 41 |
+
# filters.append(kernel)
|
| 42 |
+
# feats = [np.mean(cv2.filter2D(img, cv2.CV_8UC3, k)) for k in filters]
|
| 43 |
+
# return feats
|
| 44 |
+
|
| 45 |
+
# def extract_features(img):
|
| 46 |
+
# features = []
|
| 47 |
+
# features.extend(extract_hog(img))
|
| 48 |
+
# features.extend(extract_lbp(img))
|
| 49 |
+
# features.extend(extract_gabor(img))
|
| 50 |
+
# return features
|
| 51 |
+
|
| 52 |
+
# # ========== Dataset Loader ==========
|
| 53 |
+
# def load_dataset_features():
|
| 54 |
+
# X, y = [], []
|
| 55 |
+
# for label in EMOTIONS:
|
| 56 |
+
# folder = os.path.join(DATASET_PATH, label)
|
| 57 |
+
# if not os.path.exists(folder): continue
|
| 58 |
+
# for file in tqdm(os.listdir(folder), desc=f"Extracting {label}"):
|
| 59 |
+
# path = os.path.join(folder, file)
|
| 60 |
+
# try:
|
| 61 |
+
# img = imread(path, as_gray=True)
|
| 62 |
+
# img = resize(img, (IMG_SIZE, IMG_SIZE), anti_aliasing=True)
|
| 63 |
+
# feat = extract_features(img)
|
| 64 |
+
# X.append(feat)
|
| 65 |
+
# y.append(EMOTIONS.index(label))
|
| 66 |
+
# except Exception as e:
|
| 67 |
+
# print(f"[WARN] Skipped {file}: {e}")
|
| 68 |
+
# return np.array(X), np.array(y)
|
| 69 |
+
|
| 70 |
+
# def load_images():
|
| 71 |
+
# images, labels = [], []
|
| 72 |
+
# for label in EMOTIONS:
|
| 73 |
+
# folder = os.path.join(DATASET_PATH, label)
|
| 74 |
+
# if not os.path.exists(folder): continue
|
| 75 |
+
# for file in os.listdir(folder):
|
| 76 |
+
# path = os.path.join(folder, file)
|
| 77 |
+
# try:
|
| 78 |
+
# img = imread(path, as_gray=False)
|
| 79 |
+
# img = resize(img, (IMG_SIZE, IMG_SIZE), anti_aliasing=True)
|
| 80 |
+
# images.append(img)
|
| 81 |
+
# labels.append(EMOTIONS.index(label))
|
| 82 |
+
# except:
|
| 83 |
+
# continue
|
| 84 |
+
# return np.array(images), np.array(labels)
|
| 85 |
+
|
| 86 |
+
# # ========== Evaluation Metrics ==========
|
| 87 |
+
# def evaluate_model(y_true, y_pred, model_name):
|
| 88 |
+
# print(f"\n[RESULTS] {model_name}")
|
| 89 |
+
# print("Accuracy:", accuracy_score(y_true, y_pred))
|
| 90 |
+
# print("Precision:", precision_score(y_true, y_pred, average='weighted'))
|
| 91 |
+
# print("Recall:", recall_score(y_true, y_pred, average='weighted'))
|
| 92 |
+
# print("F1 Score:", f1_score(y_true, y_pred, average='weighted'))
|
| 93 |
+
# print("Confusion Matrix:\n", confusion_matrix(y_true, y_pred))
|
| 94 |
+
|
| 95 |
+
# # ========== Classical Models ==========
|
| 96 |
+
# # def evaluate_classical_models():
|
| 97 |
+
# # X_test, y_test = load_dataset_features()
|
| 98 |
+
# # for model_file in ["k-nn_model.joblib", "logistic_regression_model.joblib", "random_forest_model.joblib", "svm_model.joblib"]:
|
| 99 |
+
# # model = joblib.load(model_file)
|
| 100 |
+
# # y_pred = model.predict(X_test)
|
| 101 |
+
# # evaluate_model(y_test, y_pred, model_file)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
# def evaluate_classical_models():
|
| 105 |
+
# print("\n[INFO] Evaluating classical ML models...\n")
|
| 106 |
+
# X, y = load_dataset_features()
|
| 107 |
+
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 108 |
+
|
| 109 |
+
# model_files = {
|
| 110 |
+
# 'KNN': 'k-nn_model.joblib',
|
| 111 |
+
# 'Logistic Regression': 'logistic_regression_model.joblib',
|
| 112 |
+
# 'Random Forest': 'random_forest_model.joblib',
|
| 113 |
+
# 'SVM': 'svm_model.joblib',
|
| 114 |
+
# }
|
| 115 |
+
|
| 116 |
+
# for name, file in model_files.items():
|
| 117 |
+
# print(f"\n--- {name} ---")
|
| 118 |
+
# model_path = os.path.join(MODEL_PATH, file)
|
| 119 |
+
# model = joblib.load(model_path)
|
| 120 |
+
|
| 121 |
+
# expected_input_size = model.n_features_in_
|
| 122 |
+
# if X_test.shape[1] != expected_input_size:
|
| 123 |
+
# print(f"[WARNING] Feature size mismatch for {name}: "
|
| 124 |
+
# f"Expected {expected_input_size}, Got {X_test.shape[1]}. Skipping...")
|
| 125 |
+
# continue
|
| 126 |
+
|
| 127 |
+
# y_pred = model.predict(X_test)
|
| 128 |
+
# acc = accuracy_score(y_test, y_pred)
|
| 129 |
+
# prec = precision_score(y_test, y_pred, average='weighted', zero_division=0)
|
| 130 |
+
# rec = recall_score(y_test, y_pred, average='weighted', zero_division=0)
|
| 131 |
+
# f1 = f1_score(y_test, y_pred, average='weighted', zero_division=0)
|
| 132 |
+
|
| 133 |
+
# print(f"Accuracy: {acc:.4f}")
|
| 134 |
+
# print(f"Precision: {prec:.4f}")
|
| 135 |
+
# print(f"Recall: {rec:.4f}")
|
| 136 |
+
# print(f"F1 Score: {f1:.4f}")
|
| 137 |
+
# print("Confusion Matrix:")
|
| 138 |
+
# print(confusion_matrix(y_test, y_pred))
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
# # ========== CNN/RNN Models ==========
|
| 142 |
+
# def evaluate_keras_model(model_path, X_test, y_test, model_name):
|
| 143 |
+
# model = tf.keras.models.load_model(model_path)
|
| 144 |
+
# y_pred = np.argmax(model.predict(X_test), axis=1)
|
| 145 |
+
# evaluate_model(y_test, y_pred, model_name)
|
| 146 |
+
|
| 147 |
+
# def evaluate_json_model(json_path, weights_path, X_test, y_test, model_name):
|
| 148 |
+
# with open(json_path, 'r') as f:
|
| 149 |
+
# model = model_from_json(f.read())
|
| 150 |
+
# model.load_weights(weights_path)
|
| 151 |
+
# y_pred = np.argmax(model.predict(X_test), axis=1)
|
| 152 |
+
# evaluate_model(y_test, y_pred, model_name)
|
| 153 |
+
|
| 154 |
+
# # ========== ViT/CLIP Model ==========
|
| 155 |
+
# def evaluate_clip_model():
|
| 156 |
+
# processor = CLIPProcessor.from_pretrained("openai/clip-vit-base-patch32")
|
| 157 |
+
# model = CLIPModel.from_pretrained("openai/clip-vit-base-patch32")
|
| 158 |
+
# X_test, y_test = load_images()
|
| 159 |
+
# y_pred = []
|
| 160 |
+
|
| 161 |
+
# for i in tqdm(range(len(X_test))):
|
| 162 |
+
# img = Image.fromarray((X_test[i] * 255).astype(np.uint8))
|
| 163 |
+
# text = [f"a face showing {emotion} emotion" for emotion in EMOTIONS]
|
| 164 |
+
# inputs = processor(text=text, images=img, return_tensors="pt", padding=True)
|
| 165 |
+
# outputs = model(**inputs)
|
| 166 |
+
# logits_per_image = outputs.logits_per_image
|
| 167 |
+
# pred = logits_per_image.argmax().item()
|
| 168 |
+
# y_pred.append(pred)
|
| 169 |
+
|
| 170 |
+
# evaluate_model(y_test, y_pred, "ViT-B/32 (CLIP)")
|
| 171 |
+
|
| 172 |
+
|
| 173 |
+
|
| 174 |
+
# # ========== Run All ==========
|
| 175 |
+
# if __name__ == '__main__':
|
| 176 |
+
# # evaluate_classical_models()
|
| 177 |
+
|
| 178 |
+
# X_raw, y_raw = load_images()
|
| 179 |
+
# X_raw = X_raw.reshape(-1, IMG_SIZE, IMG_SIZE)
|
| 180 |
+
|
| 181 |
+
# # X_raw = X_raw.reshape(-1, IMG_SIZE, IMG_SIZE, 3)
|
| 182 |
+
|
| 183 |
+
# evaluate_keras_model("emotion_detector_model.h5", X_raw, y_raw, "CNN Emotion Model")
|
| 184 |
+
# evaluate_keras_model("cnn_rnn_model_from_dir.h5", X_raw, y_raw, "CNN + RNN Emotion Model")
|
| 185 |
+
# evaluate_json_model("model_cleaned.json", "model.weights.h5", X_raw, y_raw, "Custom JSON + Weights Model")
|
| 186 |
+
|
| 187 |
+
# # evaluate_clip_model()
|
emotion_detector_model.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e86e437571b9a100392f8911e913ce395a8f1dad3346dd3ed0e2ab556b47317d
|
| 3 |
+
size 10114720
|
haarcascade_frontalface_default.xml
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
help.txt
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
python3 -m venv venv
|
| 2 |
+
source venv/bin/activate
|
| 3 |
+
|
| 4 |
+
# Install dependencies
|
| 5 |
+
pip install --upgrade pip
|
| 6 |
+
pip install -r requirements.txt
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
python app.py
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
flask run --host=0.0.0.0 --port=5000
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
ngrok config add-authtoken 2Pz1X9nABC1234567890abcdefg
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
Authtoken saved to configuration file: /home/ubuntu/.config/ngrok/ngrok.yml
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
ngrok http 7860
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
python app.py --host=0.0.0.0 --port=7860
|
| 28 |
+
|
| 29 |
+
sudo apt install tmux
|
| 30 |
+
tmux new -s emotionapp
|
| 31 |
+
python app.py --host=0.0.0.0 --port=7860
|
| 32 |
+
ngrok http 7860
|
| 33 |
+
tmux attach -t emotionappNGORK
|
| 34 |
+
|
index.html
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection via ViT</title>
|
| 5 |
+
</head>
|
| 6 |
+
<body>
|
| 7 |
+
<h2>Webcam Emotion Detector</h2>
|
| 8 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 9 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 10 |
+
<p id="emotion"></p>
|
| 11 |
+
<script>
|
| 12 |
+
const video = document.getElementById("video");
|
| 13 |
+
const canvas = document.getElementById("canvas");
|
| 14 |
+
const context = canvas.getContext("2d");
|
| 15 |
+
const emotionText = document.getElementById("emotion");
|
| 16 |
+
|
| 17 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 18 |
+
video.srcObject = stream;
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
setInterval(() => {
|
| 22 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 23 |
+
canvas.toBlob(blob => {
|
| 24 |
+
const formData = new FormData();
|
| 25 |
+
formData.append("frame", blob, "frame.jpg");
|
| 26 |
+
|
| 27 |
+
fetch("http://127.0.0.1:7860/analyze", {
|
| 28 |
+
method: "POST",
|
| 29 |
+
body: formData,
|
| 30 |
+
})
|
| 31 |
+
.then(response => response.json())
|
| 32 |
+
.then(data => {
|
| 33 |
+
emotionText.textContent = "Detected Emotion: " + data.emotion;
|
| 34 |
+
});
|
| 35 |
+
}, "image/jpeg");
|
| 36 |
+
}, 3000); // every 3 seconds
|
| 37 |
+
</script>
|
| 38 |
+
</body>
|
| 39 |
+
</html>
|
k-nn_model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:385097ff0871d5669fcdea19e79296c722b800f8dc0f79cf531b75d959c2c8e3
|
| 3 |
+
size 182810196
|
knn.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
import base64
|
| 4 |
+
from tensorflow.keras.preprocessing import image
|
| 5 |
+
import logging
|
| 6 |
+
import joblib
|
| 7 |
+
|
| 8 |
+
# Load KNN model globally
|
| 9 |
+
knn_model = joblib.load('k-nn_model.joblib')
|
| 10 |
+
|
| 11 |
+
# Emotion classes
|
| 12 |
+
class_labels = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 13 |
+
|
| 14 |
+
def detect_knn(image_path):
|
| 15 |
+
frame = cv2.imread(image_path)
|
| 16 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 17 |
+
resized = cv2.resize(gray, (48, 48)) # 48x48
|
| 18 |
+
norm_img = resized / 255.0
|
| 19 |
+
|
| 20 |
+
# Feature extraction similar to training: horizontal chunks
|
| 21 |
+
chunks = [norm_img[:, i*8:(i+1)*8] for i in range(6)] # 6 chunks of 8px
|
| 22 |
+
sequence = np.stack([chunk.flatten() for chunk in chunks]) # (6, 384)
|
| 23 |
+
features = sequence.flatten() # (2304,)
|
| 24 |
+
features = features[:994] # match training shape
|
| 25 |
+
|
| 26 |
+
features = features.reshape(1, -1)
|
| 27 |
+
|
| 28 |
+
prediction = knn_model.predict(features)[0]
|
| 29 |
+
class_labels = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 30 |
+
emotion = class_labels[prediction]
|
| 31 |
+
|
| 32 |
+
_, buffer = cv2.imencode('.jpg', frame)
|
| 33 |
+
frame_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 34 |
+
|
| 35 |
+
return emotion, frame_base64
|
logistic_regression_model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:1ec9febd0fbd6a44614a85172d55d18ee0a45a7f0e2962475afb4e1dc6fdbf8d
|
| 3 |
+
size 56623
|
logisticregression.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
import base64
|
| 4 |
+
from tensorflow.keras.preprocessing import image
|
| 5 |
+
import logging
|
| 6 |
+
import joblib
|
| 7 |
+
|
| 8 |
+
# Load KNN model globally
|
| 9 |
+
knn_model = joblib.load('logistic_regression_model.joblib')
|
| 10 |
+
|
| 11 |
+
# Emotion classes
|
| 12 |
+
class_labels = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 13 |
+
|
| 14 |
+
def detect_lr(image_path):
|
| 15 |
+
frame = cv2.imread(image_path)
|
| 16 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 17 |
+
resized = cv2.resize(gray, (48, 48)) # 48x48
|
| 18 |
+
norm_img = resized / 255.0
|
| 19 |
+
|
| 20 |
+
# Feature extraction similar to training: horizontal chunks
|
| 21 |
+
chunks = [norm_img[:, i*8:(i+1)*8] for i in range(6)] # 6 chunks of 8px
|
| 22 |
+
sequence = np.stack([chunk.flatten() for chunk in chunks]) # (6, 384)
|
| 23 |
+
features = sequence.flatten() # (2304,)
|
| 24 |
+
features = features[:994] # match training shape
|
| 25 |
+
|
| 26 |
+
features = features.reshape(1, -1)
|
| 27 |
+
|
| 28 |
+
prediction = knn_model.predict(features)[0]
|
| 29 |
+
class_labels = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 30 |
+
emotion = class_labels[prediction]
|
| 31 |
+
|
| 32 |
+
_, buffer = cv2.imencode('.jpg', frame)
|
| 33 |
+
frame_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 34 |
+
|
| 35 |
+
return emotion, frame_base64
|
model.json
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
{"module": "keras", "class_name": "Sequential", "config": {"name": "sequential_12", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "layers": [{"module": "keras.layers", "class_name": "InputLayer", "config": {"batch_shape": [null, 48, 48, 1], "dtype": "float32", "sparse": false, "name": "input_layer_12"}, "registered_name": null}, {"module": "keras.layers", "class_name": "Conv2D", "config": {"name": "conv2d_48", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "filters": 64, "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 48, 48, 1]}}, {"module": "keras.layers", "class_name": "BatchNormalization", "config": {"name": "batch_normalization_72", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "axis": -1, "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "gamma_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "moving_mean_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "moving_variance_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null, "synchronized": false}, "registered_name": null, "build_config": {"input_shape": [null, 48, 48, 64]}}, {"module": "keras.layers", "class_name": "Activation", "config": {"name": "activation_72", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "activation": "relu"}, "registered_name": null, "build_config": {"input_shape": [null, 48, 48, 64]}}, {"module": "keras.layers", "class_name": "MaxPooling2D", "config": {"name": "max_pooling2d_48", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}, "registered_name": null, "build_config": {"input_shape": [null, 48, 48, 64]}}, {"module": "keras.layers", "class_name": "Dropout", "config": {"name": "dropout_72", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "rate": 0.25, "seed": null, "noise_shape": null}, "registered_name": null, "build_config": {"input_shape": [null, 24, 24, 64]}}, {"module": "keras.layers", "class_name": "Conv2D", "config": {"name": "conv2d_49", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "filters": 128, "kernel_size": [5, 5], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 24, 24, 64]}}, {"module": "keras.layers", "class_name": "BatchNormalization", "config": {"name": "batch_normalization_73", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "axis": -1, "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "gamma_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "moving_mean_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "moving_variance_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null, "synchronized": false}, "registered_name": null, "build_config": {"input_shape": [null, 24, 24, 128]}}, {"module": "keras.layers", "class_name": "Activation", "config": {"name": "activation_73", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "activation": "relu"}, "registered_name": null, "build_config": {"input_shape": [null, 24, 24, 128]}}, {"module": "keras.layers", "class_name": "MaxPooling2D", "config": {"name": "max_pooling2d_49", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}, "registered_name": null, "build_config": {"input_shape": [null, 24, 24, 128]}}, {"module": "keras.layers", "class_name": "Dropout", "config": {"name": "dropout_73", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "rate": 0.25, "seed": null, "noise_shape": null}, "registered_name": null, "build_config": {"input_shape": [null, 12, 12, 128]}}, {"module": "keras.layers", "class_name": "Conv2D", "config": {"name": "conv2d_50", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "filters": 256, "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 12, 12, 128]}}, {"module": "keras.layers", "class_name": "BatchNormalization", "config": {"name": "batch_normalization_74", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "axis": -1, "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "gamma_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "moving_mean_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "moving_variance_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null, "synchronized": false}, "registered_name": null, "build_config": {"input_shape": [null, 12, 12, 256]}}, {"module": "keras.layers", "class_name": "Activation", "config": {"name": "activation_74", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "activation": "relu"}, "registered_name": null, "build_config": {"input_shape": [null, 12, 12, 256]}}, {"module": "keras.layers", "class_name": "MaxPooling2D", "config": {"name": "max_pooling2d_50", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}, "registered_name": null, "build_config": {"input_shape": [null, 12, 12, 256]}}, {"module": "keras.layers", "class_name": "Dropout", "config": {"name": "dropout_74", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "rate": 0.25, "seed": null, "noise_shape": null}, "registered_name": null, "build_config": {"input_shape": [null, 6, 6, 256]}}, {"module": "keras.layers", "class_name": "Conv2D", "config": {"name": "conv2d_51", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "filters": 512, "kernel_size": [3, 3], "strides": [1, 1], "padding": "same", "data_format": "channels_last", "dilation_rate": [1, 1], "groups": 1, "activation": "linear", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "activity_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 6, 6, 256]}}, {"module": "keras.layers", "class_name": "BatchNormalization", "config": {"name": "batch_normalization_75", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "axis": -1, "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "gamma_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "moving_mean_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "moving_variance_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null, "synchronized": false}, "registered_name": null, "build_config": {"input_shape": [null, 6, 6, 512]}}, {"module": "keras.layers", "class_name": "Activation", "config": {"name": "activation_75", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "activation": "relu"}, "registered_name": null, "build_config": {"input_shape": [null, 6, 6, 512]}}, {"module": "keras.layers", "class_name": "MaxPooling2D", "config": {"name": "max_pooling2d_51", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "pool_size": [2, 2], "padding": "valid", "strides": [2, 2], "data_format": "channels_last"}, "registered_name": null, "build_config": {"input_shape": [null, 6, 6, 512]}}, {"module": "keras.layers", "class_name": "Dropout", "config": {"name": "dropout_75", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "rate": 0.25, "seed": null, "noise_shape": null}, "registered_name": null, "build_config": {"input_shape": [null, 3, 3, 512]}}, {"module": "keras.layers", "class_name": "Flatten", "config": {"name": "flatten_12", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "data_format": "channels_last"}, "registered_name": null, "build_config": {"input_shape": [null, 3, 3, 512]}}, {"module": "keras.layers", "class_name": "Dense", "config": {"name": "dense_36", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "units": 256, "activation": "linear", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 4608]}}, {"module": "keras.layers", "class_name": "BatchNormalization", "config": {"name": "batch_normalization_76", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "axis": -1, "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "gamma_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "moving_mean_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "moving_variance_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null, "synchronized": false}, "registered_name": null, "build_config": {"input_shape": [null, 256]}}, {"module": "keras.layers", "class_name": "Activation", "config": {"name": "activation_76", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "activation": "relu"}, "registered_name": null, "build_config": {"input_shape": [null, 256]}}, {"module": "keras.layers", "class_name": "Dropout", "config": {"name": "dropout_76", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "rate": 0.25, "seed": null, "noise_shape": null}, "registered_name": null, "build_config": {"input_shape": [null, 256]}}, {"module": "keras.layers", "class_name": "Dense", "config": {"name": "dense_37", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "units": 512, "activation": "linear", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 256]}}, {"module": "keras.layers", "class_name": "BatchNormalization", "config": {"name": "batch_normalization_77", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "axis": -1, "momentum": 0.99, "epsilon": 0.001, "center": true, "scale": true, "beta_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "gamma_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "moving_mean_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "moving_variance_initializer": {"module": "keras.initializers", "class_name": "Ones", "config": {}, "registered_name": null}, "beta_regularizer": null, "gamma_regularizer": null, "beta_constraint": null, "gamma_constraint": null, "synchronized": false}, "registered_name": null, "build_config": {"input_shape": [null, 512]}}, {"module": "keras.layers", "class_name": "Activation", "config": {"name": "activation_77", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "activation": "relu"}, "registered_name": null, "build_config": {"input_shape": [null, 512]}}, {"module": "keras.layers", "class_name": "Dropout", "config": {"name": "dropout_77", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "rate": 0.25, "seed": null, "noise_shape": null}, "registered_name": null, "build_config": {"input_shape": [null, 512]}}, {"module": "keras.layers", "class_name": "Dense", "config": {"name": "dense_38", "trainable": true, "dtype": {"module": "keras", "class_name": "DTypePolicy", "config": {"name": "float32"}, "registered_name": null}, "units": 7, "activation": "softmax", "use_bias": true, "kernel_initializer": {"module": "keras.initializers", "class_name": "GlorotUniform", "config": {"seed": null}, "registered_name": null}, "bias_initializer": {"module": "keras.initializers", "class_name": "Zeros", "config": {}, "registered_name": null}, "kernel_regularizer": null, "bias_regularizer": null, "kernel_constraint": null, "bias_constraint": null}, "registered_name": null, "build_config": {"input_shape": [null, 512]}}], "build_input_shape": [null, 48, 48, 1]}, "registered_name": null, "build_config": {"input_shape": [null, 48, 48, 1]}, "compile_config": {"optimizer": {"module": "keras.optimizers", "class_name": "Adam", "config": {"name": "adam", "learning_rate": 9.999999747378752e-06, "weight_decay": null, "clipnorm": null, "global_clipnorm": null, "clipvalue": null, "use_ema": false, "ema_momentum": 0.99, "ema_overwrite_frequency": null, "loss_scale_factor": null, "gradient_accumulation_steps": null, "beta_1": 0.9, "beta_2": 0.999, "epsilon": 1e-07, "amsgrad": false}, "registered_name": null}, "loss": "sparse_categorical_crossentropy", "loss_weights": null, "metrics": ["accuracy"], "weighted_metrics": null, "run_eagerly": false, "steps_per_execution": 1, "jit_compile": false}}
|
model.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from tensorflow.keras.models import model_from_json
|
| 2 |
+
from tensorflow.python.keras.backend import set_session
|
| 3 |
+
import numpy as np
|
| 4 |
+
|
| 5 |
+
import tensorflow as tf
|
| 6 |
+
|
| 7 |
+
config = tf.compat.v1.ConfigProto()
|
| 8 |
+
config.gpu_options.per_process_gpu_memory_fraction = 0.15
|
| 9 |
+
session = tf.compat.v1.Session(config=config)
|
| 10 |
+
set_session(session)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
class ERModel(object):
|
| 14 |
+
|
| 15 |
+
EMOTIONS_LIST = ["Angry", "Disgust",
|
| 16 |
+
"Fear", "Happy",
|
| 17 |
+
"Neutral", "Sad",
|
| 18 |
+
"Surprise"]
|
| 19 |
+
|
| 20 |
+
def __init__(self, model_json_file, model_weights_file):
|
| 21 |
+
|
| 22 |
+
with open(model_json_file, "r") as json_file:
|
| 23 |
+
loaded_model_json = json_file.read()
|
| 24 |
+
self.loaded_model = model_from_json(loaded_model_json)
|
| 25 |
+
|
| 26 |
+
self.loaded_model.load_weights(model_weights_file)
|
| 27 |
+
|
| 28 |
+
def predict_emotion(self, img):
|
| 29 |
+
global session
|
| 30 |
+
set_session(session)
|
| 31 |
+
self.preds = self.loaded_model.predict(img)
|
| 32 |
+
return ERModel.EMOTIONS_LIST[np.argmax(self.preds)]
|
model.weights.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:67e7e598c46e362929f4d6ccd090e4364459d09ca10f7a5518061ef5c0e35340
|
| 3 |
+
size 36100040
|
model/clip_emotion.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import clip
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 7 |
+
model, preprocess = clip.load("ViT-B/32", device=device)
|
| 8 |
+
|
| 9 |
+
EMOTION_LABELS = ["happy", "sad", "angry", "surprised", "neutral", "disgusted", "fearful"]
|
| 10 |
+
|
| 11 |
+
def detect_emotion(image_path):
|
| 12 |
+
image = preprocess(Image.open(image_path)).unsqueeze(0).to(device)
|
| 13 |
+
text_inputs = torch.cat([clip.tokenize(f"A face showing {emotion}") for emotion in EMOTION_LABELS]).to(device)
|
| 14 |
+
|
| 15 |
+
with torch.no_grad():
|
| 16 |
+
image_features = model.encode_image(image)
|
| 17 |
+
text_features = model.encode_text(text_inputs)
|
| 18 |
+
logits_per_image, _ = model(image, text_inputs)
|
| 19 |
+
probs = logits_per_image.softmax(dim=-1).cpu().numpy().flatten()
|
| 20 |
+
|
| 21 |
+
result = dict(zip(EMOTION_LABELS, probs.tolist()))
|
| 22 |
+
top_emotion = max(result, key=result.get)
|
| 23 |
+
return top_emotion, result
|
model_weights.h5
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f0511939f49907e32eeb49815ce85c963110258263a3b836a195aadc0202de0a
|
| 3 |
+
size 12075696
|
models/clip_emotion.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import clip
|
| 3 |
+
from PIL import Image
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 7 |
+
model, preprocess = clip.load("ViT-B/32", device=device)
|
| 8 |
+
|
| 9 |
+
EMOTION_LABELS = ["happy", "sad", "angry", "surprised", "neutral", "disgusted", "fearful"]
|
| 10 |
+
AGE_LABELS = [
|
| 11 |
+
"a child", "a teenager", "a young adult", "a middle-aged person", "an elderly person"
|
| 12 |
+
]
|
| 13 |
+
|
| 14 |
+
def detect_emotion(image_path):
|
| 15 |
+
image = preprocess(Image.open(image_path)).unsqueeze(0).to(device)
|
| 16 |
+
text_inputs = torch.cat([clip.tokenize(f"A face showing {emotion}") for emotion in EMOTION_LABELS]).to(device)
|
| 17 |
+
|
| 18 |
+
with torch.no_grad():
|
| 19 |
+
image_features = model.encode_image(image)
|
| 20 |
+
text_features = model.encode_text(text_inputs)
|
| 21 |
+
logits_per_image, _ = model(image, text_inputs)
|
| 22 |
+
probs = logits_per_image.softmax(dim=-1).cpu().numpy().flatten()
|
| 23 |
+
|
| 24 |
+
result = dict(zip(EMOTION_LABELS, probs.tolist()))
|
| 25 |
+
top_emotion = max(result, key=result.get)
|
| 26 |
+
return top_emotion, result
|
| 27 |
+
|
| 28 |
+
def detect_age(image_path):
|
| 29 |
+
image = preprocess(Image.open(image_path)).unsqueeze(0).to(device)
|
| 30 |
+
text_inputs = torch.cat([clip.tokenize(f"This is {label}") for label in AGE_LABELS]).to(device)
|
| 31 |
+
|
| 32 |
+
with torch.no_grad():
|
| 33 |
+
logits_per_image, _ = model(image, text_inputs)
|
| 34 |
+
probs = logits_per_image.softmax(dim=-1).cpu().numpy().flatten()
|
| 35 |
+
|
| 36 |
+
result = dict(zip(AGE_LABELS, probs.tolist()))
|
| 37 |
+
top_age = max(result, key=result.get)
|
| 38 |
+
return top_age, result
|
random_forest_model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:6c90e3878501a6d0604642695a545ba089c6fa564e17a5af8266ab1cb46453b1
|
| 3 |
+
size 122485297
|
randomforest.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
import base64
|
| 4 |
+
from tensorflow.keras.preprocessing import image
|
| 5 |
+
import logging
|
| 6 |
+
import joblib
|
| 7 |
+
|
| 8 |
+
# Load KNN model globally
|
| 9 |
+
knn_model = joblib.load('random_forest_model.joblib')
|
| 10 |
+
|
| 11 |
+
# Emotion classes
|
| 12 |
+
class_labels = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 13 |
+
|
| 14 |
+
def detect_rf(image_path):
|
| 15 |
+
frame = cv2.imread(image_path)
|
| 16 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 17 |
+
resized = cv2.resize(gray, (48, 48)) # 48x48
|
| 18 |
+
norm_img = resized / 255.0
|
| 19 |
+
|
| 20 |
+
# Feature extraction similar to training: horizontal chunks
|
| 21 |
+
chunks = [norm_img[:, i*8:(i+1)*8] for i in range(6)] # 6 chunks of 8px
|
| 22 |
+
sequence = np.stack([chunk.flatten() for chunk in chunks]) # (6, 384)
|
| 23 |
+
features = sequence.flatten() # (2304,)
|
| 24 |
+
features = features[:994] # match training shape
|
| 25 |
+
|
| 26 |
+
features = features.reshape(1, -1)
|
| 27 |
+
|
| 28 |
+
prediction = knn_model.predict(features)[0]
|
| 29 |
+
class_labels = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 30 |
+
emotion = class_labels[prediction]
|
| 31 |
+
|
| 32 |
+
_, buffer = cv2.imencode('.jpg', frame)
|
| 33 |
+
frame_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 34 |
+
|
| 35 |
+
return emotion, frame_base64
|
requirements.txt
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
tensorflow
|
| 2 |
+
torch
|
| 3 |
+
transformers
|
| 4 |
+
opencv-python
|
| 5 |
+
flask
|
| 6 |
+
flask-cors
|
| 7 |
+
Pillow
|
| 8 |
+
ftfy
|
| 9 |
+
regex
|
| 10 |
+
tqdm
|
| 11 |
+
git+https://github.com/openai/CLIP.git
|
| 12 |
+
scikit-image
|
| 13 |
+
joblib
|
| 14 |
+
scikit-learn
|
static/index.html
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection</title>
|
| 5 |
+
</head>
|
| 6 |
+
<body>
|
| 7 |
+
<h2>Webcam Emotion Detector</h2>
|
| 8 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 9 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 10 |
+
<p id="emotion"></p>
|
| 11 |
+
<script>
|
| 12 |
+
const video = document.getElementById("video");
|
| 13 |
+
const canvas = document.getElementById("canvas");
|
| 14 |
+
const context = canvas.getContext("2d");
|
| 15 |
+
const emotionText = document.getElementById("emotion");
|
| 16 |
+
|
| 17 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 18 |
+
video.srcObject = stream;
|
| 19 |
+
});
|
| 20 |
+
|
| 21 |
+
setInterval(() => {
|
| 22 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 23 |
+
canvas.toBlob(blob => {
|
| 24 |
+
const formData = new FormData();
|
| 25 |
+
formData.append("frame", blob, "frame.jpg");
|
| 26 |
+
|
| 27 |
+
fetch("http://127.0.0.1:7860/analyze", {
|
| 28 |
+
method: "POST",
|
| 29 |
+
body: formData,
|
| 30 |
+
})
|
| 31 |
+
.then(response => response.json())
|
| 32 |
+
.then(data => {
|
| 33 |
+
emotionText.textContent = "Detected Emotion: " + data.emotion;
|
| 34 |
+
});
|
| 35 |
+
}, "image/jpeg");
|
| 36 |
+
}, 3000); // every 3 seconds
|
| 37 |
+
</script>
|
| 38 |
+
</body>
|
| 39 |
+
</html>
|
svm.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import cv2
|
| 3 |
+
import base64
|
| 4 |
+
from tensorflow.keras.preprocessing import image
|
| 5 |
+
import logging
|
| 6 |
+
import joblib
|
| 7 |
+
|
| 8 |
+
# Load KNN model globally
|
| 9 |
+
knn_model = joblib.load('svm_model.joblib')
|
| 10 |
+
|
| 11 |
+
# Emotion classes
|
| 12 |
+
class_labels = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 13 |
+
|
| 14 |
+
def detect_svm(image_path):
|
| 15 |
+
frame = cv2.imread(image_path)
|
| 16 |
+
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
|
| 17 |
+
resized = cv2.resize(gray, (48, 48)) # 48x48
|
| 18 |
+
norm_img = resized / 255.0
|
| 19 |
+
|
| 20 |
+
# Feature extraction similar to training: horizontal chunks
|
| 21 |
+
chunks = [norm_img[:, i*8:(i+1)*8] for i in range(6)] # 6 chunks of 8px
|
| 22 |
+
sequence = np.stack([chunk.flatten() for chunk in chunks]) # (6, 384)
|
| 23 |
+
features = sequence.flatten() # (2304,)
|
| 24 |
+
features = features[:994] # match training shape
|
| 25 |
+
|
| 26 |
+
features = features.reshape(1, -1)
|
| 27 |
+
|
| 28 |
+
prediction = knn_model.predict(features)[0]
|
| 29 |
+
class_labels = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 30 |
+
emotion = class_labels[prediction]
|
| 31 |
+
|
| 32 |
+
_, buffer = cv2.imencode('.jpg', frame)
|
| 33 |
+
frame_base64 = base64.b64encode(buffer).decode('utf-8')
|
| 34 |
+
|
| 35 |
+
return emotion, frame_base64
|
svm_model.joblib
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:e7132d2c3f1d01003c85fe2411a2322d7a0d3d62056ae5f1b3b5343869ea988f
|
| 3 |
+
size 156484827
|
templates/classification_reports.html
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<title>Emotion Recognition Model Reports</title>
|
| 6 |
+
<style>
|
| 7 |
+
body {
|
| 8 |
+
font-family: Arial, sans-serif;
|
| 9 |
+
background-color: #f4f4f4;
|
| 10 |
+
margin: 0;
|
| 11 |
+
padding: 2rem;
|
| 12 |
+
}
|
| 13 |
+
h1 {
|
| 14 |
+
text-align: center;
|
| 15 |
+
color: #333;
|
| 16 |
+
}
|
| 17 |
+
h2 {
|
| 18 |
+
margin-top: 2rem;
|
| 19 |
+
color: #2c3e50;
|
| 20 |
+
}
|
| 21 |
+
pre {
|
| 22 |
+
background-color: #fff;
|
| 23 |
+
border-left: 5px solid #007BFF;
|
| 24 |
+
padding: 1rem;
|
| 25 |
+
overflow-x: auto;
|
| 26 |
+
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
|
| 27 |
+
white-space: pre-wrap;
|
| 28 |
+
}
|
| 29 |
+
</style>
|
| 30 |
+
</head>
|
| 31 |
+
<body>
|
| 32 |
+
<h1>Classification Reports for Emotion Recognition Models</h1>
|
| 33 |
+
|
| 34 |
+
<h2>Support Vector Machine (SVM)</h2>
|
| 35 |
+
<pre>{{ svm_report }}</pre>
|
| 36 |
+
|
| 37 |
+
<h2>Random Forest</h2>
|
| 38 |
+
<pre>{{ rf_report }}</pre>
|
| 39 |
+
|
| 40 |
+
<h2>k-Nearest Neighbors (k-NN)</h2>
|
| 41 |
+
<pre>{{ knn_report }}</pre>
|
| 42 |
+
|
| 43 |
+
<h2>Logistic Regression</h2>
|
| 44 |
+
<pre>{{ lr_report }}</pre>
|
| 45 |
+
</body>
|
| 46 |
+
</html>
|
templates/cnn.html
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection via CNN</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<h2>Webcam Emotion Detector</h2>
|
| 23 |
+
|
| 24 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 25 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 26 |
+
|
| 27 |
+
<p id="result">Waiting for detection...</p>
|
| 28 |
+
|
| 29 |
+
<button onclick="window.location.href='http://127.0.0.1:7860/'">
|
| 30 |
+
⬅ Back to Model Selection
|
| 31 |
+
</button>
|
| 32 |
+
|
| 33 |
+
<script>
|
| 34 |
+
const video = document.getElementById("video");
|
| 35 |
+
const canvas = document.getElementById("canvas");
|
| 36 |
+
const context = canvas.getContext("2d");
|
| 37 |
+
const resultText = document.getElementById("result");
|
| 38 |
+
|
| 39 |
+
// Start video stream
|
| 40 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 41 |
+
video.srcObject = stream;
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
setInterval(() => {
|
| 45 |
+
// Draw current video frame to canvas
|
| 46 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 47 |
+
|
| 48 |
+
// Convert to blob and send to backend
|
| 49 |
+
canvas.toBlob(blob => {
|
| 50 |
+
const formData = new FormData();
|
| 51 |
+
formData.append("frame", blob, "frame.jpg");
|
| 52 |
+
|
| 53 |
+
fetch("http://127.0.0.1:7860/cnn", {
|
| 54 |
+
method: "POST",
|
| 55 |
+
body: formData,
|
| 56 |
+
})
|
| 57 |
+
.then(response => response.json())
|
| 58 |
+
.then(data => {
|
| 59 |
+
resultText.innerHTML = `
|
| 60 |
+
Detected Emotion: <strong>${data.emotion}</strong><br>
|
| 61 |
+
`;
|
| 62 |
+
|
| 63 |
+
})
|
| 64 |
+
.catch(err => {
|
| 65 |
+
resultText.textContent = "Error: " + err.message;
|
| 66 |
+
});
|
| 67 |
+
}, "image/jpeg");
|
| 68 |
+
}, 300); // Every 3 seconds
|
| 69 |
+
</script>
|
| 70 |
+
</body>
|
| 71 |
+
</html>
|
templates/cnn_lstm.html
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection via ViT</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<h2>Webcam CNN LSTM Emotion Detector</h2>
|
| 23 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 24 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 25 |
+
<p id="emotion">Waiting for detection...</p>
|
| 26 |
+
<script>
|
| 27 |
+
const video = document.getElementById("video");
|
| 28 |
+
const canvas = document.getElementById("canvas");
|
| 29 |
+
const context = canvas.getContext("2d");
|
| 30 |
+
const emotionText = document.getElementById("emotion");
|
| 31 |
+
|
| 32 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 33 |
+
video.srcObject = stream;
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
setInterval(() => {
|
| 37 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 38 |
+
canvas.toBlob(blob => {
|
| 39 |
+
const formData = new FormData();
|
| 40 |
+
formData.append("frame", blob, "frame.jpg");
|
| 41 |
+
|
| 42 |
+
fetch("http://127.0.0.1:7860/cnn_lstm_video_feed", {
|
| 43 |
+
method: "POST",
|
| 44 |
+
body: formData,
|
| 45 |
+
})
|
| 46 |
+
.then(response => response.json())
|
| 47 |
+
.then(data => {
|
| 48 |
+
emotionText.textContent = "Detected Emotion: " + data.emotion;
|
| 49 |
+
});
|
| 50 |
+
}, "image/jpeg");
|
| 51 |
+
}, 300); // every 3 seconds
|
| 52 |
+
</script>
|
| 53 |
+
<button onclick="window.location.href='http://127.0.0.1:7860/'">⬅ Back to Model Selection</button>
|
| 54 |
+
</body>
|
| 55 |
+
</html>
|
templates/cnn_resnet.html
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection via CNN + RNN</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<h2>Webcam Emotion Detector</h2>
|
| 23 |
+
|
| 24 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 25 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 26 |
+
|
| 27 |
+
<p id="result">Waiting for detection...</p>
|
| 28 |
+
|
| 29 |
+
<button onclick="window.location.href='http://127.0.0.1:7860/'">
|
| 30 |
+
⬅ Back to Model Selection
|
| 31 |
+
</button>
|
| 32 |
+
|
| 33 |
+
<script>
|
| 34 |
+
const video = document.getElementById("video");
|
| 35 |
+
const canvas = document.getElementById("canvas");
|
| 36 |
+
const context = canvas.getContext("2d");
|
| 37 |
+
const resultText = document.getElementById("result");
|
| 38 |
+
|
| 39 |
+
// Start video stream
|
| 40 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 41 |
+
video.srcObject = stream;
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
setInterval(() => {
|
| 45 |
+
// Draw current video frame to canvas
|
| 46 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 47 |
+
|
| 48 |
+
// Convert to blob and send to backend
|
| 49 |
+
canvas.toBlob(blob => {
|
| 50 |
+
const formData = new FormData();
|
| 51 |
+
formData.append("frame", blob, "frame.jpg");
|
| 52 |
+
|
| 53 |
+
fetch("http://127.0.0.1:7860/cnn_resnet", {
|
| 54 |
+
method: "POST",
|
| 55 |
+
body: formData,
|
| 56 |
+
})
|
| 57 |
+
.then(response => response.json())
|
| 58 |
+
.then(data => {
|
| 59 |
+
resultText.innerHTML = `
|
| 60 |
+
Detected Emotion: <strong>${data.emotion}</strong><br>
|
| 61 |
+
`;
|
| 62 |
+
|
| 63 |
+
})
|
| 64 |
+
.catch(err => {
|
| 65 |
+
resultText.textContent = "Error: " + err.message;
|
| 66 |
+
});
|
| 67 |
+
}, "image/jpeg");
|
| 68 |
+
}, 300); // Every 3 seconds
|
| 69 |
+
</script>
|
| 70 |
+
</body>
|
| 71 |
+
</html>
|
templates/cnnkeras.html
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection via ViT</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<h2>Webcam CNN Keras Emotion Detector</h2>
|
| 23 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 24 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 25 |
+
<p id="emotion">Waiting for detection...</p>
|
| 26 |
+
<script>
|
| 27 |
+
const video = document.getElementById("video");
|
| 28 |
+
const canvas = document.getElementById("canvas");
|
| 29 |
+
const context = canvas.getContext("2d");
|
| 30 |
+
const emotionText = document.getElementById("emotion");
|
| 31 |
+
|
| 32 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 33 |
+
video.srcObject = stream;
|
| 34 |
+
});
|
| 35 |
+
|
| 36 |
+
setInterval(() => {
|
| 37 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 38 |
+
canvas.toBlob(blob => {
|
| 39 |
+
const formData = new FormData();
|
| 40 |
+
formData.append("frame", blob, "frame.jpg");
|
| 41 |
+
|
| 42 |
+
fetch("http://127.0.0.1:7860/video_feed", {
|
| 43 |
+
method: "POST",
|
| 44 |
+
body: formData,
|
| 45 |
+
})
|
| 46 |
+
.then(response => response.json())
|
| 47 |
+
.then(data => {
|
| 48 |
+
emotionText.textContent = "Detected Emotion: " + data.emotion;
|
| 49 |
+
});
|
| 50 |
+
}, "image/jpeg");
|
| 51 |
+
}, 300); // every 3 seconds
|
| 52 |
+
</script>
|
| 53 |
+
<button onclick="window.location.href='http://127.0.0.1:7860/'">⬅ Back to Model Selection</button>
|
| 54 |
+
</body>
|
| 55 |
+
</html>
|
templates/index.html
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Select Emotion Detection Model</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
#reportButton {
|
| 20 |
+
margin-top: 20px;
|
| 21 |
+
padding: 10px 20px;
|
| 22 |
+
font-size: 16px;
|
| 23 |
+
cursor: pointer;
|
| 24 |
+
}
|
| 25 |
+
</style>
|
| 26 |
+
</head>
|
| 27 |
+
<body>
|
| 28 |
+
<h2>Select Emotion Detection Model</h2>
|
| 29 |
+
|
| 30 |
+
<label for="model">Choose Model:</label>
|
| 31 |
+
<select id="model">
|
| 32 |
+
<option value="" selected disabled>-- Select Model --</option>
|
| 33 |
+
<option value="knn">KNN </option>
|
| 34 |
+
<option value="svm">SVM </option>
|
| 35 |
+
<option value="rf">Random Forest </option>
|
| 36 |
+
<option value="lr">Logistic Regression </option>
|
| 37 |
+
<option value="vit">Vision Transformer (ViT)</option>
|
| 38 |
+
<option value="cnnkeras">CNN + Keras Tensorflow</option>
|
| 39 |
+
<option value="cnnonly">CNN</option>
|
| 40 |
+
<option value="cnnrnn">CNN + RNN </option>
|
| 41 |
+
<option value="cnnlstm">CNN + LSTM </option>
|
| 42 |
+
</select>
|
| 43 |
+
|
| 44 |
+
<br>
|
| 45 |
+
|
| 46 |
+
<!-- Button to call reports -->
|
| 47 |
+
<button id="reportButton">Show Evaluation Report</button>
|
| 48 |
+
|
| 49 |
+
<script>
|
| 50 |
+
// Model selection redirect
|
| 51 |
+
document.getElementById("model").addEventListener("change", function () {
|
| 52 |
+
const model = this.value;
|
| 53 |
+
if (model === "cnnkeras") {
|
| 54 |
+
window.location.href = "/cnnkeras";
|
| 55 |
+
} else if (model === "vit") {
|
| 56 |
+
window.location.href = "/vit";
|
| 57 |
+
} else if (model === "cnnlstm") {
|
| 58 |
+
window.location.href = "/cnnlstm";
|
| 59 |
+
} else if (model === "cnnrnn") {
|
| 60 |
+
window.location.href = "/cnn_resnet";
|
| 61 |
+
} else if (model === "cnnonly") {
|
| 62 |
+
window.location.href = "/cnn";
|
| 63 |
+
}else if (model === "knn") {
|
| 64 |
+
window.location.href = "/knn";
|
| 65 |
+
}else if (model === "svm") {
|
| 66 |
+
window.location.href = "/svm";
|
| 67 |
+
}else if (model === "rf") {
|
| 68 |
+
window.location.href = "/randomforest";
|
| 69 |
+
}else if (model === "lr") {
|
| 70 |
+
window.location.href = "/logistic_regression";
|
| 71 |
+
}
|
| 72 |
+
});
|
| 73 |
+
|
| 74 |
+
// Button to call /reports endpoint
|
| 75 |
+
document.getElementById("reportButton").addEventListener("click", function () {
|
| 76 |
+
window.location.href = "/reports";
|
| 77 |
+
});
|
| 78 |
+
</script>
|
| 79 |
+
</body>
|
| 80 |
+
</html>
|
templates/knn.html
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection via KNN</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<h2>Webcam Emotion Detector</h2>
|
| 23 |
+
|
| 24 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 25 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 26 |
+
|
| 27 |
+
<p id="result">Waiting for detection...</p>
|
| 28 |
+
|
| 29 |
+
<button onclick="window.location.href='http://127.0.0.1:7860/'">
|
| 30 |
+
⬅ Back to Model Selection
|
| 31 |
+
</button>
|
| 32 |
+
|
| 33 |
+
<script>
|
| 34 |
+
const video = document.getElementById("video");
|
| 35 |
+
const canvas = document.getElementById("canvas");
|
| 36 |
+
const context = canvas.getContext("2d");
|
| 37 |
+
const resultText = document.getElementById("result");
|
| 38 |
+
|
| 39 |
+
// Start video stream
|
| 40 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 41 |
+
video.srcObject = stream;
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
setInterval(() => {
|
| 45 |
+
// Draw current video frame to canvas
|
| 46 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 47 |
+
|
| 48 |
+
// Convert to blob and send to backend
|
| 49 |
+
canvas.toBlob(blob => {
|
| 50 |
+
const formData = new FormData();
|
| 51 |
+
formData.append("frame", blob, "frame.jpg");
|
| 52 |
+
|
| 53 |
+
fetch("http://127.0.0.1:7860/knn", {
|
| 54 |
+
method: "POST",
|
| 55 |
+
body: formData,
|
| 56 |
+
})
|
| 57 |
+
.then(response => response.json())
|
| 58 |
+
.then(data => {
|
| 59 |
+
resultText.innerHTML = `
|
| 60 |
+
Detected Emotion: <strong>${data.emotion}</strong><br>
|
| 61 |
+
`;
|
| 62 |
+
|
| 63 |
+
})
|
| 64 |
+
.catch(err => {
|
| 65 |
+
resultText.textContent = "Error: " + err.message;
|
| 66 |
+
});
|
| 67 |
+
}, "image/jpeg");
|
| 68 |
+
}, 300); // Every 3 seconds
|
| 69 |
+
</script>
|
| 70 |
+
</body>
|
| 71 |
+
</html>
|
templates/lr.html
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection via Logistic Regression</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<h2>Webcam Emotion Detector</h2>
|
| 23 |
+
|
| 24 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 25 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 26 |
+
|
| 27 |
+
<p id="result">Waiting for detection...</p>
|
| 28 |
+
|
| 29 |
+
<button onclick="window.location.href='http://127.0.0.1:7860/'">
|
| 30 |
+
⬅ Back to Model Selection
|
| 31 |
+
</button>
|
| 32 |
+
|
| 33 |
+
<script>
|
| 34 |
+
const video = document.getElementById("video");
|
| 35 |
+
const canvas = document.getElementById("canvas");
|
| 36 |
+
const context = canvas.getContext("2d");
|
| 37 |
+
const resultText = document.getElementById("result");
|
| 38 |
+
|
| 39 |
+
// Start video stream
|
| 40 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 41 |
+
video.srcObject = stream;
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
setInterval(() => {
|
| 45 |
+
// Draw current video frame to canvas
|
| 46 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 47 |
+
|
| 48 |
+
// Convert to blob and send to backend
|
| 49 |
+
canvas.toBlob(blob => {
|
| 50 |
+
const formData = new FormData();
|
| 51 |
+
formData.append("frame", blob, "frame.jpg");
|
| 52 |
+
|
| 53 |
+
fetch("http://127.0.0.1:7860/logistic_regression", {
|
| 54 |
+
method: "POST",
|
| 55 |
+
body: formData,
|
| 56 |
+
})
|
| 57 |
+
.then(response => response.json())
|
| 58 |
+
.then(data => {
|
| 59 |
+
resultText.innerHTML = `
|
| 60 |
+
Detected Emotion: <strong>${data.emotion}</strong><br>
|
| 61 |
+
`;
|
| 62 |
+
|
| 63 |
+
})
|
| 64 |
+
.catch(err => {
|
| 65 |
+
resultText.textContent = "Error: " + err.message;
|
| 66 |
+
});
|
| 67 |
+
}, "image/jpeg");
|
| 68 |
+
}, 300); // Every 3 seconds
|
| 69 |
+
</script>
|
| 70 |
+
</body>
|
| 71 |
+
</html>
|
templates/rf.html
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection via Random Forest</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<h2>Webcam Emotion Detector</h2>
|
| 23 |
+
|
| 24 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 25 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 26 |
+
|
| 27 |
+
<p id="result">Waiting for detection...</p>
|
| 28 |
+
|
| 29 |
+
<button onclick="window.location.href='http://127.0.0.1:7860/'">
|
| 30 |
+
⬅ Back to Model Selection
|
| 31 |
+
</button>
|
| 32 |
+
|
| 33 |
+
<script>
|
| 34 |
+
const video = document.getElementById("video");
|
| 35 |
+
const canvas = document.getElementById("canvas");
|
| 36 |
+
const context = canvas.getContext("2d");
|
| 37 |
+
const resultText = document.getElementById("result");
|
| 38 |
+
|
| 39 |
+
// Start video stream
|
| 40 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 41 |
+
video.srcObject = stream;
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
setInterval(() => {
|
| 45 |
+
// Draw current video frame to canvas
|
| 46 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 47 |
+
|
| 48 |
+
// Convert to blob and send to backend
|
| 49 |
+
canvas.toBlob(blob => {
|
| 50 |
+
const formData = new FormData();
|
| 51 |
+
formData.append("frame", blob, "frame.jpg");
|
| 52 |
+
|
| 53 |
+
fetch("http://127.0.0.1:7860/randomforest", {
|
| 54 |
+
method: "POST",
|
| 55 |
+
body: formData,
|
| 56 |
+
})
|
| 57 |
+
.then(response => response.json())
|
| 58 |
+
.then(data => {
|
| 59 |
+
resultText.innerHTML = `
|
| 60 |
+
Detected Emotion: <strong>${data.emotion}</strong><br>
|
| 61 |
+
`;
|
| 62 |
+
|
| 63 |
+
})
|
| 64 |
+
.catch(err => {
|
| 65 |
+
resultText.textContent = "Error: " + err.message;
|
| 66 |
+
});
|
| 67 |
+
}, "image/jpeg");
|
| 68 |
+
}, 300); // Every 3 seconds
|
| 69 |
+
</script>
|
| 70 |
+
</body>
|
| 71 |
+
</html>
|
templates/svm.html
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion Detection via SVM</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<h2>Webcam Emotion Detector</h2>
|
| 23 |
+
|
| 24 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 25 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 26 |
+
|
| 27 |
+
<p id="result">Waiting for detection...</p>
|
| 28 |
+
|
| 29 |
+
<button onclick="window.location.href='http://127.0.0.1:7860/'">
|
| 30 |
+
⬅ Back to Model Selection
|
| 31 |
+
</button>
|
| 32 |
+
|
| 33 |
+
<script>
|
| 34 |
+
const video = document.getElementById("video");
|
| 35 |
+
const canvas = document.getElementById("canvas");
|
| 36 |
+
const context = canvas.getContext("2d");
|
| 37 |
+
const resultText = document.getElementById("result");
|
| 38 |
+
|
| 39 |
+
// Start video stream
|
| 40 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 41 |
+
video.srcObject = stream;
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
setInterval(() => {
|
| 45 |
+
// Draw current video frame to canvas
|
| 46 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 47 |
+
|
| 48 |
+
// Convert to blob and send to backend
|
| 49 |
+
canvas.toBlob(blob => {
|
| 50 |
+
const formData = new FormData();
|
| 51 |
+
formData.append("frame", blob, "frame.jpg");
|
| 52 |
+
|
| 53 |
+
fetch("http://127.0.0.1:7860/svm", {
|
| 54 |
+
method: "POST",
|
| 55 |
+
body: formData,
|
| 56 |
+
})
|
| 57 |
+
.then(response => response.json())
|
| 58 |
+
.then(data => {
|
| 59 |
+
resultText.innerHTML = `
|
| 60 |
+
Detected Emotion: <strong>${data.emotion}</strong><br>
|
| 61 |
+
`;
|
| 62 |
+
|
| 63 |
+
})
|
| 64 |
+
.catch(err => {
|
| 65 |
+
resultText.textContent = "Error: " + err.message;
|
| 66 |
+
});
|
| 67 |
+
}, "image/jpeg");
|
| 68 |
+
}, 300); // Every 3 seconds
|
| 69 |
+
</script>
|
| 70 |
+
</body>
|
| 71 |
+
</html>
|
templates/vit.html
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
| 2 |
+
<html>
|
| 3 |
+
<head>
|
| 4 |
+
<title>Emotion & Age Detection via ViT</title>
|
| 5 |
+
<style>
|
| 6 |
+
body {
|
| 7 |
+
font-family: Arial, sans-serif;
|
| 8 |
+
text-align: center;
|
| 9 |
+
}
|
| 10 |
+
#video, #canvas {
|
| 11 |
+
border: 2px solid #444;
|
| 12 |
+
border-radius: 8px;
|
| 13 |
+
margin: 10px;
|
| 14 |
+
}
|
| 15 |
+
#result {
|
| 16 |
+
font-size: 18px;
|
| 17 |
+
font-weight: bold;
|
| 18 |
+
}
|
| 19 |
+
</style>
|
| 20 |
+
</head>
|
| 21 |
+
<body>
|
| 22 |
+
<h2>Webcam Emotion & Age Detector</h2>
|
| 23 |
+
|
| 24 |
+
<video id="video" width="320" height="240" autoplay></video>
|
| 25 |
+
<canvas id="canvas" width="320" height="240" style="display:none;"></canvas>
|
| 26 |
+
|
| 27 |
+
<p id="result">Waiting for detection...</p>
|
| 28 |
+
|
| 29 |
+
<button onclick="window.location.href='http://127.0.0.1:7860/'">
|
| 30 |
+
⬅ Back to Model Selection
|
| 31 |
+
</button>
|
| 32 |
+
|
| 33 |
+
<script>
|
| 34 |
+
const video = document.getElementById("video");
|
| 35 |
+
const canvas = document.getElementById("canvas");
|
| 36 |
+
const context = canvas.getContext("2d");
|
| 37 |
+
const resultText = document.getElementById("result");
|
| 38 |
+
|
| 39 |
+
// Start video stream
|
| 40 |
+
navigator.mediaDevices.getUserMedia({ video: true }).then(stream => {
|
| 41 |
+
video.srcObject = stream;
|
| 42 |
+
});
|
| 43 |
+
|
| 44 |
+
setInterval(() => {
|
| 45 |
+
// Draw current video frame to canvas
|
| 46 |
+
context.drawImage(video, 0, 0, canvas.width, canvas.height);
|
| 47 |
+
|
| 48 |
+
// Convert to blob and send to backend
|
| 49 |
+
canvas.toBlob(blob => {
|
| 50 |
+
const formData = new FormData();
|
| 51 |
+
formData.append("frame", blob, "frame.jpg");
|
| 52 |
+
|
| 53 |
+
fetch("http://127.0.0.1:7860/analyze", {
|
| 54 |
+
method: "POST",
|
| 55 |
+
body: formData,
|
| 56 |
+
})
|
| 57 |
+
.then(response => response.json())
|
| 58 |
+
.then(data => {
|
| 59 |
+
resultText.innerHTML = `
|
| 60 |
+
Detected Emotion: <strong>${data.emotion}</strong><br>
|
| 61 |
+
Estimated Age Group: <strong>${data.age}</strong>
|
| 62 |
+
`;
|
| 63 |
+
|
| 64 |
+
})
|
| 65 |
+
.catch(err => {
|
| 66 |
+
resultText.textContent = "Error: " + err.message;
|
| 67 |
+
});
|
| 68 |
+
}, "image/jpeg");
|
| 69 |
+
}, 300); // Every 3 seconds
|
| 70 |
+
</script>
|
| 71 |
+
</body>
|
| 72 |
+
</html>
|
testing.py
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import tensorflow as tf
|
| 3 |
+
import keras
|
| 4 |
+
print("TensorFlow:", tf.__version__)
|
| 5 |
+
print("Keras:", keras.__version__)
|
train_basic_ml.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Classical ML Emotion Detection with HOG, LBP, Gabor (NumPy 2.x Compatible - OpenCV Removed)
|
| 2 |
+
|
| 3 |
+
import numpy as np
|
| 4 |
+
from skimage.io import imread
|
| 5 |
+
from skimage.transform import resize
|
| 6 |
+
from skimage.feature import hog, local_binary_pattern
|
| 7 |
+
from skimage.filters import gabor
|
| 8 |
+
from sklearn.model_selection import train_test_split
|
| 9 |
+
from sklearn.svm import SVC
|
| 10 |
+
from sklearn.ensemble import RandomForestClassifier
|
| 11 |
+
from sklearn.neighbors import KNeighborsClassifier
|
| 12 |
+
from sklearn.linear_model import LogisticRegression
|
| 13 |
+
from sklearn.metrics import classification_report
|
| 14 |
+
import os
|
| 15 |
+
from tqdm import tqdm
|
| 16 |
+
import joblib # Import joblib for saving models
|
| 17 |
+
|
| 18 |
+
# ----------------------
|
| 19 |
+
# Configuration
|
| 20 |
+
# ----------------------
|
| 21 |
+
IMG_SIZE = 48
|
| 22 |
+
DATASET_PATH = "./train" # Folder structure: ./dataset/<label>/<image>.jpg
|
| 23 |
+
EMOTIONS = ['angry', 'disgust', 'fear', 'happy', 'neutral', 'sad', 'surprise']
|
| 24 |
+
|
| 25 |
+
# ----------------------
|
| 26 |
+
# Feature Extraction Functions
|
| 27 |
+
# ----------------------
|
| 28 |
+
def extract_hog(img):
|
| 29 |
+
return hog(img, pixels_per_cell=(8, 8), cells_per_block=(2, 2))
|
| 30 |
+
|
| 31 |
+
def extract_lbp(img):
|
| 32 |
+
lbp = local_binary_pattern(img, P=8, R=1, method="uniform")
|
| 33 |
+
(hist, _) = np.histogram(lbp.ravel(), bins=np.arange(0, 59))
|
| 34 |
+
hist = hist.astype("float")
|
| 35 |
+
hist /= (hist.sum() + 1e-6)
|
| 36 |
+
return hist
|
| 37 |
+
|
| 38 |
+
def extract_gabor(img):
|
| 39 |
+
filt_real, _ = gabor(img, frequency=0.6)
|
| 40 |
+
return filt_real.ravel()[::64] # downsample to reduce dimensionality
|
| 41 |
+
|
| 42 |
+
# ----------------------
|
| 43 |
+
# Load Dataset and Extract Features
|
| 44 |
+
# ----------------------
|
| 45 |
+
def load_dataset():
|
| 46 |
+
data = []
|
| 47 |
+
labels = []
|
| 48 |
+
print("[INFO] Loading dataset and extracting features...")
|
| 49 |
+
|
| 50 |
+
for label in EMOTIONS:
|
| 51 |
+
folder = os.path.join(DATASET_PATH, label)
|
| 52 |
+
if not os.path.exists(folder):
|
| 53 |
+
continue
|
| 54 |
+
for file in tqdm(os.listdir(folder), desc=label):
|
| 55 |
+
path = os.path.join(folder, file)
|
| 56 |
+
try:
|
| 57 |
+
img = imread(path, as_gray=True)
|
| 58 |
+
img = resize(img, (IMG_SIZE, IMG_SIZE), anti_aliasing=True)
|
| 59 |
+
except Exception as e:
|
| 60 |
+
print(f"[WARNING] Skipped {file}: {e}")
|
| 61 |
+
continue
|
| 62 |
+
|
| 63 |
+
feat = []
|
| 64 |
+
feat.extend(extract_hog(img))
|
| 65 |
+
feat.extend(extract_lbp(img))
|
| 66 |
+
feat.extend(extract_gabor(img))
|
| 67 |
+
|
| 68 |
+
data.append(feat)
|
| 69 |
+
labels.append(EMOTIONS.index(label))
|
| 70 |
+
|
| 71 |
+
return np.array(data), np.array(labels)
|
| 72 |
+
|
| 73 |
+
# ----------------------
|
| 74 |
+
# Train & Evaluate Models
|
| 75 |
+
# ----------------------
|
| 76 |
+
def train_and_evaluate(X, y):
|
| 77 |
+
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
|
| 78 |
+
|
| 79 |
+
classifiers = {
|
| 80 |
+
"SVM": SVC(kernel='linear'),
|
| 81 |
+
"Random Forest": RandomForestClassifier(n_estimators=100),
|
| 82 |
+
"k-NN": KNeighborsClassifier(n_neighbors=5),
|
| 83 |
+
"Logistic Regression": LogisticRegression(max_iter=1000)
|
| 84 |
+
}
|
| 85 |
+
|
| 86 |
+
for name, clf in classifiers.items():
|
| 87 |
+
print(f"\n[INFO] Training {name}...")
|
| 88 |
+
clf.fit(X_train, y_train)
|
| 89 |
+
preds = clf.predict(X_test)
|
| 90 |
+
print(f"\n[RESULTS] {name} Classification Report")
|
| 91 |
+
print(classification_report(y_test, preds, target_names=EMOTIONS))
|
| 92 |
+
# Save the trained model using joblib
|
| 93 |
+
model_filename = f'{name.lower().replace(" ", "_")}_model.joblib'
|
| 94 |
+
print(f"[INFO] Saving {name} model to {model_filename}...")
|
| 95 |
+
joblib.dump(clf, model_filename)
|
| 96 |
+
|
| 97 |
+
# ----------------------
|
| 98 |
+
# Run Full Pipeline
|
| 99 |
+
# ----------------------
|
| 100 |
+
if __name__ == "__main__":
|
| 101 |
+
X, y = load_dataset()
|
| 102 |
+
train_and_evaluate(X, y)
|
train_cnn_rnn_fer2013.py
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import tensorflow as tf
|
| 3 |
+
from tensorflow.keras.models import Sequential
|
| 4 |
+
from tensorflow.keras.layers import Conv2D, MaxPooling2D, TimeDistributed, Flatten, Dense, Dropout, SimpleRNN
|
| 5 |
+
from tensorflow.keras.preprocessing.image import ImageDataGenerator
|
| 6 |
+
import os
|
| 7 |
+
|
| 8 |
+
# Constants
|
| 9 |
+
img_height, img_width = 48, 48
|
| 10 |
+
channels = 1
|
| 11 |
+
batch_size = 64
|
| 12 |
+
time_steps = 6
|
| 13 |
+
chunk_size = 8 # 6 chunks of width 8 to match 48 width
|
| 14 |
+
|
| 15 |
+
# Paths
|
| 16 |
+
train_dir = 'train'
|
| 17 |
+
test_dir = 'test'
|
| 18 |
+
|
| 19 |
+
# Image Generators (no augmentation for now)
|
| 20 |
+
train_datagen = ImageDataGenerator(rescale=1./255)
|
| 21 |
+
test_datagen = ImageDataGenerator(rescale=1./255)
|
| 22 |
+
|
| 23 |
+
# Load images from directories
|
| 24 |
+
train_generator = train_datagen.flow_from_directory(
|
| 25 |
+
train_dir,
|
| 26 |
+
target_size=(img_height, img_width),
|
| 27 |
+
color_mode='grayscale',
|
| 28 |
+
class_mode='categorical',
|
| 29 |
+
batch_size=batch_size,
|
| 30 |
+
shuffle=True
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
test_generator = test_datagen.flow_from_directory(
|
| 34 |
+
test_dir,
|
| 35 |
+
target_size=(img_height, img_width),
|
| 36 |
+
color_mode='grayscale',
|
| 37 |
+
class_mode='categorical',
|
| 38 |
+
batch_size=batch_size,
|
| 39 |
+
shuffle=False
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
# Helper: Reshape batches to (samples, time_steps, height, chunk_size, 1)
|
| 43 |
+
def reshape_batch(batch_x):
|
| 44 |
+
return batch_x.reshape(-1, time_steps, img_height, chunk_size, 1)
|
| 45 |
+
|
| 46 |
+
# Build CNN + RNN Model
|
| 47 |
+
model = Sequential()
|
| 48 |
+
model.add(TimeDistributed(Conv2D(32, (3, 3), activation='relu'), input_shape=(time_steps, img_height, chunk_size, 1)))
|
| 49 |
+
model.add(TimeDistributed(MaxPooling2D(pool_size=(2, 2))))
|
| 50 |
+
model.add(TimeDistributed(Flatten()))
|
| 51 |
+
model.add(SimpleRNN(64))
|
| 52 |
+
model.add(Dense(128, activation='relu'))
|
| 53 |
+
model.add(Dropout(0.5))
|
| 54 |
+
model.add(Dense(train_generator.num_classes, activation='softmax'))
|
| 55 |
+
|
| 56 |
+
model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
|
| 57 |
+
model.summary()
|
| 58 |
+
|
| 59 |
+
# Custom training loop to reshape batches for CNN + RNN
|
| 60 |
+
epochs = 10
|
| 61 |
+
steps_per_epoch = train_generator.samples // batch_size
|
| 62 |
+
validation_steps = test_generator.samples // batch_size
|
| 63 |
+
|
| 64 |
+
for epoch in range(epochs):
|
| 65 |
+
print(f"\nEpoch {epoch+1}/{epochs}")
|
| 66 |
+
for step in range(steps_per_epoch):
|
| 67 |
+
batch_x, batch_y = next(train_generator)
|
| 68 |
+
batch_x_rnn = reshape_batch(batch_x)
|
| 69 |
+
model.train_on_batch(batch_x_rnn, batch_y)
|
| 70 |
+
|
| 71 |
+
# Validation
|
| 72 |
+
val_accuracy = []
|
| 73 |
+
val_loss = []
|
| 74 |
+
for _ in range(validation_steps):
|
| 75 |
+
val_x, val_y = next(test_generator)
|
| 76 |
+
val_x_rnn = reshape_batch(val_x)
|
| 77 |
+
loss, acc = model.evaluate(val_x_rnn, val_y, verbose=0)
|
| 78 |
+
val_accuracy.append(acc)
|
| 79 |
+
val_loss.append(loss)
|
| 80 |
+
print(f"Validation Loss: {np.mean(val_loss):.4f}, Accuracy: {np.mean(val_accuracy):.4f}")
|
| 81 |
+
|
| 82 |
+
# Save model
|
| 83 |
+
model.save("cnn_rnn_model_from_dir.h5")
|