iOS人脸识别Demo

最近用过了支付宝的人脸识别登录,作为一个技术人员应该保持对技术探索、追求的学习心态。这两天就搜集各方资料学习了一下,但好像走了弯路/(ㄒoㄒ)/~~!!!这篇笔记记录一下自己的学习。

1、体验分析

首先思考的应该是人脸识别登录的流程。本人第一次体验到的人脸识别技术的应用当然是支付宝。

支付宝开启人脸登录流程:首先是账号密码登录 ---->  然后在app内根据提示完成信息采集 ---->  信息采集完成后下次登录时就可以使用该功能了!

然后又找到另一款带有人脸识别登录功能app应用,其开启流程:账号密码登录后 ----> 在相关功能模块调用相机,拍摄较清晰的人脸照片进行上传 ----> 之后就可以使用了!!!

分析其登录流程:第一步都是要密码验证登录,第二步需要采集当前账号的所属人面部信息,然后才可使用该功能。(应该算是一种辅助登录手段,毕竟没有使用人脸注册账号。但是真的很好用,省去了输入密码或忘记密码的麻烦)

在移动端开发此功能,(密码登录直接略过)我们应该首先考虑的是信息采集时的面部信息。那么第一个要解决的问题就是识别出人脸,其次将信息上传至后台与该账号关联。下次登录时,将扫描到的人脸图像上传至后台与存储的数据对比,返回登录结果。(因为未找到具体的相关资料,此分析目前仅是一个移动开发者的分析,如有漏洞下次更新)

2、移动端的人脸识别

根据之前的分析,我们需要在本地分析采集到的图像是否有人脸信息。根据查找的资料,有如下实现方法:1.苹果原生CoreImage.framework框架。2.OpenVC框架,该框架是一个跨平台框架,专注于做图像技术的处理。(更多介绍百度即可)3.AVFoundation框架,这是在使用CoreImage走了弯路时发现的。

(1)CoreImage.framework

我们先用该框架实现人脸识别。首先创建一个工程,导入 CoreImage.framework 库:


创建一个UIImageView,给其一张图片,我们直接用storyboard拖拽界面、设置坐标,并关联属性到控制器(步骤简单略过):


此时要注意,ImageView的图片适应模式设置为scaleAspectFit,如果要将图片拉伸展示到ImageView的话,面部位置并不在计算到的坐标框内。

插入识别代码:

 
 
  1. func detectFace(withImage image: UIImage) {
  2.    // 将图像转为CIImage,使用Core Image需要使用CIImage
  3.    guard let personCIImg = CIImage(image: image) else {
  4.        return
  5.    }
  6.    // 设置识别精度
  7.    let opts: [String: Any] = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
  8.    // 初始化识别器
  9.    let detector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: opts)
  10.    
  11.    let result: [CIFaceFeature] = (detector?.features(in: personCIImg, options: opts) as? [CIFaceFeature])!
  12.    
  13.    if result.count > 0 {
  14.        for face in result {
  15.            let faceBox = UIView(frame: face.bounds)
  16.                
  17.            // 画一个红框画出面部位置
  18.            faceBox.layer.borderWidth = 3
  19.            faceBox.layer.borderColor = UIColor.red.cgColor
  20.            faceBox.backgroundColor = UIColor.clear
  21.            // 添加红框到图片上
  22.            imgView.addSubview(faceBox)
  23.            print("面部坐标------> %d ", faceBox.frame)
  24.                
  25.        }
  26.    }
  27. }

好了,代码看似很简单的:

  • #3行:从imgView变量中取出image并转为CIImage,因为使用Core Image时需要用CIImage。

  • #9行:初始化一个识别器,并将识别精度以字典参数给它。

  • #11行:调用识别器的识别方法,并保存一个 [CIFaceFeature类型的结果。

  • #13——#25行:从返回的结果中取出数据。点开 CIFaceFeature 类我们可以看到的它的属性值,bounds就是我们要的识别到的面部区域。


  • #17——#22行:将识别到的区域用红框显示出来。

接下来调用测试一下代码结果:

 
 
  1. imgView.contentMode = .scaleAspectFit
  2. self.detectFace(withImage: imgView.image!)

  运行结果看到红框出界了,并没有出现在预期的范围内,这是因为UIKit框架中使用的坐标与Core Image 的坐标不同造成,为此我们还需要将坐标转换。


