fer-inference / predict.py
kshitizgajurel's picture
Deploy FER inference app
eec43fb
Raw
History Blame Contribute Delete
6.45 kB
"""
CLI entrypoint for FER inference.
Usage:
python predict.py --image photo.jpg
python predict.py --image photo.jpg --detect-face
python predict.py --image img1.jpg img2.jpg img3.jpg
python predict.py --folder ./test_images/
python predict.py --image photo.jpg --detect-face --save-output result.jpg
python predict.py --image photo.jpg --weights ../models/model_weights.pth
"""
import argparse
import os
import sys
from pathlib import Path
import cv2
import numpy as np
from PIL import Image
# Allow running from any directory
sys.path.insert(0, str(Path(__file__).parent))
from inference import FERPredictor
from utils import visualize_prediction, draw_face_predictions
SUPPORTED_EXTS = {'.jpg', '.jpeg', '.png', '.bmp', '.webp', '.tiff', '.tif'}
EMOTION_ORDER = ['happy', 'neutral', 'surprise', 'sad', 'angry', 'fear', 'disgust']
BAR_MAX_WIDTH = 20
def _bar(prob: float) -> str:
filled = round(prob * BAR_MAX_WIDTH)
return '█' * filled + ' ' * (BAR_MAX_WIDTH - filled)
def _print_result(image_path: str, result: dict):
print(f"\nImage: {image_path}")
print('─' * 45)
print(f"Emotion : {result['emotion']}")
print(f"Confidence : {result['confidence']*100:.1f}%")
print('─' * 45)
print("All emotions:")
sorted_probs = sorted(result['probabilities'].items(), key=lambda x: x[1], reverse=True)
for emotion, prob in sorted_probs:
bar = _bar(prob)
print(f" {emotion:<10} {bar} {prob*100:.1f}%")
print()
def _print_face_result(image_path: str, face_results: list[dict]):
print(f"\nImage: {image_path} [{len(face_results)} face(s) detected]")
for res in face_results:
idx = res.get('face_index', 0)
bbox = res.get('bbox')
bbox_str = f" bbox: {bbox}" if bbox else " (no face detected, ran on full image)"
print(f"\n Face #{idx + 1}{bbox_str}")
print(f" Emotion : {res['emotion']}")
print(f" Confidence : {res['confidence']*100:.1f}%")
sorted_probs = sorted(res['probabilities'].items(), key=lambda x: x[1], reverse=True)
print(" Probabilities:")
for emotion, prob in sorted_probs:
bar = _bar(prob)
print(f" {emotion:<10} {bar} {prob*100:.1f}%")
print()
def _collect_images(args) -> list[str]:
paths = []
if args.image:
paths.extend(args.image)
if args.folder:
folder = Path(args.folder)
if not folder.is_dir():
print(f"[ERROR] Folder not found: {args.folder}")
sys.exit(1)
for p in sorted(folder.iterdir()):
if p.suffix.lower() in SUPPORTED_EXTS:
paths.append(str(p))
if not paths:
print(f"[WARNING] No supported image files found in: {args.folder}")
return paths
def _save_annotated(image_path: str, face_results: list[dict], save_path: str):
img_bgr = cv2.imread(image_path)
if img_bgr is None:
print(f"[WARNING] Could not reload image for annotation: {image_path}")
return
annotated = draw_face_predictions(img_bgr, face_results)
cv2.imwrite(save_path, annotated)
print(f"[INFO] Annotated image saved to: {save_path}")
def main():
parser = argparse.ArgumentParser(
description='FER Inference — predict facial expressions from images.'
)
parser.add_argument('--image', nargs='+', help='Path(s) to input image(s).')
parser.add_argument('--folder', help='Folder of images to process.')
parser.add_argument('--detect-face', action='store_true',
help='Run face detection before prediction.')
parser.add_argument('--face-method', default='mtcnn', choices=['mtcnn', 'haar'],
help='Face detection method (default: mtcnn).')
parser.add_argument('--weights', default='../models/model_weights.pth',
help='Path to model_weights.pth (default: ../models/model_weights.pth).')
parser.add_argument('--device', default='auto',
help='Device: auto | cpu | cuda | cuda:0 (default: auto).')
parser.add_argument('--save-output', help='Save annotated image to this path.')
parser.add_argument('--save-plot', help='Save probability bar chart to this path.')
args = parser.parse_args()
if not args.image and not args.folder:
parser.print_help()
sys.exit(1)
image_paths = _collect_images(args)
if not image_paths:
print("[ERROR] No images to process.")
sys.exit(1)
predictor = FERPredictor(weights_path=args.weights, device=args.device)
for img_path in image_paths:
if not os.path.exists(img_path):
print(f"[WARNING] File not found, skipping: {img_path}")
continue
if args.detect_face:
face_results = predictor.predict_with_face_detection(
img_path, method=args.face_method
)
_print_face_result(img_path, face_results)
if args.save_output:
save_path = args.save_output if len(image_paths) == 1 else \
f"{Path(args.save_output).stem}_{Path(img_path).stem}{Path(args.save_output).suffix}"
_save_annotated(img_path, face_results, save_path)
if args.save_plot and face_results:
# Plot probabilities of first face
from utils import plot_emotion_bars
plot_emotion_bars(face_results[0]['probabilities'],
title=f"{face_results[0]['emotion']} ({face_results[0]['confidence']*100:.1f}%)",
save_path=args.save_plot)
else:
result = predictor.predict_image(img_path)
_print_result(img_path, result)
if args.save_output:
img = Image.open(img_path)
save_path = args.save_output if len(image_paths) == 1 else \
f"{Path(args.save_output).stem}_{Path(img_path).stem}{Path(args.save_output).suffix}"
visualize_prediction(img, result, save_path=save_path)
if args.save_plot:
from utils import plot_emotion_bars
plot_emotion_bars(result['probabilities'],
title=f"{result['emotion']} ({result['confidence']*100:.1f}%)",
save_path=args.save_plot)
if __name__ == '__main__':
main()