鍍金池/ 教程/ iOS/ Core Image 介紹
與四軸無(wú)人機(jī)的通訊
在沙盒中編寫(xiě)腳本
結(jié)構(gòu)體和值類(lèi)型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測(cè)試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動(dòng)開(kāi)發(fā)
Collection View 動(dòng)畫(huà)
截圖測(cè)試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個(gè)完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動(dòng)布局工具箱
動(dòng)畫(huà)
為 iOS 7 重新設(shè)計(jì) App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類(lèi)
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動(dòng)畫(huà)解釋
響應(yīng)式 Android 應(yīng)用
初識(shí) TextKit
客戶(hù)端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項(xiàng)目介紹
Swift 的強(qiáng)大之處
測(cè)試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計(jì)優(yōu)雅的移動(dòng)游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動(dòng)畫(huà)
常見(jiàn)的后臺(tái)實(shí)踐
糟糕的測(cè)試
避免濫用單例
數(shù)據(jù)模型和模型對(duì)象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場(chǎng)
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類(lèi)
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計(jì)的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類(lèi)的設(shè)計(jì)
置換測(cè)試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無(wú)人機(jī)項(xiàng)目
Mach-O 可執(zhí)行文件
UI 測(cè)試
值對(duì)象
活動(dòng)追蹤
依賴(lài)注入
Swift
項(xiàng)目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測(cè)試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場(chǎng)
游戲
調(diào)試核對(duì)清單
View Controller 容器
學(xué)無(wú)止境
XCTest 測(cè)試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動(dòng)畫(huà)
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲(chǔ)
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺(jué)
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫(kù)支持
Fetch 請(qǐng)求
導(dǎo)入大數(shù)據(jù)集
iOS 開(kāi)發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語(yǔ)言標(biāo)簽
同步案例學(xué)習(xí)
依賴(lài)注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識(shí)別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過(guò)程

Core Image 介紹

這篇文章會(huì)為初學(xué)者介紹一下 Core Image,一個(gè) OS X 和 iOS 的圖像處理框架。

如果你想跟著本文中的代碼學(xué)習(xí),你可以在 GitHub 上下載示例工程。示例工程是一個(gè) iOS 應(yīng)用程序,列出了系統(tǒng)提供的大量圖像濾鏡以供選擇,并提供了一個(gè)用戶(hù)界面用來(lái)調(diào)整參數(shù)并觀察效果。

雖然示例代碼是用 Swift 寫(xiě)的 iOS 程序,不過(guò)實(shí)現(xiàn)概念很容易轉(zhuǎn)換到 Objective-C 和 OS X.

基本概念

說(shuō)到 Core Image,我們首先需要介紹幾個(gè)基本的概念。

一個(gè)濾鏡是一個(gè)對(duì)象,有很多輸入和輸出,并執(zhí)行一些變換。例如,模糊濾鏡可能需要輸入圖像和一個(gè)模糊半徑來(lái)產(chǎn)生適當(dāng)?shù)哪:蟮妮敵鰣D像。

一個(gè)濾鏡圖表是一個(gè)鏈接在一起的濾鏡網(wǎng)絡(luò) (無(wú)回路有向圖),使得一個(gè)濾鏡的輸出可以是另一個(gè)濾鏡的輸入。以這種方式,可以實(shí)現(xiàn)精心制作的效果。我們將在下面看到如何連接濾鏡來(lái)創(chuàng)建一個(gè)復(fù)古的拍照效果。

熟悉 Core Image API

有了上述的這些概念,我們可以開(kāi)始探索 Core Image 的圖像濾鏡細(xì)節(jié)了。

Core Image 架構(gòu)

Core Image 有一個(gè)插件架構(gòu),這意味著它允許用戶(hù)編寫(xiě)自定義的濾鏡并與系統(tǒng)提供的濾鏡集成來(lái)擴(kuò)展其功能。我們?cè)谶@篇文章中不會(huì)用到 Core Image 的可擴(kuò)展性;我提到它只是因?yàn)樗绊懙搅丝蚣艿?API。

