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