iOS CoreImage之濾鏡簡(jiǎn)單使用

老驥伏櫪价脾,志在千里

前記

最近一直在研究圖像處理方面拂到,既上一篇iOS Quart2D繪圖之UIImage簡(jiǎn)單使用后帅刊,就一直在學(xué)習(xí)關(guān)于CoreImage圖像濾鏡處理走芋。中間也看了不少文章,也得到了不少幫助绩蜻,下面就結(jié)合這些知識(shí)和我自己的認(rèn)識(shí)铣墨,記錄一下,方便自己办绝,方便他人

  • 作用:對(duì)圖像進(jìn)行濾鏡操作伊约,比如模糊、顏色改變孕蝉、銳化屡律、人臉識(shí)別等。
  • Core Graphics對(duì)比:基于Quartz 2D繪圖引擎的繪圖API降淮,通過(guò)它可以進(jìn)行繪圖功能超埋、常用的剪切裁剪合成等搏讶。
簡(jiǎn)介

Core Image是一個(gè)很強(qiáng)大的框架。它可以讓你簡(jiǎn)單地應(yīng)用各種濾鏡來(lái)處理圖像霍殴,比如修改鮮艷程度, 色澤, 或者曝光媒惕。 它利用GPU(或者CPU)來(lái)非常快速来庭、甚至實(shí)時(shí)地處理圖像數(shù)據(jù)和視頻的幀妒蔚。并且隱藏了底層圖形處理的所有細(xì)節(jié),通過(guò)提供的API就能簡(jiǎn)單的使用了月弛,無(wú)須關(guān)心OpenGL或者OpenGL ES是如何充分利用GPU的能力的肴盏,也不需要你知道GCD在其中發(fā)揮了怎樣的作用,Core Image處理了全部的細(xì)節(jié)

大概方式.png
實(shí)現(xiàn)方式

Core Image濾鏡需要一副輸入圖像(生成圖像的濾鏡除外)以及一些定制濾鏡行為的參數(shù)帽衙。被請(qǐng)求時(shí)菜皂,Core Image將濾鏡應(yīng)用于輸入圖像,并提供一副輸出圖像厉萝。在應(yīng)用濾鏡方面恍飘,Core Image的效率極高:僅當(dāng)輸出圖像被請(qǐng)求時(shí)才應(yīng)用濾鏡,而不是在指定時(shí)就應(yīng)用它們谴垫;另外常侣,Core Image盡可能將濾鏡合并,以最大限度地減少應(yīng)用濾鏡的計(jì)算量弹渔。

涉及API
  • CIImage :這是一個(gè)模型對(duì)象,它保存能構(gòu)建圖像的數(shù)據(jù)溯祸,可以是圖像的Data肢专,可以是一個(gè)文件,也可以是CIFilter輸出的對(duì)象焦辅。
  • CIContext :上下文博杖,是框架真正工作的地方,它需要分配必要的內(nèi)存筷登,并編譯和運(yùn)行濾鏡內(nèi)核來(lái)執(zhí)行圖像處理剃根。建立一個(gè)上下文是非常昂貴的,所以你會(huì)經(jīng)常想創(chuàng)建一個(gè)反復(fù)使用的上下文前方。
  • CIFilter :濾鏡對(duì)象狈醉,主要是對(duì)圖像進(jìn)行處理的類(lèi)。通過(guò)設(shè)置一些鍵值來(lái)控制濾鏡的具體效果

注:Core ImageCore Graphics使用的是左下原點(diǎn)坐標(biāo)

到此有一個(gè)疑問(wèn)惠险?就是蘋(píng)果怎么會(huì)弄出這么多image苗傅,比如CIImageUIImage班巩、CGImageRef渣慕,有什么區(qū)別呢?為了弄清這個(gè)問(wèn)題,我也特別搜尋了一番逊桦,下面也記錄一下

UIImage:管理圖片數(shù)據(jù)眨猎,主要用來(lái)展現(xiàn),Image對(duì)象并沒(méi)有提供直接訪問(wèn)相關(guān)的圖片數(shù)據(jù)的操作, 因此你總是通過(guò)已經(jīng)存在的圖片數(shù)據(jù)來(lái)創(chuàng)建它

