Нужна рекомендация по поводу кода, потому что он убивает мою программу - PullRequest
0 голосов
/ 06 ноября 2019

) Итак, начнем. Я хочу реализовать следующую идею: я хочу соединиться с другим пользователем на другом компьютере, используя webrtc (обмениваться видео и аудио данными), а затем распознать его эмоции. Поэтому в этом проекте я использую node-webrtc addon (здесь examples ). Итак, я скачал примеры и протестировал пример видео-композитинга, и все работает отлично. Вот результат тестирования

Следующая часть - узнавать эмоции лица. Для этой задачи я использую face-api.js . Я проверил это хорошее видео . Я не буду прикреплять фото, потому что сейчас я использую Ubuntu, но проверил его на Windows, просто поверьте мне, все тоже отлично работает. Настало время объединить оба модуля.

В качестве основного проекта я использую примеры node-webrtc, все последующие объяснения будут касаться этого модуля. Поэтому, чтобы запустить результат, вы должны скопировать папку weights из face-api в папку node-webrtc / examples / video-compositing, а затем просто заменить приведенный ниже код вместо node-webrtc / example / video-compositing / server.js.

'use strict';

require('@tensorflow/tfjs-node');
const tf = require('@tensorflow/tfjs');
const nodeFetch = require('node-fetch');
const fapi = require('face-api.js');
const path = require('path');
const { createCanvas, createImageData } = require('canvas');
const { RTCVideoSink, RTCVideoSource, i420ToRgba, rgbaToI420 } = require('wrtc').nonstandard;


fapi.env.monkeyPatch({ fetch: nodeFetch });
const MODELS_URL = path.join(__dirname, '/weights');

const width = 640;
const height = 480;

Promise.all([
  fapi.nets.tinyFaceDetector.loadFromDisk(MODELS_URL),
  fapi.nets.faceLandmark68Net.loadFromDisk(MODELS_URL),
  fapi.nets.faceRecognitionNet.loadFromDisk(MODELS_URL),
  fapi.nets.faceExpressionNet.loadFromDisk(MODELS_URL)
]);

function beforeOffer(peerConnection) {
  const source = new RTCVideoSource();
  const track = source.createTrack();
  const transceiver = peerConnection.addTransceiver(track);
  const sink = new RTCVideoSink(transceiver.receiver.track);

  let lastFrame = null;

  function onFrame({ frame }) {
    lastFrame = frame;
  }

  sink.addEventListener('frame', onFrame);

  // TODO(mroberts): Is pixelFormat really necessary?
  const canvas = createCanvas(width, height);
  const context = canvas.getContext('2d', { pixelFormat: 'RGBA24' });
  context.fillStyle = 'white';
  context.fillRect(0, 0, width, height);

  let emotion = '';
  const interval = setInterval(() => {
    if (lastFrame) {
      const lastFrameCanvas = createCanvas(lastFrame.width,  lastFrame.height);
      const lastFrameContext = lastFrameCanvas.getContext('2d', { pixelFormat: 'RGBA24' });

      const rgba = new Uint8ClampedArray(lastFrame.width *  lastFrame.height * 4);
      const rgbaFrame = createImageData(rgba, lastFrame.width, lastFrame.height);
      i420ToRgba(lastFrame, rgbaFrame);

      lastFrameContext.putImageData(rgbaFrame, 0, 0);
      context.drawImage(lastFrameCanvas, 0, 0);

      const emotionsArr = { 0: 'neutral', 1: 'happy', 2: 'sad', 3: 'angry', 4: 'fearful', 5: 'disgusted', 6: 'surprised' };

      async function detectEmotion() {
        let frameTensor3D = tf.browser.fromPixels(lastFrameCanvas)
        let face = await fapi.detectSingleFace(frameTensor3D, new fapi.TinyFaceDetectorOptions()).withFaceExpressions();
        //console.log(face);
        function getEmotion(face) {
          try {
            let mostLikelyEmotion = emotionsArr[0];
            let predictionArruracy =  face.expressions[emotionsArr[0]];

            for (let i = 0; i < Object.keys(face.expressions).length; i++) {
              if (face.expressions[emotionsArr[i]] > predictionArruracy && face.expressions[emotionsArr[i]] < 1 ){
                mostLikelyEmotion = emotionsArr[i];
                predictionArruracy = face.expressions[emotionsArr[i]];
              }
            }

            return mostLikelyEmotion;
          }
          catch (e){
            return '';
          }
        }
        let emot = getEmotion(face);
        return emot;
      }


      detectEmotion().then(function(res) {
        emotion = res;
      });

    } else {
      context.fillStyle = 'rgba(255, 255, 255, 0.025)';
      context.fillRect(0, 0, width, height);
    }

    if (emotion != ''){
      context.font = '60px Sans-serif';
      context.strokeStyle = 'black';
      context.lineWidth = 1;
      context.fillStyle = `rgba(${Math.round(255)}, ${Math.round(255)}, ${Math.round(255)}, 1)`;
      context.textAlign = 'center';
      context.save();
      context.translate(width / 2, height);
      context.strokeText(emotion, 0, 0);
      context.fillText(emotion, 0, 0);
      context.restore();
    }


    const rgbaFrame = context.getImageData(0, 0, width, height);
    const i420Frame = {
      width,
      height,
      data: new Uint8ClampedArray(1.5 * width * height)
    };
    rgbaToI420(rgbaFrame, i420Frame);
    source.onFrame(i420Frame);
  });

  const { close } = peerConnection;
  peerConnection.close = function() {
    clearInterval(interval);
    sink.stop();
    track.stop();
    return close.apply(this, arguments);
  };
}

