iOS CoreImage之濾鏡簡單使用

代碼地址以下:
http://www.demodashi.com/demo/11605.htmlhtml

老驥伏櫪,志在千里api

前記

最近一直在研究圖像處理方面,既上一篇iOS Quart2D繪圖之UIImage簡單使用後,就一直在學習關於CoreImage圖像濾鏡處理。中間也看了很多文章,也獲得了很多幫助,下面就結合這些知識和我本身的認識,記錄一下,方便本身,方便他人安全

  • 做用:對圖像進行濾鏡操做,好比模糊、顏色改變、銳化、人臉識別等。
  • Core Graphics對比:基於Quartz 2D繪圖引擎的繪圖API,經過它能夠進行繪圖功能、經常使用的剪切裁剪合成等。
簡介

Core Image是一個很強大的框架。它可讓你簡單地應用各類濾鏡來處理圖像,好比修改鮮豔程度, 色澤, 或者曝光。 它利用GPU(或者CPU)來很是快速、甚至實時地處理圖像數據和視頻的幀。而且隱藏了底層圖形處理的全部細節,經過提供的API就能簡單的使用了,無須關心OpenGL或者OpenGL ES是如何充分利用GPU的能力的,也不須要你知道GCD在其中發揮了怎樣的做用,Core Image處理了所有的細節多線程

大概方式.png

實現方式

Core Image濾鏡須要一副輸入圖像(生成圖像的濾鏡除外)以及一些定製濾鏡行爲的參數。被請求時,Core Image將濾鏡應用於輸入圖像,並提供一副輸出圖像。在應用濾鏡方面,Core Image的效率極高:僅當輸出圖像被請求時才應用濾鏡,而不是在指定時就應用它們;另外,Core Image儘量將濾鏡合併,以最大限度地減小應用濾鏡的計算量。框架

涉及API
  • CIImage :這是一個模型對象,它保存能構建圖像的數據,能夠是圖像的Data,能夠是一個文件,也能夠是CIFilter輸出的對象。
  • CIContext :上下文,是框架真正工做的地方,它須要分配必要的內存,並編譯和運行濾鏡內核來執行圖像處理。創建一個上下文是很是昂貴的,因此你會常常想建立一個反覆使用的上下文。
  • CIFilter :濾鏡對象,主要是對圖像進行處理的類。經過設置一些鍵值來控制濾鏡的具體效果
    ******
    注:Core ImageCore Graphics使用的是左下原點座標

到此有一個疑問?就是蘋果怎麼會弄出這麼多image,好比CIImageUIImageCGImageRef,有什麼區別呢?爲了弄清這個問題,我也特別搜尋了一番,下面也記錄一下函數

UIImage:管理圖片數據,主要用來展示,Image對象並無提供直接訪問相關的圖片數據的操做, 所以你老是經過已經存在的圖片數據來建立它性能

CGImage:是基於像素的矩陣,每一個點都對應了圖片中點的像素信息學習

CIImage:包含了建立圖片的全部必要的數據,但其自己沒有渲染成圖片,它表明的是圖像數據或者生成圖像數據的流程(如濾鏡)。擁有與之關聯的圖片數據, 但本質上並非一張圖片,你能夠CIImage對象做爲一個圖片的"配方"。CIImage對象擁有生成一張圖片所具有的全部信息,但Core Image並不會真正的去渲染一張圖片, 除非被要求這麼作。
*******atom

使用方式
  • 1 . CIImage建立,在使用濾鏡以前,你必需要先有一個CIImage對象,在擁有該對象後結合CIFilter才能實現咱們的濾鏡效果。這裏須要注意的是,若是直接使用image.cIImage,那麼很遺憾的告訴你,你將獲得一個nil,哈哈
    以下:

image.CIImage.png
緣由在UIImageAPI中有介紹// returns underlying CIImage or nil if CGImageRef based,應該是說圖片可能不是基於CIImage而建立的
正確的方式爲線程

//獲得CIImage
 CIImage *inputCIImage = [[CIImage alloc] initWithImage:_image];
  • 2 . CIFilter建立方式大概有下面三種
+(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);

方法上都差很少,只是後面兩個在初始化的時候加入了一些鍵值,在API文檔中,能夠查到不少鍵值,這裏須要說明下,鍵值kCIInputImageKey是咱們必需要設置的,這是爲咱們的濾鏡對象設置輸入圖像,圖像的值爲CIImage對象,方法以下

[_filter setValue:inputCIImage forKey:kCIInputImageKey];

方法中的name就是咱們須要用的濾鏡效果,具體效果,能夠在官網上面進行查詢,以下

filter.png
下面,咱們以沖印效果爲例,沖印屬於CICategoryColorEffect中的CIPhotoEffectProcess

//建立濾鏡對象
CIFilter *ciFilter = [CIFilter filterWithName:@"CIPhotoEffectProcess" keysAndValues:kCIInputImageKey,ciImage, nil];

大概效果以下

沖印.png


注意:
一、在設置鍵值的時候,咱們須要有選擇性的進行設置,具體怎麼選擇呢?
好比上面的沖印效果,在官方文檔是這麼展現的

沖印展現.png
只有一個必須輸入的inputImage,所以不須要其它參數就能夠實現
又好比高斯模糊CIGaussianBlur,在官方文檔中,是這麼展現的

高斯模糊展現.png
若是咱們須要控制其模糊半徑,能夠這麼設置

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

