LoginSignup
84
80

More than 5 years have passed since last update.

Hackday優勝したので振り返る(技術)

Last updated at Posted at 2016-02-23

HackDay勝ちましてん。

スクリーンショット 2016-02-20 7.07.04.png

24時間でこういうのを作りました。今回は振り返りを含めて使った技術やアルゴリズムを中心にまとめようとおもいます。

仕組み

スマホを持ち上げてる間は自動撮影、周りの目を気にせずに料理の写真を撮れるカメラアプリ

最初アイデアがでてみんなでブレストしているときは、うわ。このアプリめっちゃ欲しいと思ってすごいテンション上がったんですけど、作ってみるとめちゃめちゃ大変で絶賛徹夜開発でした。

仕組みはいい感じに図にまとめてもらいました。
スクリーンショット 2016-02-20 7.32.44.png

主にスコアリングによる画像とフィルターの選択を思いつく限りの盛り込みと、実測値からのシーンへの最適化で実現しています。

  1. 加速度・ジャイロセンサーでiPhoneが持ち上がったのと、置かれたのを検知してカメラを管理
  2. 起動中は映像を撮影しながら超音波を出す
  3. 撮影された映像を0.1秒ごとにキャプチャして、その時々の加速度やジャイロの値、超音波から取れる数値を取得
  4. めちゃめちゃ解析して画像とフィルター選択

設計

ハッカソンでの開発って仕様や設計なんてないようなもんですが、それは超ダメです。
短い時間で移り変わる仕様と設計を柔軟に受け入れチームで共有して、がしがし仕様変えながら実装していくのが大事です。じゃないとできないことをできるようになりません。

そんなときに頼りになるのがHackpadというサービスです。リアルタイムに複数人でMarkdown風のエディタを共有できます。

こんな感じで使ってました。
スクリーンショット 2016-02-20 7.34.09.png

  • 仕様
  • 設計
  • スキーマ定義
  • 進捗
  • setupコマンド

などなど、常に更新しながら開発を進めました。Githubのissueでもいいんですが、多機能すぎて24時間でやることを共有するのにはちょっとめんどうに感じました。これで常に共有すべきことだけシンプルに共有していました。

技術

使ったライブラリ

ビルド時間がもったいないのでcarthageを使おうと思っていたんですが、今回エンジニア2人でセットアップするのが面倒だったのと、慣れてないものを使ってトラブルになるのを避けてcocoapodsにしました。

platform :ios, "9.0"
use_frameworks!
pod 'OpenCV', '2.4.9'
pod 'MagicalRecord'
pod 'KSToastView'
pod 'SVProgressHUD'
pod 'GPUImage'

pod "CameraEngine"

OpenCV

画像処理はこれしかないと思ったので、とりあえず入れました。
画像のピクセルデータにアクセスして色情報を取得する方法もあるので、がんばれば画像処理もライブラリ使わないでもいけるようだけど、あまりパフフォーマンスがでないとどこかで聞いた。今回はOpenCVは研究で使っていたこともあったので、使うことにした。
ヒストグラム管理したり、比較するのをさらっと書ける。


// 画像の類似度を計算する
+(double)calculateSimilarity:(UIImage*)sourceImg :(UIImage*)targetImg{
    cv::Mat src_img;
    UIImageToMat(sourceImg, src_img);
    cv::Mat target_img;
    UIImageToMat(targetImg, target_img);

    // グレースケールに変換
    cv::Mat grayImage1;
    cv::cvtColor(src_img, grayImage1, cv::COLOR_BGR2HSV);
    cv::Mat grayImage2;
    cv::cvtColor(target_img, grayImage2, cv::COLOR_BGR2HSV);

    // ヒストグラムを計算
    // and the saturation to 32 levels
    int hbins = 30, sbins = 32;
    int histSize[] = {hbins, sbins};
    // hue varies from 0 to 179, see cvtColor
    float hranges[] = { 0, 180 };
    // saturation varies from 0 (black-gray-white) to
    // 255 (pure spectrum color)
    float sranges[] = { 0, 256 };
    const float* ranges[] = { hranges, sranges };
    cv::MatND sourceHist;
    cv::MatND targetHist;
    // we compute the histogram from the 0-th and 1-st channels
    int channels[] = {0, 1};

    calcHist( &grayImage1, 1, channels, cv::Mat(), // do not use mask
             sourceHist, 2, histSize, ranges,
             true, // the histogram is uniform
             false );

    calcHist( &grayImage2, 1, channels, cv::Mat(), // do not use mask
             targetHist, 2, histSize, ranges,
             true, // the histogram is uniform
             false );

    /*
     比較手法.次のうちの1つ:
     CV_COMP_CORREL 相関
     CV_COMP_CHISQR カイ2乗
     CV_COMP_INTERSECT 交差
     CV_COMP_BHATTACHARYYA Bhattacharyya距離
     */
    double similarity = cv::compareHist(sourceHist, targetHist, CV_COMP_BHATTACHARYYA);
    NSLog(@"[Is Similarity Rate] %f", similarity);

    return similarity;
}

