天天看點

iOS Core Image 濾鏡實踐

最近因為項目需求,接觸了Core Image,實作了實時摳圖,人臉貼紙。

決定把這次實踐分享給大家,作為對開源社群的回饋。

相關代碼上傳在 GitHub

Core Image 介紹

以下是蘋果官方文檔給出 Core Image 的介紹:

Core Image is an image processing and analysis technology designed to provide near real-time processing for still and video images. It operates on image data types from the Core Graphics, Core Video, and Image I/O frameworks, using either a GPU or CPU rendering path. Core Image hides the details of low-level graphics processing by providing an easy-to-use application programming interface (API). You don’t need to know the details of OpenGL, OpenGL ES, or Metal to leverage the power of the GPU, nor do you need to know anything about Grand Central Dispatch (GCD) to get the benefit of multicore processing. Core Image handles the details for you.

翻譯過來的大意是:

Core Image 是一個被設計用來給圖檔和視訊提供實時的圖像處理和分析技術.它可以使用 GPU 或 CPU 渲染,操作來自 Core Graphics, Core Video,以及Image I/O 架構的圖檔資料類型. Core Image 隐藏了底層圖形處理的細節,提供簡單易用的應用程式 API 接口.你不需要知道關于OpenGL, OpenGL ES,或者 Metal 的細節來利用 GPU 的能力,也不需要你知道任何關于 GCD 的知識來進行多線程處理的優化。

CIFilter

CIFilter,是用來表示 Core Image 的各種濾鏡。通過提供對應的鍵值對,來設定濾鏡的輸入參數。這些值設定好,CIFilter就可以用來生成新的CIImage輸出圖像。

CIImage

CIImage 是 Core Image 架構中最基本代表圖像的對象。可以通過以下幾種方式來建立 CIImage:

//通過 NSURL 建立

CIImage*image=[CIImage imageWithContentsOfURL:myURL];

//通過 NSData 建立

CIImage*image=[CIImage imageWithData:myData];

//通過 CGImage 建立

CIImage*image=[CIImage imageWithCGImage:myCgimage];

//通過 CVPixelBuffer 建立

CIImage*image=[CIImage imageWithCVPixelBuffer:CVBuffer];

複制代碼
           

CIContext

所有Core Image的處理流程都通過 CIContext 來進行。

CIContext 可以是基于 CPU 的,也可以是基于 GPU 的,這兩種渲染的差別是:

  • CPU 渲染方式會采用 GCD 多線程來對圖像進行渲染,這保證了 CPU 渲染在大部分情況下更可靠,他可以在背景實作渲染過程
  • GPU 渲染方式可以使用 OpenGLES 或者 Metal 來渲染圖像,這種方式 CPU 完全無負擔,應用程式的運作循環不會受到圖像渲染的影響,而且他渲染比 CPU 渲染更快。缺點是 GPU 渲染無法在背景運作。
//建立基于 GPU 的 CIContext 對象

//内部的渲染器會根據裝置最優選擇。依次為 Metal,OpenGLES,CoreGraphics。

CIContext *context = [CIContext contextWithOptions: nil];

//建立基于 GPU 的 CIContext 對象

//支援實時渲染,渲染始終在 GPU 上進行,不會複制回 CPU 存儲器上,這就保證了更快的渲染速度和更好的性能。

EAGLContext *eaglctx = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];

CIContext *context = [CIContext contextWithEAGLContext:eaglctx];

//建立基于CPU的CIContext對象

CIContext *context = [CIContext contextWithOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:KCIContextUseSoftWareRenderer]];

複制代碼
           

一般采用第一種基于GPU的,因為效率要比CPU高很多.

基礎濾鏡使用

你可以這樣來使用它

//将UIImage轉換成CIImage

CIImage *ciImage = [[CIImage alloc] initWithImage:self.originImage];

//建立濾鏡

CIFilter *filter = [CIFilter filterWithName:filterName

keysAndValues:kCIInputImageKey,ciImage, nil];

//已有的值不改變,其他的設為預設值

[filter setDefaults];

//建立一個CIContext對象

CIContext *context = [CIContext contextWithOptions:nil];

//濾鏡渲染并輸出一個CIImage

