ARCore与ARKit实现人脸贴纸、更换材质等动画效果

近两年市面上出现了很多有关有关美颜滤镜、贴纸等各种贴纸效果的相机出现,由于项目的需要调研了google开源的一个能够提供3D角度的ARCore框架,本人也结合ARKit在iOS手机上实现了类抖音的效果
系统要求

  1. iOS11.0以上系统
    2.iPhone6s以上的Iphone手机

首先看下效果


IMG_3543.PNG

1.引入需求的库

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

2.使用AVFoundation框架进行数据的采集工作

/// 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.设置场景

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.初始化设备的运动信息

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.开始启动相机并采集数据

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.场景回调

@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()
    }
    
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342