iOS Advent Calender 2013 iOS second stage 4日目担当の@hkato193です。書籍「iPhoneアプリ開発エキスパートガイド」や「OS XとiOSのためのOpenCV環境構築ガイド」などのiPhoneアプリ開発関係の本を書いたりしています。アプリだと、プロアスリートが練習に使うスキップバックレコーダーを実現した「PastVid」や、弾むボールでリズムを表現する「Metronome Bounce」などを公開しています。
さて、iOS 7ではバーコードやQRコードなどの1次元/2次元コードをカメラで読み取ることができます。さらにQRコードは読み取りだけでなく、Core Imageを使ったコード作成も行えます。本記事では、読み取りと作成の方法をそれぞれ紹介します。
1. 読み取り編
1次元/2次元コードの読み取りは、AVFoundationフレームワークのキャプチャ出力に関連するクラスAVCaptureMetadataOutput
クラスの一機能として標準提供されるようになりました。
コード認識の処理は、顔認識の方法と基本的に同じです。
- キャプチャセッションの準備
- キャプチャセッションのアウトプットに繋ぐ
AVCaptureMetadataOutput
クラスを作成 -
setMetadataObjectsDelegate:queue:
メソッドを使って、識別結果を受けるデリゲートオブジェクトと、デリゲートメソッドを呼ぶワークキュー(GCDのシリアルキューがベター)を指定 - キャプチャセッションのアウトプットに接続
- アウトプットに接続後、識別したいコードの種類を
metadataObjectTypes
プロパティに指定 - キャプチャセッションを開始
という手順です。顔認識の手順とは5番で指定する値が異なるだけですね。これによって識別処理がキャプチャセッションの開始と同時に始まり、フレーム内にコードがあると分かったタイミングで、指定したワークキュー上でデリゲートメソッドが呼ばれ、AVMetadataMachineReadableCodeObject
オブジェクトの配列として読み取り結果が渡されます。あとは得られた情報を元にWEBサイトにジャンプするなり、メッセージを表示するなり、好きな処理を実行するだけです。
キャプチャセッションの準備
実際のコードを見てみましょう。最初は、キャプチャセッションを作成するときのAVCaptureMetadataOutput
クラスの初期化処理です。
// メタデータ識別用のオブジェクトを作成
AVCaptureMetadataOutput *metaOutput = [[AVCaptureMetadataOutput alloc] init];
// 識別結果を伝えるデリゲートオブジェクトと処理を行うシリアルキューを指定
// ※認識したメタデータが順序正しくデリゲートメソッドに届くよう、
// ワークキューにはグローバルキューではなくシリアルキューを渡すこと
[metaOutput setMetadataObjectsDelegate:self
queue:dispatch_queue_create("myQueue.metadata", DISPATCH_QUEUE_SERIAL)];
// アウトプットに追加
[self.session addOutput:metaOutput];
// アウトプットに追加後、識別したいコード(QRコード、JANコード標準タイプ)を指定
// ※顔認識とコード認識の両方を同時に渡しても、どちらも正常に動作する
metaOutput.metadataObjectTypes =
@[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code]; ←②
注意点としては、metadataObjectTypes
プロパティの指定は「 キャプチャセッションにaddOutput:
した後 」で、なおかつ「 キャプチャセッションにキャプチャデバイスがaddInput:
されている状態 」で行うようにしてください。冒頭の図だと、中央のキャプチャセッションを挟んだ左右のオブジェクトです(プレビューはなくても大丈夫です)。
これは識別できるメタデータの種類はキャプチャデバイスに依存していて、使用するキャプチャデバイスが分からない状況ではメタデータの種類を指定しようがないためです。たとえばキャプチャデバイスにはカメラだけではなくマイクもあります。マイクだけがaddInput:されている状態ではQRコードの識別が出来るはずもありませんので、この状態でQRコードを指定しても何の意味もありません。
metadataObjectTypesプロパティに設定できるタイプと、コード上の表現は次のとおりです。iOS 7時点では、各種コードの中でも特によく使われる10種類をサポートしています。日本でのバーコードと言えばEAN-13や8で、二次元コードだとQRコードが一般的ですね。
コードの種類 |
定数(NSString* ) |
---|---|
UPC-E | AVMetadataObjectTypeUPCECode |
Code 39 | AVMetadataObjectTypeCode39Code |
Code 39 mod 43 | AVMetadataObjectTypeCode39Mod43Code |
EAN-13(JANコード標準タイプ) | AVMetadataObjectTypeEAN13Code |
EAN-8(JANコード短縮タイプ) | AVMetadataObjectTypeEAN8Code |
Code 93 | AVMetadataObjectTypeCode93Code |
Code 128 | AVMetadataObjectTypeCode128Code |
PDF417 | AVMetadataObjectTypePDF417Code |
QRコード | AVMetadataObjectTypeQRCode |
Aztec code | AVMetadataObjectTypeAztecCode |
(参考)人の顔 | AVMetadataObjectTypeFace |
認識させたいコードは同時に複数種類を登録でき、また画面内に複数個のコードがあった場合も同時(最大4個)に認識します。
ちなみに現在設定可能なメタデータの種類は、AVCaptureMetadataOutput
クラスのavailableMetadataObjectTypes
プロパティを使って取得できるので、この中に含まれるタイプだけを指定するようにするとプログラマブルでベターです。
iOS 6 SDKではデフォルトで
AVMetadataObjectTypeFace
が指定された状態になっていて、何もしなくても顔認識を開始できたのですが、iOS 7 SDKを使うとデフォルトでmetadataObjectTypes
プロパティは空の配列が設定されていて、AVMetadataObjectTypeFace
は含まれていません。既存アプリをiOS 7に対応させる場合は、このデフォルト動作の違いにも注意が必要です。
コードの識別結果を取得
キャプチャデータ中のコードが識別できると、準備のときにsetMetadataObjectsDelegate:queue:
メソッドで登録したデリゲートオブジェクトのcaptureOutput:didOutputMetadataObjects:fromConnection:
メソッドを呼びます。そのときのコードを見てみましょう。
以下のコードは、識別結果のうちQRコードとJANコードに注目して、QRコードの場合はURLとして開き、JANコードの場合はISBNコードかどうかを見極めた上でamazonのページを開く処理です。
- (void)captureOutput:(AVCaptureOutput *)captureOutput
didOutputMetadataObjects:(NSArray *)metadataObjects
fromConnection:(AVCaptureConnection *)connection
{
// 認識されたメタデータは複数存在することもあるので、1つずつ調べる
for (AVMetadataObject *data in metadataObjects) {
// 一次元・二次元コード以外は無視する
// ※人物顔の識別結果だった場合など
if (![data isKindOfClass:[AVMetadataMachineReadableCodeObject class]])
continue;
// コード内の文字列を取得
NSString *strValue =
[(AVMetadataMachineReadableCodeObject *)data stringValue];
// 何のタイプとして認識されたかを確認
if ([data.type isEqualToString:AVMetadataObjectTypeQRCode]) {
// QRコードの場合、URLとしてmobileSafariを開く
NSURL *url = [NSURL URLWithString:strValue];
if ([[UIApplication sharedApplication] canOpenURL:url]) {
[[UIApplication sharedApplication] openURL:url];
}
} else if([data.type isEqualToString:AVMetadataObjectTypeEAN13Code]) {
// JANコード(標準タイプ)の場合、ISBNかどうかを調べて
// ASINコードに変換し、対応するAmazonのページを開く
long long value = strValue.longLongValue;
NSInteger prefix = value / 10000000000;
if (prefix == 978 || prefix == 979) { // ISBNかどうかをチェック
long long isbn9 = (value % 10000000000) / 10;
long long sum = 0, tmp_isbn = isbn9;
for (int i=10; i>0 && tmp_isbn>0; i--) {
long long divisor = pow(10, i-2);
sum += (tmp_isbn / divisor) * i;
tmp_isbn %= divisor;
}
long long checkdigit = 11 - (sum % 11);
// asinコードに変換
NSString *asin =
[NSString stringWithFormat:@"http://amazon.jp/dp/%lld%@",
isbn9,
(checkdigit == 10)?
@"X":[NSString stringWithFormat:@"%lld", checkdigit%11]];
[[UIApplication sharedApplication]
openURL:[NSURL URLWithString:asin]]; // URLのオープン
}
}
}
}
コードの認識結果を扱うAVMetadataMachineReadableCodeObject
クラスはAVMetadataObject
クラスのサブクラスなので、AVMetadataObject
クラスが持つ属性の他に「corners
プロパティ」と「stringValue
プロパティ」の2つを持っています。stringValue
プロパティが格納されていた文字列を示し、corners
プロパティの情報は、コードの四隅がどこにあるかを示す座標を、正しい向きでの左上から逆時計回りに格納した情報です。よく似たプロパティとしてbounds
プロパティがAVMetadataObject
クラスにありますが、こちらは画面上のコードが内接する矩形を示した情報を表すためのプロパティで、意味合いが異なります。
1次元コードは2次元コードと異なり、キャプチャデータの読み取り範囲が画面中央の一部に限定されています。アプリを作る場合は、画面上に帯を示すUIをもうけるようにしておくとUXの向上に繋がります。
2. コード生成編
続いてQRコードの作成方法です。こちらはCore ImageのCIQRCodeGenerator
フィルターを使って作成します。
実はiOS 6にもQRコード生成フィルターは用意されていてPassbookアプリでも使われていたのですが、Appleのプライベートフィルタとして分類されていたため、公式利用はできませんでした。
「格納したいデータ」と「誤り訂正レベル」の2つを設定するだけで、使用するQRコードのバージョンを自動的に判断し、出力画像には必要分のマージンも含めてくれる機能を持っています。
作成方法
以下のコードは、文字列「Hello, iOS7 world!」を含めたQRコードを作成し、画面上のUIImageView
に表示するまでの処理です。
// QRコード作成用のフィルターを作成・パラメータの初期化
CIFilter *ciFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
[ciFilter setDefaults];
// 格納する文字列をNSData形式(UTF-8でエンコード)で用意して設定
NSString *qrString = @"Hello, iOS7 world!";
NSData *data = [qrString dataUsingEncoding:NSUTF8StringEncoding];
[ciFilter setValue:data forKey:@"inputMessage"];
// 誤り訂正レベルを「L(低い)」に設定
[ciFilter setValue:@"L" forKey:@"inputCorrectionLevel"];
// Core Imageコンテキストを取得したらCGImage→UIImageと変換して描画
CIContext *ciContext = [CIContext contextWithOptions:nil];
CGImageRef cgimg =
[ciContext createCGImage:[ciFilter outputImage]
fromRect:[[ciFilter outputImage] extent]];
UIImage *image = [UIImage imageWithCGImage:cgimg scale:1.0f
orientation:UIImageOrientationUp];
CGImageRelease(cgimg);
// 画面のUIImageViewに表示
self.imageView.image = image;
CIQRCodeGeneratorフィルターは次に示す2つのパラメータを持っています。
- inputMessage:QRコードに格納するデータ。NSData形式
- inputCorrectionLevel:誤り訂正レベル。NSString形式
「inputMessage
」パラメータに文字列「Hello, iOS7 world!」を設定していますが、inputMessage
はNSData形式のデータを渡す必要があるため、いったんUTF-8でエンコードしたNSData
オブジェクトに変換してから設定しています。「inputCorrectionLevel」パラメータは、仕様と同じ「L」〜「H」のいずれか1文字をNSStringで設定します。Hに近づくほど冗長性が確保され、表面の一部が汚れて読み取れないようなときの耐性が上がります(その代わり、出来上がるQRコードは大きく、複雑になります)。
たったこれだけで、任意のデータを含むQRコードが作成できます。
生成したQRコードを表示するときの落とし穴
さて、これを画面上のUIImageViewにそのまま表示すると2つの問題がある問題に気付きます。それは、Core Imageで生成されるQRコードの画像が非常に小さいことで、もう1つは小さい画像をUIImageView
に拡大表示しても見にくいか、あるいは拡大表示してもぼけた状態で拡大されてしまうことです。
← 画面だと見やすいけれど、実際は等倍表示で小さすぎる…
← 拡大してもボケボケ…
前者のQRコードが小さい問題ですが、これは設定変更などでは解決できないので、使用するときに拡大して表示するなどしなければいけません。後者の「小さな画像を大きく表示するときのスケーリング結果が汚い」という問題は、おもに2つの方法で解決できます。1つがCGContextSetInterpolationQuality()
関数を使いつつUIImage
をリサイズする方法で、もう1つがUIImageView
のレイヤーにあるmagnificationFilter
プロパティを使って、スケーリングの補間アルゴリズムを切り替える方法です。
それぞれは次のようなコードで実現します。先のサンプルコードで、UIImageViewに表示する直前部分にどちらかを記述することを想定しています。
// その1:UIImageを最近傍法を適用しつつリサイズする
UIGraphicsBeginImageContext(CGSizeMake(300, 300));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationNone);←補間方法の指定
[image drawInRect:CGRectMake(0, 0, 300, 300)];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// その2:UIImageViewの描画方法を最近傍法に切り替える
self.imageView.layer.magnificationFilter = kCAFilterNearest;←補間方法の指定
self.imageView.contentMode = UIViewContentModeScaleAspectFit;
このどちらかの対策を施すことによって、次のように鮮明な画像を任意サイズで表示できます。
便利ですね。
3. おわりに
AVFoundationフレームワークもCore Imageフレームワークも、バージョンを重ねるたびに実現出来ることがどんどん増えている、とても強力なフレームワークです。ぜひいろいろな用途に活用してみてください。
明日以降のiOS Second Stage Advent Calendar 2013もお楽しみに!
One more thing
iOS 7 SDKの新機能を踏み込んで説明した書籍「上を目指すプログラマーのためのiPhoneアプリ開発テクニック iOS 7編」が発売されることになりました!
「iOS 7 SDKって何ができるの?どう使いこなしたらいいの?」「iOS 7のアプリ作成で、どういう点に気をつけたらいいの?」などに応える、中級〜上級者でも満足できる内容に仕上がっています。
Amazonではすでに予約が始まっていますので、ぜひぜひご覧くださいませ。
明日のiOS Advent Calender 2013 iOS second stageもお楽しみに!