CIImage *outputImage = [filter outputImage];

//建立CGImage句柄

CGImageRef cgImage = [context createCGImage:outputImage fromRect:[outputImage extent]];

//擷取圖檔

UIImage *image = [UIImage imageWithCGImage:cgImage];

//釋放CGImage句柄

CGImageRelease(cgImage);

//得到處理後的image後指派

self.imageView.image = image;

複制代碼
           

濾鏡鍊

濾鏡鍊的使用也非常簡單,将濾鏡A的輸出圖像,當作濾鏡B的輸入圖像,就可以把濾鏡效果疊加起來。

如:

//得到要處理的圖檔

CIImage *ciImage=[[CIImage alloc] initWithImage:self.originImage];

//建立濾鏡A

CIFilter *filterA = [CIFilter filterWithName:filterNameA

//得到濾鏡A的輸出圖像                          keysAndValues:kCIInputImageKey,ciImage, nil];

CIImage *ciImageA=filterA.outputImage;

//建立濾鏡B,将濾鏡A的輸出圖像做為輸入圖像

CIFilter *filterB = [CIFilter filterWithName:filterNameB

keysAndValues:kCIInputImageKey,ciImageA, nil];

//得到最後的輸出圖像

CIImage *ciImageB=filterB.outputImage;

複制代碼
           

實時濾鏡

由于視訊本身是由一幀幀的圖像組成的,是以我們在添加濾鏡的時候,隻要處理每一幀的圖檔就好了。

是以在 AVCaptureVideoDataOutputSampleBufferDelegate

對應的方法裡做處理就ok了:

-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {

//得到要處理的圖檔

CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);

CIImage *image = [CIImage imageWithCVPixelBuffer:pixelBuffer];

image=[self inputCIImageForDetector:image];

/** 使用 CISourceOverCompositing 濾鏡,制定前後的圖檔關系,可以把兩張圖檔組合**/

CIImage *resulImage = [[CIFilter filterWithName:@"CISourceOverCompositing"

keysAndValues:kCIInputImageKey,image,kCIInputBackgroundImageKey,self.bgImage,nil]

valueForKey:kCIOutputImageKey];

[self.glkView bindDrawable];

if( !(self.glContext == [EAGLContext currentContext])) {

[EAGLContext setCurrentContext:self.glContext];

}

// clear eagl view to grey

glClearColor(0.5, 0.5, 0.5, 1.0);

glClear(GL_COLOR_BUFFER_BIT);

// set the blend mode to "source over" so that CI will use that

glEnable(GL_BLEND);

glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

[self.ciContext drawImage:resulImage

inRect:CGRectMake(0, 0, ScreenWidth*2, ScreenHeight*2)

fromRect:CGRectMake(0, 0, [image extent].size.width,[image extent].size.height) ];

[self.glkView display];

}

複制代碼
           

人臉檢測

Core Image 已經提供了 CIDetector 來做特征檢測,裡面包含了 CIDetectorTypeFace 的特征類型來做人臉識别。并且它已被優化過,使用起來也很容易。

//建立一個CIDetector對象

CIDetector *faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context:context options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];

//給出要識别的圖檔,得到一個人臉結果的數組

NSArray *faces = [faceDetector featuresInImage:image];

複制代碼
           

數組 faces 中儲存着 CIFaceFeature 類的執行個體。通過CIFaceFeature 的執行個體,不僅可以得到臉部的位置資訊,也可以知道眼睛和嘴巴的位置。

for (CIFeature *f in faces){

CIFaceFeature *faceFeature=(CIFaceFeature *)f;

// 判斷是否有左眼位置

if(faceFeature.hasLeftEyePosition){

}

// 判斷是否有右眼位置

if(faceFeature.hasRightEyePosition){

}

// 判斷是否有嘴位置

if(faceFeature.hasMouthPosition){

}

}

複制代碼
           

在這裡需要注意的是,Core Image 和 UIKit 使用了不同的坐标系: Core Image 是以左下角作為原點,UIKit 是以左上角為原點。需要用 仿射變換 将 Core Image 坐标轉換成了 UIKit 坐标。

參考資料:

Core Image Programming Guide

在iOS上用 Core Image 實作人臉檢測

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

繼續閱讀