• 5

Swift[第6單元(上)] SceneKit 空間運算

補充(16) 表情+語音控制

用「表情」控制 AR 軟體,似乎是非常自然的事情,就像用手指滑動手機一樣。不過,為什麼大部分的電腦軟體都不能用表情控制呢?

答案是人類最自然的互動方式,對電腦來說,卻是天大的挑戰。要讓電腦懂得人類的溝通方式,包括語音、表情、眼球追蹤、肢體動作等,都需要人工智慧(AI) — 可以說,未來幾年的 AI 發展,將為自然的人機介面鋪路。

若從人與電腦互動方式的歷史來看,「自然介面」會是人機介面非常重大的變革,有可能成為下一代的主流。若發展成熟,將大大促進穿戴式設備、機器人、智慧家電、自動駕駛載具…等新一代智慧型產品的普及。

為什麼 Apple 開發 Vision Pro 時,特意不使用其他AR/VR廠商常用的手持控制器?相信跟 Steve Jobs 在 2007年發表 iPhone 時,特意排除觸控筆的原因一樣:因為這樣最自然。

從下圖 Apple 產品的人機介面演進的方式來看,從鍵盤、滑鼠進化到手指觸控,花了30年(1977-2007),而從iPhone手指觸控到Vision Pro用手勢/眼神/表情/語音控制,也才16年,還有很大發展空間。


[第5單元]第7課5-7a曾經介紹過AI即時語音辨識,因此,能否將語音辨識加進上一節(補充15)範例中,用「表情+語音」來控制3D模型呢?本節就來試試看。

首先複習一下5-7a範例程式,語音辨識的關鍵程式碼才10行左右:
// 5-7a 語言學習機(SFSpeechAudioBufferRecognitionRequest)
struct 語言學習機 {
@Binding var 辨識結果: String
let 辨識器 = SFSpeechRecognizer() // default: system locale "zh-TW"
let 辨識請求 = SFSpeechAudioBufferRecognitionRequest()

func 錄音即時辨識() {
// ... (將錄音加入辨識請求中)
if 辨識器 != nil && 辨識器!.isAvailable {
辨識器!.recognitionTask(with: 辨識請求) { 結果, 錯誤 in
if 結果 != nil {
辨識結果 = 結果!.bestTranscription.formattedString
print(辨識結果)
}
}
}
}
}

這段程式碼的流程如下圖,基本上就是將即時錄音的「暫存區」,持續加入「辨識請求」中,然後交給「語音辨識器」,這樣就會不斷產出「辨識結果」(匿名函式會不斷被呼叫)。


那麼,用 ARKit 如何取得即時錄音的音訊(記憶體暫存區)呢?非常簡單,只要先在設定人臉追蹤時,多加一行程式碼 — 設定 providesAudioData 屬性為 true:
let 人臉追蹤設定 = ARFaceTrackingConfiguration()
人臉追蹤設定.providesAudioData = true // Call session(_:didOutputAudioSampleBuffer:)

這樣除了啟動前相機之外,也會開始錄音,讓我們省掉 AVAudioEngine 的操作過程。

要取得錄音暫存區,還得透過代理程式(AR後台助手),不過這次是 ARSession 的代理程式,規定的函式名稱為 session():
func session(_ session: ARSession, didOutputAudioSampleBuffer audioSampleBuffer: CMSampleBuffer) {
// 取得即時音訊,提供給語音辨識
辨識請求.appendAudioSampleBuffer(audioSampleBuffer)
}

只要加一行程式,從 session() 取得的錄音暫存區,持續加入「辨識請求」即可。

附帶一提,ARSession 的代理程式,規定了5種 session() 函式,可用來取得單幀畫面或音訊內容:

1. session(_:cameraDidChangeTrackingState:) ← 取得單幀畫面
2. session(_:didChange:)
3. session(_:didOutputAudioSampleBuffer:) ← 取得錄音暫存區(本節使用)
4. session(_:didFailWithError:)
5. session(_:didOutputCollaborationData:)

我們實際用的規範 ARSCNViewDelegate 同時繼承了兩種代理(渲染器以及AR任務),因此兩種函式 renderer(), session() 可一起並用。

在此範例中,我們將第5課「粒子系統」加進來,讓3D面具噴出火焰,口令是「噴火」。這樣一來,我們就有「表情+語音」兩種控制手段 — 眨眼時切換外觀,唸口令時嘴巴噴火,再唸其他語句時停止噴火。

在 iPad Pro 11” (2018年, A12X晶片)上實際執行效果如下:


完整範例程式如下,全部才134行(這些功能在過去至少得寫1,000行以上):
// 補充(16) 表情+語音控制
// Created by Heman, 2024/07/25
// 執行前請關閉「啟用結果」(在「▶︎執行我的程式碼」左邊)
import SceneKit
import SwiftUI
import ARKit
import Speech

