Try DeepSeek-R1 on Modal! View example

Run OpenCV face detection on an image

This example shows how you can use OpenCV on Modal to detect faces in an image. We use the opencv-python package to load the image and the opencv library to detect faces. The function count_faces takes an image as input and returns the number of faces detected in the image.

The code below also shows how you can create wrap this function in a simple FastAPI server to create a web interface.

import os

import modal

app = modal.App("example-count-faces")


open_cv_image = (
    modal.Image.debian_slim(python_version="3.11")
    .apt_install("python3-opencv")
    .pip_install(
        "fastapi[standard]==0.115.4",
        "opencv-python~=4.10.0",
        "numpy<2",
    )
)


@app.function(image=open_cv_image)
def count_faces(image_bytes: bytes) -> int:
    import cv2
    import numpy as np

    # Example borrowed from https://towardsdatascience.com/face-detection-in-2-minutes-using-opencv-python-90f89d7c0f81
    # Load the cascade
    face_cascade = cv2.CascadeClassifier(
        os.path.join(
            cv2.data.haarcascades, "haarcascade_frontalface_default.xml"
        )
    )
    # Read the input image
    np_bytes = np.frombuffer(image_bytes, dtype=np.uint8)
    img = cv2.imdecode(np_bytes, cv2.IMREAD_COLOR)
    # Convert into grayscale
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    # Detect faces
    faces = face_cascade.detectMultiScale(gray, 1.1, 4)
    return len(faces)


@app.function(
    image=modal.Image.debian_slim(python_version="3.11").pip_install("inflect")
)
@modal.asgi_app()
def web():
    import inflect
    from fastapi import FastAPI, File, HTTPException, UploadFile
    from fastapi.responses import HTMLResponse

    app = FastAPI()

    @app.get("/", response_class=HTMLResponse)
    async def index():
        """
        Render an HTML form for file upload.
        """
        return """
        <html>
            <head>
                <title>Face Counter</title>
            </head>
            <body>
                <h1>Upload an Image to Count Faces</h1>
                <form action="/process" method="post" enctype="multipart/form-data">
                    <input type="file" name="file" id="file" accept="image/*" required />
                    <button type="submit">Upload</button>
                </form>
            </body>
        </html>
        """

    @app.post("/process", response_class=HTMLResponse)
    async def process(file: UploadFile = File(...)):
        """
        Process the uploaded image and return the number of faces detected.
        """
        try:
            file_content = await file.read()
            num_faces = await count_faces.remote.aio(file_content)
            return f"""
            <html>
                <head>
                    <title>Face Counter Result</title>
                </head>
                <body>
                    <h1>{inflect.engine().number_to_words(num_faces).title()} {'Face' if num_faces==1 else 'Faces'} Detected</h1>
                    <h2>{"😀" * num_faces}</h2>
                    <a href="/">Go back</a>
                </body>
            </html>
            """
        except Exception as e:
            raise HTTPException(
                status_code=400, detail=f"Error processing image: {str(e)}"
            )

    return app