OS X和iOS圖像處理框架Core Image

原鏈接:http://www.csdn.net/article/2015-02-13/2823961-core-image

摘要:本文結(jié)合實(shí)例詳解了OS X和iOS圖像處理框架Core Image的使用,如何通過Core Image來創(chuàng)建和使用iOS的內(nèi)置濾鏡,非常適合初學(xué)者學(xué)習(xí)。雖然示例代碼是用Swift寫的iOS程序,不過實(shí)現(xiàn)概念很容易轉(zhuǎn)換到Objective-C和OS X。
這篇文章會(huì)為初學(xué)者介紹一下Core Image,一個(gè)OS X和iOS的圖像處理框架。
如果你想跟著本文中的代碼學(xué)習(xí),你可以在GitHub上下載示例工程。示例工程是一個(gè)iOS應(yīng)用程序,列出了系統(tǒng)提供的大量圖像濾鏡以供選擇,并提供了一個(gè)用戶界面用來調(diào)整參數(shù)并觀察效果。
雖然示例代碼是用Swift寫的iOS程序,不過實(shí)現(xiàn)概念很容易轉(zhuǎn)換到Objective-C和OS X。
基本概念
說到Core Image,我們首先需要介紹幾個(gè)基本的概念。
一個(gè)濾鏡是一個(gè)對(duì)象,有很多輸入和輸出,并執(zhí)行一些變換。例如,模糊濾鏡可能需要輸入圖像和一個(gè)模糊半徑來產(chǎn)生適當(dāng)?shù)哪:蟮妮敵鰣D像。
一個(gè)濾鏡圖表是一個(gè)鏈接在一起的濾鏡網(wǎng)絡(luò)(無回路有向圖),使得一個(gè)濾鏡的輸出可以是另一個(gè)濾鏡的輸入。以這種方式,可以實(shí)現(xiàn)精心制作的效果。我們將在下面看到如何連接濾鏡來創(chuàng)建一個(gè)復(fù)古的拍照效果。
熟悉Core Image API
有了上述的這些概念,我們可以開始探索Core Image的圖像濾鏡細(xì)節(jié)了。
Core Image架構(gòu)
Core Image有一個(gè)插件架構(gòu),這意味著它允許用戶編寫自定義的濾鏡并與系統(tǒng)提供的濾鏡集成來擴(kuò)展其功能。我們?cè)谶@篇文章中不會(huì)用到Core Image的可擴(kuò)展性;我提到它只是因?yàn)樗绊懙搅丝蚣艿腁PI。
Core Image 是用來最大化利用其所運(yùn)行之上的硬件的。每個(gè)濾鏡實(shí)際上的實(shí)現(xiàn),即內(nèi)核,是由一個(gè)GLSL(即OpenGL的著色語言)的子集來書寫的。當(dāng)多個(gè)濾鏡連接成一個(gè)濾鏡圖表,Core Image便把內(nèi)核串在一起來構(gòu)建一個(gè)可在GPU上運(yùn)行的高效程序。
只要有可能,Core Image都會(huì)把工作延遲。通常情況下,直到濾鏡圖表的最后一個(gè)濾鏡的輸出被請(qǐng)求之前都不會(huì)發(fā)生分配或處理。
為了完成工作,Core Image需要一個(gè)稱為上下文(context)的對(duì)象。這個(gè)上下文是框架真正工作的地方,它需要分配必要的內(nèi)存,并編譯和運(yùn)行濾鏡內(nèi)核來執(zhí)行圖像處理。建立一個(gè)上下文是非常昂貴的,所以你會(huì)經(jīng)常想創(chuàng)建一個(gè)反復(fù)使用的上下文。接下來我們將看到如何創(chuàng)建一個(gè)上下文。
查詢可用的濾鏡
Core Image濾鏡是按名字創(chuàng)建的。要獲得系統(tǒng)濾鏡的列表,我們要向Core Image的kCICategoryBuiltIn類別請(qǐng)求得到濾鏡的名字:

[cpp] view plaincopy

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

iOS上可用的濾鏡列表非常接近于OS X上可用濾鏡的一個(gè)子集。在OS X上有169個(gè)內(nèi)置濾鏡,在iOS上有127個(gè)。
通過名字創(chuàng)建一個(gè)濾鏡
現(xiàn)在,我們有了可用濾鏡的列表,我們就可以創(chuàng)建和使用濾鏡了。例如,要?jiǎng)?chuàng)建一個(gè)高斯模糊濾鏡,我們傳給CIFilter初始化方法相應(yīng)的名稱就可以了:

[cpp] view plaincopy

let blurFilter = CIFilter(named:"CIGaussianBlur")

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

[cpp] view plaincopy

blurFilter.setValue(10.0 forKey:"inputRadius")

