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.
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.
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.
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
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.
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.
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.
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
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:
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:
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.
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.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.
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"
Hola. Gracias por sus comentarios. En caso de tener una duda específica puede contactarme en https://parzibyte.me/#contacto
Saludos!