Estimar pose cuerpo humano

Para este tutorial veremos como estimar la pose de un cuerpo humano, es decir, trataremos de localizar las posiciones en las que encuentra cada una de las partes del cuerpo, brazos, piernas, cabeza, tronco, etc., para esto utilizaremos PoseNet que trabaja sobre Tensorflow, esta vez lo haremos desde NodeJS.

PoseNet puede extraer las posiciones ya sea si se proporciona una una imagen que contenga un solo cuerpo o varios en la misma imagen, para esto tenemos métodos diferentes que veremos mas adelante.

Cargar modelo pre entrenado

Para esto utilizaremos el método load(multiplier) enviándole el parámetro indicado, este establece la versión del modelo que deseamos utilizar para la predicción, puede ser: 1.0, 0.75, 0.50, por defecto es 0.75, este es recomendable para versiones de hardware de rango medio, si tienes pocos recursos deberías utilizar 0.50 y si cuentas con hardware potente pues 1.0, cuanto mayor sea este valor mejor nuestras predicciones pero en contra requieren mayor tiempo de cómputo.

const net = await posenet.load(multiplier);

Estimar pose un solo cuerpo

El método estimateSinglePose(...) nos servirá para detectar las posiciones de un solo cuerpo presente en la imagen de entrada, deberemos pasar los siguientes parámetros:

  • image - Imagen de entrada para la red neuronal.
  • imageScaleFactor - Un número entre 0.2 y 1.0, el valor predeterminado es 0.50, usaremos este valor para reducir el tamaño de la imagen y así aumentar la velocidad de procesamiento pero disminuimos la precisión.
  • flipHorizontal - Valor predeterminado es falso, indica si las poses deben voltearse o reflejarse horizontalmente.
  • outputStride - Paso deseado para las salidas cuando se alimenta la imagen a través del modelo, puede ser 32, 16, 8. El valor predeterminado es 16, cuanto mayor sea el número, más rápido será el rendimiento pero más lenta será la precisión, y viceversa.
const pose = await net.estimateSinglePose(image, imageScaleFactor, flipHorizontal, outputStride);

El resultado devuelto tiene la siguiente estructura:

{
  "score": 0.32371445304906,
  "keypoints": [
    {
      "position": {
        "y": 76.291801452637,
        "x": 253.36747741699
      },
      "part": "nose",
      "score": 0.99539834260941
    },
    {
      "position": {
        "y": 71.10383605957,
        "x": 253.54365539551
      },
      "part": "leftEye",
      "score": 0.98781454563141
    },
    {
      "position": {
        "y": 71.839515686035,
        "x": 246.00454711914
      },
      "part": "rightEye",
      "score": 0.99528175592422
    },
    {
      "position": {
        "y": 72.848854064941,
        "x": 263.08151245117
      },
      "part": "leftEar",
      "score": 0.84029853343964
    },
    {
      "position": {
        "y": 79.956565856934,
        "x": 234.26812744141
      },
      "part": "rightEar",
      "score": 0.92544466257095
    },
    {
      "position": {
        "y": 98.34538269043,
        "x": 399.64068603516
      },
      "part": "leftShoulder",
      "score": 0.99559044837952
    },
    {
      "position": {
        "y": 95.082359313965,
        "x": 458.21868896484
      },
      "part": "rightShoulder",
      "score": 0.99583911895752
    },
    {
      "position": {
        "y": 94.626205444336,
        "x": 163.94561767578
      },
      "part": "leftElbow",
      "score": 0.9518963098526
    },
    {
      "position": {
        "y": 150.2349395752,
        "x": 245.06030273438
      },
      "part": "rightElbow",
      "score": 0.98052614927292
    },
    {
      "position": {
        "y": 113.9603729248,
        "x": 393.19735717773
      },
      "part": "leftWrist",
      "score": 0.94009721279144
    },
    {
      "position": {
        "y": 186.47859191895,
        "x": 257.98034667969
      },
      "part": "rightWrist",
      "score": 0.98029226064682
    },
    {
      "position": {
        "y": 208.5266418457,
        "x": 284.46710205078
      },
      "part": "leftHip",
      "score": 0.97870296239853
    },
    {
      "position": {
        "y": 209.9910736084,
        "x": 243.31219482422
      },
      "part": "rightHip",
      "score": 0.97424703836441
    },
    {
      "position": {
        "y": 281.61965942383,
        "x": 310.93188476562
      },
      "part": "leftKnee",
      "score": 0.98368924856186
    },
    {
      "position": {
        "y": 282.80120849609,
        "x": 203.81164550781
      },
      "part": "rightKnee",
      "score": 0.96947449445724
    },
    {
      "position": {
        "y": 360.62716674805,
        "x": 292.21047973633
      },
      "part": "leftAnkle",
      "score": 0.8883239030838
    },
    {
      "position": {
        "y": 347.41177368164,
        "x": 203.88229370117
      },
      "part": "rightAnkle",
      "score": 0.8255187869072
    }
  ]
}

