Анализ эмоций лица с помощью WebRTC и Python-сервера - PullRequest
1 голос
/ 18 октября 2019

У меня есть проект, в котором я хочу выполнить живое распознавание эмоций на лице.

Клиент открывает веб-страницу, и изображение его лица снимается камерой (встроенная камера ноутбука), затемэто видео пишется на холсте (элемент HTML), затем преобразуется в BLOB и отправляется через веб-сокет Python на серверную часть Python. Там - мне нужно выполнить анализ эмоций с помощью другого скрипта Python (который принимает изображения в качестве входных данных) - поэтому мне нужно преобразовать этот BLOB BACK TO IMAGES , и я не знаю, как это сделать правильно, яЯ получаю ошибку по ошибке, и, поскольку я новичок в Python, я не знаю, как я мог решить эту задачу.

Пока мне удалось открыть веб-сокет, отправить этот BLOB-объект в режиме реального времени, но затем происходит какая-то магия, и все рушится - возможно, что-то не так с тем, как я помещаю функции в циклы, а также с тем, как япреобразовать данные изображения.

Фрагмент HTML:

<div class="booth">
            <video id="video" width="320" height="240"></video>
            <canvas width="320" id="canvas" height="240" style="display: inline;"></canvas>
        </div>
    <script src="canvas.js"></script>

canvas.js:

(function () {
    var video = document.getElementsByTagName("video")[0];
    var canvas = document.getElementsByTagName("canvas");
    var ctx = canvas[0].getContext('2d');

    navigator.getMedia = navigator.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia ||
        navigator.msGetUserMedia;

    navigator.getMedia({
        video: true,
        audio: false,
    }, function (stream) {
        console.log("I'm in live function")
        console.log(stream);
        video.srcObject = stream;
        video.play();
    }, function (error) {
        console.log("Error in live" + error)
        error.code
    });
    function dataURItoBlob(dataURI) {
        // convert base64/URLEncoded data component to raw binary data held in a string
        var byteString;
        if (dataURI.split(',')[0].indexOf('base64') >= 0)
            byteString = atob(dataURI.split(',')[1]);
        else
            byteString = unescape(dataURI.split(',')[1]);

        // separate out the mime component
        var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];

        // write the bytes of the string to a typed array
        var ia = new Uint8Array(byteString.length);
        for (var i = 0; i < byteString.length; i++) {
            ia[i] = byteString.charCodeAt(i);
        }

        return new Blob([ia], { type: mimeString });
    }
    var ws = new WebSocket(" ws://127.0.0.1:5678/");
        ws.onopen = function () {
              console.log("Openened connection to websocket");
    }
    timer = setInterval(
        function () {
            ctx.drawImage(video, 0, 0, 320, 240);
            var data = canvas[0].toDataURL('image/jpeg', 1.0);
            newblob = dataURItoBlob(data);
            ws.send(newblob);
        }, 100);


})();

web_socket_server.py:

async def time(websocket, path):
    detection_model_path = r'C:\Users\karol\face_recognition\haarcascade_frontalface_default.xml'

    emotion_model_path = r'C:\Users\karol\face_recognition\models_mini_XCEPTION.88-0.65.hdf5'

    face_detection = cv2.CascadeClassifier(detection_model_path)

    emotion_classifier = load_model(emotion_model_path, compile=False)

    EMOTIONS = ["angry", "disgust", "scared", "happy", "sad", "surprised",

                "neutral"]
    # starting video streaming
    while True:
        message = await websocket.recv()
        print(f"We got message from the client!")
        print (message)
        #images = message.decode('base64')
        #message = base64.encodebytes(message)
        #print(message)
        face_rec.emotion_detection(message, face_detection, emotion_classifier, EMOTIONS)

start_server = websockets.serve(time, "127.0.0.1", 5678)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

face_rec.py:

def decode_base64(data, altchars=b'+/'):
    data = re.sub(rb'[^a-zA-Z0-9%s]+' % altchars, b'', data)  # normalize
    missing_padding = len(data) % 4
    if missing_padding:
        data += b'='* (4 - missing_padding)
    return base64.b64decode(data, altchars)

