WallStudio

技術ブログや創作ブログに届かない雑記です

喋る同人誌が作りたい!① 技術検証編

ということがやりたい訳です.VOICEROIDって本来,声が本体みたいなものじゃないですか.でも同人誌って絵だけ…ライセンス的にデジタルとは相性が悪いのは分かるのですが勿体ないですよね.これまでもアプリは無料で配って「連携できる」同人誌で開発費等の一部を賄ったりしていたのですがそんな感じです.

ということで目を付けたのがこの技術.

STARRYWORKSさんの「PLAYFUN BOOKS」です.絵本の最後のページにiPhoneを格納して,他のページには穴をあけておく.iPhoneのカメラで穴の数を検出して今開いているページを認識するという仕組みのようです.開いたページがわかればBGMを流したりバイブをかけたりして演出をすることができます.(よく聖書がくり抜いて合って中にピストルが入ってるあれを想起させますよねw)

早速穴あけポンチを買ってきて,手元にあった手ごろな黒い紙である自分の名刺を加工してみました.

普通に穴をあけただけだとこんな感じに穴の先にピントが合ってしまい穴をカメラで検出することが難しいです.PLAYFUN BOOKSはもともと照明との連携を前提としているらしく,本の真上から煌々と照らされている状況が想定されているみたいです.私のやりたいことは間接照明でも使えるようにしたいのでこれは良くない…ということで穴を半透明な素材でふさいでみました.

透明タイプのグルーボンドです.これで検出できそうになってキマシた.ちなみに,少し盛り上がるようにすることで穴に角度のある光にも強くなります.

ハードは何となく行けそうな気がしてきました!次はソフトウェアですね.

iOSアプリ開発はしたことがないので,Androidから手を付けます.といってもAndoridもBMI計算アプリが作れる程度の知識しかなかったのでして…(´;ω;`)

mTextureView.setSurfaceTextureListener(new TextureView.SurfaceTextureListener() {
    @Override
    public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture, int i, int i1) {
        ...
    }

    @Override
    public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
        Log.d("",mTextureView.getWidth() +"x"+mTextureView.getHeight() 
                + " "+mTextureView.getBitmap().isMutable());
        Bitmap bitmap = mTextureView.getBitmap();
        int[] buffer = new int[bitmap.getWidth() * bitmap.getHeight()];

        for (int j = 0; j < bitmap.getHeight(); j++){
            for(int i = 0; i < bitmap.getWidth(); i++){
                int pixel = bitmap.getPixel(i, j);
                int gray = (int)(0.299 * Color.red(pixel) 
                        + 0.587 *  Color.green(pixel) 
                        + 0.114 * Color.blue(pixel));
                gray = gray < 63 ? 10 : 244;
                buffer[j * bitmap.getWidth() + i] = Color.argb(
                        Color.alpha(pixel),
                        gray,
                        gray,
                        gray);
                }
            }
            bitmap.setPixels(buffer, 0,
                    bitmap.getWidth(), 0, 0,
                    bitmap.getWidth(), bitmap.getHeight());
            mImageView.setImageBitmap(bitmap);
        }
});

かなり泥臭いやり方なんですが,TextureViewを180x180px固定,ImageViewをActivityにフィットさせてたレイアウトXMLを用意しています.onSurfaceTextureUpdatedでTextureViewのBitmapを拾って加工してImageViewに張り付けています.TextureViewが持っているBitmapはMutableなのでこれを加工したらTextureViewにも反映されそうなもんですが,そうもいかないみたいでした.画面の書き換えが終わった後でこのonSurfaceTextureUpdatedが呼ばれているのかなとかんがえています.

最初フラグメントでやろうとしていたのですが,AndroidStudioのLayoutDesinerがフラグメントだとViewが左上に意地でも張り付いて動かない&サイズもいじれない(値をベタ打ちすれば行けるけど)く,使い勝手が悪いのであきらめてActivityに直置きしました.(想定された使い方と違うのか?)

リアルタイムでカメラの入力を処理するってケースはよくありそうなものですが,意外と情報が少なくて鉄板のやり方みたいなのがよくわかりません.OpenCVがラップしてるみたいなので普通こっちを使うのかな?

まぁお作法や堅牢さ,パフォーマンスは置いておくとして,画像は閾値処理をした結果です.用途的に十分でしょう.今回はノイズもほぼないですが,環境によってはもうちょっと出てくると思うので適当にオープニングして大津の二値化,領域をラベリングしてカウントしたらページ数が出てくるはずです.OpenCVならここまで10行程度で書けたと思うので次回からOpenCV使いましょう.