图文并茂带你走进Core Image

2019-04-09  本文已影响0人  sweetpf

Core Image框架是适合图片的苹果滤镜框架,主要用处可以给图片添加滤镜效果和图像识别功能(人脸、条形码等等)。本文将会介绍逐一介绍 Core Image相关基础概念、使用方式、注意点以及和其他图像处理方案的对比。

本文干货很多,基本介绍全了Core Image的重要内容,阅读完后请点赞支持我的辛苦。

Core Image概述

Core Image is an image processing and analysis technology designed to provide near real-time processing for still and video images. It operates on image data types from the Core Graphics, Core Video, and Image I/O frameworks, using either a GPU or CPU rendering path. Core Image hides the details of low-level graphics processing by providing an easy-to-use application programming interface (API). You don’t need to know the details of OpenGL or OpenGL ES to leverage the power of the GPU, nor do you need to know anything about Grand Central Dispatch (GCD) to get the benefit of multicore processing. Core Image handles the details for you.

这是苹果官方文档对于 Core Image 的介绍,大致意思是:Core Image 是一种为静态图像和 Video 提供处理和分析的技术,它可以使用 GPU/CPU 的方式对图像进行处理。Core Image 提供了简洁的 API 给用户,隐藏了图像处理中复杂的底层内容。你可以在不了解 OpenGL、OpenGL ES 甚至是 GCD 的基础上对其进行使用,他已经帮你对这些复杂的内容进行处理了。废话这么多,苹果就想告诉我们一件事: 所有的底层细节他都帮你做好了,你只需要放心调用API就行了。

Core Image的知识点我以图的形式归纳,如下:


Core Image.png

Core Image的工作原理:


工作原理.png

CIFilter滤镜效果使用

这一部分会结合代码,开发环境swift5.0。

CIFilter是通过操纵一个或多个输入图像或生成新的图像数据来合成图像的图像处理器。简单来说,CIFilter接收一个或多个图像作为输入源,通过键值对设置和检索CIFilter对象的参数,生成一个CIImage对象作为输出源。

CIFilter父类是NSObject,是使用CoreImage进行滤镜效果的核心类。当然,你也可以子类化CIFilter来实现自定义过滤效果,具体方案为:

CIFilter重要属性

/*
输出
*/
@available(iOS 5.0, *)
    open var outputImage: CIImage? { get }

    
    /* The name of the filter. On OSX and iOS 10, this property is read-write.
     * This can be useful when using CIFilters with CoreAnimation or SceneKit.
     * For example, to set an attribute of a filter attached to a layer,
     * a unique path such as "filters.myExposureFilter.inputEV" could be used.
     * CALayer animations may also access filter attributes via key-paths. */
/*
滤镜名
*/
    open var name: String

/*
滤镜输入参数
*/ 
    /** Returns an array containing the names of all inputs in the filter. */
    open var inputKeys: [String] { get }

 /*
滤镜输出参数
*/  
    /** Returns an array containing the names of all outputs in the filter. */
    open var outputKeys: [String] { get }

 /*
*/  
    /** Sets all inputs to their default values (where default values are defined, other inputs are left as-is). */
    open func setDefaults()

/*
滤镜支持的关键字
*/   
    /** Returns a dictionary containing key/value pairs describing the filter. (see description of keys below) */
    open var attributes: [String : Any] { get }

CIFilter滤镜效果

目前系统API提供的滤镜效果分类分为21种,每种滤镜效果对照苹果官方文档,大概做了些翻译:

/* Categories */
/*失真效果,改变几何形状创建3D效果
比如bump、旋转、hole
*/
public let kCICategoryDistortionEffect: String
/*
扭曲图片和纠正源图像问题,例如仿射变换来校正相对于地平线旋转的图像
比如仿射变换、平切、透视转换
*/
public let kCICategoryGeometryAdjustment: String
/*
合成滤镜,操作两个图像源
比如源覆盖(source over)、最小化、源在顶(source atop)、色彩混合模式
*/
public let kCICategoryCompositeOperation: String
/*
半色调效果
比如screen、line screen、hatched
*/
public let kCICategoryHalftoneEffect: String
/*
色彩调整,用于消除色彩偏移、校正亮度和对比度
比如伽马调整、白点调整、曝光
*/
public let kCICategoryColorAdjustment: String
/*
色彩效果,我们一般用的比较多,类似美图工具的滤镜效果
比如色调调整、posterize
*/
public let kCICategoryColorEffect: String
/*
图像间转换
比如dissolve、disintegrate with mask、swipe
*/
public let kCICategoryTransition: String
/*
瓦片效果 平铺图片
比如parallelogram、triangle
*/
public let kCICategoryTileEffect: String
/*
图案的过滤器,如纯色、棋盘或星星的光泽。生成的输出通常用作对另一个过滤器的输入。
比如stripes、constant color、checkerboard
*/
public let kCICategoryGenerator: String
@available(iOS 5.0, *)
/*
减少图像数据 解决图像分析问题
*/
public let kCICategoryReduction: String
/*
渐变效果
比如轴向渐变、仿射渐变、高斯渐变
*/
public let kCICategoryGradient: String
/*
风格化
比如像素化、水晶化
*/
public let kCICategoryStylize: String
/*
锐化图像 锐化掩模和提高亮度
*/
public let kCICategorySharpen: String
/*
柔滑图像,主要用于模糊图像
比如高斯模糊、焦点模糊、运动模糊
*/
public let kCICategoryBlur: String
/*
处理视频图像
*/
public let kCICategoryVideo: String
/*
处理静态图像
*/
public let kCICategoryStillImage: String
/*
处理交错图像
*/
public let kCICategoryInterlaced: String
/*
处理非方形图像
*/
public let kCICategoryNonSquarePixels: String
/*
处理高动态图像
*/
public let kCICategoryHighDynamicRange: String
/*
用于区分built-in filters  plug-in filters.
*/
public let kCICategoryBuiltIn: String
@available(iOS 9.0, *)
/*
链接几个过滤器
*/
public let kCICategoryFilterGenerator: String

每个效果分类又有很多具体的子分类,苹果号称提供180种滤镜效果,你可以通过代码获取子分类名字,以及每个分类对应支持的键值,以kCICategoryDistortionEffect为例:

//获取kCICategoryDistortionEffect类型所有滤镜的名字和属性设置
        let filterArr = CIFilter.filterNames(inCategory: kCICategoryDistortionEffect)
        filterArr.forEach { (filterName) in
            let filter = CIFilter(name: filterName)
            let attributes = filter?.attributes
            
            print("------\nfilter name is:\n\(filterName) \nfilter attributes is :\n\(String(describing: attributes))\n\n")
        }

获取到的结果为:

------
filter name is:
CIBumpDistortion 
filter attributes is :
Optional(["CIAttributeFilterAvailable_iOS": 6, "CIAttributeFilterCategories": <__NSArrayI 0x1c44457f0>(
CICategoryDistortionEffect,
CICategoryVideo,
CICategoryStillImage,
CICategoryBuiltIn
)
, "CIAttributeFilterDisplayName": Bump Distortion, "inputImage": {
    CIAttributeClass = CIImage;
    CIAttributeDescription = "The image to use as an input image. For filters that also use a background image, this is the foreground image.";
    CIAttributeDisplayName = Image;
    CIAttributeType = CIAttributeTypeImage;
}, "inputScale": {
    CIAttributeClass = NSNumber;
    CIAttributeDefault = "0.5";
    CIAttributeDescription = "The scale of the effect determines the curvature of the bump. A value of 0.0 has no effect. Positive values create an outward bump; negative values create an inward bump.";
    CIAttributeDisplayName = Scale;
    CIAttributeIdentity = 0;
    CIAttributeSliderMax = 1;
    CIAttributeSliderMin = "-1";
    CIAttributeType = CIAttributeTypeScalar;
}, "CIAttributeFilterName": CIBumpDistortion, "CIAttributeFilterAvailable_Mac": 10.4, "inputRadius": {
    CIAttributeClass = NSNumber;
    CIAttributeDefault = 300;
    CIAttributeDescription = "The radius determines how many pixels are used to create the distortion. The larger the radius, the wider the extent of the distortion.";
    CIAttributeDisplayName = Radius;
    CIAttributeMin = 0;
    CIAttributeSliderMax = 600;
    CIAttributeSliderMin = 0;
    CIAttributeType = CIAttributeTypeDistance;
}, "CIAttributeReferenceDocumentation": http://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html#//apple_ref/doc/filter/ci/CIBumpDistortion, "inputCenter": {
    CIAttributeClass = CIVector;
    CIAttributeDefault = "[150 150]";
    CIAttributeDescription = "The center of the effect as x and y coordinates.";
    CIAttributeDisplayName = Center;
    CIAttributeType = CIAttributeTypePosition;
}])

