Core Image 的高級(jí)應(yīng)用

本文內(nèi)容歸納整理自 Core Image 你需要了解的那些事~寂屏,僅作學(xué)習(xí)用途袜爪。如有侵權(quán)蠕趁,請(qǐng)聯(lián)系博主刪除。

一辛馆、獲取 CIImage

為了獲取 CIImage 圖像俺陋,很多同學(xué)會(huì)直接通過(guò) UIImage.CIImage 的方式去獲取,但是這樣的方式是無(wú)法保證獲取到 CIImage 對(duì)象的怀各。CIImage 屬性定義如下:

@property(nullable,nonatomic,readonly) CIImage *CIImage NS_AVAILABLE_IOS(5_0);
 // returns underlying CIImage or nil if CGImageRef based

這里已經(jīng)明確說(shuō)明了倔韭,UIImage 對(duì)象可能不是基于 CIImage 創(chuàng)建的(比如它是由 imageWithCIImage: 生成的),這樣就無(wú)法獲取到 CIImage 對(duì)象瓢对。

正確的方式應(yīng)為:

CIImage *ciImage = [[CIImage alloc] initWithImage:[UIImage imageNamed:imageName]];

二寿酌、CIContext

在創(chuàng)建結(jié)果 UIImage 的時(shí)候,最簡(jiǎn)單的方式就是通過(guò) imageWithCIImage: 來(lái)實(shí)現(xiàn)硕蛹。這種情況下醇疼,不需要顯示的定義 CIContext 對(duì)象,因?yàn)?imageWithCIImage: 方法內(nèi)部自動(dòng)完成了這個(gè)步驟法焰。這使得使用 Core Image 更加的方便秧荆。當(dāng)然,它也引起了另外一個(gè)問(wèn)題埃仪。每次都會(huì)重新創(chuàng)建一個(gè) CIContext 對(duì)象 乙濒,而創(chuàng)建 CIContext 對(duì)象的代價(jià)是非常高的。

并且卵蛉,CIContextCIImage 對(duì)象是不可變對(duì)象颁股,在線程之間共享這些對(duì)象是安全的。所以多個(gè)線程可以使用同一個(gè) GPU 或者 CPU 的 CIContext 對(duì)象來(lái)渲染 CIImage 對(duì)象傻丝。

所以甘有,重用 CIContext 是很有必要的。這意味著葡缰,我們不應(yīng)該使用 imageWithCIImage: 方法來(lái)生成 UIImage亏掀,而應(yīng)該自己創(chuàng)建維護(hù) CIContext忱反。

比如:

self.context = [CIContext contextWithOptions:nil];

...
 
CGImageRef cgImage = [self.context createCGImage:outputImage fromRect:[outputImage extent]];
UIImage *image = [UIImage imageWithCGImage:cgImage];

三、GPU / CPU

Core Image 的另外一個(gè)優(yōu)勢(shì)滤愕,就是可以根據(jù)需求選擇 CPU 或者 GPU 來(lái)處理温算。在 CIContext 創(chuàng)建的時(shí)候,我們可以給它設(shè)置為基于 GPU 還是 CPU该互。

基于 GPU 的話米者,處理速度更快。以為利用了 GPU 的硬件并行優(yōu)勢(shì)宇智÷悖可以使用 OpenGL ES 或者 Metal 來(lái)渲染圖像。這種方式 CPU 完全沒(méi)有負(fù)擔(dān)随橘,應(yīng)用程序的運(yùn)行循環(huán)不會(huì)受到圖像渲染的影響喂分。

但是 GPU 受限于硬件紋理尺寸,而且如果你的程序在后臺(tái)繼續(xù)處理和保存圖片的話机蔗,那么需要使用 CPU蒲祈。因?yàn)楫?dāng)應(yīng)用切換到后臺(tái)狀態(tài)時(shí),GPU 處理會(huì)被打斷萝嘁。使用 CPU 渲染的 iOS 會(huì)采用 GCD 來(lái)對(duì)圖像進(jìn)行渲染梆掸。這保證了 CPU 渲染在大部分情況下更可靠,比 GPU 渲染更容易使用牙言,可以在后臺(tái)實(shí)現(xiàn)渲染過(guò)程酸钦。

