Core Image框架详细解析(二十) —— 基于Core Image 和 Swift 学习图像过滤效果(一)

版本记录

版本号 时间
V1.0 2022.05.29 星期日

前言

Core Image是IOS5中新加入的一个框架,里面提供了强大高效的图像处理功能,用来对基于像素的图像进行操作与分析。还提供了很多强大的滤镜,可以实现你想要的效果,下面我们就一起解析一下这个框架。感兴趣的可以参考上面几篇。
1. Core Image框架详细解析(一) —— 基本概览
2. Core Image框架详细解析(二) —— Core Image滤波器参考
3. Core Image框架详细解析(三) —— 关于Core Image
4. Core Image框架详细解析(四) —— Processing Images处理图像(一)
5. Core Image框架详细解析(五) —— Processing Images处理图像(二)
6. Core Image框架详细解析(六) —— 图像中的面部识别Detecting Faces in an Image(一)
7. Core Image框架详细解析(七) —— 自动增强图像 Auto Enhancing Images
8. Core Image框架详细解析(八) —— 查询系统中的过滤器 Querying the System for Filters
9. Core Image框架详细解析(九) —— 子类化CIFilter:自定义效果的配方 Subclassing CIFilter: Recipes for Custom Effects(一)
10. Core Image框架详细解析(十) —— 子类化CIFilter:自定义效果的配方 Subclassing CIFilter: Recipes for Custom Effects(二)
11. Core Image框架详细解析(十一) —— 获得最佳性能 Getting the Best Performance
12. Core Image框架详细解析(十二) —— 使用反馈处理图像 Using Feedback to Process Images
13. Core Image框架详细解析(十三) —— 在写一个自定义滤波器之前你需要知道什么?
14. Core Image框架详细解析(十四) —— 创建自定义滤波器 Creating Custom Filters(一)
15. Core Image框架详细解析(十五) —— 创建自定义滤波器 Creating Custom Filters(二)
16. Core Image框架详细解析(十六) —— 包装和加载图像单元 Packaging and Loading Image Units
17. Core Image框架详细解析(十七) —— 一个简单说明和示例(一)
18. Core Image框架详细解析(十八) —— 使用Metal Shading Language创建你自己的Core Image滤波器进行像素级图像处理(一)
19. Core Image框架详细解析(十九) —— 使用Metal Shading Language创建你自己的Core Image滤波器进行像素级图像处理(二)

开始

首先看下主要内容:

使用 Core ImageSwift 学习图像过滤效果的基础知识。内容来自翻译

接着看下写作环境

Swift 5.5, iOS 15, Xcode 13

下面就是正文了

Core Image 是一个强大的框架,可让您将过滤器应用于图像。 它提供各种效果,例如修改活力、色调或曝光(vibrancy, hue or exposure)。 它可以使用 CPUGPU 快速处理图像数据——速度足以实时处理视频帧!

您可以将 Core Image 过滤器链接在一起,以一次将许多效果应用于图像或视频帧。 许多过滤器组合成一个过滤器并应用于图像。 与通过每个过滤器处理图像相比,这使得它非常有效,一次一个。

在本教程中,您将获得使用 Core Image 的实践经验。 您将应用一些不同的过滤器,并了解实时对图像应用炫酷效果是多么容易。

在开始之前,先看看 Core Image 框架中一些最重要的类:

  • CIContextCIContext 完成core image的所有处理。 这有点像 Core GraphicsOpenGL 上下文。
  • CIImage。 此类保存图像数据。 一个 UIImage、一个图像文件或像素数据都可以创建它。
  • CIFilterCIFilter 类有一个字典。 这定义了它所代表的特定过滤器的属性。 滤波器的示例包括活力、颜色反转、裁剪等等(vibrancy, color inversion, cropping)

您将在此项目中使用这些类中的每一个。

1. CoreImageFun

打开CoreImageFun.xcodeproj 并运行它。 这是一个简单的应用程序,一个带有图像和滑块的单一屏幕。 滑块还没有做任何事情,但我们将使用它来展示 CIFilter 的功能。 您还会注意到屏幕右上角的相机按钮。 您将在本教程后面使用它来调出图像选择器。


Image-Filtering Basics

