iOS8 Core Image In Swift:自动改善图像以及内置滤镜的使用

iOS8 Core Image In Swift:更复杂的滤镜

iOS8 Core Image In Swift:人脸检测以及马赛克

iOS8 Core Image In Swift:视频实时滤镜

Core Image不仅内置了诸多滤镜,还能检测图像中的人脸,不过Core Image只是检测,并非识别,检测人脸是指在图像中寻找符合人脸特征(只要是个人脸)的区域,识别是指在图像中寻找指定的人脸(比如某某某的脸)。Core Image在找到符合人脸特征的区域后,会返回该特征的信息,比如人脸的范围、眼睛和嘴巴的位置等。

人脸检测并标记检测到的区域

先做好以下几步:
  1. 新建一个Single View Application工程
  2. 然后在Storyboard里放入UIImageView,ContentMode设置为Aspect Fit
  3. 将UIImageView连接到VC里
  4. 放入一个名为“人脸检测”的UIButton,然后连接到VC的faceDetecting方法上
  5. 关闭Auto Layout以及Size Classes
UIImageView的frame以及VC的UI如下:
 
 
以下是工程中会用到的图,齐刷刷一排脸,点击图片显示原图:
 
 
然后在VC上添加基本的属性:懒加载的originalImage、context(Core Image框架绕不开的对象)。

class ViewController: UIViewController {

@IBOutlet var imageView: UIImageView!

lazy var originalImage: UIImage = {

return UIImage(named: "Image")

}()

lazy var context: CIContext = {

return CIContext(options: nil)

}()

......

在viewDidLoad方法里显示originalImage:

override func viewDidLoad() {

super.viewDidLoad()

// Do any additional setup after loading the view, typically from a nib.

self.imageView.image = originalImage

}

然后就可以准备实现faceDetecting方法了。

在Core Image框架中,CIDetector对象提供了对图像检测的功能,只需要通过几个APIs就能完成CIDetector的初始化并得到检测结果:

@IBAction func faceDetecing() {

let inputImage = CIImage(image: originalImage)

let detector = CIDetector(ofType: CIDetectorTypeFace,

context: context,

options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])

var faceFeatures: [CIFaceFeature]!

if let orientation: AnyObject = inputImage.properties()?[kCGImagePropertyOrientation] {

faceFeatures = detector.featuresInImage(inputImage,

options: [CIDetectorImageOrientation: orientation]

) as [CIFaceFeature]

} else {

faceFeatures = detector.featuresInImage(inputImage) as [CIFaceFeature]

}

println(faceFeatures)

......

使用kCGImagePropertyOrientation的时候,可能需要导入ImageIO框架
originalImage和context通过懒加载都得到了,在创建CIDetector对象的时候,必须告诉它要检测的内容,这里当然是传CIDetectorTypeFace了, 除了CIDetectorTypeFace外,CIDetector还能检测二维码;然后传递一个context,多个CIDetector可以共用一个 context对象;第三个参数是一个字典,我们能够指定检测的精度,除了CIDetectorAccuracyHigh以外,还有 CIDetectorAccuracyLow,精度高会识别度更高,但识别速度就更慢。
创建完CIDetector之后,把要识别的CIImage传递给它,在这里,我判断了CIImage是否带有方向的元数据,如果带的话调用就featuresInImage:options这个方法,因为方向对CIDetector来说至关重要,直接导致识别的成功与否;而有的图片没有方向这些元数据,就调用featuresInImage方法,由于这张《生活大爆炸》的图是不带方向元数据的,所以是执行的featuresInImage方法,但是大多数情况下应该会用到前者。
featuresInImage方法的返回值是一个CIFaceFeature数组,CIFaceFeature包含了面部的范围、左右眼、嘴巴的位置等,我们通过使用bounds就能标记出面部的范围。
我们很容易写出这样的代码:
  1. 获取所有的面部特征
  2. 用bounds实例化一个UIView
  3. 把View显示出来
实现出来就像这样:

@IBAction func faceDetecing() {

let inputImage = CIImage(image: originalImage)

let detector = CIDetector(ofType: CIDetectorTypeFace,

context: context,

options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])

var faceFeatures: [CIFaceFeature]!

if let orientation: AnyObject = inputImage.properties()?[kCGImagePropertyOrientation] {

faceFeatures = detector.featuresInImage(inputImage, options: [CIDetectorImageOrientation: orientation]) as [CIFaceFeature]

} else {

faceFeatures = detector.featuresInImage(inputImage) as [CIFaceFeature]

}

println(faceFeatures)

for faceFeature in faceFeatures {

let faceView = UIView(frame: faceFeature.bounds)

faceView.layer.borderColor = UIColor.orangeColor().CGColor

faceView.layer.borderWidth = 2

imageView.addSubview(faceView)

}

}

