延展實境 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/延展實境/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Mon, 28 Nov 2022 00:18:30 +0000 zh-TW hourly 1 https://wordpress.org/?v=6.2.2 https://creativecoding.in/wp-content/uploads/2022/03/cropped-cct-logo-icon-2-32x32.png 延展實境 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/延展實境/ 32 32 【徵文賞-延展實境】佳作|史上最累Debug!深蹲才能過關(Unity + OpenCV 跨軟體傳輸實作) – 林慶佳 https://creativecoding.in/2022/11/24/collection221110-xr-1/ Thu, 24 Nov 2022 03:31:26 +0000 https://creativecoding.in/?p=3141 透過互動程式創作徵文賞,我們期望讓更多人認識並加入 Creative Coding 這個新奇有趣的領域。此作品為延展實境組佳作,以 Unity 結合 OpenCV 跨軟體實作,偵測深蹲姿勢到達一定水準後,讓遊戲中的物件跳起來。

這篇文章 【徵文賞-延展實境】佳作|史上最累Debug!深蹲才能過關(Unity + OpenCV 跨軟體傳輸實作) – 林慶佳 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
透過互動程式創作徵文賞,我們期望讓更多人認識並加入 Creative Coding 這個新奇有趣的領域。此作品為延展實境組佳作,以 Unity 結合 OpenCV 跨軟體實作,偵測深蹲姿勢到達一定水準後,讓遊戲中的物件跳起來。

實作成果:

偵測臉並回傳位置,使Unity中的物件可以跟隨他。

使用C++指標傳輸

原文:Unity and OpenCV – Part three: Passing detection data to Unity – Thomas Mountainborn

偵測臉並回傳位置,使Unity中的物件可以跟隨他。

建立與Unity溝通的結構

C++

struct Circle
{
	//建構子
	Circle(int x, int y, int radius) : X(x), Y(y), Radius(radius) {}
	int X, Y, Radius;
};

編譯器在產生dll檔案時會打亂method名稱,為讓方法保持原名稱,則外顯”C”

Normally, the C++ compiler will mangle the method names when packaging them into a .dll. Therefore, we instruct it to use the classic “C” style of signatures, which leaves the method names just as you wrote them.

extern "C" int __declspec(dllexport) __stdcall  Init(int& outCameraWidth, int& outCameraHeight)
extern "C" void __declspec(dllexport) __stdcall Detect(Circle* outFaces, int maxOutFacesCount, int& outDetectedFacesCount)

Circle* outFaces 表an array of Circles。

int& outDetectedFacesCount 表該變數是傳址(ref)。

C#

與C++溝通的格式,變數欄位、宣告順序必需與c++相同

// Define the structure to be sequential and with the correct byte size (3 ints = 4 bytes * 3 = 12 bytes)
[StructLayout(LayoutKind.Sequential, Size = 12)]
public struct CvCircle
{
    public int X, Y, Radius;
}

unsafe:讓你在C#能使用指標。

fixed:使編譯器讓該變數記憶體位置不被garbage collector處理掉。

在fixed區塊中,openCV會直接將變數寫入CvCircle結構陣列中,而省去copy的成本。

void Update()
    {     
				//接收陣列大小
        int detectedFaceCount = 0;
        unsafe
        {
            //pass fixed pointer
            fixed (CvCircle* outFaces = _faces)
            {
                OpenCVInterop.Detect(outFaces, _maxFaceDetectCount, ref detectedFaceCount);
            }
        }
}

fixed中只接受:

The legal initializers for a fixed statement are:

  • The address operator & applied to a variable reference.
  • An array
  • A string
  • A fixed-size buffer.

分段解析

C++

// Declare structure to be used to pass data from C++ to Mono. (用來與Unity溝通的結構)
struct Circle
{
	//建構子
	Circle(int x, int y, int radius) : X(x), Y(y), Radius(radius) {}
	int X, Y, Radius;
};

