Foto tomada con Python y Flask

Python: acceder a cámara web con OpenCV y Flask

Hace tiempo te mostré cómo tomar una foto con la webcam usando Python, pero desde la terminal y sin una previsualización. Ahora te mostraré cómo stremear la cámara web en tiempo real a tu navegador web, tomar una foto y descargarla, o tomar una foto y guardarla en el servidor.

Acceder a la cámara web con Flask, OpenCV y Python
Acceder a la cámara web con Flask, OpenCV y Python

Para ver la cámara en tiempo real y exponerla para que un navegador web la consuma vamos a usar Flask. Y para acceder a la cámara usaremos OpenCV.

Al final tendrás la opción de descargar la foto o guardarla en el servidor. De este modo igualmente podrías ver una cámara web a través de la red, pues se va a crear un servidor web en donde la misma estará expuesta.

Descripción del proyecto

Vamos a obtener los frames de la cámara web o webcam. Esta cámara puede ser la de tu portátil, o una cámara USB. De hecho mientras tu computadora la reconozca, puedes usarla.

Después vamos a exponer esa cámara en un servidor web creado con Python y Flask. De este modo puedes vigilar la cámara a través del navegador web, ya sea desde localhost, a través de la IP o incluso a través de internet si es que tu ISP lo permite.

Además de mostrar la cámara en tiempo real usando Python, vamos a colocar dos botones para tomar una foto, de este modo podremos tomar una foto y descargarla en el cliente, o tomar una foto y guardarla en el servidor.

Leyendo frame de cámara web o USB

Primero declaramos la variable que tendrá la cámara:

camara = cv2.VideoCapture(0)

Ahora veamos una función que obtiene el frame actual de la cámara. Para ello vamos a invocar al método read, luego vamos a codificar la imagen como JPG y regresar sus bytes.

La función también devolverá una bandera indicando si la lectura fue correcta.

def obtener_frame_camara():
    ok, frame = camara.read()
    if not ok:
        return False, None
    # Codificar la imagen como JPG
    _, bufer = cv2.imencode(".jpg", frame)
    imagen = bufer.tobytes()
    return True, imagen

Generador para Response de Flask

Ya sabemos cómo obtener un frame, pero recuerda que en este caso debemos obtener los frames de manera infinita y persistente.

Para ello vamos a crear un generador propio de Flask que funciona para stremear datos al cliente. Después vamos a exponer todo esto en una ruta:

# Una función generadora para stremear la cámara
# https://flask.palletsprojects.com/en/1.1.x/patterns/streaming/
def generador_frames():
    while True:
        ok, imagen = obtener_frame_camara()
        if not ok:
            break
        else:
            # Regresar la imagen en modo de respuesta HTTP
            yield b"--frame\r\nContent-Type: image/jpeg\r\n\r\n" + imagen + b"\r\n"

# Más tarde...

# Cuando visiten la ruta
@app.route("/streaming_camara")
def streaming_camara():
    return Response(generador_frames(), mimetype='multipart/x-mixed-replace; boundary=frame')

Como puedes ver, exponemos los bytes en la respuesta en el stream. Ahora solo basta consumir ese stream, y eso lo podemos hacer con una etiqueta <img> de HTML.

Consumiendo stream

Ya en HTML podemos usar el stream como fuente de una imagen. Veamos el frontend que tendrá la previsualización de la cámara y los botones:

<!DOCTYPE html>
<html lang="es">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tomar foto con Flask, OpenCV y Python - By Parzibyte</title>
    <link rel="stylesheet" href="https://unpkg.com/bulma@0.9.1/css/bulma.min.css">
</head>

<body>
    <section class="section">
        <div class="columns">
            <div class="column has-text-centered">
                <figure class="image has-text-centered">
                    <img class="is-inline-block" src="./streaming_camara" style="width: auto">
                </figure>
            </div>
        </div>
    </section>
</body>

</html>

