ARCore與ARKit實(shí)現(xiàn)人臉貼紙、更換材質(zhì)等動(dòng)畫效果

近兩年市面上出現(xiàn)了很多有關(guān)有關(guān)美顏濾鏡瘦麸、貼紙等各種貼紙效果的相機(jī)出現(xiàn)谁撼,由于項(xiàng)目的需要調(diào)研了google開源的一個(gè)能夠提供3D角度的ARCore框架,本人也結(jié)合ARKit在iOS手機(jī)上實(shí)現(xiàn)了類抖音的效果
系統(tǒng)要求

  1. iOS11.0以上系統(tǒng)
    2.iPhone6s以上的Iphone手機(jī)

首先看下效果


IMG_3543.PNG

1.引入需求的庫

target 'ARCoreFaceDemo'
platform :ios, '10.0'
pod 'ARCore/AugmentedFaces', '~> 1.13.0'
pod 'SnapKit', '~> 4.2.0'

2.使用AVFoundation框架進(jìn)行數(shù)據(jù)的采集工作

/// Setup a camera capture session from the front camera to receive captures.
    private func setupCamera() {
        guard let device =
            AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
            let input = try? AVCaptureDeviceInput(device: device)
            else {
                NSLog("Failed to create capture device from front camera.")
                return
        }
        
        let output = AVCaptureVideoDataOutput()
        output.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA]
        output.setSampleBufferDelegate(self, queue: DispatchQueue.global(qos: .userInteractive))
        
        session.sessionPreset = .high
        
        videoInput = input
        
        session.addInput(input)
        session.addOutput(output)
        captureSession = session
        captureDevice = device
        
        cameraImageLayer.contentsGravity = .center
        cameraImageLayer.frame = self.view.bounds
        view.layer.insertSublayer(cameraImageLayer, at: 0)
        
        startCameraCapture()
    }

3.設(shè)置場景

private func setupScene() {
        let scene = SCNScene(named: "Face.scnassets/fox_face.scn")
        //    Face.scnassets/face_texture.png
        guard let faceImage = UIImage(named: "multiply01.png"),
            //      let modelRoot = scene?.rootNode.childNode(withName: "asset", recursively: false)
            let modelRoot = scene?.rootNode.childNodes.first
            else {
                NSLog("Failed to load face scene!")
                return
        }
        // SceneKit uses meters for units, while the canonical face mesh asset uses centimeters.
        modelRoot.simdScale = simd_float3(1, 1, 1) * kCentimetersToMeters
        foreheadLeftNode = modelRoot.childNode(withName: "FOREHEAD_LEFT", recursively: true)
        foreheadRightNode = modelRoot.childNode(withName: "FOREHEAD_RIGHT", recursively: true)
        noseTipNode = modelRoot.childNode(withName: "NOSE_TIP", recursively: true)
        
        faceNode.addChildNode(faceTextureNode)
        faceNode.addChildNode(faceOccluderNode)
        
        //    if let catImage = UIImage(named: "cap01.png"){
        //        let catWidth: CGFloat = 22
        //        let catHeight = catWidth * catImage.size.width/catImage.size.height
        //        let geometry = SCNBox(width: catWidth, height: catHeight, length: 0, chamferRadius: 0)
        //        //    let geometry = SCNTorus(ringRadius: 5, pipeRadius: 1)
        //        geometry.firstMaterial?.diffuse.contents = UIImage(named: "cap01.png")
        //        capNode.geometry = geometry
        //
        //
        //        if var position = foreheadRightNode?.position  {
        //            position.x = 0
        //            position.y += 1
        //            position.z += 1
        //            capNode.position = position
        //        }
        //
        //
        //        modelRoot.addChildNode(capNode)
        //    }
        
        
        
        //    let plan = SCNBox(width: 38, height: 30, length: 0, chamferRadius: 0)
        //    plan.firstMaterial?.diffuse.contents = UIImage(named: "bigcat.png")
        //    bigCapNode.geometry = plan
        //    modelRoot.addChildNode(bigCapNode)
        
        var gifImages:[UIImage] = []
        for i in 0..<23 {
            let name = String(format: "xiaohuanghua_%d.png", i)
            if let image = UIImage(named: name){
                gifImages.append(image)
            }
        }
        let layer = GifLayer()
        layer.frame = CGRect(x: 0, y: 0, width: 300, height: 400)
        layer.loadData(gifImages)
        
        let gifPlane = SCNBox(width: 30, height: 15, length: 0, chamferRadius: 0)
        gifPlane.firstMaterial?.diffuse.contents = layer
        
        headAnimationNode.geometry = gifPlane
        
        modelRoot.addChildNode(headAnimationNode)
        
        
        textNode = SCNNode()
        if var position = noseTipNode?.position {
            position.z = position.z + 1
            textNode.position = position
        }
        
        let text = SCNText(string: "Vaffle", extrusionDepth: 0.3)
        text.firstMaterial?.diffuse.contents = UIColor.red
        text.font = UIFont.systemFont(ofSize: 1)
        textNode.geometry = text
        noseTipNode?.addChildNode(textNode)
        
        scene?.rootNode.addChildNode(faceNode)
        
        let cameraNode = SCNNode()
        cameraNode.camera = sceneCamera
        scene?.rootNode.addChildNode(cameraNode)
        
        sceneView.scene = scene
        sceneView.frame = self.view.bounds
        sceneView.delegate = self
        sceneView.rendersContinuously = true
//        sceneView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        sceneView.backgroundColor = .clear
        view.addSubview(sceneView)
        
        faceTextureMaterial.diffuse.contents = faceImage
        // SCNMaterial does not premultiply alpha even with blendMode set to alpha, so do it manually.
        faceTextureMaterial.shaderModifiers =
            [SCNShaderModifierEntryPoint.fragment : "_output.color.rgb *= _output.color.a;"]
        faceOccluderMaterial.colorBufferWriteMask = []
    }