将转换代码加入会,代码如下:

 
 
  1. func detectFace(withImage image: UIImage) {
  2.    // 将图像转为CIImage,使用Core Image需要使用CIImage
  3.    guard let personCIImg = CIImage(image: image) else {
  4.        return
  5.    }
  6.    // 设置识别精度
  7.    let opts: [String: Any] = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
  8.    // 初始化识别器
  9.    let detector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: opts)
  10.        
  11.    let result: [CIFaceFeature] = (detector?.features(in: personCIImg, options: opts) as? [CIFaceFeature])!
  12.        
  13.    let CIImgSize = personCIImg.extent.size
  14.    var transform = CGAffineTransform(scaleX: 1, y: -1)
  15.    transform = transform.translatedBy(x: 0, y: -CIImgSize.height)
  16.        
  17.    if result.count > 0 {
  18.        for face in result {
  19.                
  20.            // Apply the transform to convert the coordinates
  21.            var faceViewBounds = face.bounds.applying(transform)
  22.                
  23.            // Calculate the actual position and size of the rectangle in the image view
  24.            let viewSize = imgView.bounds.size
  25.            let scale = min(viewSize.width / CIImgSize.width,
  26.                                viewSize.height / CIImgSize.height)
  27.            let offsetX = (viewSize.width - CIImgSize.width * scale) / 2
  28.            let offsetY = (viewSize.height - CIImgSize.height * scale) / 2
  29.                
  30.            faceViewBounds = faceViewBounds.applying(CGAffineTransform(scaleX: scale, y: scale))
  31.            faceViewBounds.origin.x += offsetX
  32.            faceViewBounds.origin.y += offsetY
  33.                
  34.            let faceBox = UIView(frame: faceViewBounds)
  35.                
  36.            // 画一个红框画出面部位置
  37.            faceBox.layer.borderWidth = 3
  38.            faceBox.layer.borderColor = UIColor.red.cgColor
  39.                faceBox.backgroundColor = UIColor.clear
  40.                // 添加红框到图片上
  41.            imgView.addSubview(faceBox)
  42.                
  43.            print("面部坐标------> %d ", faceBox.frame)
  44.                
  45.            if face.rightEyeClosed {
  46.                print("右眼闭着")
  47.            }
  48.            if face.leftEyeClosed {
  49.                print("左眼闭着")
  50.            }
  51.            if face.hasSmile {
  52.                print("在笑")
  53.            }
  54.        }
  55.    }
  56. }
  • #45行——#53行还可以识别图像的动作,具体的可以到类中查看。

    运行效果:


*************************************************************************************************

到这里,我们已经可以识别出人脸信息,但是在使用时我们不能拿着“这么大”的照片去用,裁剪后使用效率应该会更高。剔除人脸外无用的信息。但是进行的并不是很顺利,因为裁剪区域的坐标和界面显示的红框坐标并不相同,无奈被卡到现在还并未解决。

此处被卡时间太长,暂且搁置,后续研究

*************************************************************************************************

到这我们仅仅完成了静态人物的面部识别,而调用摄像头后动态面部信息的捕捉也是我们要使用的技术问题。前面所说的走弯路就在这了,看到了CoreImage.framework 可以识别图像,又可以自定义拍照界面,所以沿着这条路走了下去,结果并没有走通。首先说自定义界面只是简单的自定义,并不能按照自己的UI设计搭建拍照界面。其二,动态识别数据流信息(抓取人像信息,而不是拍摄后识别)貌似也不可以!!!

还好,在解这些问题的时候发现AVFoundation框架使我们需要的。


(2)AVFoundation.framework
正在研究中。。。

注:记得在plist文件中添加权限声明(调用相机,麦克风,媒体库等都要添加对应的权限),否则会崩的

