本文用于记录CoreImage框架的学习和使用,学习资源在这里。
CoreImage是高性能处理分析图像和影音图像的技术,并将晦涩难懂底部实现细节封装成简单的API,开发人没有OpenGL、OpenGL ES、Metal这些图像处理技术背景就可以使用GPU来处理图片,也可以没有GCD编程背景完成图片的多核处理。
本文将对对CoreImage的常用功能进行介绍:
- 滤镜基本使用
- 协作AV Foundation处理视频
- 图片优化
- 内容识别(人脸、条形码、文本)
滤镜
CoreImage目前提供了两百左右种类滤镜,并对他们进行了分类, 我们可以使用filterNames(inCategory: categoryName)和 filterNames(inCategories: categoryArray)接口分类检索这些内置滤镜,并且获取这些内置类型的相关信息:名字、inputKey、默认参数、滤镜支持调节的参数,基于这些内置滤镜和他们的参数,我们可以实现一个基础的多效果且可调节的图片特效功能1
2
3
4
5
6
7
8
9
10
11
12
13
14/// 获取所有内置的滤镜
func queryAllBiuldFilters() {
let allFilterNames = CIFilter.filterNames(inCategory: nil)
// inCategory获取指定种类的滤镜,nil为所有滤镜
print(allFilterNames.count);
for filterName in allFilterNames {
print("-----------------------------")
let filter = CIFilter(name: filterName)!
print(filterName)
print(filter.inputKeys)
print(filter.attributes)
}
}
按照滤镜效果来分的话,分为如下几类
Effect type | Indicates |
kCICategoryDistortionEffect | Distortion effects, such as bump, twirl, hole |
kCICategoryGeometryAdjustment | Geometry adjustment, such as affine transform, crop, perspective transform |
kCICategoryCompositeOperation | Compositing, such as source over, minimum, source atop, color dodge blend mode |
kCICategoryHalftoneEffect | Halftone effects, such as screen, line screen, hatched |
kCICategoryColorAdjustment | Color adjustment, such as gamma adjust, white point adjust, exposure |
kCICategoryColorEffect | Color effect, such as hue adjust, posterize |
kCICategoryTransition | Transitions between images, such as dissolve, disintegrate with mask, swipe |
kCICategoryTileEffect | Tile effect, such as parallelogram, triangle |
kCICategoryGenerator | Image generator, such as stripes, constant color, checkerboard |
kCICategoryGradient | Gradient, such as axial, radial, Gaussian |
kCICategoryStylize | Stylize, such as pixellate, crystallize |
kCICategorySharpen | Sharpen, luminance |
kCICategoryBlur | Blur, such as Gaussian, zoom, motion |
按照用法来分的话,分为如下几类
Use | Indicates |
kCICategoryStillImage | Can be used for still images |
kCICategoryVideo | Can be used for video |
kCICategoryInterlaced | Can be used for interlaced images |
kCICategoryNonSquarePixels | Can be used for nonsquare pixels |
kCICategoryHighDynamicRange | Can be used for high-dynamic range pixels |
滤镜的运用很简单1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* CISepiaTone:降低图像质量,老照片的感觉
* kCIInputIntensityKey:强度0-1
*/
private func base() {
let ctx = CIContext()
let filter = CIFilter(name: "CISepiaTone")!
filter.setValue(0.8, forKey: kCIInputIntensityKey)
let image = CIImage(image: UIImage(named: "girl.jpg")!)
filter.setValue(image, forKey: kCIInputImageKey)
let output = filter.outputImage!
let cgImage = ctx.createCGImage(output, from: output.extent)! // 滤镜尚未生效
processedImageView.image = UIImage(cgImage: cgImage) // 滤镜生效
}
滤镜的输出.output是一个CIImage对象,它并不负责把图片展示出来,而是一个记录如何生成图片的recipe,从本地加载图片、访问滤镜(链)的.ouput都会创建一个这样的recipe,他们只有在被渲染出来的时候才会生效,这也是滤镜生效的时机。
每一个滤镜的输出(recipe)都可以作为另外一个滤镜的输入,由此便形成了滤镜链,一条滤镜链上有多个recipe,同样也只有在渲染出来的时候,滤镜链的滤镜才会生效。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19/**
* 滤镜链
*/
private func filterChain() {
let ctx = CIContext()
let ciImage = CIImage(image: UIImage(named: "girl.jpg")!)!
let colorFilter = CIFilter(name: "CIPhotoEffectProcess",
parameters: [kCIInputImageKey: ciImage])!
let boomImage = colorFilter.outputImage!.applyingFilter("CIBloom",
parameters: [kCIInputRadiusKey: 5, kCIInputIntensityKey: 1])
let cropRect = CGRect(x: 0, y: 0, width: 100, height: 100)
let croppedImage = boomImage.cropped(to: cropRect)
let cgImage = ctx.createCGImage(croppedImage,
from: croppedImage.extent)!
print(croppedImage.extent)
processedImageView.image = UIImage(cgImage: cgImage)
}
CoreImage不会单独运用每一个滤镜,而是把所有的滤镜组合成一次操作,这样对渲染时间和内存占用比较友好,而且渲染结果不受滤镜链上的滤镜顺序影响。CoreImage会根据滤镜的信息来优化渲染过程,比如上面的demo中滤镜使用情况为CIPhotoEffectProcess->CIBloom->剪切,如果把剪切调整到第一步,那么CoreImage就不会在被剪切的部分运用CIPhotoEffectProcess和CIBloom这两个滤镜,只会运用在有效像素区域。
协作AV Foundation处理视频
前面主要讲介绍了CoreImage在AVKit框架下使用,除此之外还可以运用于AV Foundation、SpriteKit、SceneKit、Metal框架下,当然了也可以和OpenGL、OpenGL ES使用。
接下来我们使用CoreImage和AV Foundation给视频添加滤镜,思路很简单:
- 使用videoCompositionWithAsset:applyingCIFiltersWithHandler:构建一个AVVideoComposition对象,我们需要传入两个参数视频资源和视频播放的每一帧回调
- 在视频播放的每一帧回调中取得该帧图片,使用滤镜
- 使用AV Foundation框架把视频播放出来就okay了
代码如下:开发文档中提到了一点,由于模糊滤镜模糊图片像素和图片边缘的透明像素,会产生一种边缘虚化的不友好现象,解决方案是虚化之前使用imageByClampingToExtent无限扩展图片边缘像素,同时会导致图片无限大小,所以需要重新裁剪。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19let filter = CIFilter(name: "CIGaussianBlur")!
filter.setValue(30, forKey: kCIInputRadiusKey)
let url = URL(string: "http://192.168.1.101:8001/deathgod_01.mp4")!
let asset = AVAsset(url: url)
let composition = AVVideoComposition(asset: asset) { (request) in
let source = request.sourceImage.clampedToExtent()
filter.setValue(source, forKey: kCIInputImageKey)
// 在这里可以为每一秒设置不同的滤镜参数
let output = filter.outputImage!.cropped(to: request.sourceImage.extent)
request.finish(with: output, context: nil)
}
let item = AVPlayerItem(asset: asset)
item.videoComposition = composition
let player = AVPlayer(playerItem: item)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = CGRect(x: 0, y: 300, width: view.frame.width,
height: view.frame.width * 9 / 16)
view.layer.addSublayer(playerLayer)
player.play()
面部特征检测
我们一般设置UIImageView.contentMode = .scaleAspectFit来防止图片被拉伸和超出边界被剪切,这样图片就可以等比例收缩且全部显示在UIImageView中,收缩比例为mini(宽收缩比例,高搜索比例)。当检测到面部特征时,我们可以拿到面部特征的数字信息,比如面部位置bounds、左眼位置leftEyePosition、右眼位置rightEyePosition、嘴巴位置mouthPosition,这些数字都以原图片(非UIImageView或者收缩后的图片)的坐标系来的,且坐标原点在左下角(mac os的坐标系),所以使用这些数据之前,需要先对这些数据进行如下转换:
- 按照图片收缩的比例进行收缩
- 由于这些数据参考的坐标原点在左下角,所以实际的Y是boundsAfterScale.height - boundsAfterScale.maxY
- 由于图片进行等比例缩小,所以一般情况下在垂直或者水平的方向和UIImageView有空白,在转换x/y的时候要加上这部分的空白
滤镜部分代码如下,详细代码在这里同样,我们可以使用CIDetectorTypeQRCode进入二维码检测
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16/// 面部特征检测
let ctx = CIContext()
let detector = CIDetector(ofType: CIDetectorTypeFace,
context: ctx,
options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])!
let image = UIImage(named: "girl.jpg")!
let ciImage = CIImage(image: image)!
var ops = [String: Any]()
if let orientation = ciImage.properties[kCGImagePropertyOrientation as String] {
ops[CIDetectorImageOrientation] = orientation
} else {
// kCGImagePropertyOrientation 1 - 8 默认是1,分析是从左上角开始,即第一行在top + 第一列在left
ops[CIDetectorImageOrientation] = 1
}
let features = detector.features(in: ciImage, options: ops ) as! [CIFaceFeature]使用CIDetectorTypeText进行文本检测,默认按行为单位进行扫描检测,返回该行的区域和该行每个字符的四个角的位置,但是很遗憾,不能识别文字内容,而且容易受干扰,比如上图(带二维码)中的文字就没有检测到,下图中的弯曲文字也没有校测到。滤镜代码如下,全部代码在这里1
2
3
4
5
6
7
8
9
10
11
12let image = UIImage(named: "qrcode_text.png")!
let ciImage = CIImage(image: image)!
let ctx = CIContext()
let detector = CIDetector(ofType: CIDetectorTypeQRCode,
context: ctx,
options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
let features = detector?.features(in: ciImage) as? [CIQRCodeFeature] ?? []
print(features.count)
for feature in features {
// bounds的处理参考面部检测
print("位置在 \(feature.bounds) 二维码的内容是\(feature.messageString ?? "")")
}1
2
3
4
5
6let ctx = CIContext()
let detector = CIDetector(ofType: CIDetectorTypeText,
context: ctx,
options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
// CIDetectorReturnSubFeatures可以返回每个字符的四个角
let features = detector?.features(in: ciImage, options: [CIDetectorReturnSubFeatures: true]) as? [CITextFeature] ?? []
图片自动优化
CoreImage分析图片的柱状图?、面部区域内容、元数据,并根据这些情况返回一个优化该图片所需要使用的滤镜组,总共有5中滤镜:
CIRedEyeCorrection | 修复强光导致的红眼(眼底血管导致)、白眼(眼球反光导致)、眼球模糊 |
CIFaceBalance | 自动调色 |
CIVibrance | 调整色彩饱和度 |
CIToneCurve | <对比度调整 |
CIHighlightShadowAdjust | 调整阴影 |
最后我们只需将返回的滤镜组运用到图片上即可完成优化。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16@IBAction func enhanceBtnDidClick(_ sender: UIButton) {
let ctx = CIContext()
let originalImage = UIImage(named: "girl.jpg")!
var ciImage = CIImage(image: originalImage)!
let filters = ciImage.autoAdjustmentFilters()
for filter in filters {
print(filter.name)
filter.setValue(ciImage, forKey: kCIInputImageKey)
ciImage = filter.outputImage!
}
let cgImage = ctx.createCGImage(ciImage, from: ciImage.extent)!
processedImageView.image = UIImage(cgImage: cgImage)
}
从输出结果中,发现使用了4个滤镜:CIFaceBalance、CIVibrance、CIToneCurve、CIHighlightShadowAdjust,因为图片本身已经很好了,所以效果看着不是太明显,但还是能看出来饱和度和对比度稍微提升一些。
同时也发现了一点,前面提到的filter.output的时候,滤镜还没有发生作用,所以别直接将filter.ouput转化成UIimage显示到UIImageView上,而是使用ctx.createCGImage(),可能会得到莫名其妙图片,比如图片被拉伸了
注!请不要过于信赖的CoreImage的自动优化,因为有时候不能识别出所有优化点,比如这个,虽然人物面部肤色更加饱和红润了,但是从返回的滤镜组发现,没有去红眼滤镜,说明没有识别出图片的红眼问题。