上一篇寫靜態(tài)圖片檢測的寫的有點冗長,導致寫不完后面的了 ( 捂臉 )。所以重新開一篇來寫。
人臉檢測
其實攝像頭的人臉檢測和靜態(tài)圖并沒有多大的區(qū)別。不同點是我們不需要手動生成CIDetector來進行檢測,蘋果在AVFoudation中內(nèi)置了人臉的檢測。
在第一篇文章中我們實現(xiàn)攝像頭的濾鏡,這次,我們在AVCaptureSession中多加入一個輸出源--AVCaptureMetadataOutput。這個類是內(nèi)置的進行檢測的類,一般現(xiàn)在進行二維碼什么的檢測,就是用的這個類。
_metaOutput = [AVCaptureMetadataOutput new];
if ([session canAddOutput:_metaOutput]) {
[session addOutput:_metaOutput];
}
[_metaOutput setMetadataObjectsDelegate:self queue:_queue];
_metaOutput.metadataObjectTypes = [_metaOutput availableMetadataObjectTypes];
我們先生成一個新的metaOutput,并加入到session中,設置代理接受回調(diào)。
然后設置這個檢測類型,這里也有一個坑,如果你在加入到session之前獲取availableMetadataObjectTypes,你會得到空數(shù)組,這個問題我調(diào)了很久,然后看到注釋中有這么一段話,
Available metadata object types are dependent on the capabilities of the AVCaptureInputPort to which this receiver's AVCaptureConnection is connected.
可檢測的類型是跟connection有關(guān)的,因為在加入session之前,并沒有和它相關(guān)的connection,所以的到的availableMetadataObjectTypes自然是空的。
接下來在回調(diào)
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
中接收檢測到的對象數(shù)組。
數(shù)組中裝的并不是CoreImge的feature對象,而是AVMetadataObject,但是它也有對應的人臉的位置,但是如果你直接取這個值,你會發(fā)現(xiàn),并不是標準的CGRect。
If the metadata originates from video, bounds may be expressed as scalar values from 0. - 1.
注釋中寫了,它的值是在0到1之間,所以我們需要直接轉(zhuǎn)換成對應的frame。但是一旦你開始轉(zhuǎn)換,你會發(fā)現(xiàn),這個東西有點燙手。
其實這個bounds是經(jīng)過旋轉(zhuǎn)了的,順時針旋轉(zhuǎn)了90度。所以它的x和y都是換了的。
CGFloat x = (1-object.bounds.origin.y)*self.view.frame.size.width - object.bounds.size.width*self.view.frame.size.height;
CGFloat y = object.bounds.origin.x*self.view.frame.size.height;
CGRect frame = CGRectMake(x, y, object.bounds.size.height*self.view.frame.size.width, object.bounds.size.width*self.view.frame.size.height);
這樣經(jīng)過一番轉(zhuǎn)換后才是真正的frame。
這樣,我們獲取到了真的位置,就像處理靜態(tài)圖一樣處理就行了。后面會有demo,里面有具體代碼。
寫入本地
本來如果是沒有處理過的CMSampleBufferRef,我們可以直接用writer寫入就可以了,但是我們處理過之后,生成的是CIImage,所以并不能用之前的方式。
所以我們需要將CIImage轉(zhuǎn)成CVPixelBufferRef,再用AVAssetWriterInputPixelBufferAdaptor寫入。
AVAssetWriterInputPixelBufferAdaptor
Defines an interface for appending video samples packaged as CVPixelBuffer objects to a single AVAssetWriterInput object.
可以看出AVAssetWriterInputPixelBufferAdaptor是用來寫入CVPixelBuffer的。它的生成也很簡單,
AVAssetWriterInputPixelBufferAdaptor *adaptor = [AVAssetWriterInputPixelBufferAdaptor assetWriterInputPixelBufferAdaptorWithAssetWriterInput:_videoWriterInput sourcePixelBufferAttributes:nil];
主要參數(shù)是一個AVAssetWriterInput,而且這個input應該是AVMediaTypeVideo的input。后面的參數(shù)可以不傳也可以傳<CoreVideo/CVPixelBuffer.h>內(nèi)的Pixel buffer attributes keys。
接下來我們將濾鏡完成的CIImage轉(zhuǎn)換成CVPixelBuffer,
CVPixelBufferRef buffer = NULL;
CVPixelBufferPoolCreatePixelBuffer(NULL, self.adaptor.pixelBufferPool, &buffer);
[self.context render:endImage toCVPixelBuffer:buffer];
[self.adaptor appendPixelBuffer:buffer withPresentationTime:CMSampleBufferGetPresentationTimeStamp(sampleBuffer)];
CFRelease(buffer);
我們先生成一個空的CVPixelBufferRef類型的buffer,這里有一個CVPixelBufferPoolRef,這是一個類似于autoreleasepool的東西,在AVAssetWriterInputPixelBufferAdaptor的聲明有一段話
Using the provided pixel buffer pool for buffer allocation is typically more efficient than appending pixel buffers allocated using a separate pool.
簡單說就是用這個很方便的生成一個CVPixelBufferRef。
這個時候生成的CVPixelBufferRef還只是一個初始對象,里面并沒有包含任何的視頻數(shù)據(jù),我們需要將我們剛才的CIImage寫入到buffer中,這個用到CIContext中的API。然后我們就可以將這個buffer加入到寫入adaptor了,有個時間信息,我們直接獲取原始CMSampleBufferRef的信息就行。
這樣我們就能將我們處理過的視頻直接寫入到本地了。Demo在這里