Foto tomada con Python 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.
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.
En este post te quiero compartir un código de C++ para listar y cancelar trabajos…
Gracias a WebAssembly podemos ejecutar código de otros lenguajes de programación desde el navegador web…
Revisando y buscando maneras de imprimir un PDF desde la línea de comandos me encontré…
Esta semana estuve recreando la API del plugin para impresoras térmicas en Android (HTTP a…
Hoy te enseñaré a extraer la cadena base64 de una clave PEM usando una función…
Encender un foco con un Bot de Telegram es posible usando una tarjeta como la…
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!