CGImage:是基于像素的矩陣强经,每個(gè)點(diǎn)都對(duì)應(yīng)了圖片中點(diǎn)的像素信息

CIImage:包含了創(chuàng)建圖片的所有必要的數(shù)據(jù)睡陪,但其本身沒(méi)有渲染成圖片,它代表的是圖像數(shù)據(jù)或者生成圖像數(shù)據(jù)的流程(如濾鏡)夕凝。擁有與之關(guān)聯(lián)的圖片數(shù)據(jù), 但本質(zhì)上并不是一張圖片宝穗,你可以CIImage對(duì)象作為一個(gè)圖片的"配方"。CIImage對(duì)象擁有生成一張圖片所具備的所有信息码秉,但Core Image并不會(huì)真正的去渲染一張圖片, 除非被要求這么做逮矛。


使用方式
  • 1 . CIImage創(chuàng)建,在使用濾鏡之前转砖,你必須要先有一個(gè)CIImage對(duì)象须鼎,在擁有該對(duì)象后結(jié)合CIFilter才能實(shí)現(xiàn)我們的濾鏡效果。這里需要注意的是府蔗,如果直接使用image.cIImage晋控,那么很遺憾的告訴你,你將得到一個(gè)nil姓赤,哈哈
    如下:

image.CIImage.png

原因在UIImageAPI中有介紹// returns underlying CIImage or nil if CGImageRef based赡译,應(yīng)該是說(shuō)圖片可能不是基于CIImage而創(chuàng)建的
正確的方式為

 //得到CIImage
 CIImage *inputCIImage = [[CIImage alloc] initWithImage:_image];
  • 2 . CIFilter創(chuàng)建方式大概有下面三種
+(nullable CIFilter *) filterWithName:(NSString *) name
+(nullable CIFilter *)filterWithName:(NSString *)name
                        keysAndValues:key0, ... NS_REQUIRES_NIL_TERMINATION NS_SWIFT_UNAVAILABLE("");
+(nullable CIFilter *)filterWithName:(NSString *)name
                  withInputParameters:(nullable NSDictionary<NSString *,id> *)params NS_AVAILABLE(10_10, 8_0);

方法上都差不多,只是后面兩個(gè)在初始化的時(shí)候加入了一些鍵值不铆,在API文檔中蝌焚,可以查到很多鍵值,這里需要說(shuō)明下誓斥,鍵值kCIInputImageKey是我們必須要設(shè)置的只洒,這是為我們的濾鏡對(duì)象設(shè)置輸入圖像,圖像的值為CIImage對(duì)象劳坑,方法如下

[_filter setValue:inputCIImage forKey:kCIInputImageKey];

方法中的name就是我們需要用的濾鏡效果毕谴,具體效果,可以在官網(wǎng)上面進(jìn)行查詢距芬,如下

filter.png

下面涝开,我們以沖印效果為例,沖印屬于CICategoryColorEffect中的CIPhotoEffectProcess

 //創(chuàng)建濾鏡對(duì)象
CIFilter *ciFilter = [CIFilter filterWithName:@"CIPhotoEffectProcess" keysAndValues:kCIInputImageKey,ciImage, nil];

大概效果如下

沖印.png

注意:
1蔑穴、在設(shè)置鍵值的時(shí)候忠寻,我們需要有選擇性的進(jìn)行設(shè)置,具體怎么選擇呢存和?
比如上面的沖印效果奕剃,在官方文檔是這么展示的

沖印展示.png

只有一個(gè)必須輸入的inputImage衷旅,因此不需要其它參數(shù)就可以實(shí)現(xiàn)
又比如高斯模糊CIGaussianBlur,在官方文檔中纵朋,是這么展示的

高斯模糊展示.png

如果我們需要控制其模糊半徑柿顶,可以這么設(shè)置

[ciFilter setValue:@(20.f) forKey:@"inputRadius"];