module.exports = { beforeOffer };

А вот и результаты1 , результаты2 и результаты3 , все отлично работает)) ... Ну нет, через 2-3 минутымой компьютер просто перестал делать что-либо, я даже не могу пошевелить мышью, а затем я получаю сообщение об ошибке "Killed" в терминале. Я читал об этой ошибке здесь и, поскольку я изменил только один скрипт в проекте, я подозреваю, что где-то в моем коде у меня есть утечка данных, и моя оперативная память заполняется со временем. Может кто-нибудь помочь мне с этим вопросом? Почему программа заканчивается процессом убийства? Если кто-то захочет проверить это для себя, я оставлю пакет json, чтобы легко установить все требования.

{
  "name": "node-webrtc-examples",
  "version": "0.1.0",
  "description": "This project presents a few example applications using node-webrtc.",
  "private": true,
  "main": "index.js",
  "scripts": {
    "lint": "eslint index.js examples lib test",
    "start": "node index.js",
    "test": "npm run test:unit && npm run test:integration",
    "test:unit": "tape 'test/unit/**/*.js'",
    "test:integration": "tape 'test/integration/**/*.js'"
  },
  "keywords": [
    "Web",
    "Audio"
  ],
  "author": "Mark Andrus Roberts <markandrusroberts@gmail.com>",
  "license": "BSD-3-Clause",
  "dependencies": {
    "@tensorflow/tfjs": "^1.2.9",
    "@tensorflow/tfjs-core": "^1.2.9",
    "@tensorflow/tfjs-node": "^1.2.9",
    "Scope": "github:kevincennis/Scope",
    "body-parser": "^1.18.3",
    "browserify-middleware": "^8.1.1",
    "canvas": "^2.6.0",
    "color-space": "^1.16.0",
    "express": "^4.16.4",
    "face-api.js": "^0.21.0",
    "node-fetch": "^2.3.0",
    "uuid": "^3.3.2",
    "wrtc": "^0.4.1"
  },
  "devDependencies": {
    "eslint": "^5.15.1",
    "tape": "^4.10.0"
  }
}

Если у вас есть ошибка типа «someFunction is not function» или что-то в этом роде, вероятно, это может быть потому, чтовам нужно установить версию @ tenorflow / tfjs-core, tfjs и tfjs-node 1.2.9. Например, npm i @ tenorflow / tfjs-core @ 1.2.9. Для всех 3 пакетов. Спасибо за ваши ответы и понимание))

1 Ответ

0 голосов
/ 07 ноября 2019

Я работал с faceapi.js и tenorflow.js в течение этого года, я проверил ваш код и все в порядке, но увеличил мою оперативную память до 2 ГБ менее чем за одну минуту, у вас утечка памяти, при использовании Tensor вы должны освободитьПамять? Как это сделать?

Однако вы должны использовать аргумент --inspect в узле для проверки утечки памяти

Только вызов:

  frameTensor3D.dispose();

Iрефакторинг вашего кода и поделиться с вами, я надеюсь помочь вам:

    "use strict";

require("@tensorflow/tfjs-node");
const tf = require("@tensorflow/tfjs");
const nodeFetch = require("node-fetch");
const fapi = require("face-api.js");
const path = require("path");
const { createCanvas, createImageData } = require("canvas");
const {
  RTCVideoSink,
  RTCVideoSource,
  i420ToRgba,
  rgbaToI420
} = require("wrtc").nonstandard;