Estimar pose varios cuerpos

El método estimateMultiplePoses(...) nos permite obtener las posiciones de todos los cuerpos que estén presentes en la imagen de entrada, los parámetros a utilizar son los mismo que en el método anterior mas tres extras que son opcionales, estos son:

  • maxPoseDetections: el número máximo de poses a detectar. El valor predeterminado es 5.
  • scoreThreshold: solo devuelve las detecciones de instancia que tienen una puntuación mayor o igual a este valor. Por defecto es 0.5.
  • nmsRadius: Necesita ser estrictamente positivo. Dos partes se suprimen entre sí si están a menos de nmsRadius píxeles de distancia. El valor predeterminado es 20.
const poses = await net.estimateMultiplePoses(image, imageScaleFactor, flipHorizontal, outputStride, maxPoseDetections, scoreThreshold, nmsRadius);

El resultado se estructura de la siguiente manera:

[
  // pose 1
  {
    // pose score
    "score": 0.42985695206067,
    "keypoints": [
      {
        "position": {
          "x": 126.09371757507,
          "y": 97.861720561981
        },
        "part": "nose",
        "score": 0.99710708856583
      },
      {
        "position": {
          "x": 132.53466176987,
          "y": 86.429876804352
        },
        "part": "leftEye",
        "score": 0.99919074773788
      },
      {
        "position": {
          "x": 100.85626316071,
          "y": 84.421931743622
        },
        "part": "rightEye",
        "score": 0.99851280450821
      },

      ...

      {
        "position": {
          "x": 72.665352582932,
          "y": 493.34189963341
        },
        "part": "rightAnkle",
        "score": 0.0028593824245036
      }
    ],
  },
  // pose 2
  {

    // pose score
    "score": 0.13461434583673,
    "keypoints": [
      {
        "position": {
          "x": 116.58444058895,
          "y": 99.772533416748
        },
        "part": "nose",
        "score": 0.0028593824245036
      }
      {
        "position": {
          "x": 133.49897611141,
          "y": 79.644590377808
        },
        "part": "leftEye",
        "score": 0.99919074773788
      },
      {
        "position": {
          "x": 100.85626316071,
          "y": 84.421931743622
        },
        "part": "rightEye",
        "score": 0.99851280450821
      },

      ...

      {
        "position": {
          "x": 72.665352582932,
          "y": 493.34189963341
        },
        "part": "rightAnkle",
        "score": 0.0028593824245036
      }
    ],
  },
  // pose 3
  {
    // pose score
    "score": 0.13461434583673,
    "keypoints": [
      {
        "position": {
          "x": 116.58444058895,
          "y": 99.772533416748
        },
        "part": "nose",
        "score": 0.0028593824245036
      }
      {
        "position": {
          "x": 133.49897611141,
          "y": 79.644590377808
        },
        "part": "leftEye",
        "score": 0.99919074773788
      },

      ...

      {
        "position": {
          "x": 59.334579706192,
          "y": 485.5936152935
        },
        "part": "rightAnkle",
        "score": 0.004110524430871
      }
    ]
  }
]

Estimar pose Node + PoseNet

Vamos a crear un pequeño ejemplo en donde detectaremos la pose de un cuerpo presente en un imagen, para ello usaremos Node aunque también se puede hacer con JS desde el navegador, primero le dejo el package.json para nuestro proyecto de ejemplo:

{
  "name": "posenet",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "@tensorflow-models/posenet": "^1.0.0",
    "@tensorflow/tfjs": "^1.0.1",
    "canvas": "^2.3.1",
    "xhr2": "^0.1.4",
    "xmlhttprequest": "^1.8.0"
  }
}