2、CIFilter 并不是線程安全的操软,這意味著 一個(gè) CIFilter對(duì)象不能在多個(gè)線程間共享嘁锯。如果你的操作是多線程的,每個(gè)線程都必須創(chuàng)建自己的 CIFilter 對(duì)象聂薪,而CIContextCIImage對(duì)象都是不可修改的, 意味著它們可以在線程之間安全的共享家乘。多個(gè)線程可以使用同樣的GPU或者CPUCIContext對(duì)象來(lái)渲染CIImage對(duì)象

CIFilter類(lèi)中,還有一些其他函數(shù)藏澳,可能是我們需要用到的仁锯,這里也簡(jiǎn)單說(shuō)明下

//輸入的鍵值信息
NSArray<NSString *> *inputKeys;
//輸出的鍵值信息
NSArray<NSString *> *outputKeys;
//返回濾鏡的屬性描述信息
NSDictionary<NSString *,id> *attributes;
//將所有輸入鍵值的值設(shè)為默認(rèn)值(曾經(jīng)亂用,導(dǎo)致我的濾鏡效果完全沒(méi)有任何反應(yīng)翔悠,差點(diǎn)懷疑人生...)
- (void)setDefaults;
//根據(jù)濾鏡的key查找其下面的所以子類(lèi)效果
+ (NSArray<NSString *> *)filterNamesInCategory:(nullable NSString *)category

  • 3 . CIContext 在創(chuàng)建結(jié)果圖片的時(shí)候需要用到业崖,剛開(kāi)始用的時(shí)候,出于好奇用了兩種不同的方法來(lái)返回結(jié)果蓄愁,本以為....我會(huì)有一個(gè)方式獲取不到處理后的結(jié)果双炕,然而大跌眼鏡,居然有....
    CIImage *outPutImage = [ciFilter outputImage];
    //獲取上下文
    CIContext *context = [CIContext contextWithOptions:nil];
    
    CGImageRef cgImage = [context createCGImage:outPutImage fromRect:outPutImage.extent];
    
    UIImage *filter_image = [UIImage imageWithCGImage:cgImage];
    CGImageRelease(cgImage);
    
    
//    UIImage *filter_image = [UIImage imageWithCIImage:outPutImage];

就是上面屏蔽的代碼部分imageWithCIImage撮抓,這就使我納悶了妇斤,于是猜測(cè)并查閱資料,原來(lái)在調(diào)用該方法的時(shí)候丹拯,其實(shí)是隱式的聲明了CIContext趟济,這樣看來(lái),哇咽笼!好簡(jiǎn)單,省了我一堆代碼戚炫,然而剑刑,這卻引起另外的問(wèn)題了,就是每次都會(huì)重新創(chuàng)建一個(gè) CIContext双肤,然而 CIContext的代價(jià)是非常高的施掏。并且,CIContextCIImage 對(duì)象是不可變的茅糜,在線程之間共享這些對(duì)象是安全的七芭。所以多個(gè)線程可以使用同一個(gè) GPU 或者 CPU
CIContext對(duì)象來(lái)渲染 CIImage 對(duì)象。所以我們不應(yīng)該使用 imageWithCIImage 來(lái)生成UIImage蔑赘,而應(yīng)該用上述另外一種方式來(lái)獲取結(jié)果圖像狸驳。

Core Image 效率

Core Image在處理圖像的時(shí)候预明,可以有兩種選擇GPUCPU耙箍,在Context中可以對(duì)其進(jìn)行設(shè)置撰糠,通過(guò)設(shè)置鍵值,這里的鍵值為kCIContextUseSoftwareRenderer辩昆,默認(rèn)情況下阅酪,是為GPU處理方式,如果將其設(shè)置為YES汁针,則為CPU處理
如下

//CPU處理
CIContext *context = [CIContext contextWithOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]];