CascadeClassifier 是Opencv中做人臉檢測的時候的一個級聯分類器。 並且既可以使用Haar,也可以使用LBP特徵。()

C#

// Define the structure to be sequential and with the correct byte size 
//(3 ints = 4 bytes * 3 = 12 bytes)
[StructLayout(LayoutKind.Sequential, Size = 12)]
public struct CvCircle
{
    public int X, Y, Radius;
}

StructLayout :C#中StructLayout的特性 - IT閱讀 (itread01.com)

char型資料,對齊值為1,對於short型為2,對於int,float,double型別,其對齊值為4,單位位元組。

初始化鏡頭大小

C++

extern "C" int __declspec(dllexport) __stdcall  Init(int& outCameraWidth, 
																										 int& outCameraHeight)
{
	// Load LBP face cascade.
	if (!_faceCascade.load("lbpcascade_frontalface.xml"))
		return -1;

	// 打開鏡頭
	_capture.open(0);
	if (!_capture.isOpened())
		return -2;
	
	//取得視訊大小
	outCameraWidth = _capture.get(CAP_PROP_FRAME_WIDTH);
	outCameraHeight = _capture.get(CAP_PROP_FRAME_HEIGHT);

	return 0;
}

C#

在Opencv資料夾下找到lbpcascade_frontalface.xml,並放到Unity專案root資料夾下。

				int camWidth = 0, camHeight = 0;
        int result = OpenCVInterop.Init(ref camWidth, ref camHeight);
        if (result < 0)
        {
            if (result == -1)
            {
                Debug.LogWarningFormat("[{0}] Failed to find cascades definition.", GetType());
            }
            else if (result == -2)
            {
                Debug.LogWarningFormat("[{0}] Failed to open camera stream.", GetType());
            }

            return;
        }

        CameraResolution = new Vector2(camWidth, camHeight);

鏡頭大小的變數使用傳址呼叫,讓c++開啟鏡頭後順便設定好大小。使C#和c++使用相同的變數。

傳遞參數scale

C++

extern "C" void __declspec(dllexport) __stdcall SetScale(int scale)
{
	_scale = scale;
}

C#

private const int DetectionDownScale = 1;
void Start()
    {
        ...
        OpenCVInterop.SetScale(DetectionDownScale);
        _ready = true;
    }

辨識人臉

C++

extern "C" void __declspec(dllexport) __stdcall Detect(Circle* outFaces, 
																											int maxOutFacesCount,
																											int& outDetectedFacesCount)
{
	Mat frame;
	_capture >> frame;
	if (frame.empty())
		return;

	std::vector<Rect> faces;
	// Convert the frame to grayscale for cascade detection.
	Mat grayscaleFrame;
	cvtColor(frame, grayscaleFrame, COLOR_BGR2GRAY);
	Mat resizedGray;
	// Scale down for better performance.
	resize(grayscaleFrame, resizedGray, Size(frame.cols / _scale, frame.rows / _scale));
	equalizeHist(resizedGray, resizedGray);

	// Detect faces.
	_faceCascade.detectMultiScale(resizedGray, faces);

	// Draw faces.
	for (size_t i = 0; i < faces.size(); i++)
	{
		Point center(_scale * (faces[i].x + faces[i].width / 2), _scale * (faces[i].y + faces[i].height / 2));
		ellipse(frame, center, Size(_scale * faces[i].width / 2, _scale * faces[i].height / 2), 0, 0, 360, Scalar(0, 0, 255), 4, 8, 0);

		// Send to application.
		outFaces[i] = Circle(faces[i].x, faces[i].y, faces[i].width / 2);
		//返回數量用的
		outDetectedFacesCount++;

		if (outDetectedFacesCount == maxOutFacesCount)
			break;
	}

	// Display debug output.
	imshow(_windowName, frame);
}

步驟:

灰階→縮小解析度→直方圖均衡化→偵測

【補充】

直方圖均衡化(equalizeHist):

將拉伸數值分佈範圍從0-255。假設影像過曝(如藍色曲線),則直方圖均衡化能將其值範圍拉伸0-255區間內,使黑白更分明。 https://youtu.be/jWShMEhMZI4

