基于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框架(让我们轻松访问苹果的模型,用于面部检测、面部特征点、文字、矩形、条形码和物体)也可以进行面部识别。后面我将会用这两个框架讲解如何进行面部识别。敬请期待!!!

 

 

posted @ 2019-03-14 15:46  孤傲的苍狼-  阅读(763)  评论(0编辑  收藏  举报