YOLO 主要用 Python 語言開發,我通常會將它視為一個外掛程式,用來當作我的 Unity 遊戲的一個輸入,例如:用 YOLO 分析玩家在鏡頭前的畫面,辨識出肢體關節的座標,同步到遊戲內的角色身上。
而今天在這邊我們沒有要搞得那麼複雜,先從最簡單的操作開始,帶各位從用 OpenCV 來確認 YOLO 的辨識結果、JSON 編碼,最後經由 UDP 將座標傳輸到 Unity 之中。
我們大致上會經歷以下步驟,你需要先建立好開發環境:
- 安裝 OpenCV
- 用 Webcam 當作 YOLO 串流輸入
- 取得鼻子的 XY 座標
- JSON 編碼與 UDP 傳輸
- 在 Unity 接收 UDP 與解碼
說明到這邊,現在開始正式主題:
安裝 OpenCV
我們待會要用 OpenCV 來將 Webcam 的影像和辨識後的座標顯示出來,方便測試結果。請開啟終端機,輸入以下指令來安裝 Python 的 OpenCV。
pip install opencv-python
用 Webcam 當作 YOLO 串流輸入
接下來新增一個 Python 腳本,複製貼上以下程式碼與執行。這個腳本將會載入 YOLO 11 的人類骨架辨識模型,並將編號 0 的 Webcam 影像串流到模型中 (source=0
、stream=True
),以顯示卡運算 (device=0
)。
我們額外做的事情是用 cv2.imshow
將 Webcam 的影像用視窗來顯示,當作監看使用。
from ultralytics import YOLO
import cv2
# 載入 YOLO 模型
model = YOLO("yolo11n-pose.pt")
while True:
# 執行模型推論
results = model(source=0, stream=True, device=0)
for r in results:
# 取得 Webcam 影像
frame = r.orig_img
# 顯示結果
cv2.imshow("webcam", frame)
cv2.waitKey(1)
取得鼻子的 XY 座標
接下來我們在 for r in results:
裡面輸入以下程式碼,將辨識到的關鍵點 (Key Points) 提取出來,並把它轉成整數。
keypoint = r.keypoints[0].xy[0]
x = int(keypoint[0][0])
y = int(keypoint[0][1])
在上面的第 2、3 行中的 keypoint[0][0]
和 keypoint[0][1]
是代表對應於索引 0 的身體部位座標,索引 0 就是代表鼻子。你可以自行修改成不同的部位,例如右肩的 X 座標就是 keypoint[6][0]
。
索引編號 | 身體部位 |
---|---|
0 | 鼻子 |
1 | 左眼 |
2 | 右眼 |
3 | 左耳 |
4 | 右耳 |
5 | 左肩 |
6 | 右肩 |
7 | 左手肘 |
8 | 右手肘 |
9 | 左手腕 |
10 | 右手腕 |
11 | 左髖骨 |
12 | 右髖骨 |
13 | 左膝蓋 |
14 | 右膝蓋 |
15 | 左腳踝 |
16 | 右腳踝 |
為了方便觀察,我會用 OpenCV 的 cv2.putText 來將座標顯示到 Webcam 的監看視窗中。
目前的程式碼內容如下:
from ultralytics import YOLO
import cv2
# 載入 YOLO 模型
model = YOLO("yolo11n-pose.pt")
while True:
# 執行模型推論
results = model(source=0, stream=True, device=0)
for r in results:
# 取得 Webcam 影像
frame = r.orig_img
# 取得鼻子座標
keypoint = r.keypoints[0].xy[0]
x = int(keypoint[0][0])
y = int(keypoint[0][1])
text = "x: {}, y: {}".format(x, y)
# 顯示結果
cv2.putText(frame, text, (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA)
cv2.imshow("webcam", frame)
cv2.waitKey(1)
執行腳本,YOLO 會以左上角為原點,精準辨識我的鼻子座標並顯示在畫面左上方。
測試一下即時的辨識效果。
YOLO 的辨識算是告一段落,接下來的工作就是要將拿到的座標編碼成 JSON 格式,再透過 UDP 傳輸的方式將它輸入到 Unity 裡面。
JSON 編碼與 UDP 傳輸
選用 UDP 而不是 TCP 的原因在於「效率」,UDP 的發送端 (YOLO) 無須理會接收端 (Unity) 有沒有正確的拿到結果,也不會跳出傳送錯誤,效能也比 TCP 高,是個理想的方式來傳輸座標。
我們修改一下程式碼,首先需要引用 json 和 socket:
import json
import socket
再建立發送的目的地與 UDP 發送端,我們預計將資料傳輸到本機的 7000 端口:
server_addr = ("127.0.0.1", 7000)
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
最後在 for r in results:
裡面加上以下程式碼,先將座標包成物件,再編碼成 JSON 格式,並使用 UDP 把它傳出去。
output = {
"x": x,
"y": y
}
client.sendto(json.dumps(output).encode(), server_addr)
最後的程式碼內容如下:
from ultralytics import YOLO
import cv2
import json
import socket
# UDP 發送設定
server_addr = ("127.0.0.1", 7000)
client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 載入 YOLO 模型
model = YOLO("yolo11n-pose.pt")
while True:
# 執行模型推論
results = model(source=0, stream=True, device=0)
for r in results:
# 取得 Webcam 影像
frame = r.orig_img
# 取得鼻子座標
keypoint = r.keypoints[0].xy[0]
x = int(keypoint[0][0])
y = int(keypoint[0][1])
text = "x: {}, y: {}".format(x, y)
# JSON 編碼並發送
output = {
"x": x,
"y": y
}
client.sendto(json.dumps(output).encode(), server_addr)
# 顯示結果
cv2.putText(frame, text, (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA)
cv2.imshow("webcam", frame)
# 每 100 毫秒辨識一次
cv2.waitKey(100)
如果傳輸速度過快或辨識太吃資源,可以調整 cv2.waitKey
的數字來調整頻率,單位是毫秒。
YOLO 和 Python 的部分已經完畢,可以先讓它持續執行,我們要來處理 Unity 的 UDP 接收功能
在 Unity 接收 UDP 與解碼
開啟一個 Unity 專案,建立一個名為 UdpReceiver.cs 的 C# 腳本,並複製貼上以下程式碼:
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;
public class UdpReceiver : MonoBehaviour
{
// 接收的 JSON 資料格式
public class InputData
{
public int x;
public int y;
}
private UdpClient _client;
private IPEndPoint _endPoint;
private void Start()
{
// 以 UDP 監聽 7000 端口
_client = new UdpClient(7000);
_endPoint = new IPEndPoint(IPAddress.Any, 7000);
// 非同步接收資料
RunForever();
}
private async void RunForever()
{
while (true)
{
// 等待接收新資料
var raw = await Task.Run(() => _client.Receive(ref _endPoint));
// 解碼成 JSON 字串
var json = Encoding.UTF8.GetString(raw);
// 解碼成物件
var data = JsonUtility.FromJson<InputData>(json);
// 列印結果
Debug.Log($"x: {data.x}, y: {data.y}");
}
}
// 遊戲退出時關閉連線
private void OnApplicationQuit()
{
_client.Close();
}
}
在上面的程式碼中,我們用 C# 內建的 UdpClient 來當作 UDP 的接收器,並使用非同步和 Task.Run
方法來防止在接收 UDP 的資料時讓 Unity 卡死。
當接收到資料時,我們會將它解碼成 JSON 格式,再透過 Unity 內建的 JsonUtility 將 JSON 解成物件,最後再把它列印到 Console 裡面。
最後同時執行 YOLO 和 Unity,現在 Unity 理應能接收到 YOLO 辨識出來的座標,並在 Console 裡面顯示出來。
測試結果,觀察一下 OpenCV 視窗上的座標和 Unity 顯示的座標是否相同:
今天教學到這邊,YOLO 是一個好用的工具,可以快速實現影像辨識功能。目前我們只有先用別人預先訓練的模型來做辨識,未來可能還可以拿自己準備的影像當作樣本來訓練模型,就能做更多的應用!
有遇到問題或有相關技術想要分享的歡迎在下方討論區留言,我會抽空回覆!