def stringToRGB(base64_string):
    imgdata = decode_base64(base64_string)
    image = Image.open(io.BytesIO(imgdata)) //HERE I GET THE ERROR "cannot identify image file %r(filename if filename else fp))" 
    return cv2.cvtColor(np.array(image), cv2.COLOR_BGR2RGB)

def emotion_detection(bytes, face_detection, emotion_classifier, EMOTIONS):

    while True:

        image = stringToRGB(bytes)
        frame = imutils.resize(image, width=400) 
        gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)

        faces = face_detection.detectMultiScale(gray, scaleFactor=1.1, minNeighbors=5, minSize=(30, 30),
                                                flags=cv2.CASCADE_SCALE_IMAGE)

        canvas = np.zeros((250, 300, 3), dtype="uint8")

        frameClone = frame.copy()

        if len(faces) > 0:
            faces = sorted(faces, reverse=True,

                           key=lambda x: (x[2] - x[0]) * (x[3] - x[1]))[0]

            (fX, fY, fW, fH) = faces

            # Extract the ROI of the face from the grayscale image, resize it to a fixed 48x48 pixels, and then prepare

            # the ROI for classification via the CNN

            roi = gray[fY:fY + fH, fX:fX + fW]

            roi = cv2.resize(roi, (48, 48))

            roi = roi.astype("float") / 255.0

            roi = img_to_array(roi)

            roi = np.expand_dims(roi, axis=0)

            preds = emotion_classifier.predict(roi)[0]

            emotion_probability = np.max(preds)

            label = EMOTIONS[preds.argmax()]

        for (i, (emotion, prob)) in enumerate(zip(EMOTIONS, preds)):
            # construct the label text

            text = "{}: {:.2f}%".format(emotion, prob * 100)

            w = int(prob * 300)

            cv2.rectangle(canvas, (7, (i * 35) + 5),

                          (w, (i * 35) + 35), (0, 0, 255), -1)

            cv2.putText(canvas, text, (10, (i * 35) + 23),

                        cv2.FONT_HERSHEY_SIMPLEX, 0.45,

                        (255, 255, 255), 2)

            cv2.putText(frameClone, label, (fX, fY - 10),

                        cv2.FONT_HERSHEY_SIMPLEX, 0.45, (0, 0, 255), 2)

            cv2.rectangle(frameClone, (fX, fY), (fX + fW, fY + fH),

                          (0, 0, 255), 2)

        cv2.imshow('your_face', frameClone)

        cv2.imshow("Probabilities", canvas)

        if cv2.waitKey(1) & 0xFF == ord('q'):
            break

    #camera.release()

cv2.destroyAllWindows()

Я знаю, что это много кода, но я хотел дать более широкое представление о том, что я хочу делать. Пожалуйста, если кто-нибудь может дать мне подсказку, как мне правильно справиться с этим преобразованием, дайте мне знать;)

Ответы [ 2 ]

2 голосов
/ 21 октября 2019

Я сделал аналогичный проект для webrtcHacks с Tensorflow . Веб-сокеты, безусловно, быстрее, и фреймворк, такой как asyncio, делает вещи проще, но я хотел сделать что-то более простое для демонстрационных целей без использования фреймворков.

Я использовал getUserMedia для получения видеопотока:

// Get camera video
const constraints = {
    audio: false,
    video: {
        width: {min: 640, ideal: 1280, max: 1920},
        height: {min: 480, ideal: 720, max: 1080}
    }
};

navigator.mediaDevices.getUserMedia(constraints)
    .then(stream => {
        document.getElementById("myVideo").srcObject = stream;
        console.log("Got local user video");

    })
    .catch(err => {
        console.log('navigator.getUserMedia error: ', err)
    });

И использовал холст для захвата изображения:

// Start object detection
function startObjectDetection() {

    console.log("starting object detection");

    // Set canvas sizes base don input video
    drawCanvas.width = v.videoWidth;
    drawCanvas.height = v.videoHeight;

    imageCanvas.width = uploadWidth;
    imageCanvas.height = uploadWidth * (v.videoHeight / v.videoWidth);

    // Some styles for the drawcanvas
    drawCtx.lineWidth = 4;
    drawCtx.strokeStyle = "cyan";
    drawCtx.font = "20px Verdana";
    drawCtx.fillStyle = "cyan";

    //Save and send the first image
    imageCtx.drawImage(v, 0, 0, v.videoWidth, v.videoHeight, 0, 0, uploadWidth, uploadWidth * (v.videoHeight / v.videoWidth));
    imageCanvas.toBlob(postFile, 'image/jpeg');

}

