69
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

スプラトゥーンの勝敗を画像認識で記録するiOSアプリを作ってみました

Last updated at Posted at 2016-04-20

作ったもの

ikascore
https://itunes.apple.com/jp/app/ikasukoa/id1097968132?l=ja&ls=1&mt=8

こんな感じで試合終了時に試合結果を判定して記録してくれるアプリ
イカスコア.gif

iPhoneを3脚に固定してカメラでゲーム画面を撮影しています

なんで作ったか

ちょうどOpenCVの勉強をしたかったので、いろいろ調べてみると

HDMIキャプチャ( https://github.com/hasegaw/IkaLog/blob/master/doc/IkaUI.md
Raspberry Pi( http://hogesuke.hateblo.jp/entry/ikashot

など、OpenCVでスプラトゥーンの試合結果を記録するものが見つかりました。
でも正直機材用意するの面倒だし、スマホでいいと思ったのと、常にストアにあるものは手書きで記録する系のアプリしか見つからなかったので作ってみました

スプラトゥーンの旬を逃しちゃってる感はあるけど、ついでなので。

仕組み

Artboard 1.png

作り方

Raspberry Pi版のコードを参考に、作るの大変なcascade xmlもgithubに公開されてたので使わせてもらっています。
https://github.com/hogesuke/ika-camera/tree/master/cascade

SwiftからOpenCVを扱う方法

CocoaPodsでOpenCVをインストール

podfile
platform :ios, '8.0'
pod 'OpenCV'

Swiftから直接OpenCVは動かせず、C++で書かなきゃだめなのです
なのでheaderファイルを作ります

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

@interface Detector: NSObject

- (id)init;
- (NSString *)recognize:(UIImage *)image;

@end

そしてこっちがOpenCVを扱うコードになります。
UIImageを渡して、OpenCVで扱うデータ構造のcv::Matに変換。
そしてika_result_winとika_result_loseそれぞれで検出させます。
勝敗を文字列、認識しなかった場合は空文字で返すように実装しています。

#import <Foundation/Foundation.h>
#import "ikascore-Bridging-Header.h"
#import <opencv2/opencv.hpp>

@interface Detector()
{
    cv::CascadeClassifier cascadeWin;
    cv::CascadeClassifier cascadeLose;
}
@end

@implementation Detector: NSObject

- (id)init {
    self = [super init];
    
    // 分類器の読み込み
    NSBundle *bundle = [NSBundle mainBundle];
    NSString *pathWin = [bundle pathForResource:@"ika_result_win" ofType:@"xml"];
    NSString *pathLose = [bundle pathForResource:@"ika_result_lose" ofType:@"xml"];
    std::string cascadeNameWin = (char *)[pathWin UTF8String];
    std::string cascadeNameLose = (char *)[pathLose UTF8String];
    
    if(!cascadeWin.load(cascadeNameWin) || !cascadeLose.load(cascadeNameLose)) {
        return nil;
    }
    
    return self;
}

- (NSString *)recognize:(UIImage *)image {
    // UIImage -> cv::Mat変換
    CGColorSpaceRef colorSpace = CGImageGetColorSpace(image.CGImage);
    CGFloat cols = image.size.width;
    CGFloat rows = image.size.height;
    
    cv::Mat mat(rows, cols, CV_8UC4);
    
    CGContextRef contextRef = CGBitmapContextCreate(mat.data,
                                                    cols,
                                                    rows,
                                                    8,
                                                    mat.step[0],
                                                    colorSpace,
                                                    kCGImageAlphaNoneSkipLast |
                                                    kCGBitmapByteOrderDefault);
    
    CGContextDrawImage(contextRef, CGRectMake(0, 0, cols, rows), image.CGImage);
    CGContextRelease(contextRef);
    
    
    std::vector<cv::Rect> res;

    // Win検出
    cascadeWin.detectMultiScale(mat, res,
                             1.1, 2,
                             CV_HAAR_SCALE_IMAGE,
                             cv::Size(50, 50));
    std::vector<cv::Rect>::const_iterator w = res.begin();
    if (w != res.end()) {
        return @"win";
    }
    
    // Lose検出
    cascadeLose.detectMultiScale(mat, res,
                             1.1, 2,
                             CV_HAAR_SCALE_IMAGE,
                             cv::Size(50, 50));
    std::vector<cv::Rect>::const_iterator r = res.begin();
    if (r != res.end()) {
        return @"lose";
    }
    
    // 検出されない
    return @"";
}

@end

あとはSwift側でカメラから作ったUIImageを随時渡す感じです。

// 顔検出オブジェクト
let detector:Detector = Detector()
let res:NSString = detector.recognize(image)

if (res == "win") {
  // 勝ち
} else if (res == "lose") {
  // 負け
} else {
  // 試合中
}

気をつけることろ

かなり高負荷で動かしっぱなしになるので端末が熱くなります。
最初やった時に熱で電源落ちたので少し対策をする必要がありました。

カメラの解像度が高いと検出に時間がかかってしまうため、少し落としておく

mySession = AVCaptureSession()
mySession.sessionPreset = AVCaptureSessionPresetMedium

遅れてきたフレームは無視する

myOutput = AVCaptureVideoDataOutput()
myOutput.alwaysDiscardsLateVideoFrames = true

試合終了後しばらくはインターバルなので端末を休ませる

NSTimer.scheduledTimerWithTimeInterval(60.0, target: self, selector:#selector(restore), userInfo: nil, repeats: false)

ソースコード

出来上がったものはこちら
https://github.com/narikei/ikascore

おわり

台形補正がないので、カメラを真正面に向けないと認識してくれないです。位置合わせは慣れるまでシビア。

今はTwitterに吐き出すだけだけど、ちゃんとアプリの方にデータ記録したいですね。
コードもリファクタリング、というか適当に書き殴ってるので書き直さないと

プルリクください。

参考にさせてもらったURL

http://hogesuke.hateblo.jp/entry/ikashot
http://giveitashot.hatenadiary.jp/entry/2014/10/23/080930

69
65
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
65

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?