Spaces:
Runtime error
Runtime error
Update src/streamlit_app.py
Browse files- src/streamlit_app.py +108 -56
src/streamlit_app.py
CHANGED
|
@@ -11,16 +11,16 @@ from typing import Tuple, List, Dict, Any
|
|
| 11 |
import h5py
|
| 12 |
from huggingface_hub import hf_hub_download
|
| 13 |
import urllib.request
|
| 14 |
-
import traceback
|
| 15 |
import logging
|
| 16 |
|
| 17 |
# Set up logging
|
| 18 |
logging.basicConfig(level=logging.INFO)
|
| 19 |
logger = logging.getLogger(__name__)
|
| 20 |
|
| 21 |
-
#
|
|
|
|
|
|
|
| 22 |
os.environ["OPENCV_HEADLESS"] = "1"
|
| 23 |
-
cv2.ocl.setUseOpenCL(False)
|
| 24 |
|
| 25 |
# Set TensorFlow to use CPU only
|
| 26 |
try:
|
|
@@ -168,11 +168,12 @@ st.markdown("""
|
|
| 168 |
# Global variables for model and processor
|
| 169 |
model = None
|
| 170 |
face_cascade = None
|
|
|
|
| 171 |
model_input_size = (128, 128) # From model config
|
| 172 |
class_names = ['Mask', 'No Mask'] # From model config
|
| 173 |
model_loaded = False
|
| 174 |
face_detector_loaded = False
|
| 175 |
-
|
| 176 |
|
| 177 |
def download_haarcascade():
|
| 178 |
"""Download the Haar cascade file if it doesn't exist."""
|
|
@@ -190,6 +191,25 @@ def download_haarcascade():
|
|
| 190 |
return False
|
| 191 |
return True
|
| 192 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
def load_model() -> Any:
|
| 194 |
"""Load the Keras face mask detection model from Hugging Face with enhanced error handling."""
|
| 195 |
global model, model_loaded
|
|
@@ -244,14 +264,16 @@ def load_model() -> Any:
|
|
| 244 |
return model
|
| 245 |
|
| 246 |
def load_face_detector():
|
| 247 |
-
"""Load OpenCV's Haar cascade
|
| 248 |
-
global face_cascade, face_detector_loaded,
|
| 249 |
-
|
| 250 |
-
|
|
|
|
|
|
|
| 251 |
if not download_haarcascade():
|
| 252 |
-
|
| 253 |
-
|
| 254 |
-
return
|
| 255 |
|
| 256 |
try:
|
| 257 |
# Load the pre-trained Haar cascade classifier from local file
|
|
@@ -259,51 +281,36 @@ def load_face_detector():
|
|
| 259 |
|
| 260 |
# Check if the cascade was loaded successfully
|
| 261 |
if face_cascade.empty():
|
| 262 |
-
|
| 263 |
-
|
| 264 |
-
return
|
| 265 |
|
| 266 |
face_detector_loaded = True
|
| 267 |
-
|
| 268 |
return True
|
| 269 |
except Exception as e:
|
| 270 |
-
logger.error(f"Error loading
|
| 271 |
-
|
| 272 |
-
|
| 273 |
-
return False
|
| 274 |
-
return True
|
| 275 |
-
|
| 276 |
-
def detect_faces_fallback(image: np.ndarray) -> List[Tuple[int, int, int, int]]:
|
| 277 |
-
"""Fallback face detection using simple image processing."""
|
| 278 |
-
# Convert to grayscale
|
| 279 |
-
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 280 |
-
|
| 281 |
-
# Apply Gaussian blur to reduce noise
|
| 282 |
-
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
|
| 283 |
-
|
| 284 |
-
# Apply thresholding to get a binary image
|
| 285 |
-
_, thresh = cv2.threshold(blurred, 0, 255, cv2.THRESH_BINARY + cv2.THRESH_OTSU)
|
| 286 |
|
| 287 |
-
#
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
faces = []
|
| 292 |
-
for contour in contours:
|
| 293 |
-
# Get bounding rectangle
|
| 294 |
-
x, y, w, h = cv2.boundingRect(contour)
|
| 295 |
-
|
| 296 |
-
# Filter based on size and aspect ratio
|
| 297 |
-
if w > 30 and h > 30 and 0.7 < w/h < 1.3:
|
| 298 |
-
faces.append((x, y, w, h))
|
| 299 |
|
| 300 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 301 |
|
| 302 |
-
def
|
| 303 |
-
"""Detect faces
|
| 304 |
-
if use_fallback_detector or not face_detector_loaded:
|
| 305 |
-
return detect_faces_fallback(image)
|
| 306 |
-
|
| 307 |
# Convert to grayscale for face detection
|
| 308 |
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 309 |
|
|
@@ -319,6 +326,42 @@ def detect_faces(image: np.ndarray) -> List[Tuple[int, int, int, int]]:
|
|
| 319 |
# Convert to list of tuples (x, y, w, h)
|
| 320 |
return [(x, y, w, h) for (x, y, w, h) in faces]
|
| 321 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 322 |
def preprocess_image(image: np.ndarray) -> np.ndarray:
|
| 323 |
"""Preprocess image for model inference."""
|
| 324 |
# Resize to model input size
|
|
@@ -500,8 +543,8 @@ def main():
|
|
| 500 |
load_face_detector()
|
| 501 |
|
| 502 |
# Check if models loaded successfully
|
| 503 |
-
if not model_loaded:
|
| 504 |
-
st.error("Failed to load the model. Please check the files and try again.")
|
| 505 |
|
| 506 |
# Additional debugging information
|
| 507 |
st.markdown("---")
|
|
@@ -510,7 +553,7 @@ def main():
|
|
| 510 |
st.write("**Current Directory:**", os.getcwd())
|
| 511 |
st.write("**Files in Directory:**")
|
| 512 |
for file in os.listdir():
|
| 513 |
-
if file.endswith(('.h5', '.keras', '.xml')):
|
| 514 |
st.write(f"- {file}")
|
| 515 |
|
| 516 |
# Show system information
|
|
@@ -544,6 +587,15 @@ def main():
|
|
| 544 |
st.write(f"\n**Haar Cascade File Information:**")
|
| 545 |
st.write(f"- File exists: No")
|
| 546 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 547 |
return
|
| 548 |
|
| 549 |
# Sidebar
|
|
@@ -670,10 +722,10 @@ def main():
|
|
| 670 |
# Show face detector status
|
| 671 |
st.markdown("---")
|
| 672 |
st.markdown('<h3 class="sidebar-title">🔍 Face Detector Status</h3>', unsafe_allow_html=True)
|
| 673 |
-
if
|
| 674 |
-
st.
|
| 675 |
else:
|
| 676 |
-
st.success("Using Haar cascade face detection
|
| 677 |
|
| 678 |
# System information at the bottom
|
| 679 |
st.markdown("---")
|
|
@@ -688,7 +740,7 @@ def main():
|
|
| 688 |
""".format(
|
| 689 |
tf_version=tf.__version__,
|
| 690 |
model_name="mask_detection_model.h5",
|
| 691 |
-
detector_status="
|
| 692 |
), unsafe_allow_html=True)
|
| 693 |
|
| 694 |
# Footer
|
|
|
|
| 11 |
import h5py
|
| 12 |
from huggingface_hub import hf_hub_download
|
| 13 |
import urllib.request
|
|
|
|
| 14 |
import logging
|
| 15 |
|
| 16 |
# Set up logging
|
| 17 |
logging.basicConfig(level=logging.INFO)
|
| 18 |
logger = logging.getLogger(__name__)
|
| 19 |
|
| 20 |
+
# Set environment variables for headless operation
|
| 21 |
+
os.environ["DISPLAY"] = ":0"
|
| 22 |
+
os.environ["QT_QPA_PLATFORM"] = "offscreen"
|
| 23 |
os.environ["OPENCV_HEADLESS"] = "1"
|
|
|
|
| 24 |
|
| 25 |
# Set TensorFlow to use CPU only
|
| 26 |
try:
|
|
|
|
| 168 |
# Global variables for model and processor
|
| 169 |
model = None
|
| 170 |
face_cascade = None
|
| 171 |
+
dnn_net = None
|
| 172 |
model_input_size = (128, 128) # From model config
|
| 173 |
class_names = ['Mask', 'No Mask'] # From model config
|
| 174 |
model_loaded = False
|
| 175 |
face_detector_loaded = False
|
| 176 |
+
use_dnn_detector = False
|
| 177 |
|
| 178 |
def download_haarcascade():
|
| 179 |
"""Download the Haar cascade file if it doesn't exist."""
|
|
|
|
| 191 |
return False
|
| 192 |
return True
|
| 193 |
|
| 194 |
+
def download_dnn_model():
|
| 195 |
+
"""Download the DNN face detection model files."""
|
| 196 |
+
model_files = {
|
| 197 |
+
"deploy.prototxt": "https://raw.githubusercontent.com/opencv/opencv/master/samples/dnn/face_detector/deploy.prototxt",
|
| 198 |
+
"res10_300x300_ssd_iter_140000.caffemodel": "https://raw.githubusercontent.com/opencv/opencv_3rdparty/dnn_samples_face_detector_20170830/res10_300x300_ssd_iter_140000.caffemodel"
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
all_downloaded = True
|
| 202 |
+
for filename, url in model_files.items():
|
| 203 |
+
if not os.path.exists(filename):
|
| 204 |
+
try:
|
| 205 |
+
with st.spinner(f"Downloading {filename}..."):
|
| 206 |
+
urllib.request.urlretrieve(url, filename)
|
| 207 |
+
st.success(f"{filename} downloaded successfully!")
|
| 208 |
+
except Exception as e:
|
| 209 |
+
st.error(f"Failed to download {filename}: {str(e)}")
|
| 210 |
+
all_downloaded = False
|
| 211 |
+
return all_downloaded
|
| 212 |
+
|
| 213 |
def load_model() -> Any:
|
| 214 |
"""Load the Keras face mask detection model from Hugging Face with enhanced error handling."""
|
| 215 |
global model, model_loaded
|
|
|
|
| 264 |
return model
|
| 265 |
|
| 266 |
def load_face_detector():
|
| 267 |
+
"""Load OpenCV's Haar cascade or DNN face detector."""
|
| 268 |
+
global face_cascade, dnn_net, face_detector_loaded, use_dnn_detector
|
| 269 |
+
|
| 270 |
+
# First try to load Haar cascade
|
| 271 |
+
if not use_dnn_detector:
|
| 272 |
+
# Download the Haar cascade file if needed
|
| 273 |
if not download_haarcascade():
|
| 274 |
+
logger.info("Haar cascade download failed, trying DNN detector")
|
| 275 |
+
use_dnn_detector = True
|
| 276 |
+
return load_face_detector()
|
| 277 |
|
| 278 |
try:
|
| 279 |
# Load the pre-trained Haar cascade classifier from local file
|
|
|
|
| 281 |
|
| 282 |
# Check if the cascade was loaded successfully
|
| 283 |
if face_cascade.empty():
|
| 284 |
+
logger.info("Haar cascade is empty, trying DNN detector")
|
| 285 |
+
use_dnn_detector = True
|
| 286 |
+
return load_face_detector()
|
| 287 |
|
| 288 |
face_detector_loaded = True
|
| 289 |
+
use_dnn_detector = False
|
| 290 |
return True
|
| 291 |
except Exception as e:
|
| 292 |
+
logger.error(f"Error loading Haar cascade: {e}")
|
| 293 |
+
use_dnn_detector = True
|
| 294 |
+
return load_face_detector()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
+
# If we're here, we need to use the DNN detector
|
| 297 |
+
if not download_dnn_model():
|
| 298 |
+
face_detector_loaded = False
|
| 299 |
+
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 300 |
|
| 301 |
+
try:
|
| 302 |
+
# Load the DNN model
|
| 303 |
+
dnn_net = cv2.dnn.readNetFromCaffe("deploy.prototxt", "res10_300x300_ssd_iter_140000.caffemodel")
|
| 304 |
+
face_detector_loaded = True
|
| 305 |
+
use_dnn_detector = True
|
| 306 |
+
return True
|
| 307 |
+
except Exception as e:
|
| 308 |
+
logger.error(f"Error loading DNN model: {e}")
|
| 309 |
+
face_detector_loaded = False
|
| 310 |
+
return False
|
| 311 |
|
| 312 |
+
def detect_faces_haar(image: np.ndarray) -> List[Tuple[int, int, int, int]]:
|
| 313 |
+
"""Detect faces using Haar cascade."""
|
|
|
|
|
|
|
|
|
|
| 314 |
# Convert to grayscale for face detection
|
| 315 |
gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
|
| 316 |
|
|
|
|
| 326 |
# Convert to list of tuples (x, y, w, h)
|
| 327 |
return [(x, y, w, h) for (x, y, w, h) in faces]
|
| 328 |
|
| 329 |
+
def detect_faces_dnn(image: np.ndarray) -> List[Tuple[int, int, int, int]]:
|
| 330 |
+
"""Detect faces using DNN model."""
|
| 331 |
+
# Get image dimensions
|
| 332 |
+
(h, w) = image.shape[:2]
|
| 333 |
+
|
| 334 |
+
# Create a blob from the image
|
| 335 |
+
blob = cv2.dnn.blobFromImage(cv2.resize(image, (300, 300)), 1.0, (300, 300), (104.0, 177.0, 123.0))
|
| 336 |
+
|
| 337 |
+
# Pass the blob through the network and get the detections
|
| 338 |
+
dnn_net.setInput(blob)
|
| 339 |
+
detections = dnn_net.forward()
|
| 340 |
+
|
| 341 |
+
faces = []
|
| 342 |
+
# Loop over the detections
|
| 343 |
+
for i in range(0, detections.shape[2]):
|
| 344 |
+
# Extract the confidence (i.e., probability) associated with the prediction
|
| 345 |
+
confidence = detections[0, 0, i, 2]
|
| 346 |
+
|
| 347 |
+
# Filter out weak detections by ensuring the confidence is greater than a minimum threshold
|
| 348 |
+
if confidence > 0.5:
|
| 349 |
+
# Compute the (x, y)-coordinates of the bounding box for the object
|
| 350 |
+
box = detections[0, 0, i, 3:7] * np.array([w, h, w, h])
|
| 351 |
+
(startX, startY, endX, endY) = box.astype("int")
|
| 352 |
+
|
| 353 |
+
# Add to faces list
|
| 354 |
+
faces.append((startX, startY, endX - startX, endY - startY))
|
| 355 |
+
|
| 356 |
+
return faces
|
| 357 |
+
|
| 358 |
+
def detect_faces(image: np.ndarray) -> List[Tuple[int, int, int, int]]:
|
| 359 |
+
"""Detect faces in the image using Haar cascade or DNN method."""
|
| 360 |
+
if use_dnn_detector:
|
| 361 |
+
return detect_faces_dnn(image)
|
| 362 |
+
else:
|
| 363 |
+
return detect_faces_haar(image)
|
| 364 |
+
|
| 365 |
def preprocess_image(image: np.ndarray) -> np.ndarray:
|
| 366 |
"""Preprocess image for model inference."""
|
| 367 |
# Resize to model input size
|
|
|
|
| 543 |
load_face_detector()
|
| 544 |
|
| 545 |
# Check if models loaded successfully
|
| 546 |
+
if not model_loaded or not face_detector_loaded:
|
| 547 |
+
st.error("Failed to load the model or face detector. Please check the files and try again.")
|
| 548 |
|
| 549 |
# Additional debugging information
|
| 550 |
st.markdown("---")
|
|
|
|
| 553 |
st.write("**Current Directory:**", os.getcwd())
|
| 554 |
st.write("**Files in Directory:**")
|
| 555 |
for file in os.listdir():
|
| 556 |
+
if file.endswith(('.h5', '.keras', '.xml', '.prototxt', '.caffemodel')):
|
| 557 |
st.write(f"- {file}")
|
| 558 |
|
| 559 |
# Show system information
|
|
|
|
| 587 |
st.write(f"\n**Haar Cascade File Information:**")
|
| 588 |
st.write(f"- File exists: No")
|
| 589 |
|
| 590 |
+
# Check DNN model files
|
| 591 |
+
dnn_files = ["deploy.prototxt", "res10_300x300_ssd_iter_140000.caffemodel"]
|
| 592 |
+
st.write(f"\n**DNN Model Files Information:**")
|
| 593 |
+
for file in dnn_files:
|
| 594 |
+
if os.path.exists(file):
|
| 595 |
+
st.write(f"- {file}: Exists ({os.path.getsize(file) / (1024*1024):.2f} MB)")
|
| 596 |
+
else:
|
| 597 |
+
st.write(f"- {file}: Not found")
|
| 598 |
+
|
| 599 |
return
|
| 600 |
|
| 601 |
# Sidebar
|
|
|
|
| 722 |
# Show face detector status
|
| 723 |
st.markdown("---")
|
| 724 |
st.markdown('<h3 class="sidebar-title">🔍 Face Detector Status</h3>', unsafe_allow_html=True)
|
| 725 |
+
if use_dnn_detector:
|
| 726 |
+
st.success("Using DNN face detection (more accurate)")
|
| 727 |
else:
|
| 728 |
+
st.success("Using Haar cascade face detection (faster)")
|
| 729 |
|
| 730 |
# System information at the bottom
|
| 731 |
st.markdown("---")
|
|
|
|
| 740 |
""".format(
|
| 741 |
tf_version=tf.__version__,
|
| 742 |
model_name="mask_detection_model.h5",
|
| 743 |
+
detector_status="DNN" if use_dnn_detector else ("Haar Cascade" if face_detector_loaded else "Failed to load")
|
| 744 |
), unsafe_allow_html=True)
|
| 745 |
|
| 746 |
# Footer
|