概要
- Deep写輪眼とUEを連携させます。
- Deep写輪眼はPython環境でWebカメラから手で組んだ印を検出します。
- Deep写輪眼はUE側とは独立して動作し毎フレームの検出結果をTCPでUEに送信します。
- UEはDeep写輪眼の検出結果をTickで取得します。
- UEでTCPを使用するためにObject Deliverを使用します。
注意点
- Deep写輪眼の環境構築方法の詳細は公式をご確認ください。
- Deep写輪眼とUEはそれぞれ独立して動作します。Deep写輪眼はコンソールから実行します。一つのexeにまとめることや、Steam等で配布することは考慮していません。有識者~~~~
使用例
こういうことができます。
https://x.com/iochiryo/status/1731032980649607325?s=20
Deep写輪眼の検出結果を送信する
Deep写輪眼を動かす
Deep写輪眼をローカル環境で動かせる状態にしてください。リポジトリ内の「simple_demo.py」が実行できれば問題ないです。
- https://github.com/Kazuhito00/NARUTO-HandSignDetection
- https://qiita.com/Kazuhito/items/ff3b04437617427f55a1
環境構築方法
※ 2021年頃に書いたので内容が最新でない可能性があります。バージョン等は適宜読み替えてください。
わかってる人は読み飛ばしてください。公式の方が詳しいと思います。
手元のPCのGPU等によって若干変わります。一例として読んでください。
ChatGPT4が結構丁寧に教えてくれます。
Cuda cuDNNのインストール
CUDA Toolkit 11.0をダウンロードインストールします
https://developer.nvidia.com/cuda-11.0-download-archive
cuDNNをインストールします
https://developer.nvidia.com/rdp/cudnn-archive
Download cuDNN v8.0.5 (November 9th, 2020), for CUDA 11.0をクリック
cuDNN Library for Windows (x86) をダウンロードします。
フォルダを解凍後、ダウンロードしたデータのcuda配下のすべてのフォルダを以下の位置にコピーします。
C:\ProgramFile\NVIDIA GPU Computing Toolkit\CUDA\v11.0\
Anacondaの設定
以下のページからAnacondaをdowloadしてinstallしてください
https://www.anaconda.com/products/individual
64-Bit Graphical Installer (457 MB)をinstallします
Install中に
「add Anaconda to the system Pass environment variable」
のチェックボックスにチェックを入れます。
必要なパッケージのインストール
「コマンドプロンプト」を起動し以下のコマンドを順に実行します。
以下のコマンドを順に実行し必要なパッケージをインストールします。わかる人は仮想環境作ったらいいと思います。
python -m pip install --upgrade --user pip
conda install -c conda-forge opencv
conda install tensorflow-gpu
pip install numpy
pip install sockets
以上で環境構築が完了です。
動作確認
simple_demo.py をダブルクリックして起動します。
起動しないときは以下のコマンドを実行してください。
python simple_demo.py
ウィンドウが開きウェブカメラの映像が出たらDeep写輪眼の起動準備は終了です。
TCPで送信できるようにする。
「simple_demo.py」を検出結果を送信できるように書き換えます。
TCPに関する説明はこのあたりを参照したらいいと思います。
https://qiita.com/t_katsumura/items/a83431671a41d9b6358f
最終的に以下の部分で検出結果を送信しています。
conn.send(bytes(labels[class_id][0], 'utf-8'))#ラベルの送信
書き換えた後のコードは次の通りになります。並立でUEを動かす都合上パフォーマンスを下げてます。configの値は公式のREADMEを参照してください。
30010のポートを使用することを想定しています。
simple_demo_unreal.pyを実行すると「WaitForStart」のログが出て停止します。
後述の手順を行い受信できる状態にしたUE側を起動すると、諸々の処理が実行され接続後に起動します。
公式の「simple_demo.py」が動作できる環境なら動作します。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import csv
import time
import copy
import cv2 as cv
import numpy as np
import tensorflow as tf
# socket サーバを作成
import socket
def get_args():
parser = argparse.ArgumentParser()
parser.add_argument("--device", type=int, default=0)
parser.add_argument("--file", type=str, default=None)
parser.add_argument("--fps", type=int, default=10)
parser.add_argument("--width", help='cap width', type=int, default=960)
parser.add_argument("--height", help='cap height', type=int, default=540)
parser.add_argument("--model", default='model/EfficientDetD0/saved_model')
parser.add_argument("--score_th", type=float, default=0.75)
parser.add_argument("--skip_frame", type=int, default=0)
args = parser.parse_args()
return args
def run_inference_single_image(image, inference_func):
tensor = tf.convert_to_tensor(image)
output = inference_func(tensor)
output['num_detections'] = int(output['num_detections'][0])
output['detection_classes'] = output['detection_classes'][0].numpy()
output['detection_boxes'] = output['detection_boxes'][0].numpy()
output['detection_scores'] = output['detection_scores'][0].numpy()
return output
def main():
# 引数解析 #################################################################
args = get_args()
cap_device = args.device
cap_width = args.width
cap_height = args.height
fps = args.fps
skip_frame = args.skip_frame
model_path = args.model
score_th = args.score_th
#サーバーに接続
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# IPアドレスとポートを指定
s.bind(('127.0.0.1', 30010))
# 1 接続
s.listen(1)
# connection するまで待つ
print("WaitStartGame")
conn, addr = s.accept()
if args.file is not None:
cap_device = args.file
frame_count = 0
# カメラ準備 ###############################################################
cap = cv.VideoCapture(cap_device)
cap.set(cv.CAP_PROP_FRAME_WIDTH, cap_width)
cap.set(cv.CAP_PROP_FRAME_HEIGHT, cap_height)
# モデルロード #############################################################
DEFAULT_FUNCTION_KEY = 'serving_default'
loaded_model = tf.saved_model.load(model_path)
inference_func = loaded_model.signatures[DEFAULT_FUNCTION_KEY]
# ラベル読み込み ###########################################################
with open('setting/labels.csv', encoding='utf8') as f:
labels = csv.reader(f)
labels = [row for row in labels]
print("InitiarizeDetection")
while True:
start_time = time.time()
# カメラキャプチャ #####################################################
ret, frame = cap.read()
if not ret:
continue
frame_width, frame_height = frame.shape[1], frame.shape[0]
debug_image = copy.deepcopy(frame)
frame_count += 1
if (frame_count % (skip_frame + 1)) != 0:
continue
# 検出実施 #############################################################
frame = frame[:, :, [2, 1, 0]] # BGR2RGB
image_np_expanded = np.expand_dims(frame, axis=0)
output = run_inference_single_image(image_np_expanded, inference_func)
num_detections = output['num_detections']
for i in range(num_detections):
score = output['detection_scores'][i]
bbox = output['detection_boxes'][i]
class_id = output['detection_classes'][i].astype(np.int)
if score < score_th:
continue
# 検出結果可視化 ###################################################
x1, y1 = int(bbox[1] * frame_width), int(bbox[0] * frame_height)
x2, y2 = int(bbox[3] * frame_width), int(bbox[2] * frame_height)
cv.putText(
debug_image, 'ID:' + str(class_id) + ' ' +
labels[class_id][0] + ' ' + '{:.3f}'.format(score),
(x1, y1 - 15), cv.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 2,
cv.LINE_AA)
cv.rectangle(debug_image, (x1, y1), (x2, y2), (0, 255, 0), 2)
print('StartConnect')
conn.send(bytes(labels[class_id][0], 'utf-8'))#ラベルの送信
# キー処理(ESC:終了) #################################################
key = cv.waitKey(1)
if key == 27: # ESC
break
# FPS調整 #############################################################
elapsed_time = time.time() - start_time
sleep_time = max(0, ((1.0 / fps) - elapsed_time))
time.sleep(sleep_time)
cv.putText(
debug_image,
"Elapsed Time:" + '{:.1f}'.format(elapsed_time * 1000) + "ms",
(10, 30), cv.FONT_HERSHEY_SIMPLEX, 0.8, (0, 255, 0), 2, cv.LINE_AA)
# 画面反映 #############################################################
cv.imshow('NARUTO HandSignDetection Simple Demo', debug_image)
# cv.moveWindow('NARUTO HandSignDetection Simple Demo', 100, 100)
cap.release()
cv.destroyAllWindows()
if __name__ == '__main__':
main()
UE側で検出結果を受け取る
Deep写輪眼から送信された検出結果をUE側で受け取るようプロジェクト側を修正します。
ObjectDeliverの追加
Object Deliverプラグインを追加してください。
- Object Deliver
- README
受信用Actorの設定
TcpSocketConnection Classを作成します。
以下のような処理を追加します。画像はUE4ですがUE5でも同じだと思います。
これをレベル上に置いておくとTickでDeep写輪眼の値を取得します。
ここではGI_DetectedLabelに検出結果が入ります。
検出結果がString型で「Ne(Rat)」のように入ってきます。
今回はGameInstanceに送ってあちこちで使っていますが、この辺はよしなにやってください。
使い方
環境構築が済んでいる前提で説明をします。
- simple_demo_unreal.pyを実行します。
- 「WaitForStart」のログが出ていることを確認します
- UEプロジェクトをPlayします。Play in EditorではなくスタンドアローンでPlayしないと通信されない時があります。
- Deep写輪眼が起動します。しばらく時間がかかります。ログが「WaitForStart」から流れ始めれば正常に動作しています。
- Webカメラが起動します。
- 検出結果がUE側に送信されていることを確認します
応用例
Deep写輪眼を別のリポジトリに置き換えても同じ要領でUEと連携できます。
これはMediaPipeを使用して作った「ういびーむDetector」です。
絵は10秒で描きました。たすけてしぐれうい。
知りたいこと
機械学習関係の処理をUE内に埋め込むのどうやるんですか。
ゲームやろうとして「Anaconda入れてYAML叩いて環境構築してね」はハードル高い気がしてます。
Deep写輪眼まで含めて1つのexeにまとめてワンチャンSteamに公開するところまでいけないですか。onnxに対応したらしいですが、ほとんど情報が見つからないです。有識者~~~
https://historia.co.jp/archives/26238/