Hackday優勝したので振り返る（技術）

# HackDay勝ちましてん。
<img width="648" alt="スクリーンショット 2016-02-20 7.07.04.png" src="https://qiita-image-store.s3.amazonaws.com/0/31237/5f134b84-e75b-527b-2e55-ebeb0a328286.png">

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

# 仕組み

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

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

仕組みはいい感じに図にまとめてもらいました。
<img width="1021" alt="スクリーンショット 2016-02-20 7.32.44.png" src="https://qiita-image-store.s3.amazonaws.com/0/31237/a2481f1b-51ef-a99f-44aa-6447835aaccf.png">

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

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

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

そんなときに頼りになるのが[Hackpad](http://gigazine.net/news/20140418-hackpad/)というサービスです。リアルタイムに複数人でMarkdown風のエディタを共有できます。

こんな感じで使ってました。
<img width="699" alt="スクリーンショット 2016-02-20 7.34.09.png" src="https://qiita-image-store.s3.amazonaws.com/0/31237/d10c7d50-d59c-a32a-e283-b2dcf422d365.png">

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

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

# 技術

## 使ったライブラリ
ビルド時間がもったいないので[carthage](http://www.slideshare.net/kenjhaneda/carthagemvvm)を使おうと思っていたんですが、今回エンジニア2人でセットアップするのが面倒だったのと、慣れてないものを使ってトラブルになるのを避けてcocoapodsにしました。

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

pod "CameraEngine"
```

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

```objc

// 画像の類似度を計算する
+(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](https://github.com/remirobert/CameraEngine)でした。

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

```swift

    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](https://qiita-image-store.s3.amazonaws.com/0/31237/0d9e98b5-ae2d-f082-2d43-4832a5042f0f.png)

![image](https://qiita-image-store.s3.amazonaws.com/0/31237/976428a6-1a18-441f-9eae-89ea86279c9d.png)

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

# まとめ

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

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

## 反省

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