Core Image 是用來(lái)最大化利用其所運(yùn)行之上的硬件的。每個(gè)濾鏡實(shí)際上的實(shí)現(xiàn),即內(nèi)核,是由一個(gè) GLSL (即 OpenGL 的著色語(yǔ)言) 的子集來(lái)書(shū)寫(xiě)的。當(dāng)多個(gè)濾鏡連接成一個(gè)濾鏡圖表,Core Image 便把內(nèi)核串在一起來(lái)構(gòu)建一個(gè)可在 GPU 上運(yùn)行的高效程序。

只要有可能,Core Image 都會(huì)把工作延遲。通常情況下,直到濾鏡圖表的最后一個(gè)濾鏡的輸出被請(qǐng)求之前都不會(huì)發(fā)生分配或處理。

為了完成工作,Core Image 需要一個(gè)稱(chēng)為上下文 (context) 的對(duì)象。這個(gè)上下文是框架真正工作的地方,它需要分配必要的內(nèi)存,并編譯和運(yùn)行濾鏡內(nèi)核來(lái)執(zhí)行圖像處理。建立一個(gè)上下文是非常昂貴的,所以你會(huì)經(jīng)常想創(chuàng)建一個(gè)反復(fù)使用的上下文。接下來(lái)我們將看到如何創(chuàng)建一個(gè)上下文。

查詢(xún)可用的濾鏡

Core Image 濾鏡是按名字創(chuàng)建的。要獲得系統(tǒng)濾鏡的列表,我們要向 Core Image 的 kCICategoryBuiltIn 類(lèi)別請(qǐng)求得到濾鏡的名字:

let filterNames = CIFilter.filterNamesInCategory(kCICategoryBuiltIn) as [String]

iOS 上可用的濾鏡列表非常接近于 OS X 上可用濾鏡的一個(gè)子集。在 OS X 上有 169 個(gè)內(nèi)置濾鏡,在 iOS 上有 127 個(gè)。

通過(guò)名字創(chuàng)建一個(gè)濾鏡

現(xiàn)在,我們有了可用濾鏡的列表,我們就可以創(chuàng)建和使用濾鏡了。例如,要?jiǎng)?chuàng)建一個(gè)高斯模糊濾鏡,我們傳給 CIFilter 初始化方法相應(yīng)的名稱(chēng)就可以了:

let blurFilter = CIFilter(named:"CIGaussianBlur")

設(shè)置濾鏡參數(shù)

由于 Core Image 的插件結(jié)構(gòu),大多數(shù)濾鏡屬性并不是直接設(shè)置的,而是通過(guò)鍵值編碼(KVC)設(shè)置。例如,要設(shè)置模糊濾鏡的模糊半徑,我們使用 KVC 來(lái)設(shè)置 inputRadius 屬性:

blurFilter.setValue(10.0 forKey:"inputRadius")

由于這種方法需要 AnyObject? (即 Objective-C 里的 id)作為其參數(shù)值,它不是類(lèi)型安全的。因此,設(shè)置濾鏡參數(shù)需要謹(jǐn)慎一些,確保你傳值的類(lèi)型是正確的。

查詢(xún)?yōu)V鏡屬性

為了知道一個(gè)濾鏡提供什么樣的輸入和輸出參數(shù),我們就可以分別獲取 inputKeysoutputKeys 數(shù)組。它們都返回 NSString 的數(shù)組。

要獲取每個(gè)參數(shù)的詳細(xì)信息,我們可以看看由濾鏡提供的 attributes 字典。每個(gè)輸入和輸出參數(shù)名映射到它自己的字典里,描述了它是什么樣的參數(shù),如果有的話還會(huì)給出它的最大值和最小值。例如,下面是 CIColorControls 濾鏡對(duì)應(yīng)的 inputBrightness 參數(shù)字典:

inputBrightness = {
    CIAttributeClass = NSNumber;
    CIAttributeDefault = 0;
    CIAttributeIdentity = 0;
    CIAttributeMin = -1;
    CIAttributeSliderMax = 1;
    CIAttributeSliderMin = -1;
    CIAttributeType = CIAttributeTypeScalar;
};