struct 人臉追蹤: UIViewRepresentable {
let 小幫手 = AR後台助手()

func makeUIView(context: Context) -> SCNView {
let AR視圖 = ARSCNView()

if ARFaceTrackingConfiguration.isSupported {
print("支援人臉追蹤")
let 人臉追蹤設定 = ARFaceTrackingConfiguration()
人臉追蹤設定.providesAudioData = true // Call session(_:didOutputAudioSampleBuffer:)
if let 語音辨識器 = 小幫手.辨識器, 語音辨識器.isAvailable {
// print(語音辨識器.locale)
// print(小幫手.辨識請求.nativeAudioFormat)
語音辨識器.recognitionTask(with: 小幫手.辨識請求) { 結果, 錯誤 in
if let 結果 {
print(結果.bestTranscription.formattedString)
小幫手.辨識結果 = 結果.bestTranscription.formattedString
}
}
}

AR視圖.session.run(人臉追蹤設定)
AR視圖.delegate = 小幫手 // ARSCNViewDelegate
小幫手.myGPU = AR視圖.device // 臉型追蹤設備(GPU)
print(AR視圖.device)
} else {
print("不支援人臉追蹤")
let 提示 = SCNText(string: "☹︎設備不支援臉部追蹤", extrusionDepth: 1.0)
let 提示節點 = SCNNode(geometry: 提示)
let 虛擬場景 = SCNScene()
虛擬場景.rootNode.addChildNode(提示節點)
AR視圖.scene = 虛擬場景
AR視圖.allowsCameraControl = true // 移到此處,避免干擾AR效果
}

AR視圖.backgroundColor = .gray
AR視圖.autoenablesDefaultLighting = true
AR視圖.showsStatistics = true

return AR視圖
}

func updateUIView(_ uiView: SCNView, context: Context) { }
}

class AR後台助手: NSObject, ARSCNViewDelegate {
var myGPU: MTLDevice? = nil // 從外部 ARSCNView().device 傳入
// 加入即時語音辨識功能
let 辨識器 = SFSpeechRecognizer(locale: Locale(identifier: "zh-TW"))
let 辨識請求 = SFSpeechAudioBufferRecognitionRequest()
var 辨識結果: String = ""

// 加入火焰節點
func 火焰節點() -> SCNNode {
let 節點 = SCNNode(geometry: SCNSphere(radius: 0))

let 粒子雲 = SCNParticleSystem()
粒子雲.birthRate = 20
粒子雲.birthLocation = .surface
粒子雲.birthDirection = .random
粒子雲.particleLifeSpan = 0.1
粒子雲.particleImage = UIImage(named: "particleImage")
粒子雲.imageSequenceRowCount = 4
粒子雲.imageSequenceColumnCount = 4
粒子雲.imageSequenceFrameRate = 160
粒子雲.particleColor = .orange
粒子雲.particleColorVariation = SCNVector4(0, 0, 1, 1)
粒子雲.particleSize = 0.03
粒子雲.particleSizeVariation = 0.02
粒子雲.particleVelocity = 1
粒子雲.particleVelocityVariation = 1
粒子雲.emitterShape = SCNSphere(radius: 0.01)
粒子雲.emissionDuration = 0
粒子雲.loops = false
節點.addParticleSystem(粒子雲)

return 節點
}

// 偵測到人臉時呼叫一次
func renderer(_ renderer: any SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
if myGPU == nil {
return nil
} else {
let 臉型 = ARSCNFaceGeometry(device: myGPU!)
let 臉型節點 = SCNNode(geometry: 臉型)
臉型節點.geometry?.materials.first?.fillMode = .lines

return 臉型節點
}
}

// 當畫面變動時(約每1/60秒)呼叫一次
func renderer(_ renderer: any SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if let 臉部錨點 = anchor as? ARFaceAnchor,
let 臉型 = node.geometry as? ARSCNFaceGeometry {
臉型.update(from: 臉部錨點.geometry)

let 表情 = 臉部錨點.blendShapes
if 表情[.eyeBlinkLeft] as! Double > 0.5 || 表情[.eyeBlinkRight] as! Double > 0.5 {
node.geometry?.firstMaterial?.fillMode = .fill
} else {
node.geometry?.firstMaterial?.fillMode = .lines
}

let 噴火 = 火焰節點()
if 辨識結果.hasSuffix("噴火") {
// 臉部頂點位置請參考 https://facelandmarks.com/
let 舌頭位置 = (臉部錨點.geometry.vertices[24] + 臉部錨點.geometry.vertices[25]) / 2.0
噴火.position = SCNVector3(舌頭位置)
node.addChildNode(噴火)
} else {
噴火.removeFromParentNode()
}
}
}

// 錄音有變化時呼叫一次
func session(_ session: ARSession, didOutputAudioSampleBuffer audioSampleBuffer: CMSampleBuffer) {
// 取得即時音訊,提供給語音辨識
辨識請求.appendAudioSampleBuffer(audioSampleBuffer)
}
}

import PlaygroundSupport
PlaygroundPage.current.setLiveView(人臉追蹤())

💡 備註
  1. 鍵盤並不是最早的電腦輸入裝置,在1970年代開始用鍵盤之前採用打孔卡片,是早期大型電腦(Mainframe)的主要輸入方式。
  2. 觸控螢幕在1990年代已有電腦設備採用,但大多當作滑鼠替代品,也就是以點擊(click)為主。直到Apple重新改寫作業系統,專為觸控螢幕設計全新互動方式,這一小小改變,竟然造就巨大革命,可見人機介面的重要性。
  3. 人機自然介面(語音/表情/肢體)之後的下一代變革可能是所謂的「腦機介面」,直接讀取大腦神經元的訊號與電腦雙向溝通,如馬斯克(Elon Musk)創辦的 Neuralink。
  4. 【作業】如何讓噴火位置在嘴巴中間,而不是其他地方,內文並未說明,請從程式碼研究看看。
  5. 【作業】請將3D面具預設的白色材質更換為「上半單元結語」的透明材質。

◼︎◼︎►Swift [第6單元(下)] RealityKit 空間運算
雪白西丘斯

下半單元「RealityKit 空間運算」請移至 https://www.mobile01.com/topicdetail.php?f=482&t=7078051

2025-02-05 13:57
  • 5
內文搜尋
X
評分
評分
複製連結
Mobile01提醒您
您目前瀏覽的是行動版網頁
是否切換到電腦版網頁呢?