人臉偵測 (detectMultiScale):

https://blog.csdn.net/leaf_zizi/article/details/107637433

CascadeClassifier.detectMultiScale(輸入圖片, 輸出向量, scaleFactor=1.1 , minNeighbor=3);

輸入圖片: 只接受灰階

scaleFactor:每次圖像縮小的比例,

minNeighbor:每個候選矩形有多少個”鄰居”,我的理解是:一個滑動窗口中的圖元需要符合幾個條件才能判斷為真。

大概意思是Haar cascade的工作原理是一種”滑動視窗”的方法,通過在圖像中不斷的”滑動檢測視窗”來匹配人臉。

因為圖像的圖元有大有小,圖像中的人臉因為遠近不同也會有大有小,所以需要通過scaleFactor參數設置一個縮小的比例,對圖像進行逐步縮小來檢測,這個參數設置的越大,計算速度越快,但可能會錯過了某個大小的人臉。

其實可以根據圖像的圖元值來設置此參數,圖元大縮小的速度就可以快一點,通常在1~1.5之間。

那麼,經過多次的反覆運算,實際會檢測出很多很多個人臉,這一點可以通過把minNeighbors 設為0來驗證。

所以呢,minNeighbors參數的作用就來了,只有其”鄰居”大於等於這個值的結果才認為是正確結果。

返回Rect ,其包含<x,y,w,h>

使用UDP

基本的傳輸協定,因為不會檢查是否收到或重傳等資訊,因此封包小、速度較快,缺點是長度上限548 Bytes,不適合串流影片,在此只用來傳輸事件資訊。 好處是就算Unity那端的port沒有開啟,UPD因不會檢查是否收到,所以不會報錯。

成果:

偵測手掌開闔。 參考:https://www.raywenderlich.com/5475-introduction-to-using-opencv-with-unity

尋找輪廓的原理先略過,主要流程是轉灰階=>高思模糊=>Canny邊緣偵測=>向外擴張=>findContours(尋找輪廓)

Python 傳送資料

import numpy as np
import cv2
import socket

UDP_IP = "127.0.0.1"
UDP_PORT = 5065

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

while true:
    sock.sendto( ("data!").encode(), (UDP_IP, UDP_PORT) )
    print("data sent")
    
capture.release()
cv2.destroyAllWindows()

C# UPD 建立連線

// 1. Declare Variables
    Thread receiveThread;  //在背景持續接受UDP訊息
    UdpClient client;   // parse the pre-defined address for data
    int port;   //port number

// 2. Initialize variables
    void Start()
    {
        port = 5065;
        InitUDP();
    }

    // 3. InitUDP
    private void InitUDP()
    {
        print("UDP Initialized");
        receiveThread = new Thread(new ThreadStart(ReceiveData)); //開個新的帶有參數的thread,傳入方法當參數
        receiveThread.IsBackground = true;
        receiveThread.Start();
    }

C# 定義接受方法

// 4. Receive Data
    private void ReceiveData()
    {
        client = new UdpClient(port); //指定port
        while (true)
        {
            try
            {
                IPEndPoint anyIP = new IPEndPoint(IPAddress.Parse("0.0.0.0"), port); //任何ip
                byte[] data = client.Receive(ref anyIP); //資料

                string text = Encoding.UTF8.GetString(data); //binary => utf8 text
                print(">> " + text);
								//....
            }
            catch (Exception e)
            {
                print(e.ToString());
            }
        }
    }

使用TCP

需經過三項交握確認連線。
若server(這裡是unity)的port沒有開,會收到傳送失敗的exception。

Python 建立連線

import numpy as np
import cv2
import socket

TCP_IP = "127.0.0.1"
TCP_PORT = 5066

#sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # SOCK_DGRAM 長度限制 548bytes,但不需要預先connect
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP

#TCP連線
address=(TCP_IP ,TCP_PORT )
sock.connect(address)