您将首先通过 CIFilter 运行图像并将其显示在屏幕上。每次你想对图像应用 CIFilter 时,你需要做四件事:

  • 1) 创建一个 CIImage 对象。一个 CIImage 有几个初始化方法。在本教程中,您将使用 CIImage(image:)UIImage 创建 CIImage。浏览文档documentation以了解更多创建 CIImage 的方法。
  • 2) 创建一个 CIContextCIContext 可以是基于 CPUGPU 的。 CIContext的初始化成本很高,因此您可以重用它而不是一遍又一遍地创建它。输出 CIImage 对象时总是需要一个。
  • 3) 创建一个 CIFilter。创建过滤器时,您会在其上配置一些属性,这些属性取决于您使用的过滤器。
  • 4) 获取过滤器输出。过滤器为您提供作为 CIImage 的输出图像。您可以使用 CIContext 将其转换为 UIImage,如下所示。

1. Applying Filter

在理论信息之后,是时候看看它是如何工作的了。将以下代码添加到 ViewController.swift

func applySepiaFilter(intensity: Float) {
  // 1
  guard let uiImage = UIImage(named: "image") else { return }
  let ciImage = CIImage(image: uiImage)

  // 2
  guard let filter = CIFilter(name: "CISepiaTone") else { return }

  // 3
  filter.setValue(ciImage, forKey: kCIInputImageKey)
  filter.setValue(intensity, forKey: kCIInputIntensityKey)

  // 4
  guard let outputImage = filter.outputImage else { return }

  // 5
  let newImage = UIImage(ciImage: outputImage)
  imageView.image = newImage
}

这是代码的作用。 它:

  • 1) 创建一个 UIImage 并使用它来创建一个 CIImage
  • 2) 创建一个 CISepiTone 类型的 CIFilter。 这是棕褐色调的类型。
  • 3) CISepiaTone 过滤器有两个值。 首先是一个输入图像:kCIInputImageKey,它是一个 CIImage 实例。 其次,一个intensity:kCIInputIntensityKey,一个介于 0 和 1 之间的浮点值。如果没有任何值,大多数过滤器都使用它们的默认值。 CIImage 是一个例外。 这必须提供一个值,因为没有默认值。
  • 4) 使用 outputImage 属性从过滤器中获取一个 CIImage
  • 5) 将 CIImage 转回 UIImage 并将其显示在image view中。

接下来,通过将以下内容添加到 viewDidLoad()来调用添加的新方法:

applySepiaFilter(intensity: 0.5)

这会触发强度值为 0.5 的图像过滤。 在本教程的后面部分,您将使用滑块来尝试各种强度值。

构建并运行项目。 你会看到你的图像被sepia tone filter过滤:

恭喜,你已经很好地使用了 CIImageCIFilters


Putting it Into Context

在继续之前,您应该了解一项优化。

如上所述,您需要一个 CIContext 来应用 CIFilter。 但是在上面的例子中没有提到这个对象。 事实证明 UIImage(CIImage:) 为您完成了所有工作。 它创建一个 CIContext并使用它来过滤图像。 这使得 Core Image API 很容易使用。

有一个主要缺点:每次使用它都会创建一个新的 CIContextCIContext实例应该可重用以提高性能。 如果要使用滑块更新过滤器值,则每次更改过滤器时都必须创建一个新的 CIContext。 这种方法会很慢。

首先,将以下属性添加到 ViewController

let context = CIContext(options: nil)

CIContext 接受一个可选字典。 它指定诸如颜色格式或上下文是否应该在 CPU 或 GPU 上运行等选项。 对于这个应用程序,默认值很好,所以你为那个参数传入 nil

接下来,从 applySepiaFilter(intensity:)中删除 Step 5 并将其替换为以下内容:

guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return }
imageView.image = UIImage(cgImage: cgImage)

在这里,您使用 CIContext 绘制一个 CGImage 并使用它来创建一个 UIImage 以显示在image view中。

构建并运行。 确保它像以前一样工作。

在这个例子中,自己处理 CIContext 创建没有太大区别。您将了解为什么这样做对性能很重要,因为您在下一节中设置了更改过滤器的能力。


Changing Filter Values

这很棒,但这只是你可以使用 Core Image 滤镜做的事情的开始。是时候使用图像下方那个漂亮的滑块来改变滤镜效果了。

您已经为 CIContext 实例添加了一个属性。现在,您将添加一个属性来保存过滤器。

已经有一个 IBAction 连接到滑块的 Value Changed 操作。它被称为 sliderValueChanged(_:)。在此方法中,您将在滑块值更改时重做图像过滤器。但是你不想重做整个过程。那将是非常低效的,并且会花费太长时间。您需要更改类中的一些内容,因此您需要保留在 applySepiaFilter(intensity:) 中创建的一些对象。

context声明的正下方添加以下属性:

let filter = CIFilter(name: "CISepiaTone")!

