本文授權轉載,作者:吳白
Core Image初見
Core Image是一個OS X和iOS的圖像處理框架,Core Image很強大,不僅可以做圖片處理,還可以做人臉識別等多種工作。它有兩個基本概念:濾鏡和濾鏡圖表。一個濾鏡是一個對象,有很多輸入和輸出,並執行一些變換。例如,模糊濾鏡可能需要輸入圖像和一個模糊半徑來產生適當的模糊後的輸出圖像。一個濾鏡圖表是一個鏈接在一起的濾鏡網絡,使得一個濾鏡的輸出可以是另一個濾鏡的輸入。以這種方式,可以實現精心制作的效果。
Core Image有一個插件架構,它允許用戶編寫自定義的濾鏡並與系統提供的濾鏡集成來擴展其功能。提供了強大高效的圖像處理功能,用來對基於像素的圖像進行操作與分析。Core Image 最大化利用其所運行之上的硬件。每個濾鏡實際上的實現,即內核,是由一個GLSL(即OpenGL的著色語言)的子集來書寫的。當多個濾鏡連接成一個濾鏡圖表,Core Image便把內核串在一起來構建一個可在GPU上運行的高效程序。只要有可能,Core Image都會把工作延遲。通常情況下,直到濾鏡圖表的最後一個濾鏡的輸出被請求之前都不會發生分配或處理。為了完成工作,Core Image需要一個稱為上下文(context)的對象。這個上下文是框架真正工作的地方,它需要分配必要的內存,並編譯和運行濾鏡內核來執行圖像處理。建立一個上下文是非常昂貴的,所以你會經常想創建一個反復使用的上下文。
Core Image框架中的對象
CIImage
CIImage是CoreImage框架中最基本代表圖像的對象,它不僅包含元圖像數據,還包含作用在原圖像上的濾鏡鏈。在CIImage被CIContext渲染出來之前,它是依賴於濾鏡鏈的,濾鏡是不會更改CIImage中的圖像數據。關於創建CIImage,CIImage是不能直接有UIImage轉化而來的,有以下幾種創建CIImage的類方法:
1.CIImage *image=[CIImage imageWithContentsOfURL:myURL]; 2.CIImage *image=[CIImage imageWithData:myData]; 3.CIImage *image=[CIImage imageWithCGImage:myCgimage]; 4.CIImage *image=[CIImage imageWithCVPixelBuffer:CVBuffer];
更多可以參考開發者文檔CIImage。
CIFilter
CIFilter用來表示CoreImage提供的各種濾鏡。濾鏡使用鍵-值來設置輸入值,一旦這些值設置好,CIFilter就可以用來生成新的CIImage輸出圖像了。這裡的輸出的圖像不會進行實際的圖像渲染,他只包含一個對輸入圖像的引用以及需要應用與數據上的濾鏡鏈。iOS永遠在最佳的時間選擇渲染圖像。
CIFilter提供了一個簡單的方法查詢可用的濾鏡種類:
[CIFilter filterNamesInCategory:kCICategoryBuiltIn];//搜索屬於kCICategoryBuiltIn(A filter provided by Core Image)類別的所有濾鏡名字,返回一個數組 [CIFilter filterNamesInCategories];//搜索所有可用的濾鏡名稱 [CIFilter attributes]//會返回filter詳細信息
下面是我程序返回的一個叫做CISepiaTone濾鏡返回的詳細信息:
舉例分析如下:
CIAttributeFilterCategories = (//濾鏡所示種類,通常一個濾鏡可以屬於幾種 CICategoryColorEffect, //總類,這只是根據濾鏡效果,作用來分類的 CICategoryVideo, //可以用種類名來搜索Fileter; CICategoryInterlaced, CICategoryNonSquarePixels, CICategoryStillImage, CICategoryBuiltIn ); CIAttributeFilterDisplayName = "Sepia Tone"; CIAttributeFilterName = CISepiaTone; //濾鏡的名稱,通過該名稱來 inputImage = { //濾鏡使用需要輸入的參數,該 CIAttributeClass = CIImage; //參數類型為CIImage。 CIAttributeType = CIAttributeTypeImage; }; inputIntensity = { //輸入強度,參數的名稱 CIAttributeClass = NSNumber; //類型 CIAttributeDefault = 1; //默認值 CIAttributeIdentity = 0; CIAttributeMax = 1; //最大值 CIAttributeMin = 0; //最小值 CIAttributeSliderMax = 1; CIAttributeSliderMin = 0; CIAttributeType = CIAttributeTypeScalar; }; }
程序中使用CISepiaTone的代碼為:
CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone"]; [filter setValue:inputImage forKey:@"inputImage"]; [filter setValue:[NSNumber numberWithFloat:0.8] forKey:@"inputIntensity"];
第一行:指定使用哪一個過濾器,通過[CIFilter filterNamesInCategory: kCICategoryBuiltIn]能得到所有過濾器的列表
第二行:指定需要處理的圖片
第三行:指定過濾參數,每個過濾器的參數都不一樣,可以在官方文檔裡搜索“Core Image Filter Reference”查看
CIContext
CIContext用來渲染CIImage,將作用在CIImage上的濾鏡鏈應用到原始的圖片數據中。CIContext可以是基於CPU的,也可以是基於GPU的,這兩種渲染的區別是:使用CPU渲染的iOS會采用GCD來對圖像進行渲染,這保證了CPU渲染在大部分情況下更可靠,比CPU渲染更容易使用,可以在後台實現渲染過程;而GPU渲染方式使用OpenGL ES2.0來渲染圖像,這種方式CPU完全沒有負擔,應用程序的運行循環不會受到圖像渲染的影響,而且渲染比CPU渲染更快但是GPU渲染無法在後台運行。
對於復雜的圖像濾鏡使用GPU更好,但是如果在處理視頻並保存文件,或保存照片到照片庫中時為避免程序退出對圖片保存造成影響,這時應該使用CPU進行渲染。默認情況是用CPU渲染的。
//CPU渲染 CIContext *context = [CIContext contextWithOptions:[NSDictionary dictionaryWithObject:[NSNumber numberWithBool:YES] forKey:kCIContextUseSoftwareRenderer]]; // 創建基於GPU的CIContext對象 CIContext * context = [CIContext contextWithOptions: nil]; // 基於GPU EAGLContext *glContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2]; CIContext *context = [CIContext contextWithEAGLContext:glContext];
GPU受限於硬件紋理尺寸,如果你的程序在後台繼續處理和保存圖片的話,需要使用CPU,因為當app切換到後台狀態時GPU處理會被打斷。基於GPU的CIContext對象無法跨應用訪問。比如你打開UIImagePickerController要選張照片進行美化,如果你直接在UIImagePickerControllerDelegate的委托方法裡調用CIContext對象進行處理,那麼系統會自動將其降為基於CPU的,速度會變慢,所以正確的方法應該是在委托方法裡先把照片保存下來,回到主類裡再來處理。
渲染後的圖片使用:
1.在imageView中使用:
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"]; NSURL *fileNameAndPath = [NSURL fileURLWithPath:filePath]; CIImage *beginImage = [CIImage imageWithContentsOfURL:fileNameAndPath]; CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone" keysAndValues: kCIInputImageKey, beginImage, @"inputIntensity", @0.8, nil]; CIImage *outputImage = [filter outputImage]; UIImage *newImage = [UIImage imageWithCIImage:outputImage]; self.imageView.image = newImage;
結果
2.將圖片保存到photoLibrary
需要引入#import
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"image" ofType:@"png"]; NSURL *fileNameAndPath = [NSURL fileURLWithPath:filePath]; CIImage *beginImage = [CIImage imageWithContentsOfURL:fileNameAndPath]; CIFilter *filter = [CIFilter filterWithName:@"CISepiaTone" keysAndValues: kCIInputImageKey, beginImage, @"inputIntensity", @0.8, nil]; CIImage *saveToSave = [filter outputImage]; CIContext *softwareContext = [CIContext contextWithOptions:@{kCIContextUseSoftwareRenderer : @(YES)} ]; CGImageRef cgImg = [softwareContext createCGImage:saveToSave fromRect:[saveToSave extent]]; ALAssetsLibrary* library = [[ALAssetsLibrary alloc] init]; [library writeImageToSavedPhotosAlbum:cgImg metadata:[saveToSave properties] completionBlock:^(NSURL *assetURL, NSError *error) { CGImageRelease(cgImg); }];
CIDetector和CIFeature
CIDetector用來分析CIImage,得到CIFeature。每個CIDetector都要用一個探測器來初始化,這個類型高數探測器要在圖像中尋找什麼特征。當一個CIDetector分析一張圖片時,返回一個探測到的CIFeature的數組,如果CIDetector 被初始化為尋找面孔,那麼返回的數組會被填上CIFaceFeature對象,每個CIFaceFeature都包含一個面部的CGrect引用(按照圖像的坐標系),以及檢測到的面孔的左眼,右眼,嘴部位置的CGPoint;
CIContext *context = [CIContext contextWithOptions:nil]; UIImage *imageInput = [_inputImageView image]; CIImage *image = [CIImage imageWithCGImage:imageInput.CGImage]; //設置識別參數 NSDictionary *param = [NSDictionary dictionaryWithObject:CIDetectorAccuracyHigh forKey:CIDetectorAccuracy]; //聲明一個CIDetector,並設定識別類型 CIDetector* faceDetector = [CIDetector detectorOfType:CIDetectorTypeFace context:context options:param]; //取得識別結果 NSArray *detectResult = [faceDetector featuresInImage:image]; NSMutableArray *arrM = [NSMutableArray arrayWithCapacity: detectResult.count]; UIView *resultView = [[UIView alloc] initWithFrame:_inputImageView.frame]; [self.view addSubview:resultView]; for(CIFaceFeature* faceFeature in detectResult) { //臉部 UIView* faceView = [[UIView alloc] initWithFrame:faceFeature.bounds]; faceView.layer.borderWidth = 1; faceView.layer.borderColor = [UIColor orangeColor].CGColor; [resultView addSubview:faceView]; //左眼 if (faceFeature.hasLeftEyePosition) { [arrM addObject:[NSValue valueWithCGPoint:faceFeature.hasLeftEyePosition]]; } //右眼 if (faceFeature.hasRightEyePosition) { } //嘴巴 if (faceFeature.hasMouthPosition) { } } [resultView setTransform:CGAffineTransformMakeScale(1, -1)];
CoreImage關注點
CoreImage在iOS上有很高的效率,但是濾鏡和渲染操作也會對主線程造成影響。應該將CoreImage濾鏡渲染操作放在後台線程執行,當這些操作結束後再返回主線程進行界面的更新。
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void){ //CGImageRef cgImage = [self autoAdjustImage]; NSArray *filters; // Create Core Image CGImageRef cgImg = self.imageView.image.CGImage; CIImage *coreImage = [CIImage imageWithCGImage:cgImg]; // Iterate through all of our filters and apply // them to the CIImage for(CIFilter *filter in filters){ [filter setValue:coreImage forKey:kCIInputImageKey]; coreImage = filter.outputImage; } // Create a new CGImageRef by rendering through CIContext // This won't slow down main thread since we're in a background // dispatch queue CGImageRef newImg = [self.imageContext createCGImage:coreImage fromRect:[coreImage extent]]; dispatch_async(dispatch_get_main_queue(), ^(void){ // Update our image view on the main thread // You can also perform any other UI updates needed // here such as hidding activity spinners self.imageView.image = [UIImage imageWithCGImage:newImg]; [self.adjustSpinner stopAnimating]; [sender setEnabled:YES]; }); });
上面這段代碼,就是為了防止阻塞主線程,用GCD異步執行濾鏡與渲染操作,在獲取渲染後的照片以後,返回主線程進行界面的更新。
其次,不要重復應用濾鏡,即使是同一個濾鏡也不要應用兩次,因為濾鏡後輸出照片包含濾鏡鏈,在進行照片渲染是會將濾鏡鏈效果疊加到原始數據上,這時會造成問題。比如,有一個CIImage,上面配置了強度為0.5的棕色濾鏡,現在通過滑塊將強度改為0.6,這個濾鏡應該用在新的CIImage上,如果不是新的CIImage上,那麼原來的CIImage中將包含強度為0.5和0.6的棕色濾鏡,而我們只想0.6的棕色濾鏡,這樣就造成錯誤,這一點在編寫程序的時候一定要切忌。
app中應用的濾鏡太多,改變速率太快,如果是根據滑塊來產生事件的話,一定要注意在使用滑條值前要首先判斷更改的濾鏡當前是否正在起作用,如果該濾鏡正在生成新的渲染圖片,則應該這次滑塊的更新。這一點是很重要的,弄的不好常常導致程序崩潰,出現內存洩露問題。
在使用CoreImage時,一定要記住CIImage對象在開始時不會操作圖像數據,直到使用CIContext渲染圖片時才會這麼做。最好在後台執行圖像處理的操作,然後在主線程中修改界面。
CoreImage處理圖像的流程:
1:創建一個新的CIImage;
2:創建一個行的CIFIlter,並通過鍵-值設置各種輸入值,這些值有些是有默認值的,有些沒有默認值,需要編程者的設置;
3:從CIFilter中生成輸出圖像,如果存在濾鏡鏈則將輸出圖像作為輸入參數傳入到下一個濾鏡,跳回步驟2繼續進行,如果到達濾鏡末,則調用CIContext渲染CIImage對象。這個context可以是基於CPU或GPU的,基於CPU的產出CGImageRef對象,基於GPU的調用OpenGL ES在屏幕上畫出結果,默認是基於CPU的。
請記住:代碼量是提高開發水平的唯一途徑,知識點是為了服務開發而來的,千萬不要本末倒置。
如果您想深入學習,可以參考下面的參考文檔。
參考文檔:Core Image Filter Reference