print('sock init')
sock.send('Hi'.encode('utf-8'));

sock.close() #才會把累積的資料傳送

C# 建立連線

public class ImageReceiver : MonoBehaviour
{
    //TCP Port 開啟
    Thread receiveThread;
    TcpClient client;
    TcpListener listener;
    int port;
    private void Start()
    {
        InitTcp();
    }
    void InitTcp()
    {
        port = 5066;
        print("TCP Initialized");
        IPEndPoint anyIP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), port);
        listener = new TcpListener(anyIP);
        listener.Start();
				//開個新的帶有參數的thread,傳入方法當參數
        receiveThread = new Thread(new ThreadStart(ReceiveData));
        receiveThread.IsBackground = true;
        receiveThread.Start();
    }
    private void OnDestroy()
    {
        receiveThread.Abort();
    }
}

定義接收方法

private void ReceiveData()
    {
        print("received somthing...");
        try
        {
            while (true)
            {
                client = listener.AcceptTcpClient();
                NetworkStream stream = new NetworkStream(client.Client);
                StreamReader sr = new StreamReader(stream);
                print(sr.ReadToEnd());
            }
        }
        catch (Exception e)
        {
            print(e);
        }
    }

注意1,由於一開始需要經過三項交握,所以”TCP Initialized”之後會log一次”received something…”,該訊息為用來回應tcp連線的。

注意2,在python端sock.close()之前收到的訊息會一直存在sr,直到close之後才一次print出,所以傳輸每frame都會經過:建立連線=>打包資料=>傳送=>close() 的循環。

傳輸畫面

Python 端

注意資料只能傳byte,string等型態,所以這邊使用json格式。Python有另一個類次的pickel插件,但該格式只能python使用,不方便給unity。

while cap.isOpened():          
    ret, img = cap.read()
    img_data={'image':cv2.imencode('.jpg',img)[1].ravel().tolist()}
    data=json.dumps(img_data);
    
    #準備連線
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # TCP
    sock.connect(address)
    
    #傳送資料
    sock.sendall(bytes(data,encoding='utf-8'))
    #print('sock sent')
    
    cv2.imshow("Image",img)
    cv2.waitKey(10)
    sock.close()

C# 端

宣告texture:

public class ImageReceiver : MonoBehaviour
{
    //TCP Port 開啟
    Thread receiveThread;
    TcpClient client;
    TcpListener listener;
    int port;
    private void Start()
    {
        InitTcp();
    }
    void InitTcp()
    {
        port = 5066;
        print("TCP Initialized");
        IPEndPoint anyIP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), port);
        listener = new TcpListener(anyIP);
        listener.Start();
				//開個新的帶有參數的thread,傳入方法當參數
        receiveThread = new Thread(new ThreadStart(ReceiveData));
        receiveThread.IsBackground = true;
        receiveThread.Start();
    }
    private void OnDestroy()
    {
        receiveThread.Abort();
    }
}

由於Unity不支援多線程,無法在接收方法中直接設定texture,所以在fixedUpdate中設定。

private void FixedUpdate()
    {
        tex.LoadImage(imageDatas);
        img.texture = tex;
    }

成功~

Python傳輸畫面至Unity

操控物件也同理,在json資料中夾帶著操作的訊號就好。 但由於是跟著畫面資料一起串流,會有滿嚴重的延遲。 若資料改用UDP、畫面使用TCP傳輸應該會比較好!

這篇文章 【徵文賞-延展實境】佳作|史上最累Debug!深蹲才能過關(Unity + OpenCV 跨軟體傳輸實作) – 林慶佳 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
AR/VR/MR/XR這麼多RRRR,簡單搞懂你也可以快速成為科技人 https://creativecoding.in/2021/07/29/%e7%b0%a1%e5%96%ae%e6%90%9e%e6%87%82ar-vr-mr-xr/ Thu, 29 Jul 2021 02:34:00 +0000 https://creativecoding.in/?p=1338 本篇文章以簡短篇幅解釋AR、VR、MR、XR這四個R的實境科技的不同以及相關案例,除了提供簡單的區分方式,從此不會再搞混之外,也希望藉此鼓勵多加嘗試不同的作品體驗。

