IOS开发之-人脸识别

1、体验分析

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

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

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

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

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

2、移动端的人脸识别

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

(1)CoreImage.framework

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

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


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

插入识别代码:

func detectFace(withImage image: UIImage) {
   // 将图像转为CIImage,使用Core Image需要使用CIImage
   guard let personCIImg = CIImage(image: image) else {
       return
   }
   // 设置识别精度
   let opts: [String: Any] = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
   // 初始化识别器
   let detector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: opts)
   
   let result: [CIFaceFeature] = (detector?.features(in: personCIImg, options: opts) as? [CIFaceFeature])!
   
   if result.count > 0 {
       for face in result {
           let faceBox = UIView(frame: face.bounds)
               
           // 画一个红框画出面部位置
           faceBox.layer.borderWidth = 3
           faceBox.layer.borderColor = UIColor.red.cgColor
           faceBox.backgroundColor = UIColor.clear
           // 添加红框到图片上
           imgView.addSubview(faceBox)
           print("面部坐标------> %d ", faceBox.frame)
               
       }
   }
}
好了,代码看似很简单的:

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

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

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

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

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

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

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


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

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

func detectFace(withImage image: UIImage) {
   // 将图像转为CIImage,使用Core Image需要使用CIImage
   guard let personCIImg = CIImage(image: image) else {
       return
   }
   // 设置识别精度
   let opts: [String: Any] = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
   // 初始化识别器
   let detector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: opts)
       
   let result: [CIFaceFeature] = (detector?.features(in: personCIImg, options: opts) as? [CIFaceFeature])!
       
   let CIImgSize = personCIImg.extent.size
   var transform = CGAffineTransform(scaleX: 1, y: -1)
   transform = transform.translatedBy(x: 0, y: -CIImgSize.height)
       
   if result.count > 0 {
       for face in result {
               
           // Apply the transform to convert the coordinates
           var faceViewBounds = face.bounds.applying(transform)
               
           // Calculate the actual position and size of the rectangle in the image view
           let viewSize = imgView.bounds.size
           let scale = min(viewSize.width / CIImgSize.width,
                               viewSize.height / CIImgSize.height)
           let offsetX = (viewSize.width - CIImgSize.width * scale) / 2
           let offsetY = (viewSize.height - CIImgSize.height * scale) / 2
               
           faceViewBounds = faceViewBounds.applying(CGAffineTransform(scaleX: scale, y: scale))
           faceViewBounds.origin.x += offsetX
           faceViewBounds.origin.y += offsetY
               
           let faceBox = UIView(frame: faceViewBounds)
               
           // 画一个红框画出面部位置
           faceBox.layer.borderWidth = 3
           faceBox.layer.borderColor = UIColor.red.cgColor
               faceBox.backgroundColor = UIColor.clear
               // 添加红框到图片上
           imgView.addSubview(faceBox)
               
           print("面部坐标------> %d ", faceBox.frame)
               
           if face.rightEyeClosed {
               print("右眼闭着")
           }
           if face.leftEyeClosed {
               print("左眼闭着")
           }
           if face.hasSmile {
               print("在笑")
           }
       }
   }
}
#45行——#53行还可以识别图像的动作,具体的可以到类中查看。

运行效果:


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

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

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

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

(2)AVFoundation.framework

正在研究中。。。

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

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

   var session: AVCaptureSession?
   
   var device: AVCaptureDevice?
   
   var input: AVCaptureDeviceInput?
   
   var output: AVCaptureMetadataOutput?
   
   var preview: AVCaptureVideoPreviewLayer?
   // 初始化相机
   func setupCamera() {
       //session
       session = AVCaptureSession()
       session?.sessionPreset = AVCaptureSessionPresetPhoto
       session?.sessionPreset = AVCaptureSessionPreset1280x720
       
       // device
       device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
       // 设置为前置摄像头
       let devices = AVCaptureDeviceDiscoverySession(deviceTypes: [AVCaptureDeviceType.builtInWideAngleCamera], mediaType: AVMediaTypeVideo, position: AVCaptureDevicePosition.front)
       for device in (devices?.devices)! {
           self.device = device
       }
       
       // input
       do {
           try input = AVCaptureDeviceInput(device: device)
       } catch let error as NSError {
           print("error: \(error.localizedDescription)")
       }
       if (session?.canAddInput(input))! {
           session?.addInput(input)
       }
       
       // output // 人脸识别
       output = AVCaptureMetadataOutput()
       output?.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
       if (session?.canAddOutput(output))! {
           session?.addOutput(output)
       }
       output?.metadataObjectTypes = [AVMetadataObjectTypeFace]
       // preview
       preview = AVCaptureVideoPreviewLayer(session: session)
       preview?.videoGravity = AVLayerVideoGravityResizeAspectFill
       preview?.frame = CGRect(x: (self.view.frame.width-200)/2.0, y: 150, width: 200, height: 200)
       preview?.cornerRadius = 100
       preview?.borderColor = UIColor.gray.cgColor
       preview?.borderWidth = 3
//        preview?.frame = self.view.bounds
       self.view.layer.insertSublayer(preview!, at: 0)
   
       session?.startRunning()
   }
#3行,#17——#24行:硬件设备声明和初始化,调用前置摄像头
#5行:input,输入流

#7行:output,输出流

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

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

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

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

   // MARK: - AVCaptureMetadataOutputObjectsDelegate
   func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
       for item in metadataObjects {
           if (item as! AVMetadataObject).type == AVMetadataObjectTypeFace {
               let transform: AVMetadataObject = (preview?.transformedMetadataObject(for: item as? AVMetadataObject))!
               DispatchQueue.global().async {
                   DispatchQueue.main.async {
                       self.showFaceImage(withFrame: transform.bounds)
                   }
               }
           }
       }
   }
   /// 显示人脸位置视图
   func showFaceImage(withFrame rect: CGRect) {
       if isStartFaceRecognition {
           isStartFaceRecognition = false
           faceBoxView.frame = rect
           self.faceBoxView.transform = CGAffineTransform(scaleX: 1.5, y: 1.5);
           UIView.animate(withDuration: 0.3, animations: {
               [weak self] in
               self?.faceBoxView.alpha = 1.0
               self?.faceBoxView.transform = CGAffineTransform(scaleX: 1.0, y: 1.0);
           }) { (finished: Bool) in
               UIView.animate(withDuration: 0.2, animations: {
                   [weak self] in
                   self?.faceBoxView.alpha = 0.0
                   }, completion: { (finished: Bool) in
                       self.isStartFaceRecognition = true
               })
           }
       }
   }
返回的 AVMetadataObject 类中可以获取到面部位置坐标,根据坐标信息添加一个可视化界面展示给用户识别区域。


 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值