Como las cámaras han mejorado, la detección de objetos en tiempo real se ha convertido en una funcionalidad cada vez más solicitada. Desde los coches autónomos y los sistemas de vigilancia inteligentes hasta las aplicaciones de realidad aumentada, esta tecnología se utiliza en muchas situaciones.

La visión por ordenador, un nombre elegante para la tecnología que utiliza cámaras con ordenadores para realizar operaciones como las mencionadas anteriormente, es un campo vasto y complicado. Sin embargo, quizá no sepas que puedes iniciarte en la detección de objetos en tiempo real muy fácilmente desde la comodidad de tu navegador.

Este artículo explica cómo crear una aplicación de detección de objetos en tiempo real utilizando React y desplegar la aplicación en Kinsta. La aplicación de detección de objetos en tiempo real aprovecha el feed de la webcam del usuario.

Requisitos previos

Aquí tienes un desglose de las principales tecnologías utilizadas en esta guía:

  • React: React se utiliza para construir la interfaz de usuario (UI) de la aplicación. React destaca en la renderización de contenido dinámico y será útil para presentar el feed de la webcam y los objetos detectados dentro del navegador.
  • TensorFlow.js: TensorFlow.js es una biblioteca de JavaScript que lleva la potencia del aprendizaje automático al navegador. Te permite cargar modelos preentrenados para la detección de objetos y ejecutarlos directamente en el navegador, eliminando la necesidad de un complejo procesamiento en el servidor.
  • Coco SSD: La aplicación utiliza un modelo de detección de objetos preentrenado llamado Coco SSD, un modelo ligero capaz de reconocer una amplia gama de objetos cotidianos en tiempo real. Aunque Coco SSD es una herramienta potente, es importante tener en cuenta que se ha entrenado con un conjunto de datos general de objetos. Si tienes necesidades específicas de detección, puedes entrenar un modelo personalizado utilizando TensorFlow.js siguiendo esta guía.

Configura un nuevo proyecto React

  1. Crea un nuevo proyecto React. Para ello, ejecuta el siguiente comando:
    npm create vite@latest kinsta-object-detection --template react

    Esto creará un proyecto React básico para ti utilizando vite.

  2. A continuación, instala las bibliotecas TensorFlow y Coco SSD ejecutando los siguientes comandos en el proyecto:
    npm i @tensorflow-models/coco-ssd @tensorflow/tfjs

Ahora ya estás preparado para empezar a desarrollar tu aplicación.

Configurar la aplicación

Antes de escribir el código para la lógica de detección de objetos, vamos a entender qué se desarrolla en esta guía. Este es el aspecto que tendría la interfaz de usuario de la aplicación:

Una captura de pantalla de la aplicación completada con la cabecera y un botón para activar el acceso a la webcam.
Diseño de la interfaz de usuario de la aplicación.

Cuando un usuario pulsa el botón Iniciar Webcam, se le pide que conceda permiso a la aplicación para acceder a la transmisión de la webcam. Una vez concedido el permiso, la aplicación empieza a mostrar la imagen de la webcam y detecta los objetos presentes en ella. A continuación, crea un recuadro para mostrar los objetos detectados en la imagen en directo y le añade una etiqueta.

Para empezar, crea la interfaz de usuario de la aplicación pegando el siguiente código en el archivo App.jsx:

import ObjectDetection from './ObjectDetection';
function App() {
  return (
    <div className="app">
      <h1>Image Object Detection</h1>
        <ObjectDetection />
    </div>
  );
}

export default App;

Este fragmento de código especifica una cabecera para la página e importa un componente personalizado llamado ObjectDetection. Este componente contiene la lógica para capturar la señal de la webcam y detectar objetos en tiempo real.

Para crear este componente, crea un nuevo archivo llamado ObjectDetection.jsx en tu directorio src y pega en él el siguiente código:

import { useEffect, useRef, useState } from 'react';

const ObjectDetection = () => {
  const videoRef = useRef(null);
  const [isWebcamStarted, setIsWebcamStarted] = useState(false)

  const startWebcam = async () => {
    // TODO
  };

  const stopWebcam = () => {
     // TODO
  };

  return (
    <div className="object-detection">
      <div className="buttons">
        <button onClick={isWebcamStarted ? stopWebcam : startWebcam}>{isWebcamStarted ? "Stop" : "Start"} Webcam</button>
      </div>
      <div className="feed">
        {isWebcamStarted ? <video ref={videoRef} autoPlay muted /> : <div />}
      </div>
    </div>
  );
};