------------------------
太多,没有完全列举,请自行打印
------------------------

CIFilter设置关键字

@available(iOS 5.0, *)
public let kCIOutputImageKey: String
@available(iOS 5.0, *)
public let kCIInputBackgroundImageKey: String
@available(iOS 5.0, *)
public let kCIInputImageKey: String
@available(iOS 11.0, *)
public let kCIInputDepthImageKey: String
@available(iOS 11.0, *)
public let kCIInputDisparityImageKey: String
@available(iOS 12.0, *)
public let kCIInputAmountKey: String
@available(iOS 7.0, *)
public let kCIInputTimeKey: String
@available(iOS 7.0, *)
public let kCIInputTransformKey: String
@available(iOS 7.0, *)
public let kCIInputScaleKey: String
@available(iOS 7.0, *)
public let kCIInputAspectRatioKey: String
@available(iOS 7.0, *)
public let kCIInputCenterKey: String
@available(iOS 7.0, *)
public let kCIInputRadiusKey: String
@available(iOS 7.0, *)
public let kCIInputAngleKey: String
@available(iOS 9.0, *)
public let kCIInputRefractionKey: String
@available(iOS 7.0, *)
public let kCIInputWidthKey: String
@available(iOS 7.0, *)
public let kCIInputSharpnessKey: String
@available(iOS 7.0, *)
public let kCIInputIntensityKey: String
@available(iOS 7.0, *)
public let kCIInputEVKey: String
@available(iOS 7.0, *)
public let kCIInputSaturationKey: String
@available(iOS 7.0, *)
public let kCIInputColorKey: String
@available(iOS 7.0, *)
public let kCIInputBrightnessKey: String
@available(iOS 7.0, *)
public let kCIInputContrastKey: String
@available(iOS 9.0, *)
public let kCIInputBiasKey: String
@available(iOS 9.0, *)
public let kCIInputWeightsKey: String
@available(iOS 9.0, *)
public let kCIInputGradientImageKey: String
@available(iOS 7.0, *)
public let kCIInputMaskImageKey: String
@available(iOS 12.0, *)
public let kCIInputMatteImageKey: String
@available(iOS 9.0, *)
public let kCIInputShadingImageKey: String
@available(iOS 7.0, *)
public let kCIInputTargetImageKey: String
@available(iOS 7.0, *)
public let kCIInputExtentKey: String
@available(iOS 6.0, *)
public let kCIInputVersionKey: String

CIFilter使用方法

func imageFilter(with image: UIImage, filterName: String, inputValue: [String: Any]) -> UIImage? {
    //将UIImage转换成CIImage,处理前的图片
    guard let input = CIImage(image:image) else {
        return nil
    }
    
    //通过名字创建CIFilter滤镜
    let filter = CIFilter(name: filterName, parameters: [kCIInputImageKey: input])
    
    // 用KVC给CIFilter设置滤镜参数,可直接在上面的初始化方法中加到parameters
    filter?.setValue(input, forKey: kCIInputImageKey)
    
    //渲染并输出CIImage,处理后的图片
    guard let output = filter?.outputImage else {
        return nil
    }
    
    //创建CIContext上下文
    let context = CIContext(options: nil)
    
    //初始化CGImageRef对象,创建输出CGImage,赋给UIImage对象
    //注意,我们使用输入图像大小的原因是,输出图像通常和输入图像具有不同的尺寸比。例如,一个模糊图像由于采样超出了输入图像的边缘,围绕在其边界外还会有一些额外的像素。
    guard let cgImage = context.createCGImage(output, from: input.extent) else {
        return nil
    }

    let uiImage = UIImage(cgImage: cgImage)
    
    
    return uiImage
}

当然,封装的方法你需要将KVC的关键字和值都通过外面传入,例如:

if let image = UIImage(named: "girl") {
        let inputValue = ["inputRadius": 10.0]
        let output = imageFilter(with: image, filterName: "CIColorMonochrome", inputValue: inputValue)
}

