軟硬體實作 彙整 | Creative Coding TW - 互動程式創作台灣站 https://creativecoding.in/tag/軟硬體實作/ 蒐集互動設計案例、教學與業界資源,幫助你一起進入互動程式創作的產業 Wed, 05 Jul 2023 04:49:37 +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 未來 JavaScript 應用指標!不藏私 p5.js、生成式藝術與NFT 技術分享教學 https://creativecoding.in/2023/04/28/%e6%9c%aa%e4%be%86-javascript-%e6%87%89%e7%94%a8%e6%8c%87%e6%a8%99%ef%bc%81%e4%b8%8d%e8%97%8f%e7%a7%81-p5-js%e3%80%81%e7%94%9f%e6%88%90%e5%bc%8f%e8%97%9d%e8%a1%93%e8%88%87nft-%e6%8a%80%e8%a1%93/ Fri, 28 Apr 2023 08:39:02 +0000 https://creativecoding.in/?p=3643 JavaScript 未來全攻略!包括不藏私實際操作 Processing 和 p5.js,以及解密如何在 Artblocks 和 Opensea 等 NFT 平台上架作品。我們還會介紹連結硬體 OSC 和 socket,以及 MaxMSP 中 JS 模組的撰寫和基於 Tensorflowjs 的機器學習,帶領創作者一步步了解這些工具和技術,以便可以更好地創建自己的生成式藝術和互動作品,滿滿 JavaScript 乾貨讓創作者一次帶回家!

這篇文章 未來 JavaScript 應用指標!不藏私 p5.js、生成式藝術與NFT 技術分享教學 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>

哲宇老師榮幸受邀在 2021 年臺灣 JavaScript 開發者年會( JavaScript Developer Conference ,簡稱 JSDC )開講 JavaScript 在生成式、演算藝術與 NFT 的應用。 JavaScript 開發者年會是台灣最大的 JavaScript 年度性技術研討會, 提供台灣中高階 JavaScript 技術人才與世界最新 JavaScript 相關技術討論與分享。

此次分享將涵蓋到生成式藝術和互動領域的常用工具,包括不藏私實際操作 Processing 和 p5.js,以及解密如何在 Artblocks 和 Opensea 等 NFT 平台上架作品。我們還會介紹連結硬體 OSC 和 socket,以及 MaxMSP 中 JS 模組的撰寫和基於 Tensorflowjs 的機器學習,帶領創作者一步步了解這些工具和技術,以便可以更好地創建自己的生成式藝術和互動作品,滿滿 JavaScript 乾貨讓創作者一次帶回家!

JS的發展與瀏覽器虛擬化

JavaScript 在 p5.js 或是 即時 Web Base Ide 這些工具上,都能快速幫助藝術家和工程師快速的開發,甚至是結合網頁特效的 Library。當這些前端的東西都有了後,創作者通常會連結硬體。以前連接硬體,我們可能需要就是再額外安裝一些軟體或直接寫一些需要編譯的執行檔,現在你可以透過比如說像 OSC 與 socket,或者是像 web USB 這類協議直接連接硬體。在其他的軟體生態系裡面也逐步方便,比如說 Max MSP,也可以用 JS 來撰寫模組,甚至像 Tensorflowjs 的機器學習以及 ml5.js,也可以把機器學習跑在前端,然後最後再用 Electron 把它包裝成跨平臺的應用程式,都十分流暢。

JavaScript 在近年來的快速發展和優化,特別是 V8 引擎的改進和像 M1 晶片這樣的新技術的加入,使得JavaScript 的執行速度得到了大幅提升,從而使得 JavaScript 在處理一些複雜的任務,例如大量的particle 模擬或者是電腦繪圖等方面,也能夠有較好的表現。在這樣的情況下,像 Processing 和 p5.js 這樣的 JavaScript 框架也因其易學易用,而逐漸成為許多創作者和開發者的首選。這些框架提供了一個簡單的方式來設計和實現各種視覺化效果,從而使得創作者可以更輕鬆地進行創作,而不必擔心性能問題。