二、CIFilter 並非線程安全的,這意味着 一個 CIFilter對象不能在多個線程間共享。若是你的操做是多線程的,每一個線程都必須建立本身的 CIFilter 對象,而CIContextCIImage對象都是不可修改的, 意味着它們能夠在線程之間安全的共享。多個線程可使用一樣的GPU或者CPUCIContext對象來渲染CIImage對象

CIFilter類中,還有一些其餘函數,多是咱們須要用到的,這裏也簡單說明下

//輸入的鍵值信息
NSArray<NSString *> *inputKeys;
//輸出的鍵值信息
NSArray<NSString *> *outputKeys;
//返回濾鏡的屬性描述信息
NSDictionary<NSString *,id> *attributes;
//將全部輸入鍵值的值設爲默認值(曾經亂用,致使個人濾鏡效果徹底沒有任何反應,差點懷疑人生...)
- (void)setDefaults;
//根據濾鏡的key查找其下面的因此子類效果
+ (NSArray<NSString *> *)filterNamesInCategory:(nullable NSString *)category

  • 3 . CIContext 在建立結果圖片的時候須要用到,剛開始用的時候,出於好奇用了兩種不一樣的方法來返回結果,本覺得....我會有一個方式獲取不處處理後的結果,然而大跌眼鏡,竟然有....
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,這就使我納悶了,因而猜想並查閱資料,原來在調用該方法的時候,實際上是隱式的聲明瞭CIContext,這樣看來,哇!好簡單,省了我一堆代碼,然而,這卻引發另外的問題了,就是每次都會從新建立一個 CIContext,然而 CIContext的代價是很是高的。而且,CIContextCIImage 對象是不可變的,在線程之間共享這些對象是安全的。因此多個線程可使用同一個 GPU 或者 CPU
CIContext對象來渲染 CIImage 對象。因此咱們不該該使用 imageWithCIImage 來生成UIImage,而應該用上述另一種方式來獲取結果圖像。

Core Image 效率

Core Image在處理圖像的時候,能夠有兩種選擇GPUCPU,在Context中能夠對其進行設置,經過設置鍵值,這裏的鍵值爲kCIContextUseSoftwareRenderer,默認狀況下,是爲GPU處理方式,若是將其設置爲YES,則爲CPU處理
以下

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

若是經過GPU的話,速度就會更快,利用了GPU硬件的並行優點,可使用 OpenGLES 或者Metal 來渲染圖像,這種方式CPU徹底沒有負擔,應用程序的運行循環不會受到圖像渲染的影響。可是也有個問題,就是若是APP運行到後臺的時候,GPU就會中止處理,等回到前臺的時候又繼續,而若是採起CPU來處理的話,就不會出現這麼一種狀況,在前面的圖中,咱們能夠看到CPU是採用GCD的方式來對圖像進行渲染。因此在使用的時候,仍是須要分狀況,若是是處理複雜的操做,好比高斯模糊這樣的,建議仍是用GPU來處理,能夠節省CPU的開銷,若是在後臺還須要操做的話,可使用CPU來操做。

//
    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);

上面的這段代碼是經過GPU的方式來處理圖像,而後獲得結果UIImage,最後再賦值給UIImageView

1.jpg

分析下這個過程:
一、將圖像上傳到GPU,而後進行濾鏡處理
二、獲得CGImageRef cgImage的時候,又將圖像複製到了CPU
三、在賦值給UIImageView進行顯示的時候,又須要經過GPU處理位圖數據,進行渲染
這樣的話,咱們就在GPU-CPU-GPU上循環操做,在性能上確定是有必定的損耗的,那麼爲了不這種問題,咱們該這怎麼辦呢?
查看API,咱們能夠看到有這麼一個函數

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

EAGLContext:是基於OpenGL ES的上下文
經過上面的函數,咱們經過OpenGL ES的上下文建立的Core Image的上下文就能夠實時渲染了,而且渲染圖像的過程始終在 GPU上進行,可是要顯示圖像,又該怎麼辦呢?若是仍是用UIImageView的話,那麼勢必會回到CPU上,這裏,咱們能夠用GLKView,一個屬於GLKIT中的類,經過GLKView和其屬性@property (nonatomic, retain) EAGLContext *context來將圖像繪製出來,這樣的話,就能保證咱們的濾鏡,一直在GPU上進行,大大的提升效率。
針對該方案,我自定義了一個相似UIImageView的類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]];
        //超出父視圖 進行剪切
        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];
            //根據filter獲得輸出圖像
            if (_filter.outputImage) {
                //渲染開始
                [_ciContext drawImage:_filter.outputImage
                               inRect:inRect
                             fromRect:inputCIImage.extent];
            }
        }else{
            [_ciContext drawImage:inputCIImage
                           inRect:inRect
                         fromRect:inputCIImage.extent];
        }
    }
}
項目文件截圖:


如此以後,咱們就能提升濾鏡的效率,特別是一些複雜的。
關於濾鏡,能寫的就只要這麼多了,在學習中,也確實發現這是一個好東西,能夠作不少炫酷的東西出來,爲此,特地作了一個簡單的[Demo],目前還未完善,但願各位勿噴。iOS CoreImage之濾鏡簡單使用

代碼地址以下:
http://www.demodashi.com/demo/11605.html

注:本文著做權歸做者,由demo大師代發,拒絕轉載,轉載須要做者受權

相關文章
相關標籤/搜索