如果通過(guò)GPU的話术辐,速度就會(huì)更快,利用了GPU硬件的并行優(yōu)勢(shì)施无,可以使用 OpenGLES 或者Metal 來(lái)渲染圖像辉词,這種方式CPU完全沒(méi)有負(fù)擔(dān),應(yīng)用程序的運(yùn)行循環(huán)不會(huì)受到圖像渲染的影響帆精。但是也有個(gè)問(wèn)題较屿,就是如果APP運(yùn)行到后臺(tái)的時(shí)候,GPU就會(huì)停止處理卓练,等回到前臺(tái)的時(shí)候又繼續(xù)隘蝎,而如果采取CPU來(lái)處理的話,就不會(huì)出現(xiàn)這么一種情況襟企,在前面的圖中嘱么,我們可以看到CPU是采用GCD的方式來(lái)對(duì)圖像進(jìn)行渲染。所以在使用的時(shí)候顽悼,還是需要分情況曼振,如果是處理復(fù)雜的操作,比如高斯模糊這樣的蔚龙,建議還是用GPU來(lái)處理冰评,可以節(jié)省CPU的開(kāi)銷(xiāo),如果在后臺(tái)還需要操作的話木羹,可以使用CPU來(lái)操作甲雅。

    //
    CIImage *outPutImage = [ciFilter outputImage];
    //獲取上下文
    CIContext *context = [CIContext contextWithOptions:nil];
    
    CGImageRef cgImage = [context createCGImage:outPutImage fromRect:outPutImage.extent];
    
    UIImage *filter_image = [UIImage imageWithCGImage:cgImage];
    CGImageRelease(cgImage);

上面的這段代碼是通過(guò)GPU的方式來(lái)處理圖像,然后得到結(jié)果UIImage坑填,最后再賦值給UIImageView抛人。

1.jpg

分析下這個(gè)過(guò)程:
1、將圖像上傳到GPU脐瑰,然后進(jìn)行濾鏡處理
2妖枚、得到CGImageRef cgImage的時(shí)候,又將圖像復(fù)制到了CPU
3苍在、在賦值給UIImageView進(jìn)行顯示的時(shí)候绝页,又需要通過(guò)GPU處理位圖數(shù)據(jù)荠商,進(jìn)行渲染
這樣的話,我們就在GPU-CPU-GPU上循環(huán)操作抒寂,在性能上肯定是有一定的損耗的结啼,那么為了避免這種問(wèn)題,我們?cè)撨@怎么辦呢屈芜?
查看API郊愧,我們可以看到有這么一個(gè)函數(shù)

+ (CIContext *)contextWithEAGLContext:(EAGLContext *)eaglContext

EAGLContext:是基于OpenGL ES的上下文
通過(guò)上面的函數(shù),我們通過(guò)OpenGL ES的上下文創(chuàng)建的Core Image的上下文就可以實(shí)時(shí)渲染了井佑,并且渲染圖像的過(guò)程始終在 GPU上進(jìn)行属铁,但是要顯示圖像,又該怎么辦呢躬翁?如果還是用UIImageView的話焦蘑,那么勢(shì)必會(huì)回到CPU上,這里盒发,我們可以用GLKView例嘱,一個(gè)屬于GLKIT中的類(lèi),通過(guò)GLKView和其屬性@property (nonatomic, retain) EAGLContext *context來(lái)將圖像繪制出來(lái)宁舰,這樣的話拼卵,就能保證我們的濾鏡,一直在GPU上進(jìn)行蛮艰,大大的提高效率腋腮。
針對(duì)該方案,我自定義了一個(gè)類(lèi)似UIImageView的類(lèi)FilterImageView

//FilterImageView.h
#import <GLKit/GLKit.h>

@interface FilterImageView : GLKView

@property (nonatomic,strong) UIImage *image;

@property (nonatomic,strong) CIFilter *filter;

@end

.m文件核心代碼

//FilterImageView.m
- (id)initWithFrame:(CGRect)frame
{
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    self = [super initWithFrame:frame context:context];
    if (self) {
        
        _ciContext = [CIContext contextWithEAGLContext:context options:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:kCIContextUseSoftwareRenderer]];
        //超出父視圖 進(jìn)行剪切
        self.clipsToBounds = YES;
    }
    return self;
}

