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

简介: iOS8 Core Image In Swift:自动改善图像以及内置滤镜的使用iOS8 Core Image In Swift:更复杂的滤镜iOS8 Core Image In Swift:人脸检测以及马赛克iOS8 Core Image In Swift:视频实时滤镜Core Image是一个很强大的框架。

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

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

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

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


Core Image是一个很强大的框架。它可以让你简单地应用各种滤镜来处理图像,比如修改鲜艳程度, 色泽, 或者曝光。 它利用GPU(或者CPU)来非常快速、甚至实时地处理图像数据和视频的帧。并且隐藏了底层图形处理的所有细节,通过提供的API就能简单的使用了,无须关心OpenGL或者OpenGL ES是如何充分利用GPU的能力的,也不需要你知道GCD在其中发挥了怎样的作用,Core Image处理了全部的细节。 

Core Image框架给我们提供了这些东西:

  • 内置的图片滤镜
  • 特征检测能力(如人脸识别)
  • 支持自动改善图像
  • 能利用多个滤镜组合成一个自定义滤镜


自动改善图像

先上一个简单的例子。用Single View Application的工程模板建立一个工程,这个工程建好后只有一个AppDelegate和一个ViewController,另外还有一个Main.storyboard,在Main.storyboard里已经准备好一个ViewController了,我们在这个ViewController里放置一个UIImageView,调整其frame:


除此之外,因为UIImageView是默认拉伸图片的,我们不想让它变形,把它的ContentMode设置为Aspect Fit。顺便把Auto Layout和Size Classes关掉。

最后在拖两个按钮进来,一个用于显示原图,一个用于自动改善图像,整个ViewController看起来像这样:


接下来在ViewController中增加对应的IBAction方法以及IBOutlet属性:

class ViewController: UIViewController {

    @IBOutlet var imageView: UIImageView!

    

    lazy var originalImage: UIImage = {

        return UIImage(named: "Image")

    }()

......

一个连接到Storyboard的imageView,还有一个只懒加载一次的originalImage属性,这个属性在后面会用到很多次,这里有我 整个工程用到的image

然后在ViewDidLoad里像这样:

override func viewDidLoad() {

    super.viewDidLoad()

    self.imageView.layer.shadowOpacity = 0.8

    self.imageView.layer.shadowColor = UIColor.blackColor().CGColor

    self.imageView.layer.shadowOffset = CGSize(width: 1, height: 1)

    

    self.imageView.image = originalImage

}

只做了两件事:一是给imageView加上了阴影边框,只是为了好看;二是把originalImage赋值给imageView。

一行代码显示原图:

@IBAction func showOriginalImage() {

    self.imageView.image = originalImage

}

下面是自动改善的代码,我先贴出来,再慢慢说,有一个直观的效果会更有兴趣一些:

@IBAction func autoAdjust() {

    var inputImage = CIImage(image: originalImage)

    let filters = inputImage.autoAdjustmentFilters() as [CIFilter]

    for filter: CIFilter in filters {

        filter.setValue(inputImage, forKey: kCIInputImageKey)

        inputImage = filter.outputImage

    }

    self.imageView.image = UIImage(CIImage: inputImage)

}

把IBAction和IBOutlet连接对之后,运行起来应该就能看到以下效果了:


点击自动改善就能看到效果了,可能运行会有点慢,因为我们还没做优化,如果觉得改善效果不明显的话,可以多点击几次原图来对比一下。

虽然有些问题,但是已经不妨碍我们继续探索了。上面的自动改善代码显式地用到了两个类(我为什么在这里要用显式这个词?):CIImage和CIFilter,其中:

  • CIImage:这是一个模型对象,它保存能构建图像的数据,可以是图像的Data,可以是一个文件,也可以是CIFilter输出的对象。
  • CIFilter:滤镜,不同的CIFilter实例能表示不同的滤镜效果,不同的滤镜所能设置的参数也不尽相同,但它至少需要一个输入参数以及能生成一个输出对象。
另外Core Image的自动改善功能能智能的对图像的柱状图(histogram?)、人脸区域以及元数据进行分析,只需要传入一个Image作为输入参数,就能得到一组能使图像改善的滤镜。
CIImage的实例能通过UIImage来得到,然后通过两个API: autoAdjustmentFiltersautoAdjustmentFiltersWithOptions:取到能使图像得到改善的滤镜数组,在大多数情况下,你可能会用提供Option字典的那个API,因为你能设置:
  • 图片的方向,这对CIRedEyeCorrection、CIFaceBalance等滤镜来说格外重要,因为Core Image需要精确的对面部进行检测。
  • 是否只需要消除红眼(设置kCIImageAutoAdjustEnhance为false)。
  • 是否使用除了消除红眼以外的所有的滤镜(设置kCIImageAutoAdjustRedEye为false)。
如果你想提供Option字典,那么可以这样使用:
NSDictionary *options = @{ CIDetectorImageOrientation :
                 [[image properties] valueForKey:kCGImagePropertyOrientation] };
NSArray *adjustments = [myImage autoAdjustmentFiltersWithOptions:options];
在这个例子中,我就不传入Option字典了,因为不涉及图片方向的问题。
想知道自动改善功能用了哪些滤镜?只需要把filter对象打印出来即可,一般来说,会是这5个滤镜:
  • CIRedEyeCorrection:修复因相机的闪光灯导致的各种红眼
  • CIFaceBalance:调整肤色
  • CIVibrance:在不影响肤色的情况下,改善图像的饱和度
  • CIToneCurve:改善图像的对比度
  • CIHighlightShadowAdjust:改善阴影细节
大部分情况下这些滤镜就够用了。
之前也提到了,不同的CIFilter会有不同的参数,如果我们想知道CIFilter具体有哪些参数,可以调用它自己的 inputKeys方法,得到它支持的输入参数的列表,或者调用它的 outputKeys方法,得到它的输出参数的列表(一般我们只用outpuntImage就行了),又或者直接调用它的 attributes方法,得到它的所有信息,包括它的名字、所属的分类、输入参数、输出参数、各参数的取值范围以及默认值等。
调用CIFilter的inputKeys方法可以看到它的输入参数:

for filter: CIFilter in filters {

    let inputKeys = filter.inputKeys()

    print(filter.name())

    println(inputKeys)

...

打印结果:

CIFaceBalance[inputImage, inputOrigI, inputOrigQ, inputStrength, inputWarmth]
CIVibrance[inputImage, inputAmount]
CIToneCurve[inputImage, inputPoint0, inputPoint1, inputPoint2, inputPoint3, inputPoint4]
CIHighlightShadowAdjust[inputImage, inputRadius, inputShadowAmount, inputHighlightAmount]

几乎所有的滤镜都有inputImage这个输入参数,我们可以直接用系统预置的各种key来设置参数(如kCIInputImageKey),系统已经预置了大部分常用的key,如果你发现有的key系统没有预置,你可以直接使用获取的key名的字符串来作key,如:

filter.setValue(inputImage, forKey: kCIInputImageKey)

//两者设置方式完全一样

filter.setValue(inputImage, forKey: "inputImage") 

对自动改善图像功能来说,我们不需要知道太多的细节,设置inputImage就可以了。

接下来填坑。

上面的代码有两个问题:一是在每次使用自动改善的过程中感觉到明显的慢;二是图像在自动改善后变形了。原图和改善后的图像对比:


我把UIImageView的contentMode设置为Aspect Fit,就是不希望图片发生变形,而是按等比例缩放,如果把UIImageView的背景色设置为红色的话,在显示原图的时候可以看到上下均有红色,而改善之后的图片就没有红色。由于苹果表示UIImage是完全支持CIImage的,所以文档中并没有指出出现这个问题的原因,我参考了下面这个贴子:

http://stackoverflow.com/questions/15878060/setting-uiimageview-content-mode-after-applying-a-cifilter

上面有说通过UIImage(CIImage:)方法得到的UIImage并不是一个基于CGImage的标准UIImage,所以不能按一般的显示规则去理解,因此我们要换种方式去得到一个真正的UIImage,解决方法放在下面再说。

在之前介绍CIImage和CIFilter的时候我们用到显式这个词,因为在代码里可以直观的看到使用了这两个类,CIImage提供图像的信息,CIFilter提供滤镜,当我们调用CIFilter的outputImage方法时,此时Core Image并不会去渲染图像,而是通过计算各种参数,并把计算结果存储到CIImage对象中,只有当真正将要显示的时候,才会通过第三个对象去渲染。这个对象就是CIContext

CIContext是Core Image处理图像的关键,它和Core Graphics的CGContext类似但又与之不同,CIContext可以被重用,不必每次都新建一个,同时在输出CIImage的时候又必须有一个,简而言之,Core Image通过CIContext来渲染CIFilter产生的对象

在上面的例子中我们没有使用CIContext,但在调用UIImage(CIImage:)的时候Core Image隐式地在内部使用了CIContext,也就是把我们需要手动做的工作自动地完成了。但是这就有了一个问题,在每次调用UIImage(CIImage:)的时候它都会重新创建一个CIContext对象,这在用完即毁的情况下不会造成很大的影响,但在反复地使用滤镜的过程中就很影响性能了,为了防止这种情况,我们把CIContext对象重用起来,让ViewController持有一个懒加载的属性: 

lazy var context: CIContext = {

    return CIContext(options: nil)

}()

CIContext在初始化的时候需要一个字典,可以通过kCIContextUseSoftwareRenderer创建一个基于CPU的CIContext对象,默认是创建基于GPU的CIContext对象,不同之处在于GPU的CIContext对象处理起来会更快,而基于CPU的CIContext对象除了支持更大的图像以外,还能在后台处理。我们传nil创建基于GPU的CIContext对象就可以了。


有了可重用的CIContext对象,在创建UIImage的时候需要这样:  

@IBAction func autoAdjust() {

    var inputImage = CIImage(image: originalImage)

    let filters = inputImage.autoAdjustmentFilters() as [CIFilter]

    for filter: CIFilter in filters {

        filter.setValue(inputImage, forKey: kCIInputImageKey)

        inputImage = filter.outputImage

    }

    //self.imageView.image = UIImage(CIImage: inputImage)

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

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

}

虽然在第一次执行自动改善的时候会有点慢(因为要创建CIContext对象),但是在反复执行的情况下性能改善了很多,而且这样一来还解决了第二个问题,即ContentMode的问题。如果没有特殊情况,应该总是用这种方式创建一个CGImage,再把CGImage转成UIImage。


使用各种内置滤镜

通过CIFilter的类方法 filterNamesInCategory()可以得到属于某个类别下的所有滤镜:

func showFiltersInConsole() {

    let filterNames = CIFilter.filterNamesInCategory(kCICategoryBuiltIn)

    println(filterNames.count)

    println(filterNames)

    for filterName in filterNames {

        let filter = CIFilter(name: filterName as String)

        let attributes = filter.attributes()

        println(attributes)

    }

}

在这个方法里传入的类别参数是 kCICategoryBuiltIn,表示会输出iOS8 Core Image的所有滤镜:

共有127种。当然了,并不是所有的滤镜都是常用的,我们可以通过 kCICategoryColorEffect这个key取到一些常见的滤镜,就像iOS7相机应用里的一样,这些滤镜一般不需要设置什么参数(其中有些也可以设置不同的参数),只需要同上文一样,设置inputImage就行了。把这个类别下的滤镜打印一些出来看看:

我选中了其中一个滤镜,这是一个单色滤镜,虽然看上去内容很多,但并不复杂,CIAttributeFilterDisplayName是它的显示名,inputImage是参数,每个参数的详细信息通过一个字典来展示,其中包括了这个参数的类型(CIAttributeTypeImage),以及参数的Class(CIImage),之后是这个滤镜所属的类别,一个滤镜可以同时属于多个类别,最后是这个滤镜在实例化的时候需要提供的字符串,即CIPhotoEffectMono。
它上面的CIPhotoEffectInstant以及下面的CIPhotoEffectNoir、CIPhotoEffectProcess与之类似,都只需要一个inputImage参数,是不是很简单?这些滤镜的参数已经被内部设置好了,我们直接使用即可。
为了方便使用,我们给ViewController持有一个CIFilter属性,在其他的方法中实例化CIFilter,同时用一个公共的方法来显示打上滤镜后的图像:

class ViewController: UIViewController {

    @IBOutlet var imageView: UIImageView!

    

    lazy var originalImage: UIImage = {

        return UIImage(named: "Image")

    }()

    lazy var context: CIContext = {

        return CIContext(options: nil)

    }()

    var filter: CIFilter!

......

// MARK: - 怀旧

@IBAction func photoEffectInstant() {

    filter = CIFilter(name: "CIPhotoEffectInstant")

    outputImage()

}

// MARK: - 黑白

@IBAction func photoEffectNoir() {

    filter = CIFilter(name: "CIPhotoEffectNoir")

    outputImage()

}

// MARK: - 色调

@IBAction func photoEffectTonal() {

    filter = CIFilter(name: "CIPhotoEffectTonal")

    outputImage()

}

// MARK: - 岁月

@IBAction func photoEffectTransfer() {

    filter = CIFilter(name: "CIPhotoEffectTransfer")

    outputImage()

}

// MARK: - 单色

@IBAction func photoEffectMono() {

    filter = CIFilter(name: "CIPhotoEffectMono")

    outputImage()

}

// MARK: - 褪色

@IBAction func photoEffectFade() {

    filter = CIFilter(name: "CIPhotoEffectFade")

    outputImage()

}

// MARK: - 冲印

@IBAction func photoEffectProcess() {

    filter = CIFilter(name: "CIPhotoEffectProcess")

    outputImage()

}

// MARK: - 铬黄

@IBAction func photoEffectChrome() {

    filter = CIFilter(name: "CIPhotoEffectChrome")

    outputImage()

} 

func outputImage() {

    println(filter)

    let inputImage = CIImage(image: originalImage)

    filter.setValue(inputImage, forKey: kCIInputImageKey)

    let outputImage =  filter.outputImage

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

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

}

这些都写好后,在UI上把各种按钮及touch事件绑定好,UI看起来像这样:


运行后各种滤镜效果:


以上就是对Core Image内置滤镜的简单使用,如果你不需要对滤镜做更加细粒度控制的话,上述方法就够用了。


GitHub下载地址

我在GitHub上会保持更新。


UPDATED:

另外不知道有没有人注意到我在注释里用到了 // MARK: -,这和Objective-C的 #pragma mark -作用一样。


参考资料:

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

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
目录
相关文章
|
3月前
|
Swift iOS开发
iOS Swift使用Alamofire请求本地服务器报错-1002
iOS Swift使用Alamofire请求本地服务器报错-1002
89 1
|
4月前
|
Unix 调度 Swift
苹果iOS新手开发之Swift 中获取时间戳有哪些方式?
在Swift中获取时间戳有四种常见方式:1) 使用`Date`对象获取秒级或毫秒级时间戳;2) 通过`CFAbsoluteTimeGetCurrent`获取Core Foundation的秒数,需转换为Unix时间戳;3) 使用`DispatchTime.now()`获取纳秒级精度的调度时间点;4) `ProcessInfo`提供设备启动后的秒数,不表示绝对时间。不同方法适用于不同的精度和场景需求。
131 3
|
19小时前
|
安全 数据处理 Swift
深入探索iOS开发中的Swift语言特性
本文旨在为开发者提供对Swift语言在iOS平台开发的深度理解,涵盖从基础语法到高级特性的全面分析。通过具体案例和代码示例,揭示Swift如何简化编程过程、提高代码效率,并促进iOS应用的创新。文章不仅适合初学者作为入门指南,也适合有经验的开发者深化对Swift语言的认识。
15 9
|
14天前
|
安全 API Swift
探索iOS开发中的Swift语言之美
【10月更文挑战第23天】在数字时代的浪潮中,iOS开发如同一艘航船,而Swift语言则是推动这艘船前进的风帆。本文将带你领略Swift的独特魅力,从语法到设计哲学,再到实际应用案例,我们将一步步深入这个现代编程语言的世界。你将发现,Swift不仅仅是一种编程语言,它是苹果生态系统中的一个创新工具,它让iOS开发变得更加高效、安全和有趣。让我们一起启航,探索Swift的奥秘,感受编程的乐趣。
|
1月前
|
安全 Swift iOS开发
探索iOS开发中的Swift语言之美
在数字时代的浪潮中,移动应用已成为日常生活的延伸。本文将深入探讨iOS平台上的Swift编程语言,揭示其背后的设计哲学、语法特性以及如何利用Swift进行高效开发。我们将通过实际代码示例,展示Swift语言的强大功能和优雅简洁的编程风格,引导读者理解并运用Swift解决实际问题。
|
2月前
|
安全 Swift iOS开发
探索iOS开发之旅:Swift语言的魅力与挑战
【9月更文挑战第21天】在这篇文章中,我们将一起潜入iOS开发的海洋,探索Swift这门现代编程语言的独特之处。从简洁的语法到强大的功能,Swift旨在让开发者能够以更高效、更安全的方式构建应用程序。通过实际代码示例,我们会深入了解Swift如何简化复杂任务,并讨论它面临的挑战和未来的发展方向。无论你是初学者还是有经验的开发者,这篇文章都将为你提供新的视角和知识。
44 4
|
2月前
|
安全 编译器 Swift
探索iOS开发之旅:Swift编程语言的魅力与挑战
【9月更文挑战第5天】在iOS应用开发的广阔天地中,Swift作为苹果官方推荐的编程语言,以其简洁、高效和安全的特点,成为了开发者的新宠。本文将带领你领略Swift语言的独特魅力,同时探讨在实际开发过程中可能遇到的挑战,以及如何克服这些挑战,成为一名优秀的iOS开发者。
|
2月前
|
设计模式 前端开发 Swift
探索iOS开发:Swift与Objective-C的较量
在这篇文章中,我们将深入探讨iOS开发的两大编程语言——Swift与Objective-C。我们将分析这两种语言的特性、优势和局限性,并讨论它们在现代iOS开发中的应用。无论你是初学者还是有经验的开发者,这篇文章都将为你提供有价值的见解和建议。
55 3
|
3月前
|
测试技术 Swift iOS开发
探索iOS自动化测试:使用Swift编写UI测试
【8月更文挑战第31天】在软件开发的海洋中,自动化测试是保证船只不偏离航线的灯塔。本文将带领读者启航,深入探索iOS应用的自动化UI测试。我们将通过Swift语言,点亮代码的灯塔,照亮测试的道路。文章不仅会展示如何搭建测试环境,还会提供实用的代码示例,让理论知识在实践中生根发芽。无论你是新手还是有经验的开发者,这篇文章都将是你技能提升之旅的宝贵指南。
|
3月前
|
移动开发 安全 Swift
探索iOS开发:从零开始的Swift之旅
【8月更文挑战第31天】本文将带你开启一段Swift编程语言的奇幻旅程,通过简单易懂的方式介绍Swift的基本概念和编程实践。我们将一起构建一个简单的iOS应用,体验从代码到界面的转变。无论你是编程新手还是希望扩展技能的开发者,这篇文章都会为你提供宝贵的知识和启发。