接下来,在调用 applySepiaFilter(intensity:)之前将以下内容添加到 viewDidLoad() 中:

guard let uiImage = UIImage(named: "image") else { return }
let ciImage = CIImage(image: uiImage)
filter.setValue(ciImage, forKey: kCIInputImageKey)

在这里,您将图像设置给滤波器。 你在 applySepiaFilter(intensity:)中的前面这个。 但最好将其移动到 viewDidLoad() 以防止对每个滑块值更改的调用。

您将一些代码移至 viewDidLoad(),因此将 applySepiaFilter(intensity:)替换为以下内容:

func applySepiaFilter(intensity: Float) {
  filter.setValue(intensity, forKey: kCIInputIntensityKey)

  guard let outputImage = filter.outputImage else { return }

  guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return }
  imageView.image = UIImage(cgImage: cgImage)
}

最后,将以下内容添加到 sliderValueChanged(_:)

applySepiaFilter(intensity: slider.value)

当滑块值改变时,applySepiaFilter(intensity:) 将以新的强度值运行。

您的滑块设置为默认值:最小 0,最大 1,默认 0.5。 多么方便! 这些恰好是这个 CIFilter 的正确值。

构建并运行。 您应该有一个功能正常的实时滑块,可以实时更改图像的sepia值。


Getting Photos From the Photo Album

现在您可以立即更改过滤器的值,事情变得有趣了! 但是,如果您不喜欢这种花的形象怎么办? 接下来,您将设置一个 UIImagePickerController 以从相册中获取图片并进入您的应用程序,以便您可以使用它们。

已经有一个 IBAction 连接到相机按钮的 Touch Up Inside 动作。 它被称为 loadPhoto()。 将以下代码添加到 loadPhoto()

let picker = UIImagePickerController()
picker.delegate = self
present(picker, animated: true)

第一行代码实例化了一个新的 UIImagePickerController。 将图像选择器的代理设置为 self(ViewController),然后展示选择器。

这里有一个编译器错误。 您需要声明 ViewController 符合 UIImagePickerControllerDelegateUINavigationControllerDelegate 协议。

ViewController.swift 的底部添加以下扩展:

extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
  func imagePickerController(
    _ picker: UIImagePickerController,
    didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
  }
}

此代理方法返回所选图像以及info字典中的一些相关信息。 有关此字典中各种数据的更多详细信息,请查看documentation

构建并运行该应用程序,然后点击按钮,该按钮将显示图片选择器,其中包含您相册中的照片。

选择图像什么都不做。 你即将改变这一点。 将以下代码添加到 imagePickerController(_:didFinishPickingMediaWithInfo:)

//1
guard let selectedImage = info[.originalImage] as? UIImage else { return }

//2
let ciImage = CIImage(image: selectedImage)
filter.setValue(ciImage, forKey: kCIInputImageKey)

//3
applySepiaFilter(intensity: slider.value)

//4
dismiss(animated: true)

这是一个代码分解。 它:

  • 1) 使用 originalImage UIImagePickerController.InfoKey 键检索选择图像。
  • 2) 将所选图像应用到滤镜。
  • 3) 使用强度的当前滑块值调用 applySepiaFilter(intensity:)。 这将更新image view
  • 4) 完成对所选图像的过滤后关闭图像选择器。

构建并运行。 现在,您将能够更新相册中的任何图像。


What About Image Metadata?

当然,是时候谈谈图像元数据了。 在手机上拍摄的图像文件具有与之相关的各种数据,例如 GPS 坐标、图像格式和方向。

尤其是方向是您需要保留的东西。 将 UIImage 加载到 CIImage 中,渲染到 CGImage 并转换回 UIImage 会从图像中剥离元数据。 为了保留方向,你需要记录它,然后将它传回 UIImage

首先向 ViewController.swift 添加一个新属性:

var orientation = UIImage.Orientation.up

接下来,在调用 applySepiaFilter(intensity:) 之前,将以下行添加到 imagePickerController(_:didFinishPickingMediaWithInfo:)

orientation = selectedImage.imageOrientation

这会将选定的图像方向保存到属性中。

最后,更改 imageView 对象中设置的 applySepiaFilter(intensity:) 中的行:

imageView.image = UIImage(cgImage: cgImage, scale: 1, orientation: orientation)

现在,如果您以非默认方向拍摄照片,应用程序会保留它。


What Other Filters Are Available?

CIFilter 最大的优势之一是链接过滤器的能力。 为此,您将创建一个专用方法来处理 CIImage 并对其进行过滤,使其看起来像一张旧照片。