對(duì)于數(shù)值參數(shù),該字典會(huì)包含 kCIAttributeSliderMinkCIAttributeSliderMax 鍵,來(lái)限制期望的輸入域。大多數(shù)參數(shù)還包含一個(gè) kCIAttributeDefault 關(guān)鍵字,映射到該參數(shù)的默認(rèn)值。

圖片濾鏡實(shí)戰(zhàn)

圖像濾鏡的工作由三部分組成:構(gòu)建和配置濾鏡圖表,發(fā)送等待濾鏡處理的圖像,得到濾鏡處理后的圖像。下面的部分對(duì)此進(jìn)行了詳細(xì)描述。

構(gòu)建一個(gè)濾鏡圖表

構(gòu)建一個(gè)濾鏡圖表由這幾個(gè)部分組成:實(shí)例化我們需要的濾鏡,設(shè)置它們的參數(shù),把它們連接起來(lái)以便該圖像數(shù)據(jù)按順序傳過(guò)每個(gè)濾鏡。

在本節(jié)中,我們將創(chuàng)建一個(gè)用來(lái)制作 19 世紀(jì)錫版照風(fēng)格圖像的濾鏡圖表。我們將兩個(gè)效果鏈在一起來(lái)達(dá)到這種效果:同時(shí)去飽和以及染色調(diào)的黑白濾鏡,和一個(gè)暗角濾鏡來(lái)創(chuàng)建一個(gè)有陰影效果的加框圖片。

用 Quartz Composer,來(lái)做 Core Image 濾鏡圖表的原型非常有用,可以從蘋(píng)果開(kāi)發(fā)者網(wǎng)站下載。下面,我們整理了所需的照片濾鏡,把黑白濾鏡和暗角濾鏡串在一起:

http://wiki.jikexueyuan.com/project/objc/images/21-22.png" alt="" />

一旦達(dá)到了我們滿(mǎn)意的效果,我們可以重新在代碼里創(chuàng)建濾鏡圖表:

let sepiaColor = CIColor(red: 0.76, green: 0.65, blue: 0.54)
let monochromeFilter = CIFilter(name: "CIColorMonochrome",
    withInputParameters: ["inputColor" : sepiaColor, "inputIntensity" : 1.0])
monochromeFilter.setValue(inputImage, forKey: "inputImage")

let vignetteFilter = CIFilter(name: "CIVignette",
    withInputParameters: ["inputRadius" : 1.75, "inputIntensity" : 1.0])
vignetteFilter.setValue(monochromeFilter.outputImage, forKey: "inputImage")

let outputImage = vignetteFilter.outputImage

需要注意的是黑白濾鏡的輸出圖像變?yōu)榘到菫V鏡的輸入圖像。這將導(dǎo)致暗角效果要應(yīng)用到黑白圖像上。還要注意的是,我們可以在初始化中指定參數(shù),而不一定需要用 KVC 單獨(dú)設(shè)置它們。

創(chuàng)建輸入圖像

Core Image 濾鏡要求其輸入圖像是 CIImage 類(lèi)型。而對(duì)于 iOS 的程序員來(lái)說(shuō)這可能會(huì)有一點(diǎn)不尋常,因?yàn)樗麄兏?xí)慣用 UIImage,但這個(gè)區(qū)別是值得的。一個(gè) CIImage 實(shí)例實(shí)際上比 UIImage 更全面,因?yàn)?CIImage 可以無(wú)限大。當(dāng)然,我們不能存儲(chǔ)無(wú)限的圖像在內(nèi)存中,但在概念上,這意味著你可以從 2D 平面上的任意區(qū)域獲取圖像數(shù)據(jù),并得到一個(gè)有意義的結(jié)果。

所有我們?cè)诒疚闹惺褂玫膱D像都是有限的,而且也可以很容易從一個(gè) UIImage 來(lái)創(chuàng)建一個(gè) CIImage。事實(shí)上,這只需要一行代碼:

let inputImage = CIImage(image: uiImage)

也有很方便的初始化方法直接從圖像數(shù)據(jù)或文件 URL 來(lái)創(chuàng)建 CIImage。

一旦我們有了一個(gè) CIImage,我們就可以通過(guò)設(shè)置濾鏡的 inputImage 參數(shù)來(lái)將其設(shè)置為濾鏡的輸入圖像:

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

