音楽ど素人が機械学習で楽譜認識するAndroidアプリを作る話　スマホで音符の検出編

#はじめに
中学から吹奏楽部に入った息子が楽譜に音名を書くのがだるいと言いだしたんで、そんなもん今どきスマホとパシャッとすりゃAIでぱっと出てくるアプリがあるだろ？って言ったら意外となかったんで自分で作った
**五線譜の検出編**
https://qiita.com/Anadreline/items/4cf6e88c743a65b7da2c
**音符検出モデルの作成編**
https://qiita.com/Anadreline/items/f6c81774e241a33dd0d8
につづく第3弾、完結編です

#作成アプリ
**Music Score Reader**
https://play.google.com/store/apps/details?id=msreader.android.anadreline.com.musicscorereader
ちなみにiOS用だといくつかあります
**スマホ用の楽譜認識アプリをまとめてみた**
http://anadreline.blogspot.com/2019/06/wo.html

#開発者の初期スペック
- 五線譜の**どこがドなのかがわからない**
- **♯と♭って結局どっちかだけでよくない！？**と思っている
- 息子にトランペットの**ドはドじゃないからね**って言われてファッ！？ってなる
- 12音に五線譜表記って**仕様がおかしくね**って思っている

#検証環境
この記事の内容は、以下の環境で検証しました。

- Android Studio 3.2.1
- CompileSdkVersion:27
- MinSdkVersion:21
- TargetSdkVersion:27
- OpenCV for Android 3.2.3
- TensorFlow Mobile

#音符の検出プロセス
スマホ用のTensorFlow Mobileを利用して検出を行いました
ベースとなるところは**公式のサンプル**にすべて詰まってます
https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android
ただし、今やるならTensorFlow Liteを使うのがベストだと思います

##画像取得
まずは認識するための楽譜画像の取得

```java:MainActivity.java
    Uri m_uri;
    Bitmap m_image;
    
    //カメラIntent呼出
    String photoName = System.currentTimeMillis() + ".jpg";
    ContentValues contentValues = new ContentValues();
    contentValues.put(MediaStore.Images.Media.TITLE, photoName);
    contentValues.put(MediaStore.Images.Media.MIME_TYPE, "image/*");
    m_uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues);
    Intent intentCamera = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    intentCamera.putExtra(MediaStore.EXTRA_OUTPUT, m_uri);
    startActivityForResult(intentCamera, REQUEST_CAMERA);
    
    //撮影画像取得
    @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode == RESULT_OK) {
            m_image = Utl.decodeUri(this, m_uri);
        }
    }
```

よくあるカメラIntentからの取得、アプリではギャラリーからの取得も実装

##認識画像の切り出し
楽譜の全体画像で認識をかけても大きすぎて認識できないんで
**五線譜の検出編**
https://qiita.com/Anadreline/items/4cf6e88c743a65b7da2c
で検出した五線譜の部分から切り出して認識用画像としました

##認識に必要なもの
公式のサンプルでは画像分類や物体の検出をカメラのリアルタイムプレビューで行うサンプルのため、結構なボリュームのプロジェクトになってます
今回のアプリではリアルタイムプレビューでの認識ではなく、1枚の画像から認識できればいいので、必要なものはこれだけです
![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/36793/c1e5cb3f-abdc-95ba-7e18-095671389f0d.png)
[**公式のサンプル**](https://github.com/tensorflow/tensorflow/tree/master/tensorflow/examples/android)からClassifier.javaとTensorFlowObjectDetectionAPIModel.java
それにassetsに前回の[**音符検出モデルの作成編**](https://qiita.com/Anadreline/items/f6c81774e241a33dd0d8)で作成したモデルファイル(frozen_inference_graph.pb)とラベルファイル(label_map.txt)の4つだけ


##音符の認識
TensorFlowObjectDetectionAPIModel.createで認識モデルを生成して、Classifier.recognizeImage()に認識させたい画像を放り込むと、リスト形式で認識結果が取得できる

```java:Recognition.java
    private static final int TF_OD_API_INPUT_SIZE = 300;
    private static final String TF_OD_API_MODEL_FILE = "file:///android_asset/frozen_inference_graph.pb";
    
    Classifier detector = TensorFlowObjectDetectionAPIModel.create(getAssets(), TF_OD_API_MODEL_FILE, TF_OD_API_LABELS_FILE, TF_OD_API_INPUT_SIZE);
	final List<Classifier.Recognition> results = detector.recognizeImage(crop);
	for (final Classifier.Recognition result : results) {
        RectF location = result.getLocation(); //検出位置
		Float conf = result.getConfidence(); //確度
		String title = result.getTitle(); //ラベル名
	}
```    

あとは確度の値をみて一定値以上なら採用という具合で取得
このアプリでは0.5にしてます、すこしゆるめの判断でとにかく認識率を上げようという作戦
でもまあ未検出はあるものの、誤認識はほとんどなかった