这样写是否可以呢?如果你运行起来会得到这样的效果:

这是因为我们的inputImage,其实是用originalImage初始化的,而我这张originalImage的真实大小比实际看到的大得多:
它的宽实有600像素,我把它以@2x命名,实际显示有300像素,且在imageView里以Aspect Fit模 式展示(imageViwe宽为300像素),在显示的时候被缩放了,但是在内存中它是完整的,除此之外,CIImage的坐标系统和UIView的坐标 系统也不一样,CIImage的坐标系统就像数学坐标系统,原点在下,在UIView看来,就是倒置的,这是因Core Image、Core Graphics这些框架都来源于Mac OS X,在Mac OS X上这种坐标系统已存在多年,iOS直接引入了这些框架,这解决了Cocoa App和iOS App底层兼容性的问题,但是在上层就只能自己解决了。所以实际上它是这样的:
我们需要做两步工作:
  • 调整transform,让它正过来
  • 缩放bounds,让它适配imageView
然后再次很容易的写下了这样的代码:

@IBAction func faceDetecing() {

let inputImage = CIImage(image: originalImage)

let detector = CIDetector(ofType: CIDetectorTypeFace,

context: context,

options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])

var faceFeatures: [CIFaceFeature]!

if let orientation: AnyObject = inputImage.properties()?[kCGImagePropertyOrientation] {

faceFeatures = detector.featuresInImage(inputImage, options: [CIDetectorImageOrientation: orientation]) as [CIFaceFeature]

} else {

faceFeatures = detector.featuresInImage(inputImage) as [CIFaceFeature]

}

println(faceFeatures)

// 1.

let inputImageSize = inputImage.extent().size

var transform = CGAffineTransformIdentity

transform = CGAffineTransformScale(transform, 1, -1)

transform = CGAffineTransformTranslate(transform, 0, -inputImageSize.height)

for faceFeature in faceFeatures {

var faceViewBounds = CGRectApplyAffineTransform(faceFeature.bounds, transform)

// 2.

let scaleTransform = CGAffineTransformMakeScale(0.5, 0.5)

faceViewBounds = CGRectApplyAffineTransform(faceViewBounds, scaleTransform)

let faceView = UIView(frame: faceViewBounds)

faceView.layer.borderColor = UIColor.orangeColor().CGColor

faceView.layer.borderWidth = 2

imageView.addSubview(faceView)

}

}

现 在看起来就没有问题了,在第一步里我们放置了一个调整坐标系统的tranform,在第二步对bounds进行了缩放(等同于把x、y、width、 height全部乘以0.5),由于我们知道实际scale是0.5(原图600像素,imageView宽为300像素),就直接写死了0.5,但运行 后出现了一点点偏移:

这其实是因为我把imageView的ContentMode设为Aspect Fit的结果 :
 
一般来讲,我们不会拉伸照片,通常会按宽、高进行适配,所以我们还需要对Aspect Fit进行处理,上面代码修改后如下:

@IBAction func faceDetecing() {

let inputImage = CIImage(image: originalImage)

let detector = CIDetector(ofType: CIDetectorTypeFace,

context: context,

options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])

var faceFeatures: [CIFaceFeature]!

if let orientation: AnyObject = inputImage.properties()?[kCGImagePropertyOrientation] {

faceFeatures = detector.featuresInImage(inputImage, options: [CIDetectorImageOrientation: orientation]) as [CIFaceFeature]

} else {

faceFeatures = detector.featuresInImage(inputImage) as [CIFaceFeature]

}