得到一個(gè)濾鏡處理后的圖片

濾鏡都有一個(gè)名為 outputImage 的屬性。正如你可能已經(jīng)猜到的一樣,它是 CIImage 類(lèi)型的。那么,我們?nèi)绾螌?shí)現(xiàn)從一個(gè) CIImage 創(chuàng)建 UIImage 這樣一個(gè)反向操作?好了,雖然我們到此已經(jīng)花了所有的時(shí)間建立一個(gè)濾鏡圖表,現(xiàn)在是調(diào)用 CIContext 的力量來(lái)實(shí)際的做圖像濾鏡處理工作的時(shí)候了。

創(chuàng)建一個(gè)上下文最簡(jiǎn)單的方法是給它的構(gòu)造方法傳一個(gè) nil 字典:

let ciContext = CIContext(options: nil)

為了得到一個(gè)濾鏡處理過(guò)的圖像,我們需要 CIContext 從輸出圖像的一個(gè)矩形內(nèi)創(chuàng)建一個(gè) CGImage,傳入輸入圖像的范圍(bounds):

let cgImage = ciContext.createCGImage(filter.outputImage, fromRect: inputImage.extent())

我們使用輸入圖像大小的原因是,輸出圖像通常和輸入圖像具有不同的尺寸比。例如,一個(gè)模糊圖像由于采樣超出了輸入圖像的邊緣,圍繞在其邊界外還會(huì)有一些額外的像素。

現(xiàn)在,我們可以從這個(gè)新創(chuàng)建的 CGImage 來(lái)創(chuàng)建一個(gè) UIImage 了:

let uiImage = UIImage(CGImage: cgImage)

直接從一個(gè) CIImage 創(chuàng)建 UIImage 也是可以的,但這種方法有點(diǎn)讓人郁悶:如果你試圖在一個(gè) UIImageView 上顯示這樣的圖像,其 contentMode 屬性將被忽略。使用過(guò)渡的 CGImage 則需要一個(gè)額外的步驟,但可以省去這一煩惱。

用 OpenGL 來(lái)提高性能

用 CPU 來(lái)繪制一個(gè) CGImage 是非常耗時(shí)和浪費(fèi)的,它只將結(jié)果回傳給 UIKit 來(lái)做合成。我們更希望能夠在屏幕上繪制應(yīng)用濾鏡后的圖像,而不必去 Core Graphics 里繞一圈。幸運(yùn)的是,由于 OpenGL 和 Core Image 的可互操作性,我們可以這么做。

要 OpenGL 上下文和 Core Image 上下文之間共享資源,我們需要用一個(gè)稍微不同的方式來(lái)創(chuàng)建我們的 CIContext

let eaglContext = EAGLContext(API: .OpenGLES2)
let ciContext = CIContext(EAGLContext: context)

在這里,我們用 OpenGL ES 2.0 的功能集創(chuàng)建了一個(gè) EAGLContext。這個(gè) GL 上下文可以用作一個(gè) GLKView 的背襯上下文或用來(lái)繪制成一個(gè) CAEAGLLayer。示例代碼使用這種技術(shù)來(lái)有效地繪制圖像。

當(dāng)一個(gè) CIContext 具有了關(guān)聯(lián) GL 的上下文,濾鏡處理后的圖像就可用 OpenGL 來(lái)繪制,像如下這樣調(diào)用方法:

ciContext.drawImage(filter.outputImage, inRect: outputBounds, fromRect: inputBounds)

與以前一樣,fromRect 參數(shù)是用濾鏡處理后的圖像的坐標(biāo)空間來(lái)繪制的圖像的一部分。這個(gè) inRect 參數(shù)是 GL 上下文的坐標(biāo)空間的矩形應(yīng)用到需要繪制圖像上。如果你想保持圖像的長(zhǎng)寬比,你可能需要做一些數(shù)學(xué)計(jì)算來(lái)得到適當(dāng)?shù)?inRect。

強(qiáng)制在 CPU 上做濾鏡操作