綜上,對(duì)于復(fù)雜圖像濾鏡咱枉,使用 GPU 更好卑硫。但如果在處理視頻過(guò)程中,保存文件或保存照片到照片庫(kù)中時(shí)蚕断,為避免程序進(jìn)入后臺(tái)對(duì)圖片保存造成影響欢伏,這時(shí)應(yīng)該使用 CPU 進(jìn)行渲染。

用官方的一句話來(lái)描述再合適不過(guò)了:

CPU is still what will give you the best fidelity where as the GPU will give you the best performance.
大體意思:GPU 總是能提供最好的性能亿乳,而 CPU 能給你提供最好的精確性硝拧。

四、GPU / CPU 的創(chuàng)建方式

具體設(shè)置方式:

// 創(chuàng)建基于 CPU 的 CIContext 對(duì)象 (默認(rèn)是基于 GPU葛假,CPU 需要額外設(shè)置參數(shù))
context = [CIContext contextWithOptions: [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]];

// 創(chuàng)建基于 GPU 的 CIContext 對(duì)象
context = [CIContext contextWithOptions: nil];

// 創(chuàng)建基于 GPU 的 CIContext 對(duì)象
EAGLContext *eaglctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
context = [CIContext contextWithEAGLContext:eaglctx];

同樣是基于 GPU 的障陶,它們之間也是有區(qū)別的。

  1. contextWithOptions 創(chuàng)建的 context 并沒(méi)有實(shí)時(shí)性能桐款,雖然渲染是在 CPU 上執(zhí)行,但是其輸出的 image 是不能顯示的夷恍。只有當(dāng)其被復(fù)制回 CPU 存儲(chǔ)器上時(shí)魔眨,才會(huì)被轉(zhuǎn)成一個(gè)可被顯示的 image 類型媳维,比如 UIImage。

它的渲染過(guò)程大致如下:

contextWithOptions 創(chuàng)建的基于 GPU 的 context 處理流程

當(dāng)使用 Core Image 在 GPU 上渲染圖片的時(shí)候遏暴,先是把圖像傳遞到 GPU 上侄刽,然后執(zhí)行濾鏡相關(guān)操作。但是當(dāng)需要生成 CGImage 對(duì)象的時(shí)候朋凉,圖像又被復(fù)制回 CPU 上州丹。最后要在視圖上顯示的時(shí)候,又返回 GPU 進(jìn)行渲染杂彭。這樣在 GPU 和 CPU 之前來(lái)回切換墓毒,會(huì)造成很嚴(yán)重的性能損耗。

  1. contextWithEAGLContext 創(chuàng)建的 context 支持實(shí)時(shí)渲染亲怠,渲染圖像的過(guò)程始終在 GPU 上進(jìn)行所计,并且永遠(yuǎn)不會(huì)復(fù)制回 CPU 存儲(chǔ)器上,這就保證了更快的渲染速度和更好的性能团秽。當(dāng)然主胧,這個(gè)前提是利用實(shí)時(shí)渲染的特效,而不是每次操作都產(chǎn)生一個(gè) UIImage习勤,然后再設(shè)置到視圖上踪栋。比如 OpenGL ES:
// 設(shè)置 OpenGLES 渲染環(huán)境
EAGLContext *eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
self.glkView.context = eaglContext;
self.context = [CIContext contextWithEAGLContext:eaglContext];
...
  
// 實(shí)時(shí)渲染
[self.pixellateFilter setValue:@(sender.value) forKey:@"inputRadius"];

[self.context drawImage:_pixellateFilter.outputImage inRect:_targetBounds  fromRect:_inputImage.extent];
[self.glkView.context presentRenderbuffer:GL_RENDERBUFFER];

它的渲染過(guò)程大致如下:

contextWithEAGLContext 創(chuàng)建的基于 GPU 的 context 處理流程

iOS8 后增強(qiáng)了 GPU 渲染,在后臺(tái)也能繼續(xù)使用 GPU 進(jìn)行處理图毕。所以夷都,應(yīng)該盡可能的使用 GPU 去做圖像處理

另外吴旋,Apple 對(duì) Core Image 內(nèi)部進(jìn)行了優(yōu)化损肛,如果通過(guò)

// 創(chuàng)建基于 GPU 的 CIContext 對(duì)象
context = [CIContext contextWithOptions: nil];

創(chuàng)建 context ,那么它內(nèi)部的渲染器會(huì)根據(jù)設(shè)備最優(yōu)選擇荣瑟。依次為Metal治拿、OpenGL ESCore Graphics笆焰。