println(faceFeatures)

// 1.

let inputImageSize = inputImage.extent().size

var transform = CGAffineTransformIdentity

transform = CGAffineTransformScale(transform, 1, -1)

transform = CGAffineTransformTranslate(transform, 0, -inputImageSize.height)

for faceFeature in faceFeatures {

var faceViewBounds = CGRectApplyAffineTransform(faceFeature.bounds, transform)

// 2.

var scale = min(imageView.bounds.size.width / inputImageSize.width,

imageView.bounds.size.height / inputImageSize.height)

var offsetX = (imageView.bounds.size.width - inputImageSize.width * scale) / 2

var offsetY = (imageView.bounds.size.height - inputImageSize.height * scale) / 2

faceViewBounds = CGRectApplyAffineTransform(faceViewBounds, CGAffineTransformMakeScale(scale, scale))

faceViewBounds.origin.x += offsetX

faceViewBounds.origin.y += offsetY

let faceView = UIView(frame: faceViewBounds)

faceView.layer.borderColor = UIColor.orangeColor().CGColor

faceView.layer.borderWidth = 2

imageView.addSubview(faceView)

}

}

在第二步里,除了通过宽、高比计算scale外,还计算了x、y轴的偏移,以确保在宽或高缩放的情况下都能正常工作(最后除以2是因为缩放时是居中显示,上下或左右都各有一半)。

编译、运行,在不同的高度下的效果图:
 

面部马赛克

检测到面部以后,我们还能做一些有趣的操作,比如打上马赛克:
这是苹果官方例子上的一张图,展示了把一张照片中所有的面部打上马赛克的方法:
  1. 基于原图,创建一个将所有部分都马赛克的图片
  2. 为检测到的人脸创建一张蒙版图
  3. 用蒙版图,将完全马赛克的图和原图混合起来
我们在VC上添加一个名为“马赛克”的按钮,将其事件连接到VC的pixellated方法上,然后开始实现马赛克的效果。
具体步骤如下:

创建完全马赛克的图

使用CIPixellate滤镜,其参数设置:
  • 设置inputImage为原图
  • 可以根据自己的需要,选择设置inputScale参数,inputScale取值为1到100,取值越大,马赛克就越大
这一步的效果图:
 

为检测到的人脸创建蒙版图

和之前一样,使用CIDetector检测人脸,然后为每一张脸:
  • 使用CIRadialGradient滤镜创建一个把脸包围起来的圆
  • 使用CISourceOverCompositing滤镜把各个蒙版(有几张脸其实就有几个蒙版)组合起来
这一步的效果图:
 

混合马赛克图、蒙版图以及原图

CIBlendWithMask滤镜来混合三者,其参数设置如下:
  • 设置inputImage为马赛克图
  • 设置inputBackground为原图
  • 设置inputMaskImage为蒙版图
完整实现代码如下:

