iOS黑科技之(AVFoundation)动态人脸识别(二)

iOS黑科技之(AVFoundation)动态人脸识别(二)

上一篇介绍了Core Image实现的静态人脸识别, 这里介绍AVFoundation的强大功能之一的动态人脸识别

一. 首先介绍一些人脸识别的方式

1. CoreImage静态人脸识别, 可识别照片, 图像等

  • 详情可查看上一篇博客介绍

3. OpenCV

  • 由一系列 C 函数和少量 C++ 类构成, 实现了图像处理和计算机视觉方面的很多通用算法, 其他的具体的不是很了解

4. Vision

  • Vision 是 Apple 在 WWDC 2017 伴随iOS 11推出的基于CoreML的图像识别框架
  • 根据Vision官方文档看Vision 本身就有Face Detection and Recognition(人脸检测识别)、Machine Learning Image Analysis(机器学习图片分析)、Barcode Detection(条形码检测)、Text Detection(文本检测)。。。。。等等这些功能
  • 感兴趣的同学可以查看相关文档学习一下, 这里小编就不过多作介绍了

5. AVFoundation

  • 可以用来使用和创建基于时间的视听媒体的框架
  • 这里我们使用的人脸识别方式也是使用AVFoundation框架

二. 对关键类的简单介绍

1. AVCaptureDevice:代表硬件设备

  • 我们可以从这个类中获取手机硬件的照相机、声音传感器等。
  • 当我们在应用程序中需要改变一些硬件设备的属性(例如:切换摄像头、闪光模式改变、相机聚焦改变)的时候必须要先为设备加锁,修改完成后解锁。
  • 示例: 切换摄像头
//4. 移除旧输入,添加新输入
//4.1 设备加锁
session.beginConfiguration()
//4.2. 移除旧设备
session.removeInput(deviceIn)
//4.3 添加新设备
session.addInput(newVideoInput)
//4.4 设备解锁
session.commitConfiguration()

2. AVCaptureDeviceInput:设备输入数据管理对象

  • 可以根据AVCaptureDevice创建对应的AVCaptureDeviceInput对象,
  • 该对象将会被添加到AVCaptureSession中管理,代表输入设备,它配置抽象硬件设备的ports。通常的输入设备有(麦克风,相机等)

3. AVCaptureOutput: 代表输出数据

  • 输出的可以是图片(AVCaptureStillImageOutput)或者视频(AVCaptureMovieFileOutput

4. AVCaptureSession: 媒体(音、视频)捕捉会话

  • 负责把捕捉的音频视频数据输出到输出设备中。
  • 一个AVCaptureSession可以有多个输入或输出。
  • 是连接AVCaptureInputAVCaptureOutput的桥梁,它协调input到output之间传输数据。
  • 它有startRunning和stopRunning两种方法来开启会话和结束会话。
  • 每个session称之为一个会话,也就是在应用运行过程中如果你需要改变会话的一些配置(例如:切换摄像头),此时需要先开启配置,配置完成之后再提交配置。

5. AVCaptureVideoPreviewLayer: 图片预览层

  • 我们的照片以及视频是如何显示在手机上的呢?那就是通过把这个对象添加到UIViewlayer上的

好了, 上面吧啦吧啦的说了那么多废话, 那么我们的人脸识别究竟是怎样实现的呢? 下面干货来了

三. 添加扫描设备

  • 获取设备(摄像头)
  • 创建输入设备
  • 创建扫描输出
  • 创建捕捉回话

1. 输出设备

  • 这里使用AVCaptureMetadataOutput, 可以扫描人脸, 二维码, 条形码等信息
  • 必须设置代理, 否则获取不到扫描结果
  • 需要设置要输出什么样的数据: face(人脸), qr(二维码)等等
//3.创建原数据的输出对象
let metadataOutput = AVCaptureMetadataOutput()
        
//4.设置代理监听输出对象输出的数据,在主线程中刷新
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)

//7.告诉输出对象要输出什么样的数据,识别人脸, 最多可识别10张人脸
metadataOutput.metadataObjectTypes = [.face]

主要代码如下:

fileprivate func addScaningVideo(){
    //1.获取输入设备(摄像头)
    guard let device = AVCaptureDevice.default(for: .video) else { return }
    
    //2.根据输入设备创建输入对象
    guard let deviceIn = try? AVCaptureDeviceInput(device: device) else { return }
    deviceInput = deviceIn
    
    //3.创建原数据的输出对象
    let metadataOutput = AVCaptureMetadataOutput()
    
    //4.设置代理监听输出对象输出的数据,在主线程中刷新
    metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
    //4.2 设置输出代理
    faceDelegate = previewView
    
    //5.设置输出质量(高像素输出)
    session.sessionPreset = .high
    
    //6.添加输入和输出到会话
    if session.canAddInput(deviceInput!) {
        session.addInput(deviceInput!)
    }
    if session.canAddOutput(metadataOutput) {
        session.addOutput(metadataOutput)
    }
    
    //7.告诉输出对象要输出什么样的数据,识别人脸, 最多可识别10张人脸
    metadataOutput.metadataObjectTypes = [.face]
    
    //8.创建预览图层
    previewLayer = AVCaptureVideoPreviewLayer(session: session)
    previewLayer.videoGravity = .resizeAspectFill
    previewLayer.frame = view.bounds
    previewView.layer.insertSublayer(previewLayer, at: 0)
    
    //9.设置有效扫描区域(默认整个屏幕区域)(每个取值0~1, 以屏幕右上角为坐标原点)
    metadataOutput.rectOfInterest = previewView.bounds
    
    //10. 开始扫描
    if !session.isRunning {
        DispatchQueue.global().async {
            self.session.startRunning()
        }
    }
}