由于這種方法需要AnyObject? (即Objective-C里的id)作為其參數(shù)值,它不是類型安全的。因此,設(shè)置濾鏡參數(shù)需要謹(jǐn)慎一些,確保你傳值的類型是正確的。
查詢?yōu)V鏡屬性
為了知道一個(gè)濾鏡提供什么樣的輸入和輸出參數(shù),我們就可以分別獲取inputKeys和outputKeys數(shù)組。它們都返回NSString的數(shù)組。
要獲取每個(gè)參數(shù)的詳細(xì)信息,我們可以看看由濾鏡提供的attributes字典。每個(gè)輸入和輸出參數(shù)名映射到它自己的字典里,描述了它是什么樣的參數(shù),如果有的話還會(huì)給出它的最大值和最小值。例如,下面是 CIColorControls濾鏡對(duì)應(yīng)的inputBrightness參數(shù)字典:

[cpp] view plaincopy

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

對(duì)于數(shù)值參數(shù),該字典會(huì)包含 kCIAttributeSliderMin 和 kCIAttributeSliderMax 鍵,來限制期望的輸入域。大多數(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ù),把它們連接起來以便該圖像數(shù)據(jù)按順序傳過每個(gè)濾鏡。
在本節(jié)中,我們將創(chuàng)建一個(gè)用來制作 19 世紀(jì)錫版照風(fēng)格圖像的濾鏡圖表。我們將兩個(gè)效果鏈在一起來達(dá)到這種效果:同時(shí)去飽和以及染色調(diào)的黑白濾鏡,和一個(gè)暗角濾鏡來創(chuàng)建一個(gè)有陰影效果的加框圖片。
用Quartz Composer,來做Core Image濾鏡圖表的原型非常有用,可以從蘋果開發(fā)者網(wǎng)站下載。下面,我們整理了所需的照片濾鏡,把黑白濾鏡和暗角濾鏡串在一起:


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

[cpp] view plaincopy

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類型。而對(duì)于iOS的程序員來說這可能會(huì)有一點(diǎn)不尋常,因?yàn)樗麄兏?xí)慣用UIImage,但這個(gè)區(qū)別是值得的。一個(gè)CIImage實(shí)例實(shí)際上比UIImage更全面,因?yàn)镃IImage可以無限大。當(dāng)然,我們不能存儲(chǔ)無限的圖像在內(nèi)存中,但在概念上,這意味著你可以從2D平面上的任意區(qū)域獲取圖像數(shù)據(jù),并得到一個(gè)有意義的結(jié)果。
所有我們?cè)诒疚闹惺褂玫膱D像都是有限的,而且也可以很容易從一個(gè)UIImage來創(chuàng)建一個(gè)CIImage。事實(shí)上,這只需要一行代碼:

[cpp] view plaincopy

let inputImage = CIImage(image: uiImage)

也有很方便的初始化方法直接從圖像數(shù)據(jù)或文件URL來創(chuàng)建CIImage。
一旦我們有了一個(gè)CIImage,我們就可以通過設(shè)置濾鏡的inputImage參數(shù)來將其設(shè)置為濾鏡的輸入圖像:

[cpp] view plaincopy

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

得到一個(gè)濾鏡處理后的圖片
濾鏡都有一個(gè)名為outputImage的屬性。正如你可能已經(jīng)猜到的一樣,它是 CIImage 類型的。那么,我們?nèi)绾螌?shí)現(xiàn)從一個(gè)CIImage創(chuàng)建UIImage這樣一個(gè)反向操作?好了,雖然我們到此已經(jīng)花了所有的時(shí)間建立一個(gè)濾鏡圖表,現(xiàn)在是調(diào)用CIContext的力量來實(shí)際的做圖像濾鏡處理工作的時(shí)候了。
創(chuàng)建一個(gè)上下文最簡(jiǎn)單的方法是給它的構(gòu)造方法傳一個(gè)nil字典:

[cpp] view plaincopy

let ciContext = CIContext(options: nil)

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

[cpp] view plaincopy

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

我們使用輸入圖像大小的原因是,輸出圖像通常和輸入圖像具有不同的尺寸比。例如,一個(gè)模糊圖像由于采樣超出了輸入圖像的邊緣,圍繞在其邊界外還會(huì)有一些額外的像素。
現(xiàn)在,我們可以從這個(gè)新創(chuàng)建的CGImage來創(chuàng)建一個(gè)UIImage了:

[cpp] view plaincopy

let uiImage = UIImage(CGImage: cgImage)

直接從一個(gè)CIImage創(chuàng)建UIImage也是可以的,但這種方法有點(diǎn)讓人郁悶:如果你試圖在一個(gè)UIImageView上顯示這樣的圖像,其contentMode屬性將被忽略。使用過渡的CGImage則需要一個(gè)額外的步驟,但可以省去這一煩惱。
用OpenGL來提高性能
用CPU來繪制一個(gè)CGImage是非常耗時(shí)和浪費(fèi)的,它只將結(jié)果回傳給UIKit來做合成。我們更希望能夠在屏幕上繪制應(yīng)用濾鏡后的圖像,而不必去Core Graphics里繞一圈。幸運(yùn)的是,由于OpenGL和Core Image的可互操作性,我們可以這么做。
要OpenGL上下文和Core Image上下文之間共享資源,我們需要用一個(gè)稍微不同的方式來創(chuàng)建我們的 CIContext:

[cpp] view plaincopy

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

在這里,我們用OpenGL ES 2.0的功能集創(chuàng)建了一個(gè)EAGLContext。這個(gè)GL上下文可以用作一個(gè)GLKView的背襯上下文或用來繪制成一個(gè)CAEAGLLayer。示例代碼使用這種技術(shù)來有效地繪制圖像。
當(dāng)一個(gè)CIContext具有了關(guān)聯(lián)GL的上下文,濾鏡處理后的圖像就可用OpenGL來繪制,像如下這樣調(diào)用方法:

[cpp] view plaincopy

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

與以前一樣,fromRect 參數(shù)是用濾鏡處理后的圖像的坐標(biāo)空間來繪制的圖像的一部分。這個(gè)inRect 參數(shù)是GL上下文的坐標(biāo)空間的矩形應(yīng)用到需要繪制圖像上。如果你想保持圖像的長(zhǎng)寬比,你可能需要做一些數(shù)學(xué)計(jì)算來得到適當(dāng)?shù)膇nRect。
強(qiáng)制在CPU上做濾鏡操作
只要有可能,Core Image將在GPU上執(zhí)行濾鏡操作。然而,它確實(shí)有回滾到CPU上執(zhí)行的可能。濾鏡操作在CPU上完成可具有更好的精確度,因?yàn)镚PU經(jīng)常在浮點(diǎn)計(jì)算上以失真換得更快的速度。在創(chuàng)建一個(gè)上下文時(shí),你可以通過設(shè)置kCIContextUseSoftwareRenderer關(guān)鍵字的值為true來強(qiáng)制Core Image在CPU上運(yùn)行。
你可以通過在Xcode中設(shè)置計(jì)劃配置(scheme configuration)里的CI_PRINT_TREE環(huán)境變量為1來決定用CPU 還是GPU來渲染。這將導(dǎo)致每次一個(gè)濾鏡處理圖像被渲染的時(shí)候Core Image都會(huì)打印診斷信息。此設(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ù):


示例應(yīng)用程序只限于單一的圖像輸入以及零個(gè)或多個(gè)數(shù)值輸入的濾鏡。也有一些有趣的濾鏡不屬于這一類(特別是那些合成和轉(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子類。
使用內(nèi)建的照片濾鏡
除了許多其他的內(nèi)置濾鏡,示例應(yīng)用程序還展示了iOS 7中引入的照片濾鏡。這些濾鏡沒有我們可以調(diào)整的參數(shù),但它們值得被囊括進(jìn)來,因?yàn)樗鼈冋故玖巳绾卧趇OS中模擬照片應(yīng)用程序的效果:
[圖片上傳中。。。(3)]
結(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ù)來獲得想要的結(jié)果。你還學(xué)習(xí)了如何訪問系統(tǒng)提供的照片濾鏡,用以模擬在iOS上的照片應(yīng)用程序的行為。
現(xiàn)在你知道了足夠多的東西來寫你自己的照片編輯應(yīng)用程序了。隨著更多的一些探索,你就可以寫自己的濾鏡了,利用你的Mac或iPhone的神奇的力量來執(zhí)行以前無法想象的效果??烊?dòng)手做吧!
參考

Core Image Reference Collection是Core Image的權(quán)威文檔集。
Core Image Filter Reference包含了Core Image提供的圖像濾鏡的完整列表,以及用法示例。
如果想要寫更函數(shù)式風(fēng)格的Core Image代碼,可以看看Florian Kluger在objccn.io話題#16里的文章。

本文轉(zhuǎn)載自:objccn

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 最近在研究 Core Image 自定義 Filter 相關(guān)內(nèi)容,重新學(xué)習(xí)了 Core Image,對(duì) Co...
    泥孩兒0107閱讀 894評(píng)論 0 4
  • 許多UIView的子類,如一個(gè)UIButton或一個(gè)UILabel,它們知道怎么繪制自己。遲早,你也將想要做一些自...
    shenzhenboy閱讀 1,759評(píng)論 2 8
  • Core Image是一個(gè)強(qiáng)大的框架,它能夠讓你輕松地對(duì)圖像進(jìn)行過濾。你能夠通過修改圖像的飽和度、色調(diào)或曝光率來獲...
    木易林1閱讀 1,214評(píng)論 0 1
  • --繪圖與濾鏡全面解析 概述 在iOS中可以很容易的開發(fā)出絢麗的界面效果,一方面得益于成功系統(tǒng)的設(shè)計(jì),另一方面得益...
    韓七夏閱讀 2,982評(píng)論 2 10
  • 概念 Core Image:一個(gè)OS X和iOS的圖像處理框架,Core Image框架最早出現(xiàn)于iOS5,iOS...
    newmecy閱讀 789評(píng)論 0 2

友情鏈接更多精彩內(nèi)容