2013年4月24日 星期三

直接存取攝影機

轉錄自http://furnacedigital.blogspot.tw/2012/11/avcapturestillimageoutput.html#more(作者超強,內有更多好文!)

 

透過 AVCaptureStillImageOutput 做靜態影像的擷取

 

在 iOS 6 SDK(iOS 5 SDK 以上)中捕捉攝影機的靜態拍攝畫面,製作類似「錄影同時拍照的效果」可以透果很多方式,像是透過 UIImagePickerController 的方式,呼叫 iOS SDK 所提供的 API 來捕捉畫面,或是透過 AVFoundation 的 方式,建立影像的 AVCaptureSession,並且設定對應的 Input 與 Output。而本篇文章所採用的方法屬於後者,我們使用 AVCaptureStillImageOutput 來當做 AVCaptureSession 的 Output 端,輸出靜態影像。

在開始之前請先替您的專案加上 AVFoundation.framework,並且在對應的類別中引用此標頭檔。替專案加入 Framework 的方法請參考Xcode 4 新增 Framework 的方法一文。


建立 AVCaptureSession
在這部份中我們要在類別的 @interface 區段中宣告一個 AVCaptureSession 型態的全域變數,方便之後的作業。
1
AVCaptureSession *myCaptureSession;

在建立 AVCaptureSession 的部份,其實並沒有太多的設定,唯一要注意的是 setSessionPreset: 這個方法函式,它決定了攝影機捕獲影像的解析度大小,你可以使用 AVCaptureSessionPreset 的關鍵字來查閱各種解析度的定義。
1
2
3
//建立 AVCaptureSession
myCaptureSession = [[AVCaptureSession alloc] init];
[myCaptureSession setSessionPreset:AVCaptureSessionPresetPhoto];


建立 AVCaptureDeviceInput
在建立好 AVCaptureSession 之後,接下來我們要對他的輸入端做設定,你可以透過下列程式碼來取得裝置上具有輸入特性的硬體裝置和他們實際的裝置名稱。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//建立 AVCaptureDeviceInput
NSArray *myDevices = [AVCaptureDevice devices];
for (AVCaptureDevice *device in myDevices) {
    if ([device position] == AVCaptureDevicePositionBack) {
        NSLog(@"後攝影機硬體名稱: %@", [device localizedName]);
    }
    if ([device position] == AVCaptureDevicePositionFront) {
        NSLog(@"前攝影機硬體名稱: %@", [device localizedName]);
    }
    if ([device hasMediaType:AVMediaTypeAudio]) {
        NSLog(@"麥克風硬體名稱: %@", [device localizedName]);
    }
}

裝置上具有輸入特性的硬體名稱

下列我們就以後置鏡頭為例,製作 AVCaptureSession 的輸入端。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//使用後置鏡頭當做輸入
NSError *error = nil;
for (AVCaptureDevice *device in myDevices) {
    if ([device position] == AVCaptureDevicePositionBack) {
        AVCaptureDeviceInput *myDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:&error];
        if (error) {
            //裝置取得失敗時的處理常式
        } else {
            [myCaptureSession addInput:myDeviceInput];
        }
    }
}


建立 AVCaptureVideoPreviewLayer
在建立 AVCaptureSession 與設定對應的 Input 之後,我們就可以透過 AVCaptureVideoPreviewLayer 來取得攝影機所捕捉的連續畫面,在 此,AVCaptureVideoPreviewLayer 所呈現的畫面是連續的,並非單張的靜態影像,當然你也可以略過設定 AVCaptureVideoPreviewLayer 的步驟,不顯示攝影機所拍攝到的畫面,這並不會有任何影響。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//建立 AVCaptureVideoPreviewLayer
AVCaptureVideoPreviewLayer *myPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:myCaptureSession];
[myPreviewLayer setVideoGravity:AVLayerVideoGravityResizeAspectFill];
CGRect rect = CGRectMake(160, 180, 320, 240);
[myPreviewLayer setBounds:rect];
UIView *myView = [[UIView alloc]initWithFrame:rect];
[myView.layer addSublayer:myPreviewLayer];
[self.view addSubview:myView];
//啟用攝影機
[myCaptureSession startRunning];

在上述程式碼中,我們透過 AVCaptureSession 來建立 AVCaptureVideoPreviewLayer,並且設定它的 VideoGravity 屬性為 AVLayerVideoGravityResizeAspectFill,表示所取得的影像必須以等比例的方式縮放來填滿我們所指定的大小 (CGRect rect)。