export default ObjectDetection;

El código anterior define una estructura HTML con un botón para iniciar y detener la transmisión de la webcam y un elemento <video> que se utilizará para mostrar al usuario su transmisión de la webcam una vez que esté activa. Se utiliza un contenedor de estado isWebcamStarted para almacenar el estado de la transmisión de la webcam. Dos funciones, startWebcam y stopWebcam, se utilizan para iniciar y detener la transmisión de la webcam. Vamos a definirlas:

Este es el código de la función startWebcam:

const startWebcam = async () => {
    try {
      setIsWebcamStarted(true)
      const stream = await navigator.mediaDevices.getUserMedia({ video: true });

      if (videoRef.current) {
        videoRef.current.srcObject = stream;
      }
    } catch (error) {
      setIsWebcamStarted(false)
      console.error('Error accessing webcam:', error);
    }
  };

Esta función se encarga de solicitar al usuario que conceda el acceso a la webcam, y una vez concedido el permiso, configura el <video> para mostrar la transmisión de la webcam en directo al usuario.

Si el código no consigue acceder a la señal de la webcam (posiblemente por falta de webcam en el dispositivo actual o porque se deniega el permiso al usuario), la función imprimirá un mensaje en la consola. Puedes utilizar un bloque de error para mostrar al usuario el motivo del fallo.

A continuación, sustituye la función stopWebcam por el siguiente código:

const stopWebcam = () => {
    const video = videoRef.current;

    if (video) {
      const stream = video.srcObject;
      const tracks = stream.getTracks();

      tracks.forEach((track) => {
        track.stop();
      });

      video.srcObject = null;
      setPredictions([])
      setIsWebcamStarted(false)
    }
  };

Este código comprueba las pistas de la transmisión de vídeo en ejecución a las que accede el objeto <video> y detiene cada una de ellas. Por último, establece el estado isWebcamStarted en false.

En este punto, prueba a ejecutar la aplicación para comprobar si puedes acceder a la transmisión de la webcam y verla.

Asegúrate de pegar el siguiente código en el archivo index.css para comprobar que la app tiene el mismo aspecto que la vista previa que has visto antes:

#root {
  font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
  line-height: 1.5;
  font-weight: 400;
  color-scheme: light dark;
  color: rgba(255, 255, 255, 0.87);
  background-color: #242424;
  min-width: 100vw;
  min-height: 100vh;
  font-synthesis: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

a {
  font-weight: 500;
  color: #646cff;
  text-decoration: inherit;
}

a:hover {
  color: #535bf2;
}

body {
  margin: 0;
  display: flex;
  place-items: center;
  min-width: 100vw;
  min-height: 100vh;
}

h1 {
  font-size: 3.2em;
  line-height: 1.1;
}

button {
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.6em 1.2em;
  font-size: 1em;
  font-weight: 500;
  font-family: inherit;
  background-color: #1a1a1a;
  cursor: pointer;
  transition: border-color 0.25s;
}

button:hover {
  border-color: #646cff;
}

button:focus,

button:focus-visible {
  outline: 4px auto -webkit-focus-ring-color;
}

@media (prefers-color-scheme: light) {
  :root {
    color: #213547;
    background-color: #ffffff;
  }

  a:hover {
    color: #747bff;
  }

  button {
    background-color: #f9f9f9;
  }
}

.app {
  width: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-direction: column;
}

.object-detection {
  width: 100%;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;

  .buttons {
    width: 100%;
    display: flex;
    justify-content: center;
    align-items: center;
    flex-direction: row;

    button {
      margin: 2px;
    }
  }

  div {
    margin: 4px;
  }
}

Además, elimina el archivo App.css para no estropear los estilos de los componentes. Ahora ya puedes escribir la lógica para integrar la detección de objetos en tiempo real en tu aplicación.