PS:Metal 需要 iOS8 + A7劫谅,且模擬器不支持 Metal。OpenGL ES3 需要 iOS7 + A7
測(cè)試結(jié)果:
iPhone 6s嚷掠,iOS 10捏检, 模擬器:OpenGL ES3
iPhone 6s省骂,iOS 10回俐,真機(jī):Metal
iPhone 5迟郎, iOS 8宪哩, 模擬器:OpenGL ES

五通惫、CIFilter 注意點(diǎn)

之前提到過(guò) CIContext 是線程安全的翎苫,然而 CIFilter 并不是線程安全的旺聚。這意味著一個(gè) CIFilter 對(duì)象不能在多個(gè)線程間共享沈贝。如果你的操作是多線程的,每個(gè)線程都必須創(chuàng)建自己的 CIFilter 對(duì)象踩晶。否則执泰,你的應(yīng)用將產(chǎn)生不可預(yù)期的后果。

六渡蜻、Core Image 和 GPUImage 的對(duì)比

Core Image 與其他圖像處理方案的對(duì)比术吝,這里比較有爭(zhēng)議的就是 OpenGL ES 和 Core Image 了。

在 OpenGL ES 部分茸苇,拿主流的 GPUImage 來(lái)做對(duì)比排苍,分析一下它們各自的優(yōu)缺點(diǎn)。只有對(duì)比了才知道税弃,Core Image 好在哪里纪岁,是否值得使用。

PS:以下的優(yōu)勢(shì)闡述则果,撇去了兩個(gè)框架都具備的幔翰,僅保留對(duì)比后各自的優(yōu)勢(shì)。
另外西壮,GPUImage 我沒(méi)有深入學(xué)習(xí)過(guò)遗增,對(duì)于它的一些優(yōu)勢(shì),主要是總結(jié)它的開發(fā)者 Brad 描述的款青,以及簡(jiǎn)單的 Demo 進(jìn)行對(duì)比做修。

GPUImage 的優(yōu)勢(shì):

  • 最低支持 iOS4.0,iOS5.0 之后就支持自定義濾鏡抡草。
  • 在低端機(jī)型上面饰及,GPUImage 有更好的表現(xiàn)。
  • GPUImage 在視頻處理上有更好的表現(xiàn)康震。
  • GPUImage 的代碼完全公開燎含,實(shí)現(xiàn)透明。
  • 可以根據(jù)自己的業(yè)務(wù)需求腿短,定制更加復(fù)雜的管線操作屏箍。可定制程度高橘忱。

Core Image 的優(yōu)勢(shì):

  • 官方框架赴魁,使用放心,維護(hù)方便钝诚。
  • 支持 CPU 渲染颖御,可以在后臺(tái)繼續(xù)處理和保存圖片。
  • 一些濾鏡的性能更強(qiáng)勁凝颇。例如由 Metal Performance Shaders 支持的模糊濾鏡等潘拱。
  • 支持使用 Metal 渲染圖像秉继。而 Metal 在 iOS平臺(tái)上有更好的表現(xiàn)。
  • 與 Metal泽铛、SpriteKit、SceneKit辑鲤,Core Animation 等更完美的配合盔腔。
  • 支持圖像識(shí)別功能。包括人臉識(shí)別月褥、條形碼識(shí)別弛随、文本識(shí)別等。
  • 支持自動(dòng)增強(qiáng)圖像效果宁赤,會(huì)分析圖像的直方圖舀透、圖像屬性、臉部區(qū)域决左,然后通過(guò)一組濾鏡來(lái)改善圖像效果愕够。
  • 支持對(duì)原生 RAW 格式圖片的處理。
  • 濾鏡鏈的性能比 GPUImage 高佛猛。
  • 支持對(duì)大圖進(jìn)行處理惑芭,超過(guò) GPU 紋理限制(4096 * 4096)的時(shí)候,會(huì)自動(dòng)拆分成幾個(gè)小塊處理(Automatic tiling)继找。GPUImage 當(dāng)處理超過(guò)紋理限制的圖像時(shí)候遂跟,會(huì)先做判斷,壓縮成最大紋理限制的圖像婴渡,導(dǎo)致圖像質(zhì)量損失幻锁。

至此,我覺(jué)得 Core Image 的優(yōu)勢(shì)已經(jīng)很明顯了边臼,尤其是與 Metal 的配合哄尔,自動(dòng)增強(qiáng)圖像效果,支持處理大圖以及濾鏡鏈的優(yōu)化硼瓣。