V8 在十年間的快速成長(圖片來源

生成式藝術與互動的常用工具

在創作生成是藝術跟互動的常用工具,第一個首選就是 p5.js 。 p5.js 可以視為 Processing 的 JavaScript 版本,它可以在網頁瀏覽器中運行。在 Processing 和 p5.js 中,可以使用不同的圖形和動畫函數,例如線條、形狀、色彩等,來創建各種視覺效果。或是使用鍵盤、滑鼠和觸控螢幕等輸入方式來創建互動應用程式。現在也有很多可以快速實作 p5.js 的 web ID,以下簡介給各位,第一個是 open processing 也是哲宇老師最常用的平臺,Open processing 使用上對初學者的設計師或工程師非常友善,像是在介面上以視覺為主,canvas 本身已幫創作者準備好,可以直接撰寫 p5.js 簡化的程式語言在上,並即時看見其渲染圖應用效果。比起 web canvas api 需要準備基礎的結構,可以讓創作者在最短的時間內執行 prototype,快速將生產環境建構出來。只要使用簡易語法,即可在畫布上建立不同的效果,例如:

  • createCanvas():建立畫布
  • fill():填上顏色
  • stroke():線條粗細
  • rect():建立方形
  • ellipse():建立圓形
  • mouseXmouseY:利用滑鼠 X 軸與 Y 軸的移動來控制圖形呈現的效果
  • rotate():旋轉
  • sin()cos():旋轉的角度

直述式的程式語言,與公開的文件參考資料讓使用者能夠直覺式的撰寫,讓大家可以更快速的進行創作。假使在使用上想要更嚴謹,或是引用更多的函式庫,哲宇老師推薦使用 CodeSandbox ,其與他者的差異性在於可以引入 Npm ,有點像是一個在遠端的虛擬機,可直接加入特定想使用的函式,檔案結構完整,甚至可以建立自己的模板,讓撰寫嚴謹,也符合個人化的專案設定。再來是 Codepen ,純粹使用 web canvas api,相較於 p5.js 較繁雜,但使用的工具本身並無優劣,而是需要依照每個人不同的的使用情境以及需求等等,去做創作上的使用。

在創作生成式藝術上,哲宇老師最常使用的方式是 p5.js 再加上 glsl 的 shader,產出效能大約為2至3小時一張創作便能完成,以下幾張較代表性的創作圖與各位做分享:

NFT 210612 Chaos Dancer #Classic #1(圖片來源

創作上,哲宇老師經常使用留下筆跡的創作手法,在畫布上設定畫布大小、需要出現的圖形與顏色,並且記得藏起背景功能,因為背景功能在 draw 底下為在每一次重複將東西清掉,所以無法顯示圖形軌跡,如下示範:

function setup() {
  createCanvas(windowWidth, windowHeight);
}

function draw() {
  // background(220);

  fill("blue");

  push();
    fill(255, mouseY, 50);
    translate(mouseX, mouseY);
    rotate(frameCount / 50);
    rect(0, 0, 200, 200);
  pop();

  ellipse(0, 0, 200, 200);
}

而 Chaos Dancer 就是以類似產生大量的 particle,讓它們隨著時間旋轉並逐漸變小,再加上 sin cos 等力場,並將軌跡留下,形成畫布,並再疊上不同材質讓創作有更不一樣的感覺。

NFT 200506 Loop Mountain #Classic #1(圖片來源

就是單純用迴園功能,讓圓圈往漸小的往上長,與隨機的大小變化去執行。

NFT 210913 Sweet Dreams #Classic #1(圖片來源

除了留下軌跡外,哲宇老師也分享了另一種常使用的創作手法,變化的遞迴切割。

變化的遞迴切割示範(圖片來源

半圓型線條,再加上大小不同的色塊與質感組成,跳脫一般遞迴切割的相同模式,形成較新穎的氛圍。在此創作上,主要使用 divideSq() 去形成遞迴切割的部分,並且設定在不同機率下形成的多樣變化。哲宇老師分享因自己的工程背景,在創作上常使用一些數學物理相關概念,也說明到其實遞迴、粒子或是物理模擬等等進行創作。諸如此類的視覺特效也有不同的資料庫提供大家參考使用,像是專門製作 3D 的 Three.js 、圖像處理與 Shader,具有像素等級處力效能的 Pixi.js 、向量操作專門戶 Paper.js 以及類似於早期的 Flash,專門製作小型遊戲的 Phaser.js

Artblocks – 區塊鏈與生成式藝術的整合

生成式藝術與 Artblocks 的關係到底為何呢?先來簡單介紹 Artblocks 給大家。 Artblocks 是賣生成式藝術的 NFT(非同質化代幣)平台,每個作品在平台上可能會有 500至1000個版本。但其限制是,平台希望程式也要在鏈上,所以只能接受使用一種  dependency 。當創作者完成創作後,每一個版本都會有所不同,但同時間因為透過了每個作品上的 hash ,轉換成亂數並給作品做使用。 在 opensea 上面會擁有這些不同版本的創作。

Artblocks 首頁(圖片來源

平台上編寫時,主要使用 xor 演算法,此演算法會依據不同的 seed ,也就是亂數位元作操作。操作完後,在以模擬電腦亂數表方式產生亂數, 如果次數固定,則意味著在相同 hash 的狀況下,一定可以在某些特定狀況下得到相同亂數,此方法可以在 random 中設定想要更改的數字或陣列。哲宇老師也使用了 Token Art Tools 執行 debug,透過左側項目欄調整各種比例以更改 hash 的組成,並自動提供給 script ,這樣就可以快速地看到作品在不同 hash 時的不同樣貌。(更多 Artblocks 的執行細節可參考此篇 >>【老闆週六來聊聊】吳哲宇 Artblocks Project – Electriz 製作分享

Token Art Tools 網站截圖

在上架 Artblocks 平台時,會要求將所有程式碼上架到主網上,而因此需付 1 至 2 ETH 左右。另外,在 mint 的同時,即會產生隨機的 hash ,也同時間將 preview 上架到 Opensea 上。所以,從 Artblock 到 Opensea 上的流程總體結論就是從 Hash 到 Random Number Generator ,最後在看要使用 Static output(靜態輸出) 或是 Dynamic output(動態輸出),並且會在這整個流程中的某一時刻擷取快照形成 preview 。生成式藝術的應用多變性,也直接的反應在創作中。像是要形成如上述複雜的圖樣,哲宇老師通常會使用變化角度 sin() 或 cos() 等去執行,或是假使作品要應用到不同載體上,如投影或是印製到硬體上,需要放大作品的像素密度,我們也只需要簡單設定 PixelDensity(),使作品在只要能透過瀏覽器進行的載體上,都能設定出自己所需要的圖片密度大小。

連接硬體 OSC 與 socket

以前在連結硬體上,都需要再透過 processing 進行傳輸,現在其實有多種不同方式可以使用,包含 p5.js 也可以幫你執行!比較為一般人常見的傳統 Open sound control(網絡音訊傳輸協定)有應用在 DJ 控制燈光及音樂的常見場景,使用 udp 加上 socket.io client,使前端網頁可以直接連結到 bridge 上接收 osc 的訊息,範例如下,哲宇老師使用過藉由賭博的方式去控制檯燈的作品。

哲宇老師連結硬體創作作品(圖片來源

此作品連結 MaxMSP,運用偵測到骰子上的點數以及有幾個骰子加上連結 socket 去控制檯燈的明亮度。另外,像是開源式平台 Arduino 只要在網站上就能編輯,或是 Johnny-Five ,一個基於 JavaScript 的機器人和物聯網(IoT)程式框架,允許開發人員使用高級程式語言來控制硬體。 Johnny-Five 設計可於包括 Arduino 、 Raspberry Pi 和 Particle 設備等各種硬體平台一起使用。

Johnny-Five 首頁(圖片來源

還有 Node-RED 以簡單易用的圖形化用戶界面,用戶可以通過拖拽節點並用連接線連接它們來創快速構建 IoT 應用程式。

Node-RED 首頁(圖片來源

經由這些新平台的介紹,我們可以知道,其實 Javascript 現在的語言邏輯更容易上手,能整合多種生態系,甚至是跨軟硬體執行,對創作者更增加了無限的可能。

MaxMSP 的 JS 模組撰寫

講到硬體與軟體的應用,就不得不提到 MaxMSP 了。 MaxMSP 為一款音樂創作、聲音處理和音樂表演的軟體,它可以讓用戶使用圖形化的方式編寫音樂程序。界面直觀,使用滑鼠和鍵盤就可以輕鬆地創建、編輯和控制音樂和聲音,所以 VJ 或是新媒體藝術家常運用此軟體進行創作。在使用上, MaxMSP 也提供了 Node for Max ,一個專門為 Max/MSP 環境開發的軟體,讓使用者也可以運用 Javascript 撰寫並轉譯成 MaxMSP 可以運用的語言。語言資源方面,除了 Max 的 api 外,也能使用像是 tonal,擁有諸多和弦及音符等相關處理函數能夠去做運算。

Node for Max 介面(圖片來源

前端機器學習及相關運用

以下與大家介紹更多相關資源,例如 TensorFlow.js ,是 Google 開發的一個開源 JavaScript 框架,它使得開發人員可以在網頁瀏覽器上使用並進行機器學習。強大的 JavaScript API ,可以用於在瀏覽器中創建、訓練和部署機器學習模型,除了可以在瀏覽器中運行這些模型外,還可以在瀏覽器中使用預訓練模型進行影像、聲音和自然語言處理等任務。

TensorFlow.js 首頁(圖片來源

使用上,則有 ml5js 函式庫的協助。函式庫中包含影像、聲音、姿勢和自然語言處理等多種模型,提供了更簡單的方式來使用機器學習。

ml5js 首頁(圖片來源

示範案例,使用函式庫進行臉部以及動作的追蹤創作,也可以依此應用延伸出更多商業價值,像是依據追蹤動作,留下軌跡或是特別觸發某些機關等互動式創作。

ml5js 示範創作截圖(圖片來源

或是透過 Google 的 Teachable Machine,在線上訓練不同的模型,輸出為 json 檔案後再透過 ML5 做載入,例如以下為說明左傾右傾的差別,並在最後到 p5.js 進行視覺化。

Teachable Machine 示範(圖片來源

上述所講到的瀏覽器端應用,皆可以使用 Electron 進行包裝,藉由封裝方式形成桌面板可應用程式,可運行於 Windows 、 macOS 和 Linux 等多個操作系統平台上,讓開發者可以使用熟悉的前端技術來開發桌面應用程式。

JS 在創作領域發展的展望與未來

瀏覽器的完整虛擬化和純雲端應用程式使得開發者可以在雲端環境中開發和部署應用程式,從而降低開發和維護成本,同時提高應用程式的可擴展性和安全性,進而也讓 Isomorphic JavaScript 的執行效能得到了大幅優化,並且提供了 App 包裝功能,使得開發者可以將應用程式打包成原生應用程式,運行在桌面和手機等平台上。加上高階特性和語法糖的成熟以及 TypeScript 的支持使得開發者可以更加容易地編寫高質量的程式碼,與 Package 生態系的豐富性和開源社區的活躍性,都為開發者提供了更多的選擇和支持。快速開發環境和不重複造輪子的潮流下,使得開發者可以更加專注於應用程式的邏輯,並且快速開發出高品質的應用程式,同時也可以減少開發成本和風險。

商業應用範例(圖片來源

總體而言, JavaScript 的發展帶來了許多新的機會和可能性,使得創作者可以更輕鬆地實現各種想法和概念。同時,像 Processing 和 p5.js 這樣的框架也為創作者提供了更多的工具和資源,使得他們可以更加專注於創作本身,而不必花費太多時間和精力在技術層面上。最後,經過本次分享你也躍躍欲試,等不及想要開始進行快速易上手的創作了嗎?趕快訂閱 老闆,來點寇汀吧。Boss, CODING please. Youtube 頻道搶先了解第一手消息,或是加入哲宇老師在 hahow 開設的課程,一起快速上手程式藝術撰寫!也歡迎對相關項目有興趣的同學可以至 墨雨設計 聊聊天喔 ~

此篇直播筆記由幫手熊柑協助整理。

這篇文章 未來 JavaScript 應用指標!不藏私 p5.js、生成式藝術與NFT 技術分享教學 最早出現於 Creative Coding TW - 互動程式創作台灣站

]]>
【徵文賞-延展實境】佳作|史上最累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 - 互動程式創作台灣站

]]>