本文內(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à)是非常高的。
并且卵蛉,CIContext 和 CIImage 對(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ū)別的。
- contextWithOptions 創(chuàng)建的 context 并沒(méi)有實(shí)時(shí)性能桐款,雖然渲染是在 CPU 上執(zhí)行,但是其輸出的 image 是不能顯示的夷恍。只有當(dāng)其被復(fù)制回 CPU 存儲(chǔ)器上時(shí)魔眨,才會(huì)被轉(zhuǎn)成一個(gè)可被顯示的 image 類型媳维,比如 UIImage。
它的渲染過(guò)程大致如下:
當(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)重的性能損耗。
- 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ò)程大致如下:
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 ES、Core 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)單的描述。
- 濾鏡鏈
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 會(huì)自動(dòng)把多個(gè)濾鏡組合成一個(gè)新的程序(program)堂鲤,通過(guò)減少中間緩存區(qū)的數(shù)量亿傅,來(lái)提高性能和質(zhì)量。
-
支持大圖
超過(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ū)域了。
另外体捏,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蚕捉,小圖:
很明顯的,Memory 占有率高柴淘,并且調(diào)用了 decode 相關(guān)操作迫淹。
4100 * 4100,大圖:
這里的 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ì)比:
-
GPU 優(yōu)化
另外一個(gè)很重要的優(yōu)化就是:提高了 iOS 上 Core Image 使用 GPU 進(jìn)行渲染的性能。具體體現(xiàn)在:- 后臺(tái)操作
- 短時(shí)間內(nèi)庸诱,進(jìn)入后臺(tái)后會(huì)依舊使用高效的 GPU 進(jìn)行渲染悬钳。
- 后臺(tái)操作的 GPU 優(yōu)先級(jí)低,不會(huì)對(duì)前臺(tái)的渲染造成性能影響偶翅。
- 多線程
- 在 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)上最好的選擇处嫌。