iOS 11 - Vision 人脸识别

2,071 阅读6分钟
iOS 11 - Vision 人脸识别

swift 发布于 2017年09月08日

又是一年发布季,眼看新的 iPhone 和 iOS 11 就要发布了。 相信有不少朋友已经迫不及待的要入手新的 iPhone 了。 对于开发者每年的秋季发布会无疑是最兴奋的时候。正好借着这个时候和大家聊聊相关的内容。 上篇给大家介绍了 iOS 11 发布的 Core ML。 除了它之外, iOS 11 还提供了一个叫做 Vision 的库。 从名字中不难猜出,它是专门处理机器视觉的。 Vision 的接口使用起来非常简单,为我们提供了很多机器视觉能力,包括人脸识别,文字识别,场景识别等等。 从今年的 WWDC 发布的一系列库来看,各大厂商对 AI 的投入程度是前所未有的大。闲言少叙,即可开始。

Vision 人脸识别

这一篇给大家展示一个例子,如何用 Vision 识别一张图片中的人脸位置。 首先,我们来看一个直观的例子 图片中绿色的部分是 Vision 识别出来的面部区域,我们这个例子的功能就是可以识别出任何图片中的人脸位置(包括这个山姆大叔~):

下面正式开始代码部分, 首先 Vision 是通过两个 Handler 来处理整个图片识别过程, VNImageRequestHandlerVNSequenceRequestHandler, 其中前者用来处理单张图片,后者用于处理多张连续图片。

我们这里用的是 VNImageRequestHandler , 用来处理单张图片的人脸识别。 初始化这个 Handler :

import Vision
let handler = VNImageRequestHandler(ciImage: ciImage, orientation: CGImagePropertyOrientation.up)

这里我们给初始化方法传入了一个 CIImage 和一个 orientation 参数。 CIImage 不用过多解释,是我们要进行人脸识别的图片。 orientation 参数是 Vision 必须要明确知道的,它代表当前图片的方向,如果方向是颠倒的,或者不正确的,Vision 就不能正确识别出人脸了。

我们这里默认认为图片的方向都是正常的,所以传入了 CGImagePropertyOrientation.up 参数。 实际应用中需要注意这个参数要和图片的实际方向一致。

同时 VNImageRequestHandler 除了 CIImage 之外,还可以接受各种各样的图片数据类型,看看它的初始化函数列表:


init(cgImage: CGImage, options: [VNImageOption : Any] = [:])
init(ciImage: CIImage, options: [VNImageOption : Any] = [:])
init(cvPixelBuffer: CVPixelBuffer, options: [VNImageOption : Any] = [:])
init(data: Data, options: [VNImageOption : Any] = [:])
init(url: URL, options: [VNImageOption : Any] = [:])

CGImage,CIImage,CVPixelBuffer,Data,URL 这几种类型的数据形式都是可以的。 VNImageRequestHandler 提供了一个 perform 方法,这个方法用来执行具体的图片识别功能,我们可以传入各种不同的识别请求,比如文字识别,人脸识别等。 我们这个示例中使用 VNDetectFaceRectanglesRequest, 这个类代表人脸区域识别。

来看看如何初始化这个类:

let request = VNDetectFaceRectanglesRequest(completionHandler: { (request, error) in

        DispatchQueue.main.async {

                if let result = request.results {

                        let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -self.imageView!.frame.size.height)
                        let translate = CGAffineTransform.identity.scaledBy(x: self.imageView!.frame.size.width, y: self.imageView!.frame.size.height)

                        //遍历所有识别结果
                        for item in result {

                                //绿色标注框
                                let faceRect = UIView(frame: CGRect.zero)
                                faceRect.backgroundColor = UIColor(red: 0, green: 1.0, blue: 0, alpha: 0.5)
                                self.imageView!.addSubview(faceRect)

                                if let faceObservation = item as? VNFaceObservation {

                                        let finalRect = faceObservation.boundingBox.applying(translate).applying(transform)
                                        faceRect.frame = finalRect

                                }

                        }

                }
        }


})

这段代码稍长,咱们一起分析下。 首先初始化一个 VNDetectFaceRectanglesRequest 实例,它接受一个 completionHandler 参数,作为图片识别的回调函数。 这个回调会得到两个参数, request 和 error, 分别表示识别的结果和是否出错。

