ちょっと話題の記事

iOSのカメラ機能を使う方法まとめ【13日目】

2012.12.13

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

iOSのカメラ機能を使う方法

さて今回はiOSでカメラを使う方法をご紹介します。まず、iOSでカメラを使う方法はですが、大きく分けて2つあります。

一つ目はUIImagePickerControllerを使う方法です。UIImagePickerControllerはUIKitに含まれるクラスで、写真や動画の撮影機能だけでなく簡単な編集機能まで備えています。また、撮影画面のカスタマイズも可能です。この方法は比較的導入が簡単なのですが、撮影中の画像に加工を施す場合などには向いていません。

2つ目はAVFoundation Frameworkを使用する方法です。この方法は撮影中の画像への加工、例えば撮影中に落書きしたりフィルタ処理をかけたりする場合に非常に有効です。反面導入がUIImagePickerControllerより難しいです。そして、AVFoundation FrameworkではAVCaptureStillImageOutputを使用する方法AVCaptureVideoDataOutputを使用する方法があります。前者は撮影中の映像をそのまま画面に表示することができ、撮影中の映像の上に別のViewなどをかぶせるときに有効です。撮影中の映像の上にスタンプ画像を表示したり、落書きできるようにしたりする場合によく使用されます。後者は撮影中の画像を1枚ずつ加工する場合に有効です。撮影中の画像をモノクロにしたり、漫画風にしたりするのによく使用されます。

この記事では、UIImagePickerControllerを使う方法とAVFoundation Frameworkを使う2つの方法、合わせて3つの方法の基本的な使い方を紹介します。

サンプルプロジェクトの作成

まずは、ベースとなるサンプルプロジェクトXcodeよりSingle View Applicationを選択し、以下の内容でプロジェクトを作成しましょう。尚、今回はサンプルですので、保存の際に「Create local git repository for this project」はチェックを外しておきましょう。

  • Xcode 4.5.2
  • iOS SDK 6.0
項目 設定値
Product Name CameraSample
Organization Name 自分の名前(サンプルなのでテキトー)
Company Identifier 会社名(サンプルなのでテキトー)
Class Prefix なし
Devices iPhone
Use Storyboards チェックする(ストーリーボードを使用)
Use Automatic Reference Counting チェックする(ARC有効)
Include Unit Tests チェックしない(unit testのターゲットを含まない)

尚、今回のサンプルではカメラを使用するので、実機を利用できるようにしてください。

1.UIImagePickerControllerを使う

UIImagePickerControllerの導入は非常に簡単です。一般的にUIImagePickerControllerは他のビューコントローラからモーダルビューとして表示します。まず、ViewController.hを開き、以下のように編集してください。

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController <UINavigationControllerDelegate, UIImagePickerControllerDelegate>

@end

UIImagePickerControllerを使用する場合、今回の呼び出し元となるViewControllerクラスでUINavigationControllerDelegateプロトコルとUIImagePickerControllerDelegateプロトコルの実装を宣言しなければなりません。次に、ViewController.mを開き、以下のように編集してください。

ViewController.h

#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    
    [self showUIImagePicker];
}

- (void)showUIImagePicker
{
    // カメラが使用可能かどうか判定する
    if (![UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
        return;
    }
    
    // UIImagePickerControllerのインスタンスを生成
    UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
    
    // デリゲートを設定
    imagePickerController.delegate = self;
    
    // 画像の取得先をカメラに設定
    imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
    
    // 画像取得後に編集するかどうか(デフォルトはNO)
    imagePickerController.allowsEditing = YES;
    
    // 撮影画面をモーダルビューとして表示する
    [self presentViewController:imagePickerController animated:YES completion:nil];
}

// 画像が選択された時に呼ばれるデリゲートメソッド
- (void)imagePickerController:(UIImagePickerController *)picker
        didFinishPickingImage:(UIImage *)image
                  editingInfo:(NSDictionary *)editingInfo
{
    // モーダルビューを閉じる
    [self dismissViewControllerAnimated:YES completion:nil];
    
    // 渡されてきた画像をフォトアルバムに保存
    UIImageWriteToSavedPhotosAlbum(image, self, @selector(targetImage:didFinishSavingWithError:contextInfo:), NULL);
}

// 画像の選択がキャンセルされた時に呼ばれるデリゲートメソッド
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
    // モーダルビューを閉じる
    [self dismissViewControllerAnimated:YES completion:nil];
    
    // キャンセルされたときの処理を記述・・・
}

// 画像の保存完了時に呼ばれるメソッド
- (void)targetImage:(UIImage *)image
didFinishSavingWithError:(NSError *)error
        contextInfo:(void *)context
{
    if (error) {
        // 保存失敗時の処理
    } else {
        // 保存成功時の処理
    }
}

@end

今回のサンプルでは、- viewDidAppear:メソッドを用いて画面表示後UIImagePickerControllerをモーダルビューとして表示しています。

2.AVFoundation FrameworkのAVCaptureStillImageOutputを使用する方法

2つ目は、AVFoundation Frameworkを使用する方法の1つで、撮影中の映像をそのまま画面に表示することができ、撮影中の映像の上に別のViewなどをかぶせるときに有効です。撮影中の映像の上にスタンプ画像を表示したり、落書きできるようにしたりする場合によく使用されます。AVFoundation Frameworkを使用するので、プロジェクト設定にAVFoundation.Frameworkを追加します。

次にViewController.hを開き、以下のように修正しましょう。

ViewController.h

#import <UIKit/UIKit.h>

@interface ViewController : UIViewController

@end

先ほど追加したデリゲートプロトコルの実装宣言を外しただけです。続いてViewController.mを修正しましょう。

ViewController.m

#import "ViewController.h"
#import <AVFoundation/AVFoundation.h>

@interface ViewController ()

@property (strong, nonatomic) AVCaptureDeviceInput *videoInput;
@property (strong, nonatomic) AVCaptureStillImageOutput *stillImageOutput;
@property (strong, nonatomic) AVCaptureSession *session;
@property (strong, nonatomic) UIView *previewView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 撮影ボタンを配置したツールバーを生成
    UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)];
    
    UIBarButtonItem *takePhotoButton = [[UIBarButtonItem alloc] initWithTitle:@"撮影"
                                                                        style:UIBarButtonItemStyleBordered
                                                                       target:self
                                                                       action:@selector(takePhoto:)];
    toolbar.items = @[takePhotoButton];
    [self.view addSubview:toolbar];
    
    // プレビュー用のビューを生成
    self.previewView = [[UIView alloc] initWithFrame:CGRectMake(0,
                                                                toolbar.frame.size.height,
                                                                self.view.bounds.size.width,
                                                                self.view.bounds.size.height - toolbar.frame.size.height)];
    [self.view addSubview:self.previewView];

    // 撮影開始
    [self setupAVCapture];
}

- (void)setupAVCapture
{
    NSError *error = nil;
    
    // 入力と出力からキャプチャーセッションを作成
    self.session = [[AVCaptureSession alloc] init];
    
    // 正面に配置されているカメラを取得
    AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    // カメラからの入力を作成し、セッションに追加
    self.videoInput = [[AVCaptureDeviceInput alloc] initWithDevice:camera error:&error];
    [self.session addInput:self.videoInput];
    
    // 画像への出力を作成し、セッションに追加
    self.stillImageOutput = [[AVCaptureStillImageOutput alloc] init];
    [self.session addOutput:self.stillImageOutput];
    
    // キャプチャーセッションから入力のプレビュー表示を作成
    AVCaptureVideoPreviewLayer *captureVideoPreviewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
    captureVideoPreviewLayer.frame = self.view.bounds;
    captureVideoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    
    // レイヤーをViewに設定
    CALayer *previewLayer = self.previewView.layer;
    previewLayer.masksToBounds = YES;
    [previewLayer addSublayer:captureVideoPreviewLayer];
    
    // セッション開始
    [self.session startRunning];
}

- (void)takePhoto:(id)sender
{
    // ビデオ入力のAVCaptureConnectionを取得
    AVCaptureConnection *videoConnection = [self.stillImageOutput connectionWithMediaType:AVMediaTypeVideo];
    
    if (videoConnection == nil) {
        return;
    }
    
    // ビデオ入力から画像を非同期で取得。ブロックで定義されている処理が呼び出され、画像データを引数から取得する
    [self.stillImageOutput
     captureStillImageAsynchronouslyFromConnection:videoConnection
     completionHandler:^(CMSampleBufferRef imageDataSampleBuffer, NSError *error) {
         if (imageDataSampleBuffer == NULL) {
             return;
         }
         
         // 入力された画像データからJPEGフォーマットとしてデータを取得
         NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:imageDataSampleBuffer];
         
         // JPEGデータからUIImageを作成
         UIImage *image = [[UIImage alloc] initWithData:imageData];
         
         // アルバムに画像を保存
         UIImageWriteToSavedPhotosAlbum(image, self, nil, nil);
     }];
}

@end

3.AVFoundation FrameworkのAVCaptureVideoDataOutputを使用する方法

3つ目は、AVFoundation Frameworkを使用する方法の1つで、撮撮影中の画像を1枚ずつ加工する場合に有効です。この方法では、AVFoundation Framework以外に以下のフレームワークを追加する必要があります。

  • AudioToolbox.framework
  • CoreMedia.framework
  • CoreVideo.framework

ViewController.hを開き、以下のように修正します。

ViewController.h

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>

@interface ViewController : UIViewController <AVCaptureVideoDataOutputSampleBufferDelegate>

@end

AVCaptureVideoDataOutputSampleBufferDelegateプロトコルを実装を宣言します。次に、ViewController.mを以下のように修正しましょう。

ViewController.m

#import "ViewController.h"
#import <CoreVideo/CoreVideo.h>
#import <CoreImage/CoreImage.h>
#import <CoreGraphics/CoreGraphics.h>

@interface ViewController ()

@property (strong, nonatomic) AVCaptureDeviceInput *videoInput;
@property (strong, nonatomic) AVCaptureVideoDataOutput *videoDataOutput;
@property (strong, nonatomic) AVCaptureSession *session;
@property (strong, nonatomic) UIImageView *previewImageView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 撮影ボタンを配置したツールバーを生成
    UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, self.view.bounds.size.width, 44)];
    
    UIBarButtonItem *takePhotoButton = [[UIBarButtonItem alloc] initWithTitle:@"撮影"
                                                                        style:UIBarButtonItemStyleBordered
                                                                       target:self
                                                                       action:@selector(takePhoto:)];
    toolbar.items = @[takePhotoButton];
    [self.view addSubview:toolbar];
    
    // プレビュー用のビューを生成
    self.previewImageView = [[UIImageView alloc] initWithFrame:CGRectMake(0,
                                                                          toolbar.frame.size.height,
                                                                          self.view.bounds.size.width,
                                                                          self.view.bounds.size.height - toolbar.frame.size.height)];
    [self.view addSubview:self.previewImageView];

    // 撮影開始
    [self setupAVCapture];
}

- (void)setupAVCapture
{
    NSError *error = nil;
    
    // 入力と出力からキャプチャーセッションを作成
    self.session = [[AVCaptureSession alloc] init];
    
    self.session.sessionPreset = AVCaptureSessionPresetMedium;
    
    // カメラからの入力を作成
    AVCaptureDevice *camera = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    // カメラからの入力を作成し、セッションに追加
    self.videoInput = [AVCaptureDeviceInput deviceInputWithDevice:camera error:&error];
    [self.session addInput:self.videoInput];
    
    // 画像への出力を作成し、セッションに追加
    self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    [self.session addOutput:self.videoDataOutput];
    
    // ビデオ出力のキャプチャの画像情報のキューを設定
    dispatch_queue_t queue = dispatch_queue_create("myQueue", NULL);
    [self.videoDataOutput setAlwaysDiscardsLateVideoFrames:TRUE];
    [self.videoDataOutput setSampleBufferDelegate:self queue:queue];
    
    // ビデオへの出力の画像は、BGRAで出力
    self.videoDataOutput.videoSettings = @{
    (id)kCVPixelBufferPixelFormatTypeKey : [NSNumber numberWithInt:kCVPixelFormatType_32BGRA]
    };
    
    // ビデオ入力のAVCaptureConnectionを取得
    AVCaptureConnection *videoConnection = [self.videoDataOutput connectionWithMediaType:AVMediaTypeVideo];
    
    // 1秒あたり4回画像をキャプチャ
    videoConnection.videoMinFrameDuration = CMTimeMake(1, 4);
    
    [self.session startRunning];
}

// AVCaptureVideoDataOutputSampleBufferDelegateプロトコルのメソッド。新しいキャプチャの情報が追加されたときに呼び出される。
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer
       fromConnection:(AVCaptureConnection *)connection
{
    // キャプチャしたフレームからCGImageを作成
    UIImage *image = [self imageFromSampleBuffer:sampleBuffer];
    
    // 画像を画面に表示
    dispatch_async(dispatch_get_main_queue(), ^{
        self.previewImageView.image = image;
    });
}

// サンプルバッファのデータからCGImageRefを生成する
- (UIImage *)imageFromSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
    CVImageBufferRef imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer);
    
    // ピクセルバッファのベースアドレスをロックする
    CVPixelBufferLockBaseAddress(imageBuffer, 0);
    
    // Get information of the image
    uint8_t *baseAddress = (uint8_t *)CVPixelBufferGetBaseAddressOfPlane(imageBuffer, 0);
    
    size_t bytesPerRow = CVPixelBufferGetBytesPerRow(imageBuffer);
    size_t width = CVPixelBufferGetWidth(imageBuffer);
    size_t height = CVPixelBufferGetHeight(imageBuffer);
    
    // RGBの色空間
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    
    CGContextRef newContext = CGBitmapContextCreate(baseAddress,
                                                    width,
                                                    height,
                                                    8,
                                                    bytesPerRow,
                                                    colorSpace,
                                                    kCGBitmapByteOrder32Little | kCGImageAlphaPremultipliedFirst);
    
    CGImageRef cgImage = CGBitmapContextCreateImage(newContext);
    
    CGContextRelease(newContext);
    CGColorSpaceRelease(colorSpace);
    CVPixelBufferUnlockBaseAddress(imageBuffer, 0);
    
    UIImage *image = [UIImage imageWithCGImage:cgImage scale:1.0 orientation:UIImageOrientationRight];
    
    CGImageRelease(cgImage);
    
    return image;
}

- (void)takePhoto:(id)sender
{
    AudioServicesPlaySystemSound(1108);
    
    // アルバムに画像を保存
    UIImageWriteToSavedPhotosAlbum(self.previewImageView.image, self, nil, nil);
}

@end

まとめ

iOSでカメラを使う3つの方法の基本的な使い方を紹介しました。いずれの方法も一長一短があるので、カメラ機能の実装が必要な場合は目的に応じて適切な方法を選択してみてください。