Swift ARKit 臉部特效
簡介
ARKit 讚讚
基本設置
新增ARSCNView
override func viewDidLoad() {
super.viewDidLoad()
sceneView = ARSCNView()
sceneView.frame = CGRect.init(x: 0, y: 0, width: self.view.frame.width, height: self.view.frame.height)
sceneView.delegate = self
sceneView.showsStatistics = true
self.view.addSubview(sceneView)
guard ARFaceTrackingConfiguration.isSupported else {
fatalError("Face tracking is not supported on this device")
}
}
// 進入時開始執行
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
let configuration = ARFaceTrackingConfiguration()
sceneView.session.run(configuration)
}
// 退出時暫停
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
sceneView.session.pause()
}
ARSCNViewDelegate
// 設置node
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let faceMesh = ARSCNFaceGeometry(device: sceneView.device!)
var node = SCNNode(geometry: faceMesh)
return node
}
// 更新回傳
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
}
BlendShapes 表情
表情偵測
以下小範例
// 張嘴巴
func expression(anchor: ARFaceAnchor) {
let jawOpen = anchor.blendShapes[.jawOpen]
if jawOpen?.decimalValue ?? 0.0 > 0.1 {
DispatchQueue.main.async {
self.view.showToast(text: "張嘴巴")
}
}
}
更多表情可參考以下更改
decimalValue決定表情大小
表情定位符 | 描述 |
---|---|
eyeBlinkLeft | 左眼眨眼 |
eyeLookDownLeft | 左眼目視下方 |
eyeLookInLeft | 左眼注視鼻尖 |
eyeLookOutLeft | 左眼向左看 |
eyeLookUpLeft | 左眼目視上方 |
eyeSquintLeft | 左眼眯眼 |
eyeWideLeft | 左眼睜大 |
eyeBlinkRight | 右眼眨眼 |
eyeLookDownRight | 右眼目視下方 |
eyeLookInRight | 右眼注視鼻尖 |
eyeLookOutRight | 右眼向左看 |
eyeLookUpRight | 右眼目視上方 |
eyeSquintRight | 右眼眯眼 |
eyeWideRight | 右眼睜大 |
jawForward | 努嘴時下巴向前 |
jawLeft | 撇嘴時下巴向左 |
jawRight | 撇嘴時下巴向右 |
jawOpen | 張嘴時下巴向下 |
mouthClose | 閉嘴 |
mouthFunnel | 稍張嘴並雙唇張開 |
mouthPucker | 抿嘴 |
mouthLeft | 向左撇嘴 |
mouthRight | 向右撇嘴 |
mouthSmileLeft | 左撇嘴笑 |
mouthSmileRight | 右撇嘴笑 |
mouthFrownLeft | 左嘴唇下壓 |
mouthFrownRight | 右嘴唇下壓 |
mouthDimpleLeft | 左嘴唇向後 |
mouthDimpleRight | 右嘴唇向後 |
mouthStretchLeft | 左嘴角向左 |
mouthStretchRight | 右嘴角向右 |
mouthRollLower | 下嘴唇卷向里 |
mouthRollUpper | 下嘴唇卷向上 |
mouthShrugLower | 下嘴唇向下 |
mouthShrugUpper | 上嘴唇向上 |
mouthPressLeft | 下嘴唇壓向左 |
mouthPressRight | 下嘴唇壓向右 |
mouthLowerDownLeft | 下嘴唇壓向左下 |
mouthLowerDownRight | 下嘴唇壓向右下 |
mouthUpperUpLeft | 上嘴唇壓向左上 |
mouthUpperUpRight | 上嘴唇壓向右上 |
browDownLeft | 左眉向外 |
browDownRight | 右眉向外 |
browInnerUp | 蹙眉 |
browOuterUpLeft | 左眉向左上 |
browOuterUpRight | 右眉向右上 |
cheekPuff | 臉頰向外 |
cheekSquintLeft | 左臉頰向上並回旋 |
cheekSquintRight | 右臉頰向上並回旋 |
noseSneerLeft | 左蹙鼻子 |
noseSneerRight | 右蹙鼻子 |
tongueOut | 吐舌頭 |
臉部矛點 臉部定位
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let faceMesh = ARSCNFaceGeometry(device: sceneView.device!)
let node = SCNNode(geometry: faceMesh)
// 臉上矛點
let clearMaterial = SCNMaterial(color: .link)
node.geometry!.materials = [clearMaterial]
node.geometry?.firstMaterial?.fillMode = .lines
for x in 0..<1220 {
let text = SCNText(string: "\(x)", extrusionDepth: 1)
let txtnode = SCNNode(geometry: text)
txtnode.scale = SCNVector3(x: 0.0002, y: 0.0002, z: 0.0002)
txtnode.name = "\(x)"
node.addChildNode(txtnode)
txtnode.geometry?.firstMaterial?.fillMode = .fill
}
return node
}
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry {
faceGeometry.update(from: faceAnchor.geometry)
expression(anchor: faceAnchor)
DispatchQueue.main.async {
self.faceLabel.text = self.analysis
}
}
guard let faceAnchor = anchor as? ARFaceAnchor,
let faceGeometry = node.geometry as? ARSCNFaceGeometry
else {
return
}
// 臉上矛點
for x in 0..<1220 {
let child = node.childNode(withName: "\(x)", recursively: false)
child?.position = SCNVector3(faceAnchor.geometry.vertices[x])
}
faceGeometry.update(from: faceAnchor.geometry)
}
DEMO
顯示每個臉部節點數字+網格
臉部特效
宣告圖片名稱+臉部座標
let noseOptions = ["nose_1", "nose_2", "nose_3", "nose_4"]
let eyeOptions = ["eye_1", "eye_2", "eye_3"]
let mouthOptions = ["mouth_1", "mouth_2", "mouth_3", "mouth_4"]
let hatOptions = ["hat_1", "hat_2", "hat_3", "hat_4"]
let features = ["nose", "leftEye", "rightEye", "mouth", "hat"]
let featureIndices = [[9], [1064], [42], [24, 25], [20]]
// 處理不同臉部表情位置
func updateFeatures(for node: SCNNode, using anchor: ARFaceAnchor) {
for (feature, indices) in zip(features, featureIndices) {
let child = node.childNode(withName: feature, recursively: false) as? ImageNode
let vertices = indices.map { anchor.geometry.vertices[$0] }
child?.updatePosition(for: vertices)
switch feature {
case "leftEye":
let scaleX = child?.scale.x ?? 1.0
let eyeBlinkValue = anchor.blendShapes[.eyeBlinkLeft]?.floatValue ?? 0.0
child?.scale = SCNVector3(scaleX, 1.0 - eyeBlinkValue, 1.0)
case "rightEye":
let scaleX = child?.scale.x ?? 1.0
let eyeBlinkValue = anchor.blendShapes[.eyeBlinkRight]?.floatValue ?? 0.0
child?.scale = SCNVector3(scaleX, 1.0 - eyeBlinkValue, 1.0)
case "mouth":
let jawOpenValue = anchor.blendShapes[.jawOpen]?.floatValue ?? 0.2
child?.scale = SCNVector3(1.2, 0.8 + jawOpenValue, 1.2)
case "hat":
child?.scale = SCNVector3(1.0, 1.0, 1.0)
default:
break
}
}
}
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry {
faceGeometry.update(from: faceAnchor.geometry)
expression(anchor: faceAnchor)
DispatchQueue.main.async {
self.faceLabel.text = self.analysis
}
}
guard let faceAnchor = anchor as? ARFaceAnchor else {
return
}
updateFeatures(for: node, using: faceAnchor)
}
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
// 臉部圖片
guard let faceAnchor = anchor as? ARFaceAnchor,
let device = sceneView.device else { return nil }
let faceGeometry = ARSCNFaceGeometry(device: device)
let node = SCNNode(geometry: faceGeometry)
node.geometry?.firstMaterial?.fillMode = .lines
node.geometry?.firstMaterial?.transparency = 0.0
let noseNode = ImageNode(with: noseOptions)
noseNode.name = "nose"
node.addChildNode(noseNode)
let leftEyeNode = ImageNode(with: eyeOptions)
leftEyeNode.name = "leftEye"
leftEyeNode.rotation = SCNVector4(0, 1, 0, GLKMathDegreesToRadians(180.0))
node.addChildNode(leftEyeNode)
let rightEyeNode = ImageNode(with: eyeOptions)
rightEyeNode.name = "rightEye"
node.addChildNode(rightEyeNode)
let mouthNode = ImageNode(with: mouthOptions)
mouthNode.name = "mouth"
node.addChildNode(mouthNode)
let hatNode = ImageNode(with: hatOptions)
hatNode.name = "hat"
node.addChildNode(hatNode)
updateFeatures(for: node, using: faceAnchor)
return node
}
ImageNode
class ImageNode: SCNNode {
var options: [String]
var index = 0
init(with options: [String], width: CGFloat = 0.06, height: CGFloat = 0.06) {
self.options = options
super.init()
let plane = SCNPlane(width: width, height: height)
plane.firstMaterial?.diffuse.contents = UIImage.init(named: options.first ?? "")
plane.firstMaterial?.isDoubleSided = true
geometry = plane
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
// MARK: - Custom functions
extension ImageNode {
func updatePosition(for vectors: [vector_float3]) {
let newPos = vectors.reduce(vector_float3(), +) / Float(vectors.count)
position = SCNVector3(newPos)
}
func next() {
index = (index + 1) % options.count
if let plane = geometry as? SCNPlane {
plane.firstMaterial?.diffuse.contents = UIImage.init(named: options[index])
plane.firstMaterial?.isDoubleSided = true
}
}
}
DEMO
搖頭/點頭偵測
var isLeft = false
var isRight = false
var isUp = false
var isDown = false
/// 檢查搖頭
func checkShakingHead() {
if isLeft && isRight {
DispatchQueue.main.async {
self.view.showToast(text: "搖頭成立")
}
isLeft = false
isRight = false
}
}
/// 檢查點頭
func checkNod() {
if isUp && isDown {
DispatchQueue.main.async {
self.view.showToast(text: "點頭成立")
}
isUp = false
isDown = false
}
}
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if let faceAnchor = anchor as? ARFaceAnchor, let faceGeometry = node.geometry as? ARSCNFaceGeometry {
faceGeometry.update(from: faceAnchor.geometry)
expression(anchor: faceAnchor)
DispatchQueue.main.async {
self.faceLabel.text = self.analysis
}
}
// 搖頭偵測
if node.orientation.y > 0.2 {
isLeft = true
} else if node.orientation.y < -0.2 {
isRight = true
}
checkShakingHead()
// 點頭偵測
if node.orientation.x > 0.2 {
isUp = true
} else if node.orientation.x < -0.03 {
isDown = true
}
checkNod()
}
DEMO
專案下載:
Github
參考文件:
AR Face Tracking Tutorial for iOS: Getting Started
https://www.raywenderlich.com/5491-ar-face-tracking-tutorial-for-ios-getting-started
ARFoundation之路-人脸检测增强之四
https://blog.csdn.net/yolon3000/article/details/101388015
Swift更多文章
Swift 彈出視窗 AlertController 的使用方法 💥
how to get promethazine without a prescription cheap erectile dysfunction is ivermectin a prescription drug
duodenal mucosa with focal erosion get online prescription for uti best pain killer for uti