[YOLO 11] 透過 Webcam 即時辨識骨架並傳輸座標至 Unity

2025.01.16 / 人工智慧
YOLO 可以透過 Webcam 來即時擷取分析我們的人體骨架,今天要來教大家如何擷取頭的中心座標 (鼻子),並經由 UDP 網路連線傳輸到 Unity 之中。

YOLO 主要用 Python 語言開發,我通常會將它視為一個外掛程式,用來當作我的 Unity 遊戲的一個輸入,例如:用 YOLO 分析玩家在鏡頭前的畫面,辨識出肢體關節的座標,同步到遊戲內的角色身上。

而今天在這邊我們沒有要搞得那麼複雜,先從最簡單的操作開始,帶各位從用 OpenCV 來確認 YOLO 的辨識結果、JSON 編碼,最後經由 UDP 將座標傳輸到 Unity 之中。

我們大致上會經歷以下步驟,你需要先建立好開發環境

  • 安裝 OpenCV
  • 用 Webcam 當作 YOLO 串流輸入
  • 取得鼻子的 XY 座標
  • JSON 編碼與 UDP 傳輸
  • 在 Unity 接收 UDP 與解碼

說明到這邊,現在開始正式主題:

安裝 OpenCV

我們待會要用 OpenCV 來將 Webcam 的影像和辨識後的座標顯示出來,方便測試結果。請開啟終端機,輸入以下指令來安裝 Python 的 OpenCV。

1pip install opencv-python

用 Webcam 當作 YOLO 串流輸入

接下來新增一個 Python 腳本,複製貼上以下程式碼與執行。這個腳本將會載入 YOLO 11 的人類骨架辨識模型,並將編號 0 的 Webcam 影像串流到模型中 (source=0stream=True),以顯示卡運算 (device=0)。

我們額外做的事情是用 cv2.imshow 將 Webcam 的影像用視窗來顯示,當作監看使用。

 1from ultralytics import YOLO
 2import cv2
 3
 4# 載入 YOLO 模型
 5model = YOLO("yolo11n-pose.pt")
 6
 7while True:
 8    # 執行模型推論
 9    results = model(source=0, stream=True, device=0)
10
11    for r in results:
12        # 取得 Webcam 影像
13        frame = r.orig_img
14        
15        # 顯示結果
16        cv2.imshow("webcam", frame)
17        cv2.waitKey(1)

取得鼻子的 XY 座標

接下來我們在 for r in results: 裡面輸入以下程式碼,將辨識到的關鍵點 (Key Points) 提取出來,並把它轉成整數。

1keypoint = r.keypoints[0].xy[0]
2x = int(keypoint[0][0])
3y = 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 的監看視窗中。

目前的程式碼內容如下:

 1from ultralytics import YOLO
 2import cv2
 3
 4# 載入 YOLO 模型
 5model = YOLO("yolo11n-pose.pt")
 6
 7while True:
 8    # 執行模型推論
 9    results = model(source=0, stream=True, device=0)