到目前為止,我們還並未對 AVCaptureSession 做 Output 的設定,但是已經可以透過 AVCaptureVideoPreviewLayer 在畫面上看到攝影機所拍攝的影像。下面,我們將針對靜態影像的擷取來製作對應的 Output。


建立 AVCaptureStillImageOutput
要使用單張靜態影像的擷取,就可以考慮使用 AVCaptureStillImageOutput 來製作你的 Output 端,否則最好是使用 AVCaptureVideoDataOutput 來當做輸出端,這兩個不同類型的 Output 可以做的事情也不盡相同,關於 AVCaptureVideoDataOutput 可以參考透過 AVCaptureVideoDataOutput 做連續影像片段的擷取一文,來獲得更多資訊,下面我們只針對 AVCaptureStillImageOutput 做說明。

在這部份中我們要在類別的 @interface 區段中宣告一個 AVCaptureStillImageOutput 型態的全域變數,方便之後的作業。
1
AVCaptureStillImageOutput *myStillImageOutput;

接著,在根據以下程式碼對 AVCaptureStillImageOutput 做設定,並將此 Output 與 AVCaptureSession 做連接。
1
2
3
4
5
6
//建立 AVCaptureStillImageOutput
myStillImageOutput = [[AVCaptureStillImageOutput alloc] init];
NSDictionary *myOutputSettings = [[NSDictionary alloc] initWithObjectsAndKeys:AVVideoCodecJPEG,AVVideoCodecKey,nil];
[myStillImageOutput setOutputSettings:myOutputSettings];
[myCaptureSession addOutput:myStillImageOutput];

在設定 AVCaptureStillImageOutput 上,我們將所擷取到的影像做 JPEG 的編碼以節省空間,雖然說使用 AVVideoCodec 這個關鍵字時,你可以看到 AVVideoCodecH264 的選項,但是他並不是靜態影像的編碼方式,而是使用在 Video 上的一種壓縮編碼,所以無法使用。


擷取單張靜態影像
在成功建立好 AVCaptureSession 的 Output 之後,接下來就是製作靜態影像擷取,你可以透過下列的方法函式,來與拍照的按鈕做互動,取得靜態影像。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
AVCaptureConnection *myVideoConnection = nil;
//從 AVCaptureStillImageOutput 中取得正確類型的 AVCaptureConnection
for (AVCaptureConnection *connection in myStillImageOutput.connections) {
    for (AVCaptureInputPort *port in [connection inputPorts]) {
        if ([[port mediaType] isEqual:AVMediaTypeVideo]) {
            myVideoConnection = connection;
            break;
        }
    }
}
//擷取影像(包含拍照音效)
[myStillImageOutput captureStillImageAsynchronouslyFromConnection:myVideoConnection completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
    //完成擷取時的處理常式(Block)
    if (imageDataSampleBuffer) {
        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
         //取得的靜態影像
         UIImage *myImage = [[UIImage alloc] initWithData:imageData];
         [self setImage:myImage];
         //取得影像資料(需要ImageIO.framework 與 CoreMedia.framework)
         CFDictionaryRef myAttachments = CMGetAttachment(imageDataSampleBuffer, kCGImagePropertyExifDictionary, NULL);
         NSLog(@"影像屬性: %@", myAttachments);
    }
}];

在上述程式碼中,我們必須先從 AVCaptureStillImageOutput 中取得正確類型的 AVCaptureConnection,接著,才利用此 AVCaptureConnection 做影像的擷取,另外,如果你希望改變擷取影像時的方向,也可以對 AVCaptureConnection 做 setVideoOrientation: 旋轉影像,或 setVideoMirrored: 鏡射影像。

另外,在擷影像時會包含拍照的音效,無法消除,這是基於 iOS SDK 安全條款的使用原則,如果你是想製作每秒擷取 N 張靜態影像的話,最好還是使用 AVCaptureVideoDataOutput 來當做輸出,避免產生連續的拍照音效。

最後,我們也可以使用 CMGetAttachment 來取得該影像的其他屬性,但是這必須使用 ImageIO 與 CoreMedia 兩個 Framework 才行。

使用 CMGetAttachment 所取的的影像屬性

沒有留言:

張貼留言