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的话,面部位置并不在计算到的坐标框内。
插入识别代码:
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 类中可以获取到面部位置坐标,根据坐标信息添加一个可视化界面展示给用户识别区域。
以上就是我们这一阶段完成的效果展示