Seguido el código de ejemplo, el mismo usa canvas para leer una imagen y enviarla a la red neuronal, una vez obtengamos las posiciones marcamos los puntos y los conectamos con lineas para visualizarlos mejor.

global.XMLHttpRequest = require("xhr2");

const fs = require('fs');

const posenet = require('@tensorflow-models/posenet');

const { Image } = require('canvas')
const { createCanvas } = require('canvas')
const { loadImage } = require('canvas')


const imageScaleFactor = 1.0;
const outputStride     = 16;
const flipHorizontal   = false;

function predictPose(img, mul) {

	const canvas = createCanvas(512, 512)
	const ctx = canvas.getContext('2d')

	ctx.drawImage(img, 0, 0, canvas.width, canvas.height);

	posenet.load(mul).then(net => {

		console.log('Net loaded!');

		net.estimateSinglePose(canvas, imageScaleFactor, flipHorizontal, outputStride).then(pose => {
	
			for(var i = 0; i < pose.keypoints.length; i++) {

				var kp = pose.keypoints[i];
				var pos = kp.position;

				ctx.fillStyle = "#00FF00";
				ctx.beginPath();
				ctx.arc(pos.x, pos.y, 4, 0, 2 * Math.PI);
				ctx.fill();

				//ctx.fillText(kp.part, pos.x, pos.y);
	
				console.log('> Pos (X, Y): ' + pos.x + ', ' + pos.y);
				console.log('> Score: ' + kp.score + ', Part: ' + kp.part + '\n');
			}

			ctx.strokeStyle = "#00FF00";

			drawLine(ctx, pose, 6, 8);
			drawLine(ctx, pose, 8, 10);

			drawLine(ctx, pose, 5, 7);
			drawLine(ctx, pose, 7, 9);

			drawLine(ctx, pose, 11, 13);
			drawLine(ctx, pose, 13, 15);

			drawLine(ctx, pose, 12, 14);
			drawLine(ctx, pose, 14, 16);

			fs.writeFileSync('./images/pose_2.jpg', canvas.toBuffer())
		});

	});
}

function drawLine(ctx, pose, p1, p2) {

	var pt1 = pose.keypoints[p1].position;
	var pt2 = pose.keypoints[p2].position;

	ctx.beginPath();
	ctx.moveTo(pt1.x, pt1.y);
	ctx.lineTo(pt2.x, pt2.y);
	ctx.stroke();
}

loadImage('./images/test_2.jpg').then(img => predictPose(img, 0.75))

Probando nuestro código obtenemos la siguiente salida:

PoseNet NodeJS

Podemos aplicar lo mismo para trabajar sobre varios cuerpos solo debemos tener presente que el método estimateMultiplePoses(...) devolverá un conjunto de poses iguales al que tenemos en el ejemplo previo.

Descargar código: estimar_pose.zip

Comentarios

  1. Definitivamente, la estimación de la pose del cuerpo humano es un campo fascinante y fundamental en visión por computadora. Al integrar esta funcionalidad, estás abriendo las puertas a una amplia gama de aplicaciones, desde análisis de movimiento hasta interacciones persona-máquina más intuitivas. Además, al agregar
    servicios de ciberseguridad esta aplicación, demuestras un compromiso serio con la protección de la privacidad y la integridad de los datos. La seguridad es una consideración esencial en cualquier aplicación tecnológica, y tu enfoque en implementar medidas de ciberseguridad refleja una mentalidad proactiva y responsable. La combinación de la estimación de la pose del cuerpo humano y la ciberseguridad no solo proporciona una solución técnica avanzada, sino que también garantiza que la experiencia del usuario sea segura y confiable. Este enfoque integral es clave en un mundo digital en constante evolución. En resumen, tu trabajo en estimar la pose del cuerpo humano y fortalecer la seguridad demuestra un equilibrio admirable entre innovación técnica y responsabilidad en la gestión de datos. ¡Excelente trabajo!

    ResponderEliminar

Publicar un comentario

Temas relacionados

Entradas populares de este blog

tkinter Grid

Controles y Contenedores JavaFX 8 - I

Conectar SQL Server con Java

tkinter Canvas