Lo importante aquí es la imagen de la línea 16. Aquí vamos a estar obteniendo los frames de la cámara que estará exponiendo Flask.

Tomar foto

Cuando tomemos una foto vamos a obtener el frame en el momento exacto en el que se presione el botón, y vamos a guardarlo como imagen o forzar su descarga en el navegador.

Para descargar la foto, el código es:

# Cuando toman la foto
@app.route("/tomar_foto_descargar")
def descargar_foto():
    ok, frame = obtener_frame_camara()
    if not ok:
        abort(500)
        return
    respuesta = Response(frame)
    respuesta.headers["Content-Type"] = "image/jpeg"
    respuesta.headers["Content-Transfer-Encoding"] = "Binary"
    respuesta.headers["Content-Disposition"] = "attachment; filename=\"foto.jpg\""
    return respuesta

Y para guardarla en el disco duro obtenemos un UUID para el nombre de la imagen y escribimos el contenido en el servidor, devolviendo el nombre de la foto como JSON.

@app.route("/tomar_foto_guardar")
def guardar_foto():
    nombre_foto = str(uuid.uuid4()) + ".jpg"
    ok, frame = camara.read()
    if ok:
        cv2.imwrite(nombre_foto, frame)
    return jsonify({
        "ok": ok,
        "nombre_foto": nombre_foto,
    })

Ahora solo tenemos que crear enlaces que lleven a esa ruta. Para hacer la toma de foto en el disco duro de manera dinámica podemos usar AJAX y hacer la petición asíncrona desde JavaScript:

/*
    En el clic del botón hacemos una petición a ./tomar_foto_guardar 
*/
const $btnTomarFotoServidor = document.querySelector("#btnTomarFotoServidor"),
    $estado = document.querySelector("#estado");
$btnTomarFotoServidor.onclick = async () => {
    $estado.textContent = "Tomando foto...";
    const respuestaRaw = await fetch("./tomar_foto_guardar");
    const respuesta = await respuestaRaw.json();
    let mensaje = "";
    if (respuesta.ok) {
        mensaje = `Foto guardada como ${respuesta.nombre_foto}`;
    } else {
        mensaje = `Error tomando foto`;
    }
    $estado.textContent = mensaje;
};

Básicamente estamos visitando la ruta en el background y mostrando el nombre en el cliente.

Usando código

El código completo incluyendo el lado del cliente lo dejo en mi GitHub. Es totalmente gratuito y open source. Recuerda que necesitas Python y PIP instalados.

Una vez que tengas el código instala las dependencias con:

pip install opencv-python

Y:

pip install flask

Poniendo todo junto

Una vez que tenemos el código lo ejecutamos con:

python app.py

Luego visitamos localhost:5000 y ya podemos ver el stream. También podemos tomar una foto:

Foto tomada con Python y Flask
Foto tomada con Python y Flask

O tomar la foto y descargarla directamente. Sea como sea, gracias a esto podemos visualizar nuestra cámara web desde el navegador. Y obviamente si entramos en otro dispositivo igualmente podemos ver la cámara:

Ver cámara de manera remota usando Python, Flask y OpenCV
Ver cámara de manera remota usando Python, Flask y OpenCV

Para terminar te dejo una foto de mi perro de juguete en una resolución aceptable; lo que pasa es que mi webcam no es de una alta calidad.

Foto de perro de juguete en alta resolución
Foto de perro de juguete en alta resolución

En mi blog tengo otros tutoriales sobre Python y Flask, puede que te interese leerlos.

Actualización: si también quieres grabar vídeos, mira este post.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

2 comentarios en “Python: acceder a cámara web con OpenCV y Flask”

  1. Gracias por el tutorial, muy bueno y didáctico.

    Me surge una duda y es como se podrían recolectar y guardar en el servidor mas de 1 imagen simultáneamente al darle clic en el botón “Tomar y guardar en el servidor”

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *