2012年1月7日 星期六

Filter4Cam 學習之 Getting Raw Video Data

since: 2012/01/06
update: 2012/01/07

reference: Getting Raw Video Data into My App Quick and Dirty | Indie Ambitions

A. 建立專案
    1. Xcode > File > New > New Project... > iOS > Application > OpenGL Game > Next
        Product Name: RawVideoData
        Device Family: iPhone
        Use StroyBoard: checked
        Use Automatic Reference Counting: checked
        > Next

    2. 接下來將會使用到藉由 core image 直接畫到 render buffer 的功能.
        可以先將從樣板產生的 code 都移除(ViewController.m 中的), 我們不會使用到.
        預設產生的 frameworks 如下:

    3. 接著, 加入以下的 frameworks. 有些是 video 會用到的, 有則些是 Core Image
        會用到的.
        AVFoundation
        CoreVideo
        CoreMedia
        QuartzCore
        ImageIO
        CoreImage
   
---------------------------------------------------------------------------------------------

B. 引入 frameworks 並設定符合協定
   1. 開啓 ViewController.h 檔案, 修改如下:
#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
//@add
#import <AVFoundation/AVFoundation.h>
#import <CoreMedia/CoreMedia.h>
#import <CoreVideo/CoreVideo.h>
#import <QuartzCore/QuartzCore.h>
#import <CoreImage/CoreImage.h>
#import <ImageIO/ImageIO.h>

//@interface ViewController : GLKViewController
@interface ViewController : GLKViewController <AVCaptureVideoDataOutputSampleBufferDelegate>
{
    //@add
    AVCaptureSession *session;
   
    CIContext *coreImageContext;
    EAGLContext *context;
    GLuint _renderBuffer;
}

//@add
@property (strong, nonatomic) EAGLContext *context;

@end

    說明: a. 這邊所使用的委派協定(delegate protocol), 表示這個類別將會接收到由
                   傳送者(delivers)所回傳(callback) 的原始像素資料(raw pixel data).
                   AVCaptureSession 將會來作此設定, 並且配置好控制此 session 的參數,
                   如: 解析度, 相機輸入 .... 等. 要畫出經由濾鏡處理過的 Core Image 結果,
                   需要使用到 Core Image context(CIContext).

              b. Core Image context 需要一個 render buffer 來寫入, 所以先在此宣告實體變數:
                  GLuint _renderBuffer , 之後將會拿來設定. 我們也需要一個 EAGLContext
                  參照, 來呈現 render buffer 的內容.

   2. 開啓 ViewController.m 檔案, 修改如下:
      (在 viewDidLoad 中, 實作設定相機與 context)

@implementation ViewController
//@add
@synthesize context = _context;

- (void)viewDidLoad
{
    [super viewDidLoad];
   
    //@add
    // section 1: 大致上與樣板預設的 code 相同, 主要是設置 EAGLContext
    self.context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
   
    if (!self.context) {
        NSLog(@"Failed to create ES context");
    }
   
    GLKView *view = (GLKView *)self.view;
    view.context = self.context;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
   
    // section 2: 設置 render buffer
    glGenRenderbuffers(1, &_renderBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, _renderBuffer);
   
    //section 3: 初始化 Core Image context.
    coreImageContext = [CIContext contextWithEAGLContext:self.context];
   
    // section 4: 設置相機輸入, 建立 session 並設定. 在這邊設定 session 為 640 pixels 寬,
    //                    480 pixels 高. 還有其他的選項, 包括 720
pixels 跟 1080 pixels.
    //                    Core Image 的解析度越高, 效能就越低. 一個單一簡單的濾鏡就能夠來
    //                    處理高解析度, 在此僅為測試的處理.

    NSError *error;

    session = [[AVCaptureSession alloc] init];
   
    [session beginConfiguration];
    [session setSessionPreset:AVCaptureSessionPreset640x480];
   
    // section 5: 設定輸入設備. 假如要指定前置或後置相機, 需要呼叫
    //                   devicesWithMediaType , 它會回傳一個設備的陣列. 若要取得前置相機,
    //                   可在  AVCaptureDevicePosition 屬性中, 重複地在陣列中尋找
    //                   AVCaptureDevicePositionFront. 在此為了簡單, 我們使用預設的設備.
    //
    AVCaptureDevice *videoDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];

    AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:videoDevice error:&error];

    [session addInput:input];
   
    //
section 6: 設定輸出. 並忽略較慢的影格(frames). 如果需要記錄改變的地方, 可以
    //                    設定輸入資料的顏色格式.
    AVCaptureVideoDataOutput *dataOutput = [[AVCaptureVideoDataOutput alloc] init];

    [dataOutput setAlwaysDiscardsLateVideoFrames:YES];

    [dataOutput setVideoSettings:[NSDictionary dictionaryWithObject:[NSNumber numberWithInt:kCVPixelFormatType_32BGRA] forKey:(id)kCVPixelBufferPixelFormatTypeKey]];    
   
    //
section 7: 設定 delegate. 它將會收到回傳(callback)的每個 frame, 並且設為主要
    //                   的駐列(queue). 
    [dataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
    [session addOutput:dataOutput];

    //
section 8: 完成設定並讓相機開始執行
    [session commitConfiguration];
    [session startRunning];
}

@end

---------------------------------------------------------------------------------------------

C. 回呼函式
   1. 開啓 ViewController.m 檔案, 新增 code 如下:
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
   
    // 將取得的 sampleBuffer 轉成 CVPixelBuffer
    CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
   
    // 接著, 將 CVPixelBuffer 利用 Core Image 的初始化方法再轉成 CIImage.
    CIImage *image = [CIImage imageWithCVPixelBuffer:pixelBuffer];
   
    // 然後使用 CIContext 物件將其內容畫到 render buffer 
    [coreImageContext drawImage:image atPoint:CGPointZero fromRect:[image extent] ];
   
    // 最後, 在 螢幕上呈現出來.
    [self.context presentRenderbuffer:GL_RENDERBUFFER];
}

    說明: 當要記錄 core image 濾鏡的輸出, 可以用 CIContext 的另一個方法,
               來將其寫入到 CVPixelBuffer.

   2. 編譯並執行: (上下顛倒, 左右相反)

---------------------------------------------------------------------------------------------

D. 相關處理
       開啓 ViewController.m 檔案, 修改如下:

- (void)viewDidUnload
{   
    [super viewDidUnload];
    //@add
    if ([EAGLContext currentContext] == self.context) {
        [EAGLContext setCurrentContext:nil];
    }
    self.context = nil;
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Release any cached data, images, etc. that aren't in use.
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    // Return YES for supported orientations
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
    } else {
        return YES;
    }
}

---------------------------------------------------------------------------------------------

E. 濾鏡
   1. 開啓 ViewController.m 檔案, 修改如下:

//@add
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
   
    CVPixelBufferRef pixelBuffer = (CVPixelBufferRef)CMSampleBufferGetImageBuffer(sampleBuffer);
   
    CIImage *image = [CIImage imageWithCVPixelBuffer:pixelBuffer];
   
    //@add filter
    image = [CIFilter filterWithName:@"CIFalseColor" keysAndValues:
             kCIInputImageKey, image,
             @"inputColor0", [CIColor colorWithRed:0.0 green:0.2 blue:0.0],
             @"inputColor1", [CIColor colorWithRed:0.0 green:0.0 blue:1.0],
             nil].outputImage;
   
    [coreImageContext drawImage:image atPoint:CGPointZero fromRect:[image extent] ];
   
    [self.context presentRenderbuffer:GL_RENDERBUFFER];
}

    說明: 假色濾鏡(False Color filter), 將圖片的內容對應到二種顏色, 在此為
               暗綠與亮藍.

   2. 編譯並執行: (上下顛倒, 左右相反)

沒有留言:

張貼留言

注意:只有此網誌的成員可以留言。