こういう処理を無茶苦茶書いた。

GPUImage/CameraEngine

最初映像を保存してアプリ内に保存する処理を自前で書いたんですね。
でもずっと落ち続ける問題が出ていてあきらめました・・・

self.fileWriter.finishWritingWithCompletionHandler(callback)

fileWriterはAVAssetWriterなのですがこいつがクラッシュし続けてあきらめました・・・
昔は全く同じコードでうまくいったんですがね。これに2時間くらいはまってました。

「作り直そう・・・」
そう思って泣きながら探したライブラリがCameraEngineでした。

インターフェースがとってもきれいで「カメラを映像名を指定して録画して、録画後のアクションを実装する」というのがいい感じにかけます。細かいプロパティとかも指定できるので便利です。


    func startCapture(sender: UIButton){
        guard let url = CameraEngineFileManager.documentPath("video.mp4") else {
            return
        }

        self.cameraEngine.startRecordingVideo(url, blockCompletion: { (url, error) -> (Void) in
            // Run and Save Capture Image
            self.sceneAnalyze = SceneAnalyze(moviePath: url!, accelerationList: accelerations, gravityList: gravities)
        })
    }

検証

  • 画像の特定領域の明度の分散値
  • 画像の緑色の割合
  • 画像のHSVヒストグラムの類似度 ...

みたいのを延々算出しては「あかん!」「ちがう!」「眠い!」っていうのを繰り返し続けてました。
思いついたメソッドとかちょっと試すのにはユニットテストめちゃめちゃ書いてました。
いろいろスコアリングするので、信頼性のないコードはいやだったんでばしばし書いてました。
コードをアクションに組み込んだ後にあれ?あってる?とかすぐ確かめられるので、効率がいいです。

たくさん検証する

アプリでは目につかない検証用の画面とかずっとみてました。
例えば「おいしそうな画像を選ぶ」ために、お寿司の画像を二値化して領域を楕円区分してシズル感のありそうな箇所をマッングしている様子です。
この二値化の部分に大津のアルゴリズムを使っていると書いてありますが、理論は正直よくわかってなくてorz 何回もテスト回して今回のターゲットに対していい精度が出ていることを実測値から確認したから採用しました。
こういうのをいくつもやりました、とにかく繰り返しです。実測値こそ命。

image

image

そういう感じでOpenCVの楕円検出とその領域の画素値を見て光沢っぽい場所=シズル感のある場所を見つけてました。

まとめ

要約するとめちゃめちゃいろいろやったらなんかいい感じのスコアリングの仕組みができてそれなりに動いたって感じです。優勝しておいて、ちょっと心もとない感想ですが・・・

でも短い時間にいかに検証につぎ込めるかが大事だと思うので、それ以外の基本的な実装とか検証用のインターフェースのすぐ作れるかとか、どうチームで作るかとかは、仕事でも個人の開発の中でも意識して勉強してそれを使わないとダメです。

反省

  • ユニットテストのコードが汚い、関数名適当すぎてあとで探索の手間が・・・
  • 検証用のUIとかがっつりOSS使えばよかった
  • 必要のないpodfileいれすぎてビルドが重かった
  • 設計と検証の前後が多かった
  • デモは立ってやることを想定してなかったので、デモデイで失敗があった
    • デモでのユーザーの動作の想定が漏れた
  • 幾つかの実験に関わる定数はplistでもたせて状態管理をすればよかった
  • Playgroundで検証した部分もコッミットしてRxSwiftのサンプルみたいにすればよかった
84
80
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
84
80