- (void)drawRect:(CGRect)rect
{
    if (_ciContext && _image) {
        //得到CIImage
        CIImage *inputCIImage = [[CIImage alloc] initWithImage:_image];
        
        CGRect inRect = [self imageBoundsForContentModeWithFromRect:inputCIImage.extent
                                                             toRect:CGRectMake(0, 0, self.drawableWidth, self.drawableHeight)];
        
        if (_filter) {
            [_filter setValue:inputCIImage forKey:kCIInputImageKey];
            //根據(jù)filter得到輸出圖像
            if (_filter.outputImage) {
                //渲染開(kāi)始
                [_ciContext drawImage:_filter.outputImage
                               inRect:inRect
                             fromRect:inputCIImage.extent];
            }
        }else{
            [_ciContext drawImage:inputCIImage
                           inRect:inRect
                         fromRect:inputCIImage.extent];
        }
    }
}

如此之后壤蚜,我們就能提高濾鏡的效率即寡,特別是一些復(fù)雜的。
關(guān)于濾鏡袜刷,能寫(xiě)的就只要這么多了聪富,在學(xué)習(xí)中,也確實(shí)發(fā)現(xiàn)這是一個(gè)好東西著蟹,可以做很多炫酷的東西出來(lái)善涨,為此,特意做了一個(gè)簡(jiǎn)單的Demo草则,目前還未完善,希望各位勿噴蟹漓。

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末葡粒,一起剝皮案震驚了整個(gè)濱河市份殿,隨后出現(xiàn)的幾起案子膜钓,更是在濱河造成了極大的恐慌,老刑警劉巖卿嘲,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件颂斜,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡拾枣,警方通過(guò)查閱死者的電腦和手機(jī)沃疮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)梅肤,“玉大人司蔬,你說(shuō)我怎么就攤上這事∫毯” “怎么了俊啼?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)左医。 經(jīng)常有香客問(wèn)我授帕,道長(zhǎng),這世上最難降的妖魔是什么浮梢? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任跛十,我火速辦了婚禮,結(jié)果婚禮上黔寇,老公的妹妹穿的比我還像新娘偶器。我一直安慰自己,他們只是感情好缝裤,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布屏轰。 她就那樣靜靜地躺著,像睡著了一般憋飞。 火紅的嫁衣襯著肌膚如雪霎苗。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,166評(píng)論 1 284
  • 那天榛做,我揣著相機(jī)與錄音唁盏,去河邊找鬼。 笑死检眯,一個(gè)胖子當(dāng)著我的面吹牛厘擂,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播锰瘸,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼刽严,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了避凝?” 一聲冷哼從身側(cè)響起舞萄,我...
    開(kāi)封第一講書(shū)人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤眨补,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后倒脓,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體撑螺,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年崎弃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了甘晤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吊履,死狀恐怖安皱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情艇炎,我是刑警寧澤酌伊,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站缀踪,受9級(jí)特大地震影響居砖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驴娃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一奏候、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧唇敞,春花似錦蔗草、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至旷档,卻和暖如春模叙,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背鞋屈。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工范咨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人厂庇。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓渠啊,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親权旷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子替蛉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

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

  • 前言 最近在研究 Core Image 自定義 Filter 相關(guān)內(nèi)容,重新學(xué)習(xí)了 Core Image,對(duì) Co...
    泥孩兒0107閱讀 755評(píng)論 0 4
  • 原鏈接:http://www.csdn.net/article/2015-02-13/2823961-core-i...
    hament閱讀 992評(píng)論 0 1
  • --繪圖與濾鏡全面解析 概述 在iOS中可以很容易的開(kāi)發(fā)出絢麗的界面效果灭返,一方面得益于成功系統(tǒng)的設(shè)計(jì),另一方面得益...
    韓七夏閱讀 2,710評(píng)論 2 10
  • 許多UIView的子類(lèi)坤邪,如一個(gè)UIButton或一個(gè)UILabel熙含,它們知道怎么繪制自己。遲早艇纺,你也將想要做一些自...
    shenzhenboy閱讀 1,626評(píng)論 2 8
  • Core Image是一個(gè)強(qiáng)大的框架怎静,它能夠讓你輕松地對(duì)圖像進(jìn)行過(guò)濾。你能夠通過(guò)修改圖像的飽和度黔衡、色調(diào)或曝光率來(lái)獲...
    木易林1閱讀 1,125評(píng)論 0 1