LoginSignup
4
2

More than 5 years have passed since last update.

怒りを爆発させて地球人を少しだけ超える

Posted at

概要

音声感情解析セミナーに行ってきたので、早速使ってみる。
なんと300コール/月まで無料という太っ腹だ。使わざるをえない。

使い道を2分位考えた結果、怒りも数値で取れる事だし、怒りで目覚める事にした。

image.png
何を思い浮かべるかで年がバレる瞬間

結果

こんな感じ。

image.png
何か想像より人造人間に近くなった。

コード

SuperTikyujin

詳細

怒りで目覚めるとどうなるかというと、金髪になる。
なので怒りを検知したら金髪になるようなプログラムを作ることになる。

他にも

  • 眉毛がなくなる
  • 髪が長くなる
  • 青くなる
  • すごいゴリラになる

などなどあるが、似せれば似せるほど訴えられかねないのでやめる。

下準備

まずはアカウント作成
ココから新規アカウントを発行する。

image.png
新規ならSign up

ログインしたら、APIキーを発行する。Addボタンを押すだけ。

image.png

作成

録音してEmpathAPIを呼び出す

EmpathAPI自体はシンプルなWebAPIなので基本的になんにでも対応している。
ドキュメント上は下記言語のサンプルがある。

  • java
  • php
  • nodejs

なんでもよいのだが、今回はandroid javaで利用する。折角作るなら持ち運んで人に見せたいからだ。
音声を保存する部分はココが超絶参考になる。

まずは適当に画面を作る。シンプルにボタンとラベルだけ。

image.png

後はボタンを押したら録音開始。一定時間たったら、AsyncTask経由でファイルに保存した音声を乗せたAPI呼び出すだけ。簡単。

MainActivity.java
    private final String EMPATH_ENDPOINT = "https://api.webempath.net/v2/analyzeWav";
    private final String EMPATH_APIKEY = "";

    private final String filePath = Environment.getExternalStorageDirectory() + "/empath.wav";

    private Button btnDetectEmotion = null;
    private TextView labelResult = null;
    private Handler handler = null;
    private final int MSG_STOP_RECORDING = 1000;

    AudioRecord audioRecord;
    private int bufSize;
    private short[] shortData;
    private WaveFile wav = new WaveFile();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btnDetectEmotion = this.findViewById(R.id.btnDetectEmotion);
        btnDetectEmotion.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                startRecording();
            }
        });

        labelResult = this.findViewById(R.id.textViewResult);
        handler = new Handler(new Handler.Callback(){

            @Override
            public boolean handleMessage(Message message) {
                switch (message.what){
                    case MSG_STOP_RECORDING:
                        stopRecoding();
                        break;
                }
                return false;
            }
        });
        wav.createFile(filePath);
        bufSize = android.media.AudioRecord.getMinBufferSize(SoundDefine.SAMPLING_RATE,
                 AudioFormat.CHANNEL_IN_MONO,
                 AudioFormat.ENCODING_PCM_16BIT);
        audioRecord = new AudioRecord(MediaRecorder.AudioSource.MIC,
            SoundDefine.SAMPLING_RATE,
            AudioFormat.CHANNEL_IN_MONO,
            AudioFormat.ENCODING_PCM_16BIT,
            bufSize);
        shortData = new short[bufSize / 2];
        audioRecord.setRecordPositionUpdateListener(new AudioRecord.OnRecordPositionUpdateListener() {
            @Override
            public void onPeriodicNotification(AudioRecord recorder) {
                audioRecord.read(shortData, 0, bufSize / 2); // 読み込む
                wav.addBigEndianData(shortData); // ファイルに書き出す
            }

            @Override
            public void onMarkerReached(AudioRecord recorder) { }
        });
        audioRecord.setPositionNotificationPeriod(bufSize / 2);
    }

    private void startRecording() {
        labelResult.setText("now recording");
        audioRecord.startRecording();
        audioRecord.read(shortData, 0, bufSize/2);

        Message msg = Message.obtain();
        msg.what = MSG_STOP_RECORDING;
        handler.sendMessageDelayed(msg, 3000);
        btnDetectEmotion.setEnabled(false);
    }

    private void stopRecoding() {
       btnDetectEmotion.setEnabled(true);
       labelResult.setText("now detecting");
       try {
           audioRecord.stop();
           new GetEmotion().execute();
       } catch (Exception e) {
           e.printStackTrace();
       }
    }

    private class GetEmotion extends AsyncTask<String,Void,Long>{
        private String result = null;

        @Override
        protected Long doInBackground(String... strings) {
            try{
                OkHttpClient client = new OkHttpClient();
                File file = new File(filePath);
                RequestBody requestBody = new MultipartBody.Builder()
                        .setType(FORM)
                        .addFormDataPart("apikey",EMPATH_APIKEY)
                        .addFormDataPart("wav", "empath.wav", RequestBody.create(MediaType.parse("audio/wav"), file))
                        .build();
                Request request = new Request.Builder().url(EMPATH_ENDPOINT).post(requestBody).build();
                Response response = client.newCall(request).execute();
                this.result = response.body().string();
            }catch(Exception e){
                result = "error";
            }
            return 0L;
        }

        @Override
        protected void onPostExecute(Long aLong) {
            labelResult.setText(result);
            super.onPostExecute(aLong);
        }
    }