4.初始化設(shè)備的運(yùn)動(dòng)信息

private lazy var motionManager = CMMotionManager()
private func setupMotion() {
        guard motionManager.isDeviceMotionAvailable else {
            NSLog("Device does not have motion sensors.")
            return
        }
        motionManager.deviceMotionUpdateInterval = kMotionUpdateInterval
        motionManager.startDeviceMotionUpdates()
    }

5.開始啟動(dòng)相機(jī)并采集數(shù)據(jù)

private func startCameraCapture() {
        getVideoPermission(permissionHandler: { granted in
            guard granted else {
                NSLog("Permission not granted to use camera.")
                return
            }
            self.captureSession?.startRunning()
        })
    }
@available(iOS 11.0, *)
extension CameraController : AVCaptureVideoDataOutputSampleBufferDelegate {
    
    public func captureOutput(
        _ output: AVCaptureOutput,
        didOutput sampleBuffer: CMSampleBuffer,
        from connection: AVCaptureConnection
        ) {
        guard let imgBuffer = CMSampleBufferGetImageBuffer(sampleBuffer),
            let deviceMotion = motionManager.deviceMotion
            else { return }
        
        let frameTime = CMTimeGetSeconds(CMSampleBufferGetPresentationTimeStamp(sampleBuffer))
        
        // Use the device's gravity vector to determine which direction is up for a face. This is the
        // positive counter-clockwise rotation of the device relative to landscape left orientation.
        let rotation =  2 * .pi - atan2(deviceMotion.gravity.x, deviceMotion.gravity.y) + .pi / 2
        let rotationDegrees = (UInt)(rotation * 180 / .pi) % 360
        
        debugPrint(rotationDegrees)
        
        faceSession?.update(
            with: imgBuffer,
            timestamp: frameTime,
            recognitionRotation: rotationDegrees)
        
        
        
        
    }
    
    
}

6.場景回調(diào)

@available(iOS 11.0, *)
extension CameraController : SCNSceneRendererDelegate {
    
    public func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
        guard nextFaceFrame != nil && nextFaceFrame != currentFaceFrame else { return }
        
        currentFaceFrame = nextFaceFrame
        
