基于iOS用CoreImage实现人脸识别
2018-09-04更新: 很久没有更新文章了,工作之余花时间看了之前写的这篇文章并运行了之前写的配套Demo,通过打印人脸特征CIFaceFeature的属性,发现识别的效果并不是很好,具体说明见文章最底部的更新标题,后续我将分别用OpenCV(跨平台计算机视觉库) 和 Vision (iOS 11新API)两种库实现人脸面部识别,敬请期待~~
OC版下载地址, swift版下载地址
```
CoreImage是Cocoa Touch中一个强大的API,也是iOS SDK中的关键部分,不过它经常被忽视。在本篇教程中,我会带大家一起验证CoreImage的人脸识别特性。在开始之前,我们先要简单了解下CoreImage framework 组成
CoreImage framework组成
Apple 已经帮我们把image的处理分类好,来看看它的结构:
主要分为三个部分:
1.定义部分:CoreImage 和CoreImageDefines。见名思义,代表了CoreImage 这个框架和它的定义。
2.操作部分:
滤镜(CIFliter):CIFilter 产生一个CIImage。典型的,接受一到多的图片作为输入,经过一些过滤操作,产生指定输出的图片。
检测(CIDetector):CIDetector 检测处理图片的特性,如使用来检测图片中人脸的眼睛、嘴巴、等等。
特征(CIFeature):CIFeature 代表由 detector处理后产生的特征。
3.图像部分:
画布(CIContext):画布类可被用与处理Quartz 2D 或者 OpenGL。可以用它来关联CoreImage类。如滤镜、颜色等渲染处理。
颜色(CIColor): 图片的关联与画布、图片像素颜色的处理。
向量(CIVector): 图片的坐标向量等几何方法处理。
图片(CIImage): 代表一个图像,可代表关联后输出的图像。
在了解上述基本知识后,我们开始通过创建一个工程来带大家一步步验证Core Image的人脸识别特性。
将要构建的应用
iOS的人脸识别从iOS 5(2011)就有了,不过一直没怎么被关注过。人脸识别API允许开发者不仅可以检测人脸,也可以检测到面部的一些特殊属性,比如说微笑或眨眼。
首先,为了了解Core Image的人脸识别技术我们会创建一个app来识别照片中的人脸并用一个方框来标记它。在第二个demo中,让用户拍摄一张照片,检测其中的人脸并检索人脸位置。这样一来,就充分掌握了iOS中的人脸识别,并且学会如何利用这个强大却总被忽略的API。
话不多说,开搞!
建立工程(我用的是Xcode8.0)
这里提供了初始工程,当然你也可以自己创建(主要是为了方便大家)点我下载 用Xcode打开下载后的工程,可以看到里面只有一个关联了IBOutlet和imageView的StoryBoard。
使用CoreImage识别人脸
在开始工程中,故事板中的imageView组件与代码中的IBOutlet已关联,接下来要编写实现人脸识别的代码部分。在ViewController.swift文件中写下如下代码:
import UIKit
import CoreImage // 引入CoreImage
class ViewController: UIViewController {
@IBOutlet weak var personPic: UIImageView! override func viewDidLoad() {
super.viewDidLoad() personPic.image = UIImage(named: "face-1")
// 调用detect
detect() }
//MARK: - 识别面部
func detect() {
// 创建personciImage变量保存从故事板中的UIImageView提取图像并将其转换为CIImage,使用Core Image时需要用CIImage
guard let personciImage = CIImage(image: personPic.image!) else {
return
}
// 创建accuracy变量并设为CIDetectorAccuracyHigh,可以在CIDetectorAccuracyHigh(较强的处理能力)与CIDetectorAccuracyLow(较弱的处理能力)中选择,因为想让准确度高一些在这里选择CIDetectorAccuracyHigh
let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
// 这里定义了一个属于CIDetector类的faceDetector变量,并输入之前创建的accuracy变量
let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy)
// 调用faceDetector的featuresInImage方法,识别器会找到所给图像中的人脸,最后返回一个人脸数组
let faces = faceDetector?.features(in: personciImage)
// 循环faces数组里的所有face,并将识别到的人脸强转为CIFaceFeature类型
for face in faces as! [CIFaceFeature] { print("Found bounds are \(face.bounds)")
// 创建名为faceBox的UIView,frame设为返回的faces.first的frame,绘制一个矩形框来标识识别到的人脸
let faceBox = UIView(frame: face.bounds)
// 设置faceBox的边框宽度为3
faceBox.layer.borderWidth = 3
// 设置边框颜色为红色
faceBox.layer.borderColor = UIColor.red.cgColor
// 将背景色设为clear,意味着这个视图没有可见的背景
faceBox.backgroundColor = UIColor.clear
// 最后,把这个视图添加到personPic imageView上
personPic.addSubview(faceBox)
// API不仅可以帮助你识别人脸,也可识别脸上的左右眼,我们不在图像中标识出眼睛,只是给你展示一下CIFaceFeature的相关属性
if face.hasLeftEyePosition {
print("Left eye bounds are \(face.leftEyePosition)")
} if face.hasRightEyePosition {
print("Right eye bounds are \(face.rightEyePosition)")
}
}
}
}
编译并运行app,结果应如下图所示:
2.png
根据控制台的输出来看,貌似识别器识别到了人脸:
Found bounds are (314.0, 243.0, 196.0, 196.0)
当前的实现中没有解决的问题:
人脸识别是在原始图像上进行的,由于原始图像的分辨率比image view要高,因此需要设置image view的content mode为aspect fit(保持纵横比的情况下缩放图片)。为了合适的绘制矩形框,需要计算image view中人脸的实际位置与尺寸
还要注意的是,CoreImage与UIView使用两种不同的坐标系统(看下图),因此要实现一个CoreImage坐标到UIView坐标的转换。
UIView坐标系:
CoreImage坐标系:
现在使用下面的代码替换detect()方法:
func detect1() { guard let personciImage = CIImage(image: personPic.image!) else { return }
let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy)
let faces = faceDetector?.features(in: personciImage) // 转换坐标系
let ciImageSize = personciImage.extent.size
var transform = CGAffineTransform(scaleX: 1, y: -1)
transform = transform.translatedBy(x: 0, y: -ciImageSize.height) for face in faces as! [CIFaceFeature] {
print("Found bounds are \(face.bounds)")
// 应用变换转换坐标
var faceViewBounds = face.bounds.applying(transform)
// 在图像视图中计算矩形的实际位置和大小
let viewSize = personPic.bounds.size
let scale = min(viewSize.width / ciImageSize.width, viewSize.height / ciImageSize.height)
let offsetX = (viewSize.width - ciImageSize.width * scale) / 2
let offsetY = (viewSize.height - ciImageSize.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
personPic.addSubview(faceBox) if face.hasLeftEyePosition {
print("Left eye bounds are \(face.leftEyePosition)")
} if face.hasRightEyePosition {
print("Right eye bounds are \(face.rightEyePosition)")
}
}
}
上述代码中,首先使用仿射变换(AffineTransform)将Core Image坐标转换为UIKit坐标,然后编写了计算实际位置与矩形视图尺寸的代码。
再次运行app,应该会看到人的面部周围会有一个框。OK,你已经成功使用Core Image识别出了人脸。
但是有的童鞋在使用了上面的代码运行后可能会出现方框不存在(即没有识别人脸)这种情况,这是由于忘记关闭Auto Layout以及Size Classes了。 选中storyBoard中的ViewController,选中view下的imageView。然后在右边的面板中的第一个选项卡中找到use Auto Layout ,将前面的✔️去掉就可以了
经过上面的设置后我们再次运行App,就会看到图三出现的效果了。
构建一个人脸识别的相机应用
想象一下你有一个用来照相的相机app,照完相后你想运行一下人脸识别来检测一下是否存在人脸。若存在一些人脸,你也许想用一些标签来对这些照片进行分类。我们不会构建一个保存照片后再处理的app,而是一个实时的相机app,因此需要整合一下UIImagePicker类,在照完相时立刻进行人脸识别。
在开始工程中已经创建好了CameraViewController类,使用如下代码实现相机的功能:
class CameraViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
@IBOutlet var imageView: UIImageView!
let imagePicker = UIImagePickerController() override func viewDidLoad() {
super.viewDidLoad()
imagePicker.delegate = self
} @IBAction func takePhoto(_ sender: AnyObject) { if !UIImagePickerController.isSourceTypeAvailable(.camera) {
return
} imagePicker.allowsEditing = false
imagePicker.sourceType = .camera present(imagePicker, animated: true, completion: nil)
} func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
imageView.contentMode = .scaleAspectFit
imageView.image = pickedImage
} dismiss(animated: true, completion: nil)
self.detect()
} func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
dismiss(animated: true, completion: nil)
}
}
前面几行设置UIImagePicker委托为当前视图类,在didFinishPickingMediaWithInfo方法(UIImagePicker的委托方法)中设置imageView为在方法中所选择的图像,接着返回上一视图调用detect函数。
还没有实现detect函数,插入下面代码并分析一下
func detect() {
let imageOptions = NSDictionary(object: NSNumber(value: 5) as NSNumber, forKey: CIDetectorImageOrientation as NSString)
let personciImage = CIImage(cgImage: imageView.image!.cgImage!)
let accuracy = [CIDetectorAccuracy: CIDetectorAccuracyHigh]
let faceDetector = CIDetector(ofType: CIDetectorTypeFace, context: nil, options: accuracy)
let faces = faceDetector?.features(in: personciImage, options: imageOptions as? [String : AnyObject]) if let face = faces?.first as? CIFaceFeature {
print("found bounds are \(face.bounds)") let alert = UIAlertController(title: "提示", message: "检测到了人脸", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "确定", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil) if face.hasSmile {
print("face is smiling");
} if face.hasLeftEyePosition {
print("左眼的位置: \(face.leftEyePosition)")
} if face.hasRightEyePosition {
print("右眼的位置: \(face.rightEyePosition)")
}
} else {
let alert = UIAlertController(title: "提示", message: "未检测到人脸", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "确定", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
这个detect()函数与之前实现的detect函数非常像,不过这次只用它来获取图像不做变换。当识别到人脸后显示一个警告信息“检测到了人脸!”,否则显示“未检测到人脸”。运行app测试一下:
我们已经使用到了一些CIFaceFeature的属性与方法,比如,若想检测人物是否微笑,可以调用.hasSmile,它会返回一个布尔值。可以分别使用.hasLeftEyePosition与.hasRightEyePosition检测是否存在左右眼。
同样,可以调用hasMouthPosition来检测是否存在嘴,若存在则可以使用mouthPosition属性,如下所示:
if (face.hasMouthPosition) {
print("mouth detected")
}
如你所见,使用Core Image来检测面部特征是非常简单的。除了检测嘴、笑容、眼睛外,也可以调用leftEyeClosed与rightEyeClosed检测左右眼是否睁开,这里就不在贴出代码了。
总结
在这篇教程中尝试了CoreImage的人脸识别API与如何在一个相机app中应用它,构建了一个简单的UIImagePicker来选取照片并检测图像中是否存在人物。
如你所见,Core Image的人脸识别是个强大的API!希望这篇教程能给你提供一些关于这个鲜为人知的iOS API有用的信息。
点击swift版地址,OC版地址下载最终工程, 如果觉得对您有帮助的话,请帮我点个星星哦,您的星星是对我最大的支持。(__) 嘻嘻……**
更新:
很久没有更新文章了,工作之余花时间回顾了之前写的这篇文章并运行了之前写的配套Demo,通过打印人脸特征CIFaceFeature的属性(如下),发现识别的效果并不是很好,如下图:
人脸特征CIFaceFeature的属性
/** CIDetector发现的脸部特征。
所有的位置都是相对于原始图像. */
NS_CLASS_AVAILABLE(10_7, 5_0)
@interface CIFaceFeature : CIFeature
{
CGRect bounds;
BOOL hasLeftEyePosition;
CGPoint leftEyePosition;
BOOL hasRightEyePosition;
CGPoint rightEyePosition;
BOOL hasMouthPosition;
CGPoint mouthPosition; BOOL hasTrackingID;
int trackingID;
BOOL hasTrackingFrameCount;
int trackingFrameCount; BOOL hasFaceAngle;
float faceAngle; BOOL hasSmile;
BOOL leftEyeClosed;
BOOL rightEyeClosed;
} /** coordinates of various cardinal points within a face.
脸部各个基点的坐标。 Note that the left eye is the eye on the left side of the face
from the observer's perspective. It is not the left eye from
the subject's perspective.
请注意,左眼是脸左侧的眼睛从观察者的角度来看。 这不是左眼主体的视角.
*/
@property (readonly, assign) CGRect bounds; // 指示图像坐标中的人脸位置和尺寸的矩形。
@property (readonly, assign) BOOL hasLeftEyePosition; // 指示检测器是否找到了人脸的左眼。
@property (readonly, assign) CGPoint leftEyePosition; // 左眼的坐标
@property (readonly, assign) BOOL hasRightEyePosition; // 指示检测器是否找到了人脸的右眼。
@property (readonly, assign) CGPoint rightEyePosition; // 右眼的坐标
@property (readonly, assign) BOOL hasMouthPosition; // 指示检测器是否找到了人脸的嘴部
@property (readonly, assign) CGPoint mouthPosition; // 嘴部的坐标 @property (readonly, assign) BOOL hasTrackingID; // 指示面部对象是否具有跟踪ID。
/**
* 关于trackingID:
* coreImage提供了在视频流中检测到的脸部的跟踪标识符,您可以使用该标识符来识别在一个视频帧中检测到的CIFaceFeature对象是在先前视频帧中检测到的同一个脸部。
* 只有在框架中存在人脸并且不与特定人脸相关联时,该标识符才会一直存在。如果脸部移出视频帧并在稍后返回到帧中,则分配另一个ID。 (核心图像检测面部,但不识别特定的面部。)
* 这个有点抽象
*/
@property (readonly, assign) int trackingID;
@property (readonly, assign) BOOL hasTrackingFrameCount; // 指示面部对象的布尔值具有跟踪帧计数。
@property (readonly, assign) int trackingFrameCount; // 跟踪帧计数 @property (readonly, assign) BOOL hasFaceAngle; // 指示是否有关于脸部旋转的信息可用。
@property (readonly, assign) float faceAngle; // 旋转是以度数逆时针测量的,其中零指示在眼睛之间画出的线相对于图像方向是水平的。 @property (readonly, assign) BOOL hasSmile; // 是否有笑脸
@property (readonly, assign) BOOL leftEyeClosed; // 左眼是否闭上
@property (readonly, assign) BOOL rightEyeClosed; // 右眼是否闭上
问题:那么如何让人脸识别的效果更好呢? 如何让面部识别点更加精确呢?有没有别的方法呢? 答案是肯定的。
现在市面上有很多成熟的面部识别产品:
Face++, 收费
Video++,收费
ArcFace 虹软人脸认知引擎, 收费
百度云人脸识别, 收费
阿里云识别, 收费
等等, 我们看到都是收费的。 当然这些sdk是可以试用的。如果你有折腾精神,想自己尝试人脸识别的实现,我们可以一起交流。 毕竟市面上的这些sdk也不是一开始就有的, 也是通过人们不断研究开发出来的。 而且自己折腾过程中,通过不断地遇坑爬坑,对知识的理解更加深透,自己的技术也会有增进,不是吗? 不好意思,有点扯远了。
Core Image只是简单的图像识别, 并不能对流中的人脸进行识别。 它只适合对图片的处理。比较有名的OpenCV(跨平台计算机视觉库)就可以用来进行面部识别,识别精度自然很高。还有就是iOS 11.0+ 推出的Vision框架(让我们轻松访问苹果的模型,用于面部检测、面部特征点、文字、矩形、条形码和物体)也可以进行面部识别。后面我将会用这两个框架讲解如何进行面部识别。敬请期待!!!
基于iOS用CoreImage实现人脸识别的更多相关文章
- 转:基于开源项目OpenCV的人脸识别Demo版整理(不仅可以识别人脸,还可以识别眼睛鼻子嘴等)【模式识别中的翘楚】
文章来自于:http://blog.renren.com/share/246648717/8171467499 基于开源项目OpenCV的人脸识别Demo版整理(不仅可以识别人脸,还可以识别眼睛鼻子嘴 ...
- 关于运行“基于极限学习机ELM的人脸识别程序”代码犯下的一些错误
代码来源 基于极限学习机ELM的人脸识别程序 感谢文章主的分享 我的环境是 win10 anaconda Command line client (version 1.6.5)(conda 4.3.3 ...
- opencv基于PCA降维算法的人脸识别
opencv基于PCA降维算法的人脸识别(att_faces) 一.数据提取与处理 # 导入所需模块 import matplotlib.pyplot as plt import numpy as n ...
- C#实现基于ffmepg加虹软的人脸识别
关于人脸识别 目前的人脸识别已经相对成熟,有各种收费免费的商业方案和开源方案,其中OpenCV很早就支持了人脸识别,在我选择人脸识别开发库时,也横向对比了三种库,包括在线识别的百度.开源的OpenCV ...
- iOS 使用百度的人脸识别登录验证,解决认证失败不跳转界面连续认证,认证相似度对比
在使用百度人脸识别出现的问题:小米6调用摄像机是黑白的一个情况,iOS上会出现识别准确性上的问题(多次代开认证,会通过) 人脸识别(活体验证): 1.芝麻认证 : 0.4元/次,需要企业企业认证.不能 ...
- C#实现基于ffmpeg加虹软的人脸识别demo及开发分享
对开发库的C#封装,屏蔽使用细节,可以快速安全的调用人脸识别相关API.具体见github地址.新增对.NET Core的支持,在Linux(Ubuntu下)测试通过.具体的使用例子和Demo详解,参 ...
- C#实现基于ffmpeg加虹软的人脸识别
关于人脸识别 目前的人脸识别已经相对成熟,有各种收费免费的商业方案和开源方案,其中OpenCV很早就支持了人脸识别,在我选择人脸 识别开发库时,也横向对比了三种库,包括在线识别的百度.开源的OpenC ...
- 基于Dlib、OpenCV开发人脸识别程序的开发建议
前言 在去年十月的时候参加了一个小比赛,做了一个人脸识别程序并很意外地获得省里面的一等奖,视频演示链接在这里,有同学想要做这方面的毕业设计or课程设计,发一篇博客来分享一下当时的开发过程. 视频演示链 ...
- 利用iOS原生系统进行人脸识别+自定义滤镜(GPUImage)
人脸识别+滤镜效果(基于GPUImage实现的自定义滤镜) 最近碰到一个好玩的需求.说要客户端这边判定一下是否有人脸.在有的基础上.对相片做进一步的美化滤镜处理. 首先是人脸的识别判定; //将图片对 ...
随机推荐
- Python 内置函数sorted()在高级用法
对于Python内置函数sorted(),先拿来跟list(列表)中的成员函数list.sort()进行下对比.在本质上,list的排序和内建函数sorted的排序是差不多的,连参数都基本上是一样的. ...
- Scrapy学习笔记(5)-CrawlSpider+sqlalchemy实战
基础知识 class scrapy.spiders.CrawlSpider 这是抓取一般网页最常用的类,除了从Spider继承过来的属性外,其提供了一个新的属性rules,它提供了一种简单的机制,能够 ...
- spring boot log4j2与三方依赖库log4j冲突无法初始化问题解决方法
因为从Spring Boot 1.4开始,spring boot就不支持log4j了,必须是log4j2或者logback,具体两者如何配置以及NDC的支持可以参考spring boot精华版. 这里 ...
- mysql优化之使用iotop+pt-ioprofile定位具体top io文件
今天,将一个环境切换成行情优化后的版本后,发现io等待还是挺高,这还是第一次出现的.其他很多套环境都没有这个问题了,故iotop看了下,基本可以确定为是mysql进程的问题,如下: 但是iotop只能 ...
- Deep Learning for NLP
Deep Learning for NLP The First Paper Proposed Bi-LSTM+CRF 我认为,第一篇提出 Bi-LSTM+CRF 架构的文章是: Huang Z, Xu ...
- Link Cut Tree学习笔记
从这里开始 动态树问题和Link Cut Tree 一些定义 access操作 换根操作 link和cut操作 时间复杂度证明 Link Cut Tree维护链上信息 Link Cut Tree维护子 ...
- 解决Visual Studio(2017)软件无法重新生成问题
https://blog.csdn.net/qq_38265674/article/details/80539228 笔者用VS2017打开VS2015创建的工程,出现如下图的问题. 不小心没有升级平 ...
- [CodeForces - 276A] Lunch Rush
题目链接:http://codeforces.com/problemset/problem/276/A 从这n个输入中求最大值,注意 和 k的比较,定义一个maxn,对每个输入进行计算即可. AC代码 ...
- 【一】、搭建Hadoop环境----本地、伪分布式
## 前期准备 1.搭建Hadoop环境需要Java的开发环境,所以需要先在LInux上安装java 2.将 jdk1.7.tar.gz 和hadoop 通过工具上传到Linux服务器上 3. ...
- 【sql server】索引详解
索引可以理解为一种特殊的目录结构. sql server提供两种索引形式: 聚集索引和非聚集索引. 怎么理解这两种形式. 拿我们常用的字典举例来说, 一个字典好比数据库中的一个表.那么当我们想从字典中 ...