CIFilter APImacOS 上有 160 多个过滤器,其中大部分在 iOS 上也可用。 现在也可以创建自定义过滤器。

要查看任何可用的过滤器或属性,请查看documentation

1. Creating Old Photo Filter

ViewController 中添加以下方法:

func applyOldPhotoFilter(intensity: Float) {
  // 1
  filter.setValue(intensity, forKey: kCIInputIntensityKey)

  // 2
  let random = CIFilter(name: "CIRandomGenerator")

  // 3
  let lighten = CIFilter(name: "CIColorControls")
  lighten?.setValue(random?.outputImage, forKey: kCIInputImageKey)
  lighten?.setValue(1 - intensity, forKey: kCIInputBrightnessKey)
  lighten?.setValue(0, forKey: kCIInputSaturationKey)

  // 4
  guard let ciImage = filter.value(forKey: kCIInputImageKey) as? CIImage else { return }
  let croppedImage = lighten?.outputImage?.cropped(to: ciImage.extent)

  // 5
  let composite = CIFilter(name: "CIHardLightBlendMode")
  composite?.setValue(filter.outputImage, forKey: kCIInputImageKey)
  composite?.setValue(croppedImage, forKey: kCIInputBackgroundImageKey)

  // 6
  let vignette = CIFilter(name: "CIVignette")
  vignette?.setValue(composite?.outputImage, forKey: kCIInputImageKey)
  vignette?.setValue(intensity * 2, forKey: kCIInputIntensityKey)
  vignette?.setValue(intensity * 30, forKey: kCIInputRadiusKey)

  // 7
  guard let outputImage = vignette?.outputImage else { return }
  guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { return }
  imageView.image = UIImage(cgImage: cgImage, scale: 1, orientation: orientation)
}

这是正在做的事情 :

  • 1) 在您之前使用的sepia-tone中设置强度。
  • 2) 建立一个过滤器,创建一个如下所示的随机噪声模式:

它不带任何参数。您将使用此噪点模式为最终的“旧照片”外观添加纹理。

  • 3) 改变随机噪声发生器的输出。您想将其更改为灰度并使其变亮一点,因此效果不那么引人注目。输入图像键设置为随机过滤器的 outputImage 属性。这是一种将一个过滤器的输出作为下一个过滤器的输入传递的便捷方式。

  • 4) 已cropped(to:)获取输出 CIImage 并将其裁剪为提供的矩形。在这种情况下,您需要裁剪 CIRandomGenerator 过滤器的输出,因为它会一直持续下去。如果你在某个时候不裁剪它,你会得到一个错误,说过滤器有“an infinite extent”CIImages 实际上并不包含图像数据;他们描述了创建它的“配方”。直到您调用 CIContext 上的方法才处理数据。

  • 5) 结合sepiaCIRandomGenerator 过滤器的输出。后者执行与 Adobe Photoshop 图层中的“Hard Light”设置相同的操作。 Photoshop 中的大多数(如果不是全部)滤镜选项都可以使用 Core Image 实现。

  • 6) 在此合成输出上运行vignette滤镜,使照片的边缘变暗。您可以使用强度值来设置此效果的半径和强度。

  • 7) 获取输出图像并将其设置给image view

2. Applying Old Photo Filter

这就是这个过滤器链的全部内容。 您现在已经了解这些过滤器链可能变得多么复杂。 您可以将 Core Image 滤镜组合到这些类型的链中,这样您就可以实现无穷无尽的各种效果。

如果您想查看所有这些操作,请将所有对 applySepiaFilter(intensity:) 的调用替换为 applyOldPhotoFilter(intensity:)

构建并运行。 您应该获得更精致的旧照片效果,包括棕褐色、少许噪点和一些暗角。

亲爱的读者,这种噪音可能会更微妙,但如何完善取决于您。 现在,您可以使用 Core Image 的全部功能。 疯了!

这大约涵盖了使用 Core Image 过滤器的基础知识。 这是一种非常方便的技术,您应该能够使用它快速地对图像应用一些简洁的过滤器。

此外,此站点上还有更多 Core Image 教程,包括:

您可以查看 Core Image filter reference documentation以获取有关所有过滤器的说明。

后记

本篇主要讲述了使用 Core ImageSwift 学习图像过滤效果的基础知识,感兴趣的给个赞或者关注~~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,198评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,663评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,985评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,673评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,994评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,399评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,717评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,407评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,112评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,371评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,891评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,255评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,881评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,010评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,764评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,412评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,299评论 2 260

推荐阅读更多精彩内容