python

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

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

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

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

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.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

  • 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"

Entradas recientes

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

3 días hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

3 días hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

3 días hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

3 días hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

3 días hace

Solución: Apache – Server unable to read htaccess file

Ayer estaba editando unos archivos que son servidos con el servidor Apache y al visitarlos…

4 días hace

Esta web usa cookies.