這篇文章 AR/VR/MR/XR這麼多RRRR,簡單搞懂你也可以快速成為科技人 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
精選圖片Photo by stephan sorkin on Unsplash

你是不是常常在新聞或朋友之間聽到AR、VR、MR、XR等等這些名詞呢?你是不是還分不清楚這麼多個R之間到底有哪些不同呢?

近幾年科技進步,五十年前被視為艱困的技術,如今設備製造成本大幅降低,不再只是科學實驗室裡面的儀器,普通人如你我也能以相對低的價格入手設備,甚至,只需要一台智慧型手機即可達成。

這篇文章帶你認識這些RRRR,我們會先依序個別介紹每個R、舉例作品,了解各有甚麼樣的特別之處,更重要的是,告訴你從此不再搞混的定義方式。

什麼是AR?

擴增實境Augmented Reality,簡稱AR。

以真實世界為基礎,使用其他數位方式增加新的視聽覺感知。

我們拿著有攝影機功能的電子產品如手機、平板,螢幕上的現實景象通過圖像分析,疊加、「擴增」上虛擬(也就是不存在於現實)的影像,讓我們同時看到真實世界與虛擬並存的內容,並且與之互動。數位的體驗和所處現實發生關係,讓感受更有趣、為現實增添更多想像。

AR舉例

寶可夢Pokémon GO!

2016年在台灣的街頭巷口,常常可看到不分年女老少一起抓寶可夢,轟動全球的《精靈寶可夢Pokemon Go!》遊戲是一款手機結合AR科技,使用IP以及延展原本劇情,創造了虛實整合的新型娛樂體驗。

當玩家開啟AR功能,可以將寶可夢放置眼前捕捉,或是放到實際的環境中,和玩家進行互動並拍攝照片,甚至是與現實生活中的場景互動,讓寶可夢的真實度更高。

今年滿五周年之際,即使疫情肆虐,仍然有許多玩家繼續抓寶,風潮似乎沒有退燒。

Facebook/Instagram/Snapchat等社群媒體的濾鏡

相信大家也十分熟悉,社群平台的限時動態提供相當多種類的濾鏡,如變顏色濾鏡、變臉特效、利用臉部表情或動作與圖像互動,或是各種測驗題目(如自己是哪個迪士尼角色)等等。

什麼是VR?

虛擬實境Virtual Reality,簡稱VR。

在數位裝置內打造完全虛擬的世界,由電腦產生的影像和聲音構成,依據不同類型,有些影片可360度觀看、有些體驗或遊戲可用手握控制器操控虛擬世界的物件以及移動等。

VR遊戲
Photo by Minh Pham on Unsplash
Photo by Minh Pham on Unsplash

知名電影《一級玩家》就是在描述著2045年的人類如何花大把時間與金錢在虛擬世界中打造理想的世界。最早VR這個概念出現在1930 年代,科幻小說家希望聯合科學家共同打造一個讓人逃離現實的虛擬世界。今日,經過幾十年的技術進步,已經將設備縮減至頭戴式顯示器 (HMD) 或頭戴式裝置加上一台電腦主機的大小。

現在的技術已經有高品質的視覺效果(減少眩暈),大廠如HTC以及被Facebook買下的Oculus算是目前最大的兩個廠牌,其中Oculus近幾年無線、輕型又平價的頭顯 Quest系列十分受歡迎,另外還有像Samsung、Sony Play Station等品牌緊追在後。追求的目標是完整的視聽覺饗宴,打造更全面沉浸的體驗,讓我們的大腦甚至以為真的是身在另一個世界中。

VR舉例