実行結果はこんな感じ

image.png
悲しみが出てしまった

呼べたらちゃんと記録される。

image.png
後298回呼べる

怒った時金髪になる

動作としては

  • ボタンを押すとレコーディング開始
  • レコーディングで怒りが規定値を超えたら撮影
  • 顔を検知する
  • 金髪を合成
  • 数秒間は表示

金髪の表示位置はこんな感じ

image.png
大きさは大体顔と同じぐらいかな

とりあえず怒りの判定と金髪の仕込みはこんな感じ。

    private void createSuperTikyuJin(JSONObject emotion)  {
        try{
            int anger = emotion.getInt("anger");
            if(anger >= 30){
                boolean takePictureResult;
                if (mCamera.takePicture(new ImageReader.OnImageAvailableListener() {
                    @Override
                    public void onImageAvailable(ImageReader imageReader) {
                        Log.d(TAG, "onImageAvailable");
                        Matrix matrix = new Matrix();
                        matrix.preScale(0.3f, -0.3f);
                        final Image image = imageReader.acquireLatestImage();
                        ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                        byte[] buff = new byte[buffer.remaining()];
                        buffer.get(buff);
                        Bitmap bitmap = BitmapFactory.decodeByteArray(buff, 0, buff.length);
                        image.close();

                        mCorrectRotateBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, false);

                        Frame frame = new Frame.Builder()
                                .setBitmap(mCorrectRotateBitmap)
                                .build();
                        SparseArray detectedFaces = mFaceDetector.detect(frame);
                        if (detectedFaces != null && detectedFaces.size() > 0) {
                            float maxSize = 0;
                            int idx = 0;
                            for (int i = 0; i < detectedFaces.size(); i++) {
                                Face face = (Face) detectedFaces.valueAt(i);
                                if (maxSize < (face.getWidth() * face.getHeight())) {
                                    maxSize = (face.getWidth() * face.getHeight());
                                    idx = i;
                                }
                            }
                            Face face = (Face) detectedFaces.valueAt(idx);
                            Bitmap kinpatsu = BitmapFactory.decodeResource(
                                    getResources(),
                                    R.drawable.super_hair);
                            float scaleWidth = face.getWidth() / (float) kinpatsu.getWidth();
                            float scaleHeight = face.getWidth() / (float) kinpatsu.getWidth();
                            Matrix kinpatsuScale = new Matrix();
                            kinpatsuScale.postScale(scaleWidth, scaleHeight);
                            Bitmap resizedKinpatsu = Bitmap.createBitmap(
                                    kinpatsu,
                                    0,
                                    0,
                                    kinpatsu.getWidth(),
                                    kinpatsu.getHeight(),
                                    kinpatsuScale,
                                    false);

                            Canvas canvas = new Canvas(mCorrectRotateBitmap);
                            canvas.drawBitmap(
                                    resizedKinpatsu,
                                    face.getPosition().x,
                                    face.getPosition().y - ((face.getHeight() / 2.0f)),
                                    null);

                            imgSuperTikykujin.setImageBitmap(mCorrectRotateBitmap);


                            resizedKinpatsu.recycle();
                            kinpatsu.recycle();

                            Message msgShowImage = Message.obtain();
                            msgShowImage.what = MSG_SHOW_SUPER_TIKYUJIN;
                            handler.sendMessage(msgShowImage);

                            Message msgHideImage = Message.obtain();
                            msgShowImage.what = MSG_HIDE_SUPER_TIKYUJIN;
                            handler.sendMessageDelayed(msgHideImage, 5000);
                        } else {
                            Toast.makeText(getApplicationContext(), "face not exists", Toast.LENGTH_SHORT).show();
                        }

                        bitmap.recycle();
                        bitmap = null;
                    }
                })) takePictureResult = true;
                else takePictureResult = false;
            } else {
                // not angry(like a hotoke)
                Toast.makeText(this, "怒りが足りない", Toast.LENGTH_LONG).show();
            }
        }catch(JSONException ex){
            Toast.makeText(this, "怒りが感じられない(おかしい)", Toast.LENGTH_LONG).show();
        }
    }

振り返り

音声APIを使って何か作るのが目的だったけど、
ビデオの画像加工をやろうとして時間かかってしまった。

時間かかりそうだったから結局やめた。
泥沼にはまりそうだったから、結局諦めてよかった。

諦め大事。

あと、何回かやってると「穏やかさ」が増えてきた

image.png
年取ると丸くなるんだろうな

4
2
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
4
2