И затем использовал XHR для отправки этого изображения на мой сервер Flask:

// Add file blob to a form and post
function postFile(file) {

    //Set options as form data
    let formdata = new FormData();
    formdata.append("image", file);
    formdata.append("threshold", scoreThreshold);

    let xhr = new XMLHttpRequest();
    xhr.open('POST', apiServer, true);
    xhr.onload = function () {
        if (this.status === 200) {
            let objects = JSON.parse(this.response);

            //draw the boxes
            drawBoxes(objects);

            //Save and send the next image
            imageCtx.drawImage(v, 0, 0, v.videoWidth, v.videoHeight, 0, 0, uploadWidth, uploadWidth * (v.videoHeight / v.videoWidth));
            imageCanvas.toBlob(postFile, 'image/jpeg');
        }
        else {
            console.error(xhr);
        }
    };
    xhr.send(formdata);
}

Мой сервер Flask тогдапроанализируйте изображение:

import object_detection_api
import os
from PIL import Image
from flask import Flask, request, Response

app = Flask(__name__)

# for CORS
@app.after_request
def after_request(response):
    response.headers.add('Access-Control-Allow-Origin', '*')
    response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
    response.headers.add('Access-Control-Allow-Methods', 'GET,POST') # Put any other methods you need here
    return response

@app.route('/image', methods=['POST'])
def image():
    try:
        image_file = request.files['image']  # get the image

        # Set an image confidence threshold value to limit returned data
        threshold = request.form.get('threshold')
        if threshold is None:
            threshold = 0.5
        else:
            threshold = float(threshold)

        # finally run the image through tensor flow object detection`
        image_object = Image.open(image_file)
        objects = object_detection_api.get_objects(image_object, threshold)
        return objects

    except Exception as e:
        print('POST /image error: %e' % e)
        return e


if __name__ == '__main__':
    # without SSL
    app.run(debug=True, host='0.0.0.0')

Вместо отправки большого двоичного объекта на object_detection_api.get_objects вы можете отправить его в свою функцию OpenCV.

Полный репозиторий находится здесь: https://github.com/webrtcHacks/tfObjWebrtc иУ меня есть подробное пошаговое руководство по сообщению webrtcHacks .

1 голос
/ 19 октября 2019

У меня есть кое-что работающее, которое может вам помочь (оно отправляет двоичные данные из веб-сокета javascript на сервер python и преобразует полученные данные в изображения библиотеки подушек, которые вы, похоже, используете). Попробуйте это как скрипт сайта клиента:

(async () => {
    const useFrameRate = 30;
    const stream = await navigator.mediaDevices.getUserMedia({video: true});
    const capture = new ImageCapture(stream.getVideoTracks()[0]);
    const socket = new WebSocket('ws://localhost:5678');
    const options = {imageWidth: 640, imageHeight: 480};
    socket.addEventListener('open', () => {
        const send = () => capture.takePhoto(options).then(blob => socket.send(blob)).catch(() => {});
        const sendloop = setInterval(send ,1000/useFrameRate);
    });
 })();

А затем на вашем сервере что-то вроде этого:

import asyncio
import websockets
import io
from PIL import Image, ImageMode

async def time(websocket, path):
    while True:
        message = await websocket.recv()
        image = Image.open(io.BytesIO(message))
        # now do with your images whatever you want. I used image.show to check it, it was spamming my monitor

start_server = websockets.serve(time, "127.0.0.1", 5678)

asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()

Объяснение:
Сначала я хотел обойти подход холста инапрямую получить BLOB-объект из getUserMedia-Stream. Для этого я использовал ImageCapture-API, особенно takePhoto (), который возвращает Blob.
Если вы хотите, вы можете оставить свой холст-подход и просто вызвать .toBlob () на вашем холсте.
Стандартный веб-сокет принимает не только строки, но и объекты типа Byte, такие как Blob, см. здесь . Он автоматически преобразует их в двоичный фрейм, который вы можете обрабатывать как двоичную строку в python, как вы уже правильно попробовали с io.BytesIO.

...