注意:
Context 创建的时候,我们可以给它设置为是基于 GPU 还是 CPU。
基于 GPU 的话,处理速度更快,因为利用了 GPU 硬件的并行优势。可以使用 OpenGLES 或者 Metal 来渲染图像,这种方式CPU完全没有负担,应用程序的运行循环不会受到图像渲染的影响。
但是 GPU 受限于硬件纹理尺寸,而且如果你的程序在后台继续处理和保存图片的话,那么需要使用 CPU,因为当 App 切换到后台状态时 GPU 处理会被打断。使用 CPU 渲染的 iOS 会采用 GCD 来对图像进行渲染,这保证了 CPU 渲染在大部分情况下更可靠,比 GPU 渲染更容易使用,可以在后台实现渲染过程。综上,对于复杂的图像滤镜使用 GPU 更好,但是如果在处理视频并保存文件,或保存照片到照片库中时,为避免程序进入后台对图片保存造成影响,这时应该使用 CPU 进行渲染。
用 Apple 官方的一句话来描述再合适不过了:
CPU is still what will give you the best fidelity where as the GPU will give you the best performance.

具体的设置方式,可以参考下面的例子:

// 创建基于 CPU 的 CIContext 对象 (默认是基于 GPU,CPU 需要额外设置参数)
context = [CIContext contextWithOptions: [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]];

// 创建基于 GPU 的 CIContext 对象
context = [CIContext contextWithOptions: nil];

// 创建基于 GPU 的 CIContext 对象
EAGLContext *eaglctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
context = [CIContext contextWithEAGLContext:eaglctx];

同样是基于 GPU 的,它们之间也是有区别的。
contextWithOptions 创建的 context 并没有实时性能, 虽然渲染是在 GPU 上执行,但是其输出的 image 是不能显示的,只有当其被复制回 CPU 存储器上时,才会被转成一个可被显示的 image 类型,比如 UIImage。当使用 Core Image 在 GPU 上渲染图片的时候,先是把图像传递到 GPU 上,然后执行滤镜相关操作。但是当需要生成 CGImage 对象的时候,图像又被复制回 CPU 上。最后要在视图上显示的时候,又返回 GPU 进行渲染。这样在 GPU 和 CPU 之前来回切换,会造成很严重的性能损耗。创建 context,那么它内部的渲染器会根据设备最优选择。依次为 Metal,OpenGLES,CoreGraphics。
它的渲染过程大致如下:


contextWithOptions.png

contextWithEAGLContext 创建的 context 支持实时渲染,渲染图像的过程始终在 GPU 上进行,并且永远不会复制回 CPU 存储器上,这就保证了更快的渲染速度和更好的性能。当然,这个前提是利用实时渲染的特效,而不是每次操作都产生一个 UIImage,然后再设置到视图上。
它的渲染过程大致如下:


contextWithEAGLContext.png

用 OpenGL 来提高性能

用 CPU 来绘制一个 CGImage 是非常耗时和浪费的,它只将结果回传给 UIKit 来做合成。我们更希望能够在屏幕上绘制应用滤镜后的图像,而不必去 Core Graphics 里绕一圈。
幸运的是,由于 OpenGL、和Metal等GPU渲染框架与Core Image 的可互操作性,我们可以这么做。
等后面介绍OpenGL、和Metal再详细讲解。

CIFilter滤镜链

是一个链接在一起的滤镜网络,使得一个滤镜的输出可以是另一个滤镜的输入。以这种方式,可以实现精心制作的效果。

自定义 CIFilter, Core Image 的可扩展性

iOS8 之后更是支持自定义 CIFilter,可以定制满足业务需求的复杂效果。
我会单独拿一章出来介绍自定义 CIFilter

滤镜图表

(本篇不是重点)
你可以构建一个滤镜图表原型查看效果:实例化我们需要的滤镜,设置它们的参数,把它们连接起来以便该图像数据按顺序传过每个滤镜。
一旦达到了我们满意的效果,我们可以重新在代码里还原滤镜图表效果。

两篇很好的文章,介绍了Quartz Composer构建滤镜图表原型

CIDetector

Core Image框架中提供的一个识别类,包括对人脸、形状、条码、文本的识别。

Demo

**Demo地址:后期会整理到一起发布,期待中

鸣谢

以下是参考文献

上一篇下一篇

猜你喜欢

热点阅读