下面關(guān)于這幾點(diǎn)優(yōu)化究飞,做個(gè)簡(jiǎn)單的描述。

  1. 濾鏡鏈

if you chain together a sequence of filters, Core Image will automatically concatenate these subroutines into a single program.The idea behind this is to improve performance and quality, by reducing the number of intermediate buffers.

Core Image 對(duì)濾鏡鏈的處理

Core Image 會(huì)自動(dòng)把多個(gè)濾鏡組合成一個(gè)新的程序(program)堂鲤,通過(guò)減少中間緩存區(qū)的數(shù)量亿傅,來(lái)提高性能和質(zhì)量。

  1. 支持大圖
    超過(guò) GPU 紋理限制(4096 * 4096)的時(shí)候瘟栖,會(huì)自動(dòng)拆分成幾個(gè)小塊處理(Automatic tiling)葵擎。

圖片大小:(8374半哟,7780)酬滤,驗(yàn)證結(jié)果:

PS: rois 表示當(dāng)前處理區(qū)域签餐。 extent 表示圖像實(shí)際大小。
這個(gè)輸出是 Core Image 在處理過(guò)程中打印的盯串。

(1) rois=[0 0 2092 3888] extent=[0 0 8374 7780]  
(2) rois=[2092 0 2092 3888] extent=[0 0 8374 7780]
(3) rois=[0 3888 2092 3892] extent=[0 0 8374 7780]
(4) rois=[2092 3888 2092 3892] extent=[0 0 8374 7780]
(5) rois=[4184 0 2092 3888] extent=[0 0 8374 7780]
(6) rois=[6276 0 2098 3888] extent=[0 0 8374 7780]
(7) rois=[4184 3888 2092 3892] extent=[0 0 8374 7780]
(8) rois=[6276 3888 2098 3892] extent=[0 0 8374 7780]

如果按序講每個(gè)區(qū)域進(jìn)行拼湊氯檐,就是原圖的實(shí)際區(qū)域了。


Automatic tiling 原理圖

另外体捏,Core Image 對(duì)大圖和小圖的處理上冠摄,也有所不同。小圖提前解碼几缭,大圖延遲解碼 !

當(dāng)傳入的 image 是小圖 (size < inputImageMaximumSize)時(shí)河泳,在調(diào)用 initWithCGImage 獲取輸入圖像 CIImage 的時(shí)候,這個(gè) image 就被完全解碼了年栓。這是很有必要的拆挥。因?yàn)樾D可能多次被用到,把編碼的工作提前并且只做一次某抓,一定程度上優(yōu)化性能纸兔。

而對(duì)于大圖來(lái)說(shuō),它的解碼操作是盡可能延后的(being lazy)否副,直到真正需要顯示食拜, CIContext 執(zhí)行 render 相關(guān)操作。因?yàn)榇髨D的解碼代價(jià)較大副编,并且不常用负甸,無(wú)腦提前解碼,放到內(nèi)存中是沒(méi)有必要的痹届。

下面是驗(yàn)證結(jié)果呻待,選了兩個(gè)相差不大的圖片,但是 size 介于 4096 左右队腐。
4000 * 4000蚕捉,小圖:

小圖內(nèi)存占用

小圖程序處理過(guò)程

很明顯的,Memory 占有率高柴淘,并且調(diào)用了 decode 相關(guān)操作迫淹。

4100 * 4100,大圖:

大圖內(nèi)存占用

大圖程序處理過(guò)程

這里的 Memory 占用較低为严,并且沒(méi)有看到 decode 相關(guān)操作敛熬。

同樣的,當(dāng)通過(guò) CIImage 獲取輸出 CGImage 的時(shí)候第股,如果輸出 CGImage 是小圖的話应民,那么當(dāng) [CIContext createCGImage] 調(diào)用的時(shí)候,image 就被完全渲染了。而對(duì)于大圖诲锹,要等到 CGImage 真正需要渲染顯示的時(shí)候繁仁,這個(gè) image 才會(huì)被渲染。

經(jīng)過(guò)這樣的優(yōu)化處理后归园,對(duì)于大圖黄虱,Session 514 給出了直觀的數(shù)據(jù)對(duì)比:

iOS7 與 iOS8 圖像處理對(duì)比

  1. GPU 優(yōu)化
    另外一個(gè)很重要的優(yōu)化就是:提高了 iOS 上 Core Image 使用 GPU 進(jìn)行渲染的性能。具體體現(xiàn)在:

    1. 后臺(tái)操作
    • 短時(shí)間內(nèi)庸诱,進(jìn)入后臺(tái)后會(huì)依舊使用高效的 GPU 進(jìn)行渲染悬钳。
    • 后臺(tái)操作的 GPU 優(yōu)先級(jí)低,不會(huì)對(duì)前臺(tái)的渲染造成性能影響偶翅。
    1. 多線程
    • 在 iOS8 之前,如果主線程使用 GPU 進(jìn)行相關(guān)操作碉渡,次要線程想要使用 Core Image 的時(shí)候聚谁,通常要使用安全的 CPU 來(lái)實(shí)現(xiàn),避免引起意想不到的問(wèn)題滞诺。
    • 在 iOS8 之后形导,可以在次要線程設(shè)置 Context 的 kCIContextPriorityRequestLow 值為 YES,這樣就標(biāo)記為當(dāng)前 Context 在 GPU 上渲染的時(shí)候優(yōu)先級(jí)低习霹,從而不影響到 GPU 上高優(yōu)先級(jí)的渲染朵耕。
CIContext *context = [CIContext contextWithOptions: [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextPriorityRequestLow]];

所以,應(yīng)該盡可能的使用 GPU 進(jìn)行渲染淋叶,來(lái)提高性能阎曹。

總結(jié)

綜上,我認(rèn)為在某需求 Core Image 能實(shí)現(xiàn)的時(shí)候煞檩,使用 Core Image 應(yīng)該是 iOS 平臺(tái)上最好的選擇处嫌。

參考鏈接

Core Image 你需要了解的那些事~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市斟湃,隨后出現(xiàn)的幾起案子熏迹,更是在濱河造成了極大的恐慌,老刑警劉巖凝赛,帶你破解...
    沈念sama閱讀 221,406評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件注暗,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡墓猎,警方通過(guò)查閱死者的電腦和手機(jī)捆昏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,395評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)毙沾,“玉大人屡立,你說(shuō)我怎么就攤上這事。” “怎么了膨俐?”我有些...
    開封第一講書人閱讀 167,815評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵勇皇,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我焚刺,道長(zhǎng)敛摘,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,537評(píng)論 1 296
  • 正文 為了忘掉前任乳愉,我火速辦了婚禮兄淫,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蔓姚。我一直安慰自己捕虽,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,536評(píng)論 6 397
  • 文/花漫 我一把揭開白布坡脐。 她就那樣靜靜地躺著泄私,像睡著了一般。 火紅的嫁衣襯著肌膚如雪备闲。 梳的紋絲不亂的頭發(fā)上晌端,一...
    開封第一講書人閱讀 52,184評(píng)論 1 308
  • 那天,我揣著相機(jī)與錄音恬砂,去河邊找鬼咧纠。 笑死,一個(gè)胖子當(dāng)著我的面吹牛泻骤,可吹牛的內(nèi)容都是我干的漆羔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,776評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼狱掂,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼钧椰!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起符欠,我...
    開封第一講書人閱讀 39,668評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嫡霞,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后希柿,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體诊沪,經(jīng)...
    沈念sama閱讀 46,212評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,299評(píng)論 3 340
  • 正文 我和宋清朗相戀三年曾撤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了端姚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,438評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡挤悉,死狀恐怖渐裸,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤昏鹃,帶...
    沈念sama閱讀 36,128評(píng)論 5 349
  • 正文 年R本政府宣布尚氛,位于F島的核電站,受9級(jí)特大地震影響洞渤,放射性物質(zhì)發(fā)生泄漏阅嘶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,807評(píng)論 3 333
  • 文/蒙蒙 一载迄、第九天 我趴在偏房一處隱蔽的房頂上張望讯柔。 院中可真熱鬧,春花似錦护昧、人聲如沸魂迄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,279評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)捣炬。三九已至,卻和暖如春怠晴,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背浴捆。 一陣腳步聲響...
    開封第一講書人閱讀 33,395評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工蒜田, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人选泻。 一個(gè)月前我還...
    沈念sama閱讀 48,827評(píng)論 3 376
  • 正文 我出身青樓冲粤,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親页眯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子梯捕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,446評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容