Configurar la detección de objetos en tiempo real

  1. Comienza añadiendo las importaciones para Tensorflow y Coco SSD en la parte superior de ObjectDetection.jsx:
    import * as cocoSsd from '@tensorflow-models/coco-ssd';
    
    import '@tensorflow/tfjs';
  2. A continuación, crea un estado en el componente ObjectDetection para almacenar el array de predicciones generado por el modelo Coco SSD:
    const [predictions, setPredictions] = useState([]);
  3. A continuación, crea una función que cargue el modelo Coco SSD, recoja el vídeo y genere las predicciones:
    const predictObject = async () => {
        const model = await cocoSsd.load();
    
        model.detect(videoRef.current).then((predictions) => {
          setPredictions(predictions);
        })
    
          .catch(err => {
            console.error(err)
          });
      };

    Esta función utiliza el vídeo y genera predicciones para los objetos presentes en el vídeo. Te proporcionará un array de objetos previstos, cada uno de los cuales contendrá una etiqueta, un porcentaje de confianza y un conjunto de coordenadas que muestran la ubicación del objeto en el fotograma de vídeo.

    Tienes que llamar continuamente a esta función para procesar los fotogramas de vídeo a medida que llegan y, a continuación, utilizar las predicciones almacenadas en el estado predictions para mostrar recuadros y etiquetas de cada objeto identificado en la secuencia de vídeo en directo.

  4. A continuación, utiliza la función setInterval para llamar continuamente a la función. También debes impedir que se llame a esta función después de que el usuario haya detenido la transmisión de la webcam. Para ello, utiliza la función clearInterval de JavaScript. Añade el siguiente contenedor de estado y el hook useEffect en el componente ObjectDetection para configurar la función predictObject para que se llame continuamente cuando la webcam esté activada y se elimine cuando la webcam esté desactivada:
    const [detectionInterval, setDetectionInterval] = useState()
    
      useEffect(() => {
        if (isWebcamStarted) {
          setDetectionInterval(setInterval(predictObject, 500))
        } else {
          if (detectionInterval) {
            clearInterval(detectionInterval)
            setDetectionInterval(null)
          }
        }
      }, [isWebcamStarted])

    Esto configura la app para que detecte los objetos presentes delante de la webcam cada 500 milisegundos. Puedes considerar cambiar este valor en función de lo rápida que quieras que sea la detección de objetos, teniendo en cuenta que hacerlo con demasiada frecuencia puede hacer que tu app utilice mucha memoria en el navegador.

  5. Ahora que tienes los datos de la predicción en el contenedor de estado prediction, puedes utilizarlos para mostrar una etiqueta y un recuadro alrededor del objeto en la secuencia de vídeo en directo. Para ello, actualiza la sentencia return del ObjectDetection para que devuelva lo siguiente:
    return (
        <div className="object-detection">
          <div className="buttons">
            <button onClick={isWebcamStarted ? stopWebcam : startWebcam}>{isWebcamStarted ? "Stop" : "Start"} Webcam</button>
          </div>
          <div className="feed">
            {isWebcamStarted ? <video ref={videoRef} autoPlay muted /> : <div />}
            {/* Add the tags below to show a label using the p element and a box using the div element */}
            {predictions.length > 0 && (
              predictions.map(prediction => {
                return <>
                  <p style={{
                    left: `${prediction.bbox[0]}px`, 
                    top: `${prediction.bbox[1]}px`,
                    width: `${prediction.bbox[2] - 100}px`
                }}>{prediction.class  + ' - with ' 
                + Math.round(parseFloat(prediction.score) * 100) 
                + '% confidence.'}</p>
                <div className={"marker"} style={{
                  left: `${prediction.bbox[0]}px`,
                  top: `${prediction.bbox[1]}px`,
                  width: `${prediction.bbox[2]}px`,
                  height: `${prediction.bbox[3]}px`
                }} />
                </>
              })
            )}
          </div>
          {/* Add the tags below to show a list of predictions to user */}
          {predictions.length > 0 && (
            <div>
              <h3>Predictions:</h3>
              <ul>
                {predictions.map((prediction, index) => (
                  <li key={index}>
                    {`${prediction.class} (${(prediction.score * 100).toFixed(2)}%)`}
                  </li>
                ))}
              </ul>
            </div>
          )}
    
        </div>
      );

    Esto mostrará una lista de predicciones justo debajo de la imagen de la cámara web y dibujará una caja alrededor del objeto predicho utilizando las coordenadas de Coco SSD junto con una etiqueta en la parte superior de las cajas.

  6. Para estilizar correctamente las cajas y la etiqueta, añade el siguiente código al archivo index.css:
    .feed {
      position: relative;
    
      p {
        position: absolute;
        padding: 5px;
        background-color: rgba(255, 111, 0, 0.85);
        color: #FFF;
        border: 1px dashed rgba(255, 255, 255, 0.7);
        z-index: 2;
        font-size: 12px;
        margin: 0;
      }
    
      .marker {
        background: rgba(0, 255, 0, 0.25);
        border: 1px dashed #fff;
        z-index: 1;
        position: absolute;
      }
    
    }

    Esto completa el desarrollo de la aplicación. Ahora puedes reiniciar el servidor dev para probar la aplicación. Este es el aspecto que debería tener una vez completada:

    Un GIF que muestra al usuario ejecutando la aplicación, permitiendo el acceso de la cámara a la misma, y luego la aplicación mostrando recuadros y etiquetas alrededor de los objetos detectados en el feed.
    Demostración de la detección de objetos en tiempo real mediante la webcam