直接上代码再注释吧,网上系统的资料比较少,基本都是一些简单的代码,也不能直接拿过来用!

  
  
  1.    var session: AVCaptureSession?
  2.    
  3.    var device: AVCaptureDevice?
  4.    
  5.    var input: AVCaptureDeviceInput?
  6.    
  7.    var output: AVCaptureMetadataOutput?
  8.    
  9.    var preview: AVCaptureVideoPreviewLayer?
  10.    // 初始化相机
  11.    func setupCamera() {
  12.        //session
  13.        session = AVCaptureSession()
  14.        session?.sessionPreset = AVCaptureSessionPresetPhoto
  15.        session?.sessionPreset = AVCaptureSessionPreset1280x720
  16.        
  17.        // device
  18.        device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
  19.        // 设置为前置摄像头
  20.        let devices = AVCaptureDeviceDiscoverySession(deviceTypes: [AVCaptureDeviceType.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: AVCaptureDevicePosition.front)
  21.        for device in (devices?.devices)! {
  22.            self.device = device
  23.        }
  24.        
  25.        // input
  26.        do {
  27.            try input = AVCaptureDeviceInput(device: device)
  28.        } catch let error as NSError {
  29.            print("error: \(error.localizedDescription)")
  30.        }
  31.        if (session?.canAddInput(input))! {
  32.            session?.addInput(input)
  33.        }
  34.        
  35.        // output // 人脸识别
  36.        output = AVCaptureMetadataOutput()
  37.        output?.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
  38.        if (session?.canAddOutput(output))! {
  39.            session?.addOutput(output)
  40.        }
  41.        output?.metadataObjectTypes = [AVMetadataObjectTypeFace]
  42.        // preview
  43.        preview = AVCaptureVideoPreviewLayer(session: session)
  44.        preview?.videoGravity = AVLayerVideoGravityResizeAspectFill
  45.        preview?.frame = CGRect(x: (self.view.frame.width-200)/2.0, y: 150, width: 200, height: 200)
  46.        preview?.cornerRadius = 100
  47.        preview?.borderColor = UIColor.gray.cgColor
  48.        preview?.borderWidth = 3
  49. //        preview?.frame = self.view.bounds
  50.        self.view.layer.insertSublayer(preview!, at: 0)
  51.    
  52.        session?.startRunning()
  53.    }
  • #3行,#17——#24行:硬件设备声明和初始化,调用前置摄像头
  • #5行:input,输入流

  • #7行:output,输出流

  • #9行:preview,预览界面,可以设置为想要的扫描效果界面。

  • #54行:初始化完成后一定要启动session

注:此处有一个巨大的坑,在设置output输出流,添加扫描识别类型时,一定要先添加input输入流,否则崩掉,然后网上也找不到相关资料,这是自己一步步试出来的!!!

上边的初始化完成后,需要实现一个协议方法,该方法在扫描到有人脸时会持续返回数据:

  
  
  1.    // MARK: - AVCaptureMetadataOutputObjectsDelegate
  2.    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
  3.        for item in metadataObjects {
  4.            if (item as! AVMetadataObject).type == AVMetadataObjectTypeFace {
  5.                let transform: AVMetadataObject = (preview?.transformedMetadataObject(for: item as? AVMetadataObject))!
  6.                DispatchQueue.global().async {
  7.                    DispatchQueue.main.async {
  8.                        self.showFaceImage(withFrame: transform.bounds)
  9.                    }
  10.                }
  11.            }
  12.        }
  13.    }
  14.    /// 显示人脸位置视图
  15.    func showFaceImage(withFrame rect: CGRect) {
  16.        if isStartFaceRecognition {
  17.            isStartFaceRecognition = false
  18.            faceBoxView.frame = rect
  19.            self.faceBoxView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5);
  20.            UIView.animate(withDuration: 0.3, animations: {
  21.                [weak self] in
  22.                self?.faceBoxView.alpha = 1.0
  23.                self?.faceBoxView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0);
  24.            }) { (finished: Bool) in
  25.                UIView.animate(withDuration: 0.2, animations: {
  26.                    [weak self] in
  27.                    self?.faceBoxView.alpha = 0.0
  28.                    }, completion: { (finished: Bool) in
  29.                        self.isStartFaceRecognition = true
  30.                })
  31.            }
  32.        }
  33.    }
返回的  AVMetadataObject 类中可以获取到面部位置坐标,根据坐标信息添加一个可视化界面展示给用户识别区域。


以上就是我们这一阶段完成的效果展示,





  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值