只要有可能,Core Image 將在 GPU 上執(zhí)行濾鏡操作。然而,它確實(shí)有回滾到 CPU 上執(zhí)行的可能。濾鏡操作在 CPU 上完成可具有更好的精確度,因?yàn)?GPU 經(jīng)常在浮點(diǎn)計(jì)算上以失真換得更快的速度。在創(chuàng)建一個(gè)上下文時(shí),你可以通過(guò)設(shè)置 kCIContextUseSoftwareRenderer 關(guān)鍵字的值為 true 來(lái)強(qiáng)制 Core Image 在 CPU 上運(yùn)行。

你可以通過(guò)在 Xcode 中設(shè)置計(jì)劃配置(scheme configuration)里的 CI_PRINT_TREE 環(huán)境變量為 1 來(lái)決定用 CPU 還是 GPU 來(lái)渲染。這將導(dǎo)致每次一個(gè)濾鏡處理圖像被渲染的時(shí)候 Core Image 都會(huì)打印診斷信息。此設(shè)置用來(lái)檢查合成圖像濾鏡樹(shù)也很有用。

示例應(yīng)用一覽

本文的示例代碼是一個(gè) iPhone 應(yīng)用程序,展示了 iOS 里大量的各式 Core Image 圖像濾鏡。

為濾鏡參數(shù)創(chuàng)建一個(gè) GUI

為了盡可能多的演示各種濾鏡,示例應(yīng)用程序利用了 Core Image 的內(nèi)省特點(diǎn)生成了一個(gè)界面,用于控制它支持的濾鏡參數(shù):

http://wiki.jikexueyuan.com/project/objc/images/21-23.png" alt="" />

示例應(yīng)用程序只限于單一的圖像輸入以及零個(gè)或多個(gè)數(shù)值輸入的濾鏡。也有一些有趣的濾鏡不屬于這一類(lèi)(特別是那些合成和轉(zhuǎn)換濾鏡)。即便如此,該應(yīng)用程序仍然很好的概述了 Core Image 支持的功能。

對(duì)于每個(gè)濾鏡的輸入?yún)?shù),都有一個(gè)滑動(dòng)條可以用于配置參數(shù)的最小值和最大值,其值被設(shè)置為默認(rèn)值。當(dāng)滑動(dòng)條的值發(fā)生變化時(shí),它把改變后的值傳給它的 delegate,一個(gè)持有 CIFilter 引用的 UIImageView 子類(lèi)。

使用內(nèi)建的照片濾鏡

除了許多其他的內(nèi)置濾鏡,示例應(yīng)用程序還展示了 iOS 7 中引入的照片濾鏡。這些濾鏡沒(méi)有我們可以調(diào)整的參數(shù),但它們值得被囊括進(jìn)來(lái),因?yàn)樗鼈冋故玖巳绾卧?iOS 中模擬照片應(yīng)用程序的效果:

http://wiki.jikexueyuan.com/project/objc/images/21-24.png" alt="" />

結(jié)論

這篇文章簡(jiǎn)要介紹了 Core Image 這個(gè)高性能的圖像處理框架。我們一直在試圖在如此簡(jiǎn)短的形式內(nèi)盡可能多的展示這個(gè)框架的功能。你現(xiàn)在已經(jīng)學(xué)會(huì)了如何實(shí)例化和串聯(lián) Core Image 的濾鏡,在濾鏡圖表傳入和輸出圖像,以及調(diào)整參數(shù)來(lái)獲得想要的結(jié)果。你還學(xué)習(xí)了如何訪問(wèn)系統(tǒng)提供的照片濾鏡,用以模擬在 iOS 上的照片應(yīng)用程序的行為。

現(xiàn)在你知道了足夠多的東西來(lái)寫(xiě)你自己的照片編輯應(yīng)用程序了。隨著更多的一些探索,你就可以寫(xiě)自己的濾鏡了,利用你的 Mac 或 iPhone 的神奇的力量來(lái)執(zhí)行以前無(wú)法想象的效果??烊?dòng)手做吧!

參考

Core Image Filter Reference 包含了 Core Image 提供的圖像濾鏡的完整列表,以及用法示例。

如果想要寫(xiě)更函數(shù)式風(fēng)格的 Core Image 代碼,可以看看 Florian Kluger 在 objccn.io 話題 #16 里的文章