Detectar y filtrar imágenes de desnudos con JavaScript y NSFWJs

Te enseño a detectar y filtrar imágenes de desnudos con JavaScript y NSFWJs. Evita que dañen la reputación de tu app con material erótico.

En la última sección de esta publicación te enseño a implementar la librería en un proyecto real. Puedes hacer click en la siguiente imágen para ver el resultado acabado en una ventana nueva.

Ejercicio con NSFWjs. Haz click en la imágen para verlo en una ventana nueva.
Ejercicio con NSFWjs. Haz click en la imágen para verlo en una ventana nueva.

Si quieres saltar directamente a la parte práctica, haz click aquí

Una de las principales preocupaciones que aparecen cuando creas una aplicación web, es que los usuarios hagan un mal uso de ella.

Un claro ejemplo de esto puede darse al habilitar un campo de subida de imágenes en un formulario.

Aunque el objetivo de dicho campo sea, por ejemplo, permitir al usuario mostrar su “avatar” de perfil, nada nos garantiza que este lo use para publicar imágenes inapropiadas, de contenido erótico o directamente pornográficas.

Algo así puede dañar enormemente la reputación de la aplicación, e incluso provocar que otros usuarios se sientan incómodos, y abandonen la plataforma.

De hecho, redes sociales tan conocidas como Instagram o Facebook, tienen implementados filtros para evitar este tipo de publicaciones.

Por eso, hoy voy a echarle un vistazo a una librería JavaScript creada para ese mismo propósito:

NSFWjs la librería para detectar cuando los usuarios cuelgan fotos subidas de tono.

Evita que usuarios malintencionados suban contenido erótico a tu plataforma.
Evita que usuarios malintencionados suban contenido erótico a tu plataforma.

Así es, gracias a la biblioteca JavaScript NSFWjs (de las siglas en inglés Not Safe For Work) se puede identificar en qué medida una imagen muestra contenido explícito o inapropiado.

Modelo de machine learning pre-entrenado con TensorflowJs

NSFWjs no solo es interesante por lo que ofrece, sinó también por cómo ha sido creado, técnicamente hablando.

Lograr que un programa tenga esa capacidad de comprensión, sólo es posible gracias a aplicar técnicas de “machine learning” con herramientas como TensorflowJs.

Un modelo creado con TensorFlow.js es un modelo de aprendizaje automático que se ha entrenado en una tarea específica a partir de un conjunto de datos.

Estos modelos pre-entrenados se pueden utilizar para tareas como clasificación de imágenes, detección de objetos, procesamiento del lenguaje natural y más.

Y lo mejor es que se pueden ejecutar por igual en el navegador o en un entorno Node.js.

Sin lugar a dudas, un día dedicaré un post entero a TensorflowJs, pero de momento volvamos a NSFWjs.

Identificar imágenes de carácter erótico con JavaScript y NSFWjs

Vayamos a ver cómo implementar este recurso en nuestro proyecto.

Para ello, primero es necesario visitar el repositorio en GitHub de la librería.

https://github.com/infinitered/nsfwjs

El repositorio original pertenece al equipo del estudio Infinite Red, bajo licencia MIT.

Actualmente está valorada con 7.4k estrellas, y se descarga una media de 5488 veces por semana.

Es importante mencionar que ésta librería se puede ejecutar tanto en el navegador como en un servidor con Nodejs.

Su implementación difiere un poco de lo que estamos acostumbrados, pero verás que no es muy complejo.

Instalación y API de NSFWjs

El primer paso, como es habitual, será instalar la dependencia con el comando NPM.

npm i nsfwjs

Acto seguido, importamos el recurso.

import * as nsfwjs from "nsfwjs";

Al tratarse de un librería generada con TensorflowJs, es preciso cargar los archivos del modelo.

Puedes encontrarlos en el mismo repositorio.

https://github.com/infinitered/nsfwjs/tree/master/examples/nsfw_demo/public/quant_mid

En realidad existen tres tamaños de modelo, según la necesidad de tu aplicación.

  • El tamaño completo: Lo encontrarás en el directorio public/model. Se trata de la versión más fiable, pero también la más pesada.
  • Quant_mid: La versión mediana, es la que utilizaremos en el ejercicio del final del post.
  • Quant_nsfw_mobilenet: Es la más ligera, y por lo tanto, la menos fiable.