@IBAction func pixellated() {

// 1.

var filter = CIFilter(name: "CIPixellate")

println(filter.attributes())

let inputImage = CIImage(image: originalImage)

filter.setValue(inputImage, forKey: kCIInputImageKey)

// filter.setValue(max(inputImage.extent().size.width, inputImage.extent().size.height) / 60, forKey: kCIInputScaleKey)

let fullPixellatedImage = filter.outputImage

// let cgImage = context.createCGImage(fullPixellatedImage, fromRect: fullPixellatedImage.extent())

// imageView.image = UIImage(CGImage: cgImage)

// 2.

let detector = CIDetector(ofType: CIDetectorTypeFace,

context: context,

options: nil)

let faceFeatures = detector.featuresInImage(inputImage)

// 3.

var maskImage: CIImage!

for faceFeature in faceFeatures {

println(faceFeature.bounds)

// 4.

let centerX = faceFeature.bounds.origin.x + faceFeature.bounds.size.width / 2

let centerY = faceFeature.bounds.origin.y + faceFeature.bounds.size.height / 2

let radius = min(faceFeature.bounds.size.width, faceFeature.bounds.size.height)

let radialGradient = CIFilter(name: "CIRadialGradient",

withInputParameters: [

"inputRadius0" : radius,

"inputRadius1" : radius + 1,

"inputColor0" : CIColor(red: 0, green: 1, blue: 0, alpha: 1),

"inputColor1" : CIColor(red: 0, green: 0, blue: 0, alpha: 0),

kCIInputCenterKey : CIVector(x: centerX, y: centerY)

])

println(radialGradient.attributes())

// 5.

let radialGradientOutputImage = radialGradient.outputImage.imageByCroppingToRect(inputImage.extent())

if maskImage == nil {

maskImage = radialGradientOutputImage

} else {

println(radialGradientOutputImage)

maskImage = CIFilter(name: "CISourceOverCompositing",

withInputParameters: [

kCIInputImageKey : radialGradientOutputImage,

kCIInputBackgroundImageKey : maskImage

]).outputImage

}

}

// 6.

let blendFilter = CIFilter(name: "CIBlendWithMask")

blendFilter.setValue(fullPixellatedImage, forKey: kCIInputImageKey)

blendFilter.setValue(inputImage, forKey: kCIInputBackgroundImageKey)

blendFilter.setValue(maskImage, forKey: kCIInputMaskImageKey)

// 7.

let blendOutputImage = blendFilter.outputImage

let blendCGImage = context.createCGImage(blendOutputImage, fromRect: blendOutputImage.extent())

imageView.image = UIImage(CGImage: blendCGImage)

}

我详细的分为了7个部分:

  1. 用CIPixellate滤镜对原图先做个完全马赛克
  2. 检测人脸,并保存在faceFeatures中
  3. 初始化蒙版图,并开始遍历检测到的所有人脸
  4. 由于我们要基于人脸的位置,为每一张脸都单独创建一个蒙版,所以要先计算出脸的中心点,对应为x、y轴坐标,再基于脸的宽度或高度给一个半径,最后用这些计算结果初始化一个CIRadialGradient滤镜(我将inputColor1的alpha赋值为0,表示将这些颜色值设为透明,因为我不关心除了蒙版以外的颜色,这点和苹果官网中的例子有太一样,苹果将其赋值为了1)
  5. 由于CIRadialGradient滤镜创建的是一张无限大小的图,所以在使用之前先对它进行裁剪(苹果官网例子中没有对其裁剪。。),然后把每一张脸的蒙版图合在一起
  6. CIBlendWithMask滤镜把马赛克图、原图、蒙版图混合起来
  7. 输出,在界面上显示
运行效果:
一个简单的对照片进行马赛克处理的例子就完成了。
 
 

GitHub下载地址

我在GitHub上会保持更新。
 

UPDATED:

细心的朋友会发现马赛克的面积比检测到的面积要大:
这是因为计算马赛克radius时没有考虑缩放的因素,只要先计算出scale,再把scale和现在的radius相乘就能得到精确的范围。
计算scale:

var scale = min(imageView.bounds.size.width / inputImage.extent().size.width,

imageView.bounds.size.height / inputImage.extent().size.height)

修正radius:

let radius = min(faceFeature.bounds.size.width, faceFeature.bounds.size.height) * scale

修正后的马赛克效果与人脸检测效果:

参考资料:

https://developer.apple.com/library/mac/documentation/graphicsimaging/conceptual/CoreImaging/ci_intro/ci_intro.html