2. 切换摄像头

  • 获取当前摄像头方向
  • 创建新的输入input
  • 移除旧输入capture, 添加新的输入capture
  • 具体代码如下:
@IBAction func switchCameraAction(_ sender: Any) {
    //1. 执行转场动画
    let anima = CATransition()
    anima.type = "oglFlip"
    anima.subtype = "fromLeft"
    anima.duration = 0.5
    view.layer.add(anima, forKey: nil)
    
    //2. 获取当前摄像头
    guard let deviceIn = deviceInput else { return }
    let position: AVCaptureDevice.Position = deviceIn.device.position == .back ? .front : .back
    
    //3. 创建新的input
    let deviceSession = AVCaptureDevice.DiscoverySession(deviceTypes: [.builtInWideAngleCamera], mediaType: .video, position: position)
    guard let newDevice = deviceSession.devices.filter({ $0.position == position }).first else { return }
    guard let newVideoInput = try? AVCaptureDeviceInput(device: newDevice) else { return }
    
    //4. 移除旧输入,添加新输入
    //4.1 设备加锁
    session.beginConfiguration()
    //4.2. 移除旧设备
    session.removeInput(deviceIn)
    //4.3 添加新设备
    session.addInput(newVideoInput)
    //4.4 设备解锁
    session.commitConfiguration()
    
    //5. 保存最新输入
    deviceInput = newVideoInput
}

3. 处理扫描结果

实现AVCaptureMetadataOutputObjectsDelegate该协议的协议方法(只有一个方法)

//`metadataObjects`就是返回的扫描结果
optional public func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection)

4. AVMetadataFaceObject介绍

  • faceID: 人脸的唯一标识
    • 扫描出来的每一个人, 有不同的faceID
    • 同一个人, 不同的状态下(摇头, 歪头, 抬头等), 都会有不同faceID
  • hasRollAngle: 是否有倾斜角,侧倾角(左右歪头)(BOOL类型)
  • rollAngle: 倾斜角,侧倾角的角度(CGFloat类型)
  • hasYawAngle: 是否有偏转角(左右摇头)
  • yawAngle: 偏转角角度

5. 处理扫描结果

5.1 获取预览图层的人脸数组

  • 遍历扫描的人脸数组, 转换成在预览图层的人脸数组
  • 主要是人脸在图层的左边的转换
  • 返回转换后的新的数组
fileprivate func transformedFaces(faceObjs: [AVMetadataObject]) -> [AVMetadataObject] {
    var faceArr = [AVMetadataObject]()
    for face in faceObjs {
        //将扫描的人脸对象转成在预览图层的人脸对象(主要是坐标的转换)
        if let transFace = previewLayer.transformedMetadataObject(for: face){
            faceArr.append(transFace)
        }
    }
    return faceArr
}

5.2 根据人脸位置添加红框

  • 设置红框的frame
faceLayer?.frame = face.bounds
  • 根据偏转角和倾斜角的角度获取CATransform3D
    fileprivate func transformDegress(yawAngle: CGFloat) -> CATransform3D {
        let yaw = degreesToRadians(degress: yawAngle)
        //围绕Y轴旋转
        let yawTran = CATransform3DMakeRotation(yaw, 0, -1, 0)
        //红框旋转问题
        return CATransform3DConcat(yawTran, CATransform3DIdentity)
    }
    
    //处理偏转角问题
    fileprivate func transformDegress(rollAngle: CGFloat) -> CATransform3D {
        let roll = degreesToRadians(degress: rollAngle)
        //围绕Z轴旋转
        return CATransform3DMakeRotation(roll, 0, 0, 1)
    }
    
    //角度转换
    fileprivate func degreesToRadians(degress: CGFloat) -> CGFloat{
        return degress * CGFloat(Double.pi) / 180
    }

  • 根据有无偏转角和倾斜角旋转红框
//3.4 设置偏转角(左右摇头)
if face.hasYawAngle{
    let tranform3D = transformDegress(yawAngle: face.yawAngle)
    
    //矩阵处理
    faceLayer?.transform = CATransform3DConcat(faceLayer!.transform, tranform3D)
}

//3.5 设置倾斜角,侧倾角(左右歪头)
if face.hasRollAngle{
    let tranform3D = transformDegress(rollAngle: face.rollAngle)
    
    //矩阵处理
    faceLayer?.transform = CATransform3DConcat(faceLayer!.transform, tranform3D)
}

  • 至此, 动态的人脸识别就完成了, 会在人脸位置增加红框显示, 并且红框会根据人脸的位置动态的, 实时的调整
  • 下面就快拿起你的相机测试吧

GitHub--Demo地址

  • 注意:
  • 这里只是列出了主要的核心代码,具体的代码逻辑请参考demo
  • 文中相关介绍有的地方如果有不是很详细或者有更好建议的,欢迎联系小编

其他相关文章


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

推荐阅读更多精彩内容