LoginSignup
0
2

More than 3 years have passed since last update.

Face LandmarksをiPadからudpで送り、Pythonで中継、受信、表示したら驚いた!

Last updated at Posted at 2020-08-22

Face LandmarksをiPadからudpで送り、Pythonで中継、受信、表示したら驚いた!

 何に驚いたのかというと、短いコードで、求める結果が得られることです。
下図は、dlibの68点の顔のランドマークデータをiPadのアプリで生成し、インターネット経由でサーバーで中継し、macで表示した画面(リアルタイム動画)ですが、この表示用のPythonコードは【コード1】示すようにわずか、30行くらいです。Pythonistの方々には当たり前なんでしょうが、初心者にとっては驚異的です。アイデアのプロトタイピングには最強と感じました。

2020-08-22 11.45のイメージ.jpeg

Face LandmarksをiPadからudpで送る

face landmarksのデータをカメラの顔の画像から作成し、udpで送る側ですが、
下記のgitのXcodeのプロジェクトを使いました。(同じ作者のgitに、Android版、
Javascript版もあります。)

その中のtrack_single_face.hppを以下のように変更します。

addr.sin_port = htons(5000);
addr.sin_addr.s_addr = inet_addr("XXX.XX.XXX.XXX");

のポート番号5000と
"XXX.XX.XXX.XXX"のIPは、ご自分の設定に合わせます。
Xcodeのプロジェクトをビルドして起動するし、自分の顔を撮影すると、68点の顔のランドマークの位置データを68x2バイトのデータとしてサーバーに送信します。

track_single_face.hpp

#ifndef __brf__cpp__BRFCppExample_hpp
#define __brf__cpp__BRFCppExample_hpp

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

namespace brf {



class BRFCppExample: public BRFBasicCppExample {

    int sock;
    struct sockaddr_in addr;
    uint16_t shorts;
    uint8_t  bytes[sizeof(uint8_t)*136*2];

public: BRFCppExample() : BRFBasicCppExample()
{

    addr.sin_family = AF_INET;
    addr.sin_port = htons(5000);
    addr.sin_addr.s_addr = inet_addr("XXX.XX.XXX.XXX");
    //sock = socket(AF_INET, SOCK_DGRAM, 0);
}

public: void initCurrentExample(brf::BRFManager& brfManager, brf::Rectangle& resolution) {

    brf::trace("BRFv4 - basic - face tracking - track single face" + brf::to_string("\n")+
        "Detect and track one face and draw the 68 facial landmarks.");
}

public: void updateCurrentExample(brf::BRFManager& brfManager, brf::DrawingUtils& draw) {

    // In a webcam example imageData is the mirrored webcam video feed.
    // In an image example imageData is the (not mirrored) image content.

    brfManager.update();

    // Drawing the results:

    draw.clear();

    // Face detection results: a rough rectangle used to start the face tracking.

    //draw.drawRects(brfManager.getAllDetectedFaces(),    false, 1.0, 0x00a1ff, 0.5);
    //draw.drawRects(brfManager.getMergedDetectedFaces(), false, 2.0, 0xffd200, 1.0);

    // Get all faces. The default setup only tracks one face.

    std::vector< std::shared_ptr<brf::BRFFace> >& faces = brfManager.getFaces();

    for(size_t i = 0; i < faces.size(); i++) {

        brf::BRFFace& face = *faces[i];

        if(     face.state == brf::BRFState::FACE_TRACKING_START ||
                face.state == brf::BRFState::FACE_TRACKING) {

            sock = socket(AF_INET, SOCK_DGRAM, 0);
            for(int i = 0; i < 136; i++){
                shorts = (int)face.vertices[i];
                bytes[i * 2] = (uint8_t) (shorts & 0xFF);
                bytes[(i * 2) + 1] = (uint8_t) (shorts >> 8);
            }

            sendto(sock, bytes, sizeof(bytes), 0, (struct sockaddr *)&addr, sizeof(addr));
            close(sock);
            //draw.drawTriangles(   face.vertices, face.triangles, false, 1.0, 0x00a0ff, 0.4);
            draw.drawVertices(  face.vertices, 2.0, false, 0x00a0ff, 0.4);

        }
    }
}

};

}
#endif // __brf__cpp__BRFCppExample_hpp

