初衷
CoreImage系列是關(guān)于最近學(xué)習(xí)CoreImage處理圖片和視頻的一些總結(jié)接癌,如果有高手看到有錯誤的地方請幫我指出來趁猴,免得誤導(dǎo)了大家杨伙。謝謝淘衙。
CoreImage一瞥
CoreImage是蘋果在iOS5出的一個對圖片和視頻的圖像數(shù)據(jù)進(jìn)行實時分析横浑、加工的圖像框架剔桨。通過運(yùn)用CPU或GPU進(jìn)行圖片處理,開發(fā)者不用太關(guān)注于底層的OpenGL等技術(shù)就能進(jìn)行強(qiáng)大的圖片編輯徙融。
它的核心類有以下幾個:
- CIContext:CoreImage的上下文,這個上下文是框架真正工作的地方洒缀,它需要分配必要的內(nèi)存,并編譯和運(yùn)行濾鏡內(nèi)核來執(zhí)行圖像處理欺冀。
- CIFilter:CoreImage進(jìn)行圖像的濾鏡處理的對象树绩。濾鏡可以單獨(dú)使用,也可以組成一個濾鏡鏈來進(jìn)行處理 ( 因為CoreImage并不是一個真正的圖片對象隐轩,而是一個圖片生成的"模板"饺饭,所以在運(yùn)用濾鏡鏈的時候,并不會對一個圖片進(jìn)行多次濾鏡职车,而是會把這些濾鏡在底層的kernel進(jìn)行像素處理混合瘫俊,所以只會處理一次圖片,效率大大增加 ) 悴灵。
- CIKernel:對圖片進(jìn)行處理的核心模塊军援,負(fù)責(zé)進(jìn)行像素變化等工作,最后返回加工完成的圖片称勋。開發(fā)者可以自定義Kernel進(jìn)行濾鏡開發(fā)胸哥。
。赡鲜。空厌。
當(dāng)然,還有一些其他的類银酬,在這里就不一一介紹嘲更,在接下來需要的時候會進(jìn)行介紹。
1.運(yùn)用CoreImage進(jìn)行圖片濾鏡
CoreImage進(jìn)行圖片的濾鏡開發(fā)其實還是挺簡單的揩瞪,最主要的是選擇合適的濾鏡赋朦,在CoreImage中提供了127個濾鏡進(jìn)行處理 ( 看今年的WWDC上的CoreImage session,好像今年又加入了很多濾鏡,達(dá)到了196個 ,Amazing! ) 宠哄。
選擇濾鏡首先選擇合適的濾鏡分類壹将,有些分類是用來處理圖片修補(bǔ)的、有些是用來合并或者轉(zhuǎn)場的等等毛嫉。在這兒我們就進(jìn)行簡單的進(jìn)行圖片的處理就行了诽俯。
首先我們需要生成一個CIFilter,我們可以通過filterNamesInCategory或者filterNamesInCategories來獲取到一個分類或多個分類的濾鏡 ( 如果你想獲取所有的 ,傳入nil就可以了 )承粤。
你拿到了filter的名字之后就可以用filterWithName來生成一個CIFilter:
CIFilter *filter = [CIFilter filterWithName:@"CIPhotoEffectMono"];
但是一般濾鏡都有一些參數(shù)可以設(shè)置的暴区,我們怎么知道這些參數(shù)喃?而且每個濾鏡的參數(shù)都可能不一樣靶岭O闪弧!
別著急彻舰,蘋果爸爸肯定不會為難我們的伐割。
我們可以獲取filter的inputKeys和outputKeys來獲取輸入?yún)?shù)和輸出參數(shù)。而且這些參數(shù)是接受什么參數(shù)也有標(biāo)志淹遵,當(dāng)然口猜,蘋果的官方文檔也是有個,傳送門透揣。
接下來我們就可以傳入圖片了济炎,傳圖片直接用KVC進(jìn)行傳入,key是kCIInputImageKey辐真,value是CIImage類型的對象须尚,你可以用NSData、CIColor侍咱、UIImage耐床、CGImgae、CVImageBufferRef等等來創(chuàng)建楔脯。
這樣就完成了所以準(zhǔn)備撩轰,現(xiàn)在你只要去取outputImage就行了,它會傳出一個CIImage對象昧廷,你可以用它來生成真正的Image進(jìn)行顯示堪嫂。
2.進(jìn)行視頻濾鏡
現(xiàn)在我們能夠進(jìn)行簡單的圖片處理了,現(xiàn)在我們更深入一點(diǎn)木柬,進(jìn)行視頻處理皆串。
因為我們需要實時的處理視頻,所以需要拿到視頻每一幀的圖像眉枕,所以像UIImagePickerController這樣的視頻錄制就不能滿足我們的需求了恶复,我們需要進(jìn)行深度挖掘怜森,我們就會用到AVCaptureAudioDataOutput,它能將我們錄制的每一幀傳出供我們處理谤牡,但是我們必須在一定的時間內(nèi)進(jìn)行處理副硅,否則它就會將這一幀丟掉,從而出現(xiàn)卡頓現(xiàn)象拓哟,就像我們的UITableView一樣想许。像這樣實時的處理伶授,如果我們用CPU的話顯然是不行的断序,Core Graphics的效率我們都是知道的,嘻嘻糜烹。所以需要GPU出場违诗,基于GPU處理,我們可以用GLKit和Metal來實現(xiàn)疮蹦,這篇文章我們先用GLKit來實現(xiàn)诸迟,下一篇我會用Metal來實現(xiàn)。
對于GLKit愕乎,我就用官方的原話來介紹了
The GLKit framework provides functions and classes that reduce the effort required to create new shader-based apps or to port existing apps that rely on fixed-function vertex or fragment processing provided by earlier versions of OpenGL ES or OpenGL阵苇。
簡單說就是基于OpenGL進(jìn)行封裝吧,讓我們好用感论。
回歸正題绅项,我們首先需要建立視頻連接,讓我們能看到攝像頭拍攝的東西比肄,
//視頻輸入
AVCaptureDevice *video = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
AVCaptureDeviceInput *videoInput = [AVCaptureDeviceInput deviceInputWithDevice:video error:nil];
AVCaptureSession *session = [[AVCaptureSession alloc] init];
if ([session canAddInput:videoInput]) {
[session addInput:videoInput];
}
_session = session;
//視頻輸出
_queue = dispatch_queue_create("DataOutputQueue", DISPATCH_QUEUE_SERIAL);
_videoOutput = [AVCaptureVideoDataOutput new];
NSDictionary *videoSettings = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithInt:kCVPixelFormatType_32BGRA], kCVPixelBufferPixelFormatTypeKey,
nil];
_videoOutput.videoSettings = videoSettings;
[_videoOutput setAlwaysDiscardsLateVideoFrames:YES];
[_videoOutput setSampleBufferDelegate:self queue:self.queue];
if ([session canAddOutput:_videoOutput]){
[session addOutput:_videoOutput];
}
AVCaptureConnection *connection = [_videoOutput connectionWithMediaType:AVMediaTypeVideo];
connection.videoOrientation = AVCaptureVideoOrientationPortrait;
[session startRunning];
這樣我們就能在videoOutPut的代理回調(diào)
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection
只不過拿到錄制的視頻幀了快耿,就是CMSampleBufferRef對象。
然后我們需要創(chuàng)建濾鏡相關(guān)的東西了芳绩,首先是GLKView的生成掀亥,它有一個方法來進(jìn)行創(chuàng)建
- (instancetype)initWithFrame:(CGRect)frame context:(EAGLContext *)context;
但是這個EAGLContext又是什么喃?其實這個是GLKit相關(guān)的上下文對象妥色,跳到EAGLContext的定義里面搪花,我們能看到它的創(chuàng)建方式:
- (instancetype) init NS_UNAVAILABLE;
- (instancetype) initWithAPI:(EAGLRenderingAPI) api;
- (instancetype) initWithAPI:(EAGLRenderingAPI) api sharegroup:(EAGLSharegroup*) sharegroup NS_DESIGNATED_INITIALIZER;
第一種不能使用,二三種都有一個EAGLRenderingAPI的枚舉嘹害,其實就是你要使用的OpenGL 的版本撮竿,sharegroup也只是一個用于debug時方便查看的對象,所以我們直接用第二種:[[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
因為我們需要對圖像進(jìn)行處理吼拥,所以我們需要關(guān)閉GLKView的自動渲染倚聚,.enableSetNeedsDisplay = NO;
有了GLKit的上下文,我們就能生成CIFilterd的上下文了凿可,
[CIContext contextWithEAGLContext:_eaglContext options:@{kCIContextWorkingColorSpace : [NSNull null]} ]
因為CIContext的創(chuàng)建開銷很大惑折,所以我們很多時候都會復(fù)用一個context授账。
接下來我們生成一個濾鏡就行了。
準(zhǔn)備工作已經(jīng)完成惨驶,接下來就是對實時視頻幀進(jìn)行處理了白热。
CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
CIImage *sourceImage = [CIImage imageWithCVPixelBuffer:(CVPixelBufferRef)imageBuffer options:nil];
CIImage *filteredImage = RunFilter(sourceImage, self.filter);
[_videoPreviewView bindDrawable];
if (filteredImage)
[self.context drawImage:filteredImage inRect:CGRectMake(0, 0, self.videoPreviewView.drawableWidth, self.videoPreviewView.drawableHeight) fromRect:sourceImage.extent];
[_videoPreviewView display];
我們首先需要將CoreMedia的數(shù)據(jù)對象轉(zhuǎn)換成圖像數(shù)據(jù),通過CMSampleBufferRef-> CVImageBufferRef-> CIImage,我們就拿到了我們需要的原始圖像數(shù)據(jù)粗卜,接下來就是對圖像進(jìn)行濾鏡處理屋确,這里和上邊處理圖片一樣的,就不貼代碼了续扔。
接下來我們就讓濾鏡上下文開始著色和讓GLKView進(jìn)行渲染攻臀。
這樣,我們就實現(xiàn)了拍攝實時濾鏡了纱昧,demo在此刨啸。
參考文章
ps
最近在學(xué)習(xí)視頻相關(guān)的東西,本來想的是仿寫一個VUE來試試的识脆;恰逢今天純銀大大的新產(chǎn)品發(fā)布了设联,而且看那個準(zhǔn)維密模特看得我一愣一愣的,準(zhǔn)備參考一下貓餅來寫一個灼捂,不知道純銀大大有沒有意見啊离例,咳咳~
如果這篇還可以的話,接下來就再寫一些Core Image的文章悉稠。如果我寫的不好或者理解的不深甚至有錯誤的話宫蛆,請幫忙指出。