iOS8 Core Image In Swift:人脸检测以及马赛克的更多相关文章

  1. iOS8 Core Image In Swift:更复杂的滤镜

    iOS8 Core Image In Swift:自动改善图像以及内置滤镜的使用 iOS8 Core Image In Swift:更复杂的滤镜 iOS8 Core Image In Swift:人脸 ...

  2. iOS8 Core Image In Swift:自动改善图像以及内置滤镜的使用

    iOS8 Core Image In Swift:自动改善图像以及内置滤镜的使用 iOS8 Core Image In Swift:更复杂的滤镜 iOS8 Core Image In Swift:人脸 ...

  3. iOS8 Core Image In Swift:视频实时滤镜

    iOS8 Core Image In Swift:自己主动改善图像以及内置滤镜的使用 iOS8 Core Image In Swift:更复杂的滤镜 iOS8 Core Image In Swift: ...

  4. 用caffe一步一步实现人脸检测

    学习深度学习已有一段时间了,总想着拿它做点什么,今天终于完成了一个基于caffe的人脸检测,这篇博文将告诉你怎样通过caffe一步步实现人脸检测.本文主要参考唐宇迪老师的教程,在这里感谢老师的辛勤付出 ...

  5. Android+openCV人脸检测2(静态图片)

    前几篇文章中有提到对openCV环境配置,这里再重新梳理导入和使用openCV进行简单的人脸检测(包括使用级联分类器) 一 首先导入openCVLibrary320 二 设置gradle的sdk版本号 ...

  6. 基于opencv3.0下的人脸检测和检测部分的高斯模糊处理

    如题 这里将任务分解为三大部分: 1.录播放视频 2.人脸检测 3.部分高斯模糊 其中重点放在人脸检测和部分高斯模糊上 1.录播放视频(以opencv中的VideoCapture类进行实现) 首先罗列 ...

  7. Atitti opencv2.4 实现的人脸检测 attilax总结

    Atitti opencv2.4 实现的人脸检测 attilax总结 1.1. 1.OpenCV人脸检测的方法1 1.2. /atiplat_img/src/com/attilax/facedetec ...

  8. 【计算机视觉】如何使用opencv自带工具训练人脸检测分类器

    前言 使用opencv自带的分类器效果并不是很好,由此想要训练自己的分类器,正好opencv有自带的工具进行训练.本文就对此进行展开. 步骤 1.查找工具文件: 2.准备样本数据: 3.训练分类器: ...

  9. javacv 340使用 人脸检测例子【转载】

    Java下使用opencv进行人脸检测 工作需要,研究下人脸识别,发现opencv比较常用,尽管能检测人脸,但识别率不高,多数是用来获取摄像头的视频流的,提取里面的视频帧,实现人脸识别时通常会和其他框 ...

随机推荐

  1. 【转】深入理解篇UIScrollerView

    转自:http://www.mamicode.com/info-detail-1144770.html 接下来,我整理一下自己的思路,深入理解 UIScrollView 基本点 : 1 . UIScr ...

  2. iOS应用性能调优的25个建议和技巧【转】

    转载自:http://blog.jobbole.com/37984/ 首页 最新文章 资讯 程序员 设计 IT技术 创业 在国外 营销 趣文 特别分享 更多 > - Navigation -  ...

  3. js获取上传文件扩展名

    File_box.value.substring(File_box.value.lastIndexOf(".") + 1);

  4. linux服务器伪分布模式安装hadoop-1.1.2

    1:环境准备    1台linux服务器, hadoop安装包(apache官方网下载) jdk1.6+   2:安装jdk ,配置好环境变量(etc/profile),java -version 测 ...

  5. C# Linq Group By 多个字段并返回给实体类List

    using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace stud ...

  6. C++中cin输入类型不匹配解决方法

    #include <iostream> #include <set> using namespace std; int main() { int a; cin>>a ...

  7. hdu5347 MZL's chemistry(打表)

    转载请注明出处: http://www.cnblogs.com/fraud/          ——by fraud MZL's chemistry Time Limit: 2000/1000 MS ...

  8. Apache配置多个监听端口和不同的网站目录的简单方法(转)

    转自http://www.waaqi.com/archives/707.html 由于开发的多项目,每个项目又要独立,要用根目录地址. 所以这时候我们需要配置多个不同目录的Apache,如果是外部网可 ...

  9. encodeURI后台乱码(解决)

    window.location.href = xxxx?a=encodeURI(encodeURI(name)) ; name是中文,页面部分需要编码两次 name = java.net.URLDec ...

  10. 使用 PHP 读取文本(TXT)文件 并分页显示

    <?php //----------------you should save this file as m.php---------------- session_start(); if (e ...