近幾年許多藝術家、電影導演開始對這樣的創作媒材感到好奇,開始了相關創作。如藉由電影《返校》得到金馬最佳新導演獎的的徐漢強導演近幾年拍了兩支征戰國際影展的VR作品《全能元神宮改造王》、《星際大騙局之登月計畫》都能找到他作品中獨特的觀點與喜劇戲謔的成分。

《全能元神宮改造王》預告片

《全能元神宮改造王》作品預告片

《星際大騙局之登月計畫》預告片

什麼是MR?

混合實境Mixed Reality,簡稱MR。

混合實境(MR)是虛擬實境VR以及擴增實境AR的混合體,一般也會搭配頭戴顯示器,但是使用者看到的是現實環境,額外再堆疊混合出虛擬的物體,將想像與現實相互交疊在一起,同時與實體和虛擬世界中的物件及環境去互動及操控。

MR舉例

圖片取自Microsoft官網

微軟的HoloLens頭戴式裝置,備有 4 個環境感知鏡頭、1 個深度鏡頭、1 個視訊鏡頭,將虛擬影像投射在真實世界,用戶在空中以手勢及語音和現實世界互動。

目前能想像最終極的應用,可能像是電影《鋼鐵人》中,主角東尼在他的地下工作室利用3D全息投影在空中的面板上操作。

圖片取自Inhabitat網站

什麼是XR?

延展實境Extended Reality,簡稱XR。

XR 即是以上三種的技術的集合,也就是 VR+AR+MR=XR 。XR 沒有太嚴謹的定義,任何 VR、AR、MR 的應用都可以視為 XR 的一環,也可說 XR 是虛擬現實交錯融合技術的總稱。

說了這麼多,要怎麼樣從此不再搞混這四個R?

擴增實境(AR)是在現實世界的環境中加入虛擬介面的東西,

虛擬實境(VR)是打造一個虛擬世界讓你進去沉浸,並且相信它的真實,

混合實境(MR)算加入了一點VR沉浸感的進階版擴增實境,

延展實境(XR)就是前面三者的總合。

簡單地以圖表示,有點像是光譜一樣地共同在介於現實與虛擬世界之間存在:

以光譜表示不同實境的定義。製圖:Chia編

發展與挑戰

目前的技術應用,擴增實境(AR)以商業使用為主,社群經營、遊戲、藝術作品等,透過AR將場域未說的故事繼續說盡;虛擬實境(VR)有許多的電影、短片導演嘗試拍攝VR360作品,也有從3D動畫切入的精美互動體驗,另外也有其他遊戲、模擬訓練上的應用;混合實境(MR)的應用較少,不過像是微軟、Magic Leap和Apple都正在開發這一塊。延展實境(XR)集合了三者,許多國際影展也開始特意建立XR的獎項,作為一種以影像敘述為基礎、增加上不同互動體驗的作品類型,值得許多創作人以各自的角度鑽研。

結語

閱讀完這篇文章,希望你們對這些RRRR有更多的了解囉!身為專業領域圈外的我們,除了時不時在社群媒體上玩濾鏡發布給好友觀看之外,在台灣也有一些機會可以去更認識VR的作品,像是高雄駁二藝術特區的VR FILM LAB是常設的VR播映場館,或是高雄電影節、台北電影節等也有另外設置XR專區體驗。也許,你也會愛上這樣的作品!

也許你對互動生成式藝術比較有興趣?來看看老闆的《互動藝術程式創作入門》課程,跟著將近兩千位同學一起把程式碼當作畫筆創作,或是先看看這篇文章,欣賞同學們完成的作品吧!

XR延伸閱讀

邁向共感的現實及未來-《面向未來-共感聯覺》虛擬實境展覽(上)
邁向共感的現實及未來-《面向未來-共感聯覺》虛擬實境展覽(下)
XR3:當國際沉浸式影展聯盟起來,在超現實博物館裡舉辦線上展覽活動-新影像藝術節、翠貝卡電影節、坎城影展

撰文:Chia編

墨雨設計banner

這篇文章 AR/VR/MR/XR這麼多RRRR,簡單搞懂你也可以快速成為科技人 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>