        if let face = currentFaceFrame?.face {
            faceTextureNode.geometry = faceMeshConverter.geometryFromFace(face)
            faceTextureNode.geometry?.firstMaterial = faceTextureMaterial
            faceOccluderNode.geometry = faceTextureNode.geometry?.copy() as? SCNGeometry
            faceOccluderNode.geometry?.firstMaterial = faceOccluderMaterial
            
            faceNode.simdWorldTransform = face.centerTransform
            updateTransform(face.transform(for: .nose), for: noseTipNode)
            updateTransform(face.transform(for: .foreheadLeft), for: foreheadLeftNode)
            updateTransform(face.transform(for: .foreheadRight), for: foreheadRightNode)
            
            if let simdScale = noseTipNode?.simdScale{
                textNode.simdScale = simdScale
            }
            
            capNode.simdScale = faceTextureNode.simdScale
            
            updateTransform(face.transform(for: .nose), for: capNode)
            
            
            if let leftPosition = foreheadLeftNode?.position, let rightPosition = foreheadRightNode?.position{
                var y = CGFloat((leftPosition.y + rightPosition.y)/2.0)
                var x = CGFloat((leftPosition.x + rightPosition.x)/2.0)
                x -= 1
                var z = CGFloat((leftPosition.z + rightPosition.z)/2.0)
                z += 1.5
                capNode.position = SCNVector3(x, y, z)
            }
            
            updateTransform(face.transform(for: .nose), for: bigCapNode)
            
            
            if let leftPosition = foreheadLeftNode?.position, let rightPosition = foreheadRightNode?.position{
                var y = CGFloat((leftPosition.y + rightPosition.y)/2.0)
                var x = CGFloat((leftPosition.x + rightPosition.x)/2.0)
                x -= 0
                var z = CGFloat((leftPosition.z + rightPosition.z)/2.0)
                z -= 3
                bigCapNode.position = SCNVector3(x, y, z)
            }
            
            updateTransform(face.transform(for: .nose), for: headAnimationNode)
            if let leftPosition = foreheadLeftNode?.position, let rightPosition = foreheadRightNode?.position{
                var y = CGFloat((leftPosition.y + rightPosition.y)/2.0)
                var x = CGFloat((leftPosition.x + rightPosition.x)/2.0)
                x -= 12
                var z = CGFloat((leftPosition.z + rightPosition.z)/2.0)
                z -= 15
                headAnimationNode.position = SCNVector3(x, y, z)
            }
            
            
            //        if let position = faceTextureNode.position{
            //
            //        }
            
        }
        
        // Only show AR content when a face is detected
        sceneView.scene?.rootNode.isHidden = currentFaceFrame?.face == nil
    }
    
    public func renderer(
        _ renderer: SCNSceneRenderer,
        didRenderScene scene: SCNScene,
        atTime time: TimeInterval
        ) {
        guard let frame = currentFaceFrame else { return }
        
        CATransaction.begin()
        CATransaction.setAnimationDuration(0)
        cameraImageLayer.contents = frame.capturedImage as CVPixelBuffer
        cameraImageLayer.setAffineTransform(
            frame.displayTransform(
                forViewportSize: cameraImageLayer.bounds.size,
                presentationOrientation: .portrait,
                mirrored: videoInput?.device.position == .front ? true : false)
        )
        CATransaction.commit()
    }
    
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市厉碟,隨后出現(xiàn)的幾起案子喊巍,更是在濱河造成了極大的恐慌,老刑警劉巖箍鼓,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件崭参,死亡現(xiàn)場離奇詭異,居然都是意外死亡款咖,警方通過查閱死者的電腦和手機(jī)何暮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铐殃,“玉大人海洼,你說我怎么就攤上這事「焕埃” “怎么了坏逢?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赘被。 經(jīng)常有香客問我是整,道長,這世上最難降的妖魔是什么帘腹? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任贰盗,我火速辦了婚禮,結(jié)果婚禮上阳欲,老公的妹妹穿的比我還像新娘舵盈。我一直安慰自己,他們只是感情好球化,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布秽晚。 她就那樣靜靜地躺著,像睡著了一般筒愚。 火紅的嫁衣襯著肌膚如雪赴蝇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天巢掺,我揣著相機(jī)與錄音句伶,去河邊找鬼。 笑死陆淀,一個(gè)胖子當(dāng)著我的面吹牛考余,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轧苫,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼楚堤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起身冬,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤衅胀,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后酥筝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體滚躯,經(jīng)...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年樱哼,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了哀九。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,424評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搅幅,死狀恐怖阅束,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情茄唐,我是刑警寧澤息裸,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站沪编,受9級特大地震影響呼盆,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蚁廓,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一访圃、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧相嵌,春花似錦腿时、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至看铆,卻和暖如春徽鼎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背弹惦。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工否淤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人棠隐。 一個(gè)月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓石抡,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宵荒。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評論 2 359