10
11    for r in results:
12        # 取得 Webcam 影像
13        frame = r.orig_img
14        
15        # 取得鼻子座標
16        keypoint = r.keypoints[0].xy[0]
17        x = int(keypoint[0][0])
18        y = int(keypoint[0][1])
19        text = "x: {}, y: {}".format(x, y)
20        
21        # 顯示結果
22        cv2.putText(frame, text, (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA)
23        cv2.imshow("webcam", frame)
24        cv2.waitKey(1)

執行腳本,YOLO 會以左上角為原點,精準辨識我的鼻子座標並顯示在畫面左上方。

python_NVozeVWYFy.png

python_MgDOfc1zDq.png

測試一下即時的辨識效果。

YOLO 的辨識算是告一段落,接下來的工作就是要將拿到的座標編碼成 JSON 格式,再透過 UDP 傳輸的方式將它輸入到 Unity 裡面。

JSON 編碼與 UDP 傳輸

選用 UDP 而不是 TCP 的原因在於「效率」,UDP 的發送端 (YOLO) 無須理會接收端 (Unity) 有沒有正確的拿到結果,也不會跳出傳送錯誤,效能也比 TCP 高,是個理想的方式來傳輸座標。

我們修改一下程式碼,首先需要引用 json 和 socket:

1import json
2import socket

再建立發送的目的地與 UDP 發送端,我們預計將資料傳輸到本機的 7000 端口:

1server_addr = ("127.0.0.1", 7000)
2client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

最後在 for r in results: 裡面加上以下程式碼,先將座標包成物件,再編碼成 JSON 格式,並使用 UDP 把它傳出去。

1output = {
2    "x": x,
3    "y": y
4}
5client.sendto(json.dumps(output).encode(), server_addr)

最後的程式碼內容如下:

 1from ultralytics import YOLO
 2import cv2
 3import json
 4import socket
 5
 6# UDP 發送設定
 7server_addr = ("127.0.0.1", 7000)
 8client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
 9
10# 載入 YOLO 模型
11model = YOLO("yolo11n-pose.pt")
12
13while True:
14        # 執行模型推論
15    results = model(source=0, stream=True, device=0)
16
17    for r in results:
18        # 取得 Webcam 影像
19        frame = r.orig_img
20
21        # 取得鼻子座標
22        keypoint = r.keypoints[0].xy[0]
23        x = int(keypoint[0][0])
24        y = int(keypoint[0][1])
25        text = "x: {}, y: {}".format(x, y)
26        
27        # JSON 編碼並發送
28        output = {
29            "x": x,
30            "y": y
31        }
32        client.sendto(json.dumps(output).encode(), server_addr)
33        
34        # 顯示結果
35        cv2.putText(frame, text, (0, 40), cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 1, cv2.LINE_AA)
36        cv2.imshow("webcam", frame)
37        
38        # 每 100 毫秒辨識一次
39        cv2.waitKey(100)

如果傳輸速度過快或辨識太吃資源,可以調整 cv2.waitKey 的數字來調整頻率,單位是毫秒。

YOLO 和 Python 的部分已經完畢,可以先讓它持續執行,我們要來處理 Unity 的 UDP 接收功能

在 Unity 接收 UDP 與解碼

開啟一個 Unity 專案,建立一個名為 UdpReceiver.cs 的 C# 腳本,並複製貼上以下程式碼:

 1using System.Net;
 2using System.Net.Sockets;
 3using System.Text;
 4using System.Threading.Tasks;
 5using UnityEngine;
 6
 7public class UdpReceiver : MonoBehaviour
 8{
 9    // 接收的 JSON 資料格式
10    public class InputData
11    {
12        public int x;
13        public int y;
14    }
15    
16    private UdpClient _client;
17    private IPEndPoint _endPoint;
18
19    private void Start()
20    {
21        // 以 UDP 監聽 7000 端口
22        _client = new UdpClient(7000);
23        _endPoint = new IPEndPoint(IPAddress.Any, 7000);
24        
25        // 非同步接收資料
26        RunForever();
27    }
28
29    private async void RunForever()
30    {
31        while (true)
32        {
33            // 等待接收新資料
34            var raw = await Task.Run(() => _client.Receive(ref _endPoint));
35            
36            // 解碼成 JSON 字串
37            var json = Encoding.UTF8.GetString(raw);
38            
39            // 解碼成物件
40            var data = JsonUtility.FromJson<InputData>(json);
41            
42            // 列印結果
43            Debug.Log($"x: {data.x}, y: {data.y}");
44        }
45    }
46
47    // 遊戲退出時關閉連線
48    private void OnApplicationQuit()
49    {
50        _client.Close();
51    }
52}

在上面的程式碼中,我們用 C# 內建的 UdpClient 來當作 UDP 的接收器,並使用非同步和 Task.Run 方法來防止在接收 UDP 的資料時讓 Unity 卡死。

當接收到資料時,我們會將它解碼成 JSON 格式,再透過 Unity 內建的 JsonUtility 將 JSON 解成物件,最後再把它列印到 Console 裡面。

最後同時執行 YOLO 和 Unity,現在 Unity 理應能接收到 YOLO 辨識出來的座標,並在 Console 裡面顯示出來。

Unity_VtefSWB31b.png

測試結果,觀察一下 OpenCV 視窗上的座標和 Unity 顯示的座標是否相同:

今天教學到這邊,YOLO 是一個好用的工具,可以快速實現影像辨識功能。目前我們只有先用別人預先訓練的模型來做辨識,未來可能還可以拿自己準備的影像當作樣本來訓練模型,就能做更多的應用!

有遇到問題或有相關技術想要分享的歡迎在下方討論區留言,我會抽空回覆!

相關文章

Ted Liou

雲科碩士在讀中,專注於 Unity C#、TouchDesigner 技術。
只要願意以超連結標註本文,歡迎轉載或用於教材製作!