Puedes encontrar el código completo en este repositorio de GitHub.

Despliega la aplicación completa en Kinsta

El último paso es desplegar la aplicación en Kinsta para ponerla a disposición de tus usuarios. Para ello, Kinsta te permite alojar hasta 100 sitios web estáticos de forma gratuita directamente desde tu proveedor Git preferido (Bitbucket, GitHub o GitLab).

Una vez que tu repositorio git esté listo, sigue estos pasos para desplegar tu app de detección de objetos en Kinsta:

  1. Inicia sesión o crea una cuenta para ver tu panel MyKinsta.
  2. Autoriza a Kinsta con tu proveedor Git.
  3. Haz clic en Sitios Estáticos en la barra lateral izquierda y, a continuación, en Añadir sitio.
  4. Selecciona el repositorio y la rama desde la que deseas desplegar.
  5. Asigna un nombre único a tu sitio.
  6. Añade la configuración de construcción en el siguiente formato:
    • Comando de construcción: yarn build o npm run build
    • Versión de Node: 20.2.0
    • Directorio de publicación: dist
  7. Por último, haz clic en Crear sitio.

Una vez desplegada la aplicación, puedes hacer clic en Visitar Sitio desde el panel de control para acceder a la aplicación. Ahora puedes probar a ejecutar la aplicación en varios dispositivos con cámaras para ver cómo funciona.

Como alternativa al Alojamiento de Sitios Estáticos, puedes desplegar tu sitio estático con el Alojamiento de Aplicaciones de Kinsta, que proporciona una mayor flexibilidad de alojamiento, una gama más amplia de ventajas y acceso a funciones más robustas. Por ejemplo, escalabilidad, despliegue personalizado mediante un Dockerfile y análiticas completas que abarcan datos históricos y en tiempo real.

Resumen

Has construido con éxito una aplicación de detección de objetos en tiempo real utilizando React, TensorFlow.js y Kinsta. Esto te permite explorar el apasionante mundo de la visión por ordenador y crear experiencias interactivas directamente en el navegador del usuario.

Recuerda que el modelo Coco SSD que hemos utilizado es sólo un punto de partida. Con una mayor exploración, puedes profundizar en la detección de objetos personalizada utilizando TensorFlow.js, lo que te permitirá adaptar la aplicación para identificar objetos específicos relevantes para tus necesidades.

¡Las posibilidades son enormes! Esta aplicación te servirá de base para crear aplicaciones más detalladas, como experiencias de realidad aumentada o sistemas de vigilancia inteligentes. Al desplegar tu app en la fiable plataforma de Kinsta, podrás compartir tu creación con el mundo y ser testigo de cómo cobra vida el poder de la visión por ordenador.

¿Qué problema te has encontrado que crees que puede resolver la detección de objetos en tiempo real? ¡Háznoslo saber en los comentarios de abajo!

Kumar Harsh

Kumar is a software developer and a technical author based in India. He specializes in JavaScript and DevOps. You can learn more about his work on his website.