サーバーサイド

 続いてサーバーサイドです。ランドマークデータをLAN側に送り出すだけのコードです。
ポート番号、IPはご自分の環境に合わせてます。
Pythonで書くとサーバー側もシンプルに書けます。

sendBak.py
import socket
import time

print (time.ctime(),flush=True)

host = '192.168.10.101'
port =  5001
bufsize = 512
portFace = 5000

sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
#sock.settimeout(0.01)
sock.bind((host,port))
#
sock2 = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
sock2.settimeout(30.0)
sock2.bind((host,portFace))

while True:
    global data, addr
    string,addr = sock.recvfrom(bufsize)
    print("from:",addr,time.ctime())
    while True:
        try:     
            data, (host, port) = sock2.recvfrom(bufsize)
            result = sock.sendto(data,addr)
            #if result  != 272:
            #     print(result) 
        except socket.error as e:
            #print ('Error: %s' % e)
            break



OpenCV版 【コード1】

 最後に受信側です。これはPythonの動く環境ならMac、Windows、Linuxを問いません。
iOSのPythoinistaでもOKです。

FaceLineOpenCV.py

import cv2
import socket
import numpy as np
from struct import unpack
from datetime import datetime

host = 'XXXX.com'
port =  5001
message = b'hello'

if __name__ == "__main__" :
    server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    #server.bind(('192.168.2.100', 5000))
    width = 600
    height = 800
    img = np.zeros((height, width, 3), np.uint8)
    server.sendto(message, (host, port))

    while True:

        data, addr = server.recvfrom(1024)
        cv2.rectangle(img, (0, 0), (600, 700), color=(255, 0, 0), thickness=-1)
        val = unpack('<'+'H'*(len(data)//2), data)
        j = 0
        for i in range(64):
            cv2.circle(img, (int(val[j]), int(val[j+1])), 4, (255, 255, 256), -1)
            j = j + 2

        cv2.imshow('camera capture', img)
        k = cv2.waitKey(1) #
        if k == 27:
            break

    cv2.destroyAllWindows()

上はOpenCV版ですが、下記のようにPyQtでも動作します。

FaceLineQt.py
import sys

from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import QTimer
import socket
import numpy as np
from struct import unpack


host = 'XXXX.com'
port =  5001
bufsize = 512
message = b'hello'


class Widget(QWidget):

    def __init__(self, parent=None):

        super(Widget, self).__init__(parent)
        self.timer = QTimer(self)
        self.timer.timeout.connect(self.update)
        self.timer.start(20)#更新
        #
        self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        #self.server.settimeout(0.5)
        #self.server.setblocking(False)
        #self.img = np.zeros((height, width, 3), np.uint8)
        self.server.sendto(message, (host, port))

    def paintEvent(self, event):



        data, addr = self.server.recvfrom(bufsize)
        val = unpack('<'+'H'*(len(data)//2), data)

        painter = QPainter(self)
        painter.setPen(Qt.red)
        painter.setBrush(Qt.red)
        j = 0
        for i in range(68):
                painter.drawRect(int(val[j]), int(val[j+1]), 5, 5)
                j = j + 2


def main():

    app = QApplication(sys.argv)
    w = Widget()
    w.show()
    w.raise_()
    app.exec_()


if __name__ == '__main__':

    main()

コードの説明は追って追加したいと思います。

Pythonista版を追加しました。
Pythonistaの場合は高速のsceneを使います。

FalceLinePythonista.py

from scene import *
import socket
import numpy as np
from struct import unpack

host = 'XXXX.com'
port =  5001
bufsize = 512

class FaceLine (Scene):
    def setup(self):

        self.landmarks = []
        for i in range(68):
                shape = ShapeNode(ui.Path.oval(0,0,5,5), 'white')
                self.landmarks.append(shape)
                self.add_child(shape)

        self.server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        #
        message = b'hello'
        self.server.sendto(message, (host, port))

    def update(self):   
        #
        data, addr = self.server.recvfrom(bufsize)
        val = unpack('<'+'H'*(len(data)//2), data)
        dtx = np.array(val[::2])
        dty = np.array(val[1::2])

        for i in range(68):
                self.landmarks[i].position = (dtx[i]+200,800-dty[i])


run(FaceLine())


0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2