fapi.env.monkeyPatch({ fetch: nodeFetch });
const MODELS_URL = path.join(__dirname, "/weights");

const width = 640;
const height = 480;

Promise.all([
  fapi.nets.tinyFaceDetector.loadFromDisk(MODELS_URL),
  fapi.nets.faceLandmark68Net.loadFromDisk(MODELS_URL),
  fapi.nets.faceRecognitionNet.loadFromDisk(MODELS_URL),
  fapi.nets.faceExpressionNet.loadFromDisk(MODELS_URL)
]);

function beforeOffer(peerConnection) {
  const source = new RTCVideoSource();
  const track = source.createTrack();
  const transceiver = peerConnection.addTransceiver(track);
  const sink = new RTCVideoSink(transceiver.receiver.track);

  let lastFrame = null;

  function onFrame({ frame }) {
    lastFrame = frame;
  }

  sink.addEventListener("frame", onFrame);

  // TODO(mroberts): Is pixelFormat really necessary?
  const canvas = createCanvas(width, height);
  const context = canvas.getContext("2d", { pixelFormat: "RGBA24" });
  context.fillStyle = "white";
  context.fillRect(0, 0, width, height);
  const emotionsArr = {
    0: "neutral",
    1: "happy",
    2: "sad",
    3: "angry",
    4: "fearful",
    5: "disgusted",
    6: "surprised"
  };
  async function detectEmotion(lastFrameCanvas) {
    const frameTensor3D = tf.browser.fromPixels(lastFrameCanvas);
    const face = await fapi
      .detectSingleFace(
        frameTensor3D,
        new fapi.TinyFaceDetectorOptions({ inputSize: 160 })
      )
      .withFaceExpressions();
    //console.log(face);
    const emo = getEmotion(face);
    frameTensor3D.dispose();
    return emo;
  }
  function getEmotion(face) {
    try {
      let mostLikelyEmotion = emotionsArr[0];
      let predictionArruracy = face.expressions[emotionsArr[0]];

      for (let i = 0; i < Object.keys(face.expressions).length; i++) {
        if (
          face.expressions[emotionsArr[i]] > predictionArruracy &&
          face.expressions[emotionsArr[i]] < 1
        ) {
          mostLikelyEmotion = emotionsArr[i];
          predictionArruracy = face.expressions[emotionsArr[i]];
        }
      }
      //console.log(mostLikelyEmotion);
      return mostLikelyEmotion;
    } catch (e) {
      return "";
    }
  }
  let emotion = "";
  const interval = setInterval(() => {
    if (lastFrame) {
      const lastFrameCanvas = createCanvas(lastFrame.width, lastFrame.height);
      const lastFrameContext = lastFrameCanvas.getContext("2d", {
        pixelFormat: "RGBA24"
      });

      const rgba = new Uint8ClampedArray(
        lastFrame.width * lastFrame.height * 4
      );
      const rgbaFrame = createImageData(
        rgba,
        lastFrame.width,
        lastFrame.height
      );
      i420ToRgba(lastFrame, rgbaFrame);

      lastFrameContext.putImageData(rgbaFrame, 0, 0);
      context.drawImage(lastFrameCanvas, 0, 0);

      detectEmotion(lastFrameCanvas).then(function(res) {
        emotion = res;
      });
    } else {
      context.fillStyle = "rgba(255, 255, 255, 0.025)";
      context.fillRect(0, 0, width, height);
    }

    if (emotion != "") {
      context.font = "60px Sans-serif";
      context.strokeStyle = "black";
      context.lineWidth = 1;
      context.fillStyle = `rgba(${Math.round(255)}, ${Math.round(
        255
      )}, ${Math.round(255)}, 1)`;
      context.textAlign = "center";
      context.save();
      context.translate(width / 2, height);
      context.strokeText(emotion, 0, 0);
      context.fillText(emotion, 0, 0);
      context.restore();
    }

    const rgbaFrame = context.getImageData(0, 0, width, height);
    const i420Frame = {
      width,
      height,
      data: new Uint8ClampedArray(1.5 * width * height)
    };
    rgbaToI420(rgbaFrame, i420Frame);
    source.onFrame(i420Frame);
  });

  const { close } = peerConnection;
  peerConnection.close = function() {
    clearInterval(interval);
    sink.stop();
    track.stop();
    return close.apply(this, arguments);
  };
}

module.exports = { beforeOffer };

Извините мой английский, удачи

...