Para hacer uso de este modelo, debes descargarte los archivos y almacenarlos en tu servidor.

Después podrás cargarlos mediante el método “load()” de la instancia, y el parámetro de tipo de dato.

const model = await nsfwjs.load("./model/quant_mid/", { type: "graph" });

Date cuenta de que la carga del modelo se realiza de forma asíncrona, de modo que asegúrate de manejar su ejecución dentro de una función async / await.

Acto seguido ya podremos empezar a clasificar el contenido de etiquetas “img”, “canvas”, e incluso “video”.

Esta acción se hace mediante el método “classify()”, pasándole como parámetro una referencia a un elemento válido del DOM.

const image = document.querySelector(".image");
const predictions = await model.classify(image);

De nuevo, este método es asíncrono de modo que acuérdate de manejar al “promise” mediante “async / await”.

Esta función devolverá un array de cinco objetos.

console.log(predictions);
/*
[
    {
        "className": "Drawing",
        "probability": 0.7364396452903748
    },
    {
        "className": "Neutral",
        "probability": 0.26124143600463867
    },
    {
        "className": "Hentai",
        "probability": 0.0022574211470782757
    },
    {
        "className": "Sexy",
        "probability": 0.0000307871559925843
    },
    {
        "className": "Porn",
        "probability": 0.000030603547202190384
    }
]
*/

Cada objeto tiene una propiedad “className” con el nombre de la categoría.

Así como una propiedad “probability”, con un valor del 0 al 1, para determinar en qué medida se trata de una imagen de ese tipo.

Las cinco posibles categorías son:

  • Drawing: Ilustraciones y dibujos sin ningún contenido erótico.
  • Hentai: Ilustraciones y dibujos sin con contenido erótico, normalmente anime japonés.
  • Neutral: Imágenes y fotos sin contenido sexual o erótico.
  • Porn: Imágenes con contenido pornográfico.
  • Sexy: Imágenes con contenido sexual explícito, no pornográfico.

A partir del resultado arrojado por la librería, podrás decidir si evitas la carga de ese material en la aplicación.

Es importante mencionar que, tal como se indica en la página oficial, NSFWjs no es perfecto.

En líneas generales funciona muy bien, pero en ocasiones puede dar falsos positivos o no ser capaz detectar correctamente un contenido inapropiado.

Clasificar imágenes por su nivel de contenido erótico

Como siempre digo, la única forma de aprender realmente una herramienta nueva, es tratando de hacer uso de ella.

Así que vamos a crear un categorizador de imágenes NSFW.

En ésta guía desarrollaremos una sencilla aplicación web, que permita al usuario subir cualquier imagen, para indicarle si se trata de material pornográfico.

Te dejo un enlace al código del ejercicio terminado, por si te ayuda a seguir el tutorial.

https://github.com/Danivalldo/libreriasjs/tree/master/NSFWJs

Antes de empezar a programar, es necesario instalar en tu proyecto las dependencias necesarias.

npm i nsfwjs dropzone

Para mejorar la experiencia del usuario he instalado también el recurso Dropzone, un paquete para crear zonas para arrastrar y soltar archivos. No me detendré mucho a explicar esta biblioteca, pero verás que es sencilla de implementar.

Empezamos preparando la estructura HTML, compuesta por una cabecera, un cuerpo, una área de “loading” y un pié de página.

<div class="container">
  <div class="header">
    <h1>NSFWJs</h1>
  </div>
  <div class="main hidden">
    ...
  </div>
  <div class="loading">Cargando el modelo...</div>
  <div class="footer">
    <a href="https://libreriasjs.com" target="_blank">LibreriasJs.com</a>
  </div>
</div>

En el cuerpo de este layout, agregamos una zona para la subida de imágenes, una área para visualizar la última fotografía subida, y un área donde leer el resultado del análisis.

<div class="main hidden">
  <p>
    <b>NSFWJs</b> es una <b>libreria JavaScript</b> que permite verificar
    si una imagen es
    <b>pornográfica, con desnudos, hentai, sexy, etc.</b>
  </p>
  <div class="wrapper-dropzone-image">
    <div>
      <form action="/" id="my-form" class="dropzone">
        <span class="description">
          Haz click <b>aqui</b> o <b>arrastra una imagen</b> para
          verificar si es <b>NSFW</b><br />
          <i>(pornografía, desnudos, hentai, sexy, etc.)</i>
        </span>
      </form>
    </div>
    <div>
      <img
        alt=""
        class="preview-image"
        src="./imgs/placeholder_nsfwjs.png"
      />
    </div>
  </div>
  <div id="output" class="hidden"></div>
</div>

Cómo ves, en el área principal existe una etiqueta “form” necesaria para el funcionamiento de Dropzone.

También hay una imagen que actúa de “placeholder”, esa misma etiqueta la aprovecharemos para mostrar las imágenes que suba el usuario.

En la parte inferior, habilitamos una “div” que inicialmente estará oculta, posteriormente la haremos visible, para dar “feedback” al usuario.

Añadimos también un contenedor para informar al usuario que se está cargando el modelo.

<div class="loading">Cargando el modelo...</div>

Seguimos desarrollando la aplicación aplicando unos estilos CSS muy simples.

https://github.com/Danivalldo/libreriasjs/blob/master/NSFWJs/style.css

Por supuesto, te animo a alterar estos estilos para darle un acabado más personalizado. Puedes incluso importar una librería de estilos como, por ejemplo Tailwind, si quieres simplificar el trabajo.

Ya lo tenemos todo listo para programar el comportamiento con JavaScript y la librería NSFWjs.

Iniciamos el script del archivo main.js importando las dependencias.

import * as nsfwjs from "nsfwjs";
import Dropzone from "dropzone";
import "./style.css";

A continuación declaramos una función asíncrona “init” que invocamos inmediatamente.

const init = async () => {
/*...*/
};
init();

Es necesario hacerlo así ya que, como ya vimos, la carga del modelo es asíncrona, y no queremos que la app sea interactiva hasta cargar totalmente el modelo.

Guardamos en variables una serie de referencias a varios elementos de la página.

  const loading = document.querySelector(".loading");
  const mainContainer = document.querySelector(".main");
  const output = document.getElementById("output");
  const previewImage = document.querySelector(".preview-image");

Cargamos el modelo de forma asíncrona.

const model = await nsfwjs.load("./model/quant_mid/", { type: "graph" });

Una vez cargado, ocultamos el área de “loading” y mostramos la interfaz.

loading.classList.add("hidden");
mainContainer.classList.remove("hidden");

Habilitamos el componente DropZone para aceptar imágenes y mostrarlas en el área habilitada para ello.

const myDropzone = new Dropzone("#my-form", {
  autoProcessQueue: false,
  autoQueue: false,
  acceptedFiles: "image/*",
  maxFiles: 1,
  disablePreviews: true,
  init: function () {
    this.on("maxfilesexceeded", function (file) {
      this.removeAllFiles();
      this.addFile(file);
    });
  },
});
myDropzone.on("addedfile", (file) => {
  previewImage.src = URL.createObjectURL(file);
});

Finalmente, escuchamos cuando una nueva imágen ha sido seleccionada y lanzamos el método “model.classify()” pasándole la referencia al DOM de la etiqueta img.

Por último, actualizamos el contenido del área “output” para mostrar al usuario los resultados del análisis.

previewImage.addEventListener("load", async () => {
  const predictions = await model.classify(previewImage);
  console.log(predictions);
  output.classList.remove("hidden");
  output.innerHTML = `
    <ul>
      ${predictions
        .map((prediction) => {
          return `<li><b>${
            prediction.className
          }</b>: ${prediction.probability.toFixed(5)}</li>`;
        })
        .join("")}
    <ul>
  `;
});

Con esto todo debería funcionar correctamente. 

Por razones obvias no voy a proveerte de fotografías para testear la aplicación, estoy seguro de que serás perfectamente capaz de encontrar material de sobra por tí mismo ;).

Protege a tus usuarios controlando el contenido que se publica en tus apps

Espero que esta publicación te sirva para hacer tus aplicaciones más seguras y confiables.

En otra ocasión estuve analizando la librería Yup para la validación de campos de formularios web.

Yup y el recurso que hemos visto hoy pueden ir de la mano en muchos proyectos, así que te animo a que le eches un vistazo. 

Validar formularios con Yup y JavaScript

También te dejo un listado de otros recursos que te ayudarán en tu aprendizaje.

Un saludo y hasta la próxima.

Deja un comentario