回调函数中的 request 对象和我们上面初始化的 VNDetectFaceRectanglesRequest 是同一个实例,不同的是,在回调中我们还可以得到 request.results 属性。这个属性表示的就是图片识别执行完之后的结果。

我们上面代码中的 if let result = request.results 就是判断是否得到识别结果, 如果得到结果就在 for item in result 中遍历所有的结果。 可以再看下这个 for 循环内部对于每一个识别结果的处理:

//绿色标注框
let faceRect = UIView(frame: CGRect.zero)
faceRect.backgroundColor = UIColor(red: 0, green: 1.0, blue: 0, alpha: 0.5)
self.imageView!.addSubview(faceRect)

if let faceObservation = item as? VNFaceObservation {

        let finalRect = faceObservation.boundingBox.applying(translate).applying(transform)
        faceRect.frame = finalRect

}

这段代码就是我们前面那一大段中的 for 循环部分, 可以看到,对于每一个识别结果,我们都创建了一个 faceRectUIView 对象, 给他设置了绿色背景。 然后我们在下面把每一个识别结果转换成 VNFaceObservation 对象,这个对象代表人脸识别的结果。 它有一个 boundingBox 属性, 是一个 CGRect 对象,代表识别出来的人脸区域。

但是这个 boundingBox 的原始值不是绝对的尺寸和坐标,而是一个相对的比例值,比如这样:

(0.401315122842789, 0.480968445539474, 0.201700419187546, 0.230584695935249)

我们还需要把它进行一系列转换才能正常使用,这就用到了这两个变换:

let translate = CGAffineTransform.identity.scaledBy(x: self.imageView!.frame.size.width, y: self.imageView!.frame.size.height)

这里面的 translate 用到了 scaledBy 变换。 这个变换其实就相当于把 boundingBox 里面所有的四个值,各自乘以 scaledByx,y 参数。 可以理解为把 boundingBox 的比例值,通过乘以 UIImageView 的长宽之后,变成可以显示在 UIImageView 坐标系中的绝对值。

经过第一步转换,我们得到了一个可以使用的区域值。 但这还不够,我们还需要进一步转换。 这是因为 boundingBox 属性的 y 坐标的起始方向和我们 UIView 中的不一样。 我们都知道 UIView 使用的 y 坐标是从上到下的。 而 boundingBox 的 y 坐标正好相反。 所以我们还需要应用另外一个变换:

let transform = CGAffineTransform(scaleX: 1, y: -1).translatedBy(x: 0, y: -self.imageView!.frame.size.height)

这个细节不过多解释,它的作用就是把 boundingBox 反过来的 y 轴,翻转成 UIView 可以使用的。 仔细看看的话应该不难理解。

最后,我们对 boundingBox 应用这两个变换后, 得到了最终可以显示在屏幕上的坐标区域:

let finalRect = faceObservation.boundingBox.applying(translate).applying(transform)
faceRect.frame = finalRect

然后,把这个坐标区域赋值给 faceRect,这样绿色的矩形就能正确的放置在人脸区域了。

准备工作基本完成, 最后一部,运行这个识别请求:


let handler = VNImageRequestHandler(ciImage: ciImage, orientation: CGImagePropertyOrientation.up)
let request = VNDetectFaceRectanglesRequest(completionHandler: { (request, error) {

    //...

}

DispatchQueue.global(qos: .userInteractive).async {

        do {

                try handler.perform([request])

        } catch {

        }

}

这里需要注意一点, handler 的 perform 方法要在一个独立的 DispatchQueue 中调用,否则会阻塞 UI 线程渲染。 这样,我们这个人脸识别程序就完成了。 你可以试着用你自己手机相册中的照片来验证一下。

另外,这个示例的完整代码已经为你备上,可以慢慢研究 github.com/swiftcafex/…

总结

iOS 11 推出的 Core ML 和 Vision 让我们能够更加低成本的使用 AI 带来的各种能力。 当然这里所提及的只是使用。 如果想深入的研究的话,还是需要大量知识的。 当然对于做产品来说,如果这些库提供的能力恰巧能帮助你提升用户体验的话,那它们就能发挥价值。 相信通过 Core ML 和 Vision 会有更多的 APP 能够发挥这些价值。


如果你觉得这篇文章有帮助,还可以关注微信公众号 swift-cafe,会有更多我的原创内容分享给你~

本站文章均为原创内容,如需转载请注明出处,谢谢。
关注微信公众号
发现更多精彩
swift-cafe