ゴッホのひまわりを検出して、しゃべらせるARを作ってみましょう
で、「何」を検出するか考えているうちに、ゴッホの「ひまわり」はどうかな?と。
よく似ているものを識別して検出できるのか?
最初は半信半疑で、特に「15本の向日葵」と「12本の向日葵」は、構図も色合いもよく似ているので
7割方無理だろうと思っていました。
で、やってみて驚きました。問題なくすんなりと識別して検出。
SATCHというAR用SDKのエンジンは色認識はやっていない、ということのようなので、特徴点のみでデータを作っているのか?
「しゃべる」という目的は二の次になり、どうなっているのか?...が今はメインです。
これから調べてみます。わかり次第ご報告します。
NEWS
独立行政法人情報通信研究機構(NICT)が多言語に対応した音声翻訳アプリを国際研究チームと開発、無償で一般公開してくれるそうな。
現在は、iPhoneのみの対応だけど、今年中にはAndroidでも使えるようになる....そうです。
VoiceTra4U-M
TOP
対象作品と言語
検出対象は、現存する以下の6点。
(Wikipediaから引用)
1888年8月に制作されたとされる3点
12本の向日葵(ノイエ・ピナコテーク)
15本の向日葵(ナショナル・ギャラリー)
3本の向日葵(個人所蔵)
1889年1月に制作されたとされる3点
15本の向日葵(ゴッホ美術館)
15本の向日葵(損保ジャパン東郷青児美術館)
12本の向日葵(フィラデルフィア美術館)
日・英・仏の3ケ国語で、しゃべってみます。
TOP
SATCHシナリオ作成
SATCHでのシナリオのつくり方は以下を参照。
SATCHを使ってArt_ProjectでAR(マルチ・トラッキング)
ご注意!
個人所蔵の「3本の向日葵」は、検出に手間がかかります。
データ作成のための元絵の解像度が低かったのが原因....だと思います。
こんな画像でデータを作りました。
TOP
Eclipseでアプリ作成
Eclipse側の基本コードはこのページを参照
TextToSpeechのEclipse側のコードはこんな感じ。
vvvv,wwww,xxxx,yyyy,zzzzは適当に読み替えてください。
【抽象クラス】
package com.kddi.satch.zzzz; import android.app.Activity; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.graphics.Color; import android.os.Bundle; import android.os.Handler; import android.view.KeyEvent; import android.view.View; import android.widget.FrameLayout; import com.kddi.satch.LoadScenarioStatus; import com.kddi.satch.ARViewer; import com.kddi.satch.sunflowersspeaks.SunflowersSpeaksActivity; import android.util.Log; import java.util.Locale; import android.speech.tts.TextToSpeech;//追加4tts import android.speech.tts.TextToSpeech.OnInitListener;//追加4tts import android.widget.LinearLayout; import android.widget.TextView; import android.widget.TextView.BufferType; import android.view.WindowManager.LayoutParams; import android.os.Handler; import android.widget.Toast; public abstract class xxxx extends Activity implements TextToSpeech.OnInitListener { protected abstract String getSampleScenarioName(); protected abstract String getSampleLogTag(); public TextToSpeech tts;//追加4tts public TextView Text_View; public LinearLayout textLayout; private Handler mHandler; private String textview_content = ""; public String disp_lang = "en"; protected boolean _isInitializedCorrectly; protected ARViewer _kddiComponent; protected FrameLayout _frameLayout; public void resetMembers(){ _isInitializedCorrectly = false; _frameLayout = null; _kddiComponent = null; } private static final int DIALOG_EXIT = 0; public void initComponent(){ _isInitializedCorrectly = false; _kddiComponent = new ARViewer(this); // This FrameLayout must be empty (but initialized) when you pass it to the kddiComponent.initialize() method. _frameLayout = new FrameLayout(this); _kddiComponent.initialize(_frameLayout); _isInitializedCorrectly = true; } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Add null to AR Viewer Library compornent's reference. resetMembers(); //追加4tts tts = new TextToSpeech(getApplicationContext(), this); mHandler = new Handler(); } @Override public void onRestart() { super.onRestart(); } @Override public void onStart() { super.onStart(); // Create AR Viewer Library compornent. initComponent(); postInitComponent(); initContentView(); if (_isInitializedCorrectly) { // Do authorize and madia is loaded. // You must call loadScenario() method. loadScenario(); } } @Override public void onResume(){ super.onResume(); if (_isInitializedCorrectly) { // GL context is recreated and media is reloaded. _kddiComponent.onResume(); reservePlayScenario(); } } @Override public void onPause() { // When the activity is paused the GL context is destroyed, so all media is unloaded. if (_isInitializedCorrectly) { cancelReservePlayScenario(); if (_kddiComponent.checkLoadScenarioStatus() == LoadScenarioStatus.COMPLETE) { _kddiComponent.pauseScenario(); } _kddiComponent.onPause(); } super.onPause(); } @Override public void onStop() { releaseContentView(); // Destroy AR Viewer Library Objects. if (_isInitializedCorrectly){ _kddiComponent.terminate(); _kddiComponent = null; _frameLayout = null; } super.onStop(); } //追加4tts @Override public void onInit(int status) { //空 // } @Override public void onDestroy() { // Destroy AR Viewer Library Objects. if (_isInitializedCorrectly){ _frameLayout = null; _kddiComponent = null; } super.onDestroy(); resetMembers(); // forced clean //追加4tts tts.shutdown(); } @Override public boolean onKeyDown(int keyCode, KeyEvent msg){ switch(keyCode){ case android.view.KeyEvent.KEYCODE_BACK : showDialog( DIALOG_EXIT ); return true; } return false; } public void postInitComponent(){ // override this if you need to do some special handling on the component after standard initialization if (_isInitializedCorrectly) { _kddiComponent.activateAutoFocusOnDownEvent(true); } } public void initContentView(){ // override this if you need to do some special handling on the component after standard initialization if (_isInitializedCorrectly) { // you'll probably use some other UI object as the content view that itself will embed the component's frame layout -- here you can change all this // by default, the frame layout containing DFusion will be the activity content view setContentView(_frameLayout); } // textLayout = new LinearLayout(this); Text_View = new TextView(this); Text_View.setTextColor(Color.YELLOW); Text_View.setText(""); textLayout.addView(Text_View, new LinearLayout.LayoutParams(200,200)); //textLayout.setVisibility(View.VISIBLE); addContentView(textLayout, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); } public void set_text(String str_content){ if (str_content.equals("private")) { if (disp_lang.equals("ja")) { textview_content = "3本の向日葵(個人所蔵)"; }else if (disp_lang.equals("en")) { textview_content = "First version(Private collection)"; }else if (disp_lang.equals("fr")) { textview_content = "Vase avec trois tournesols(Collection privée)"; } }else if (str_content.equals("gogh")){ if (disp_lang.equals("ja")) { textview_content = "15本の向日葵(ゴッホ美術館)"; }else if (disp_lang.equals("en")) { textview_content = "Repetition of the 4th version(Van Gogh Museum)"; }else if (disp_lang.equals("fr")) { textview_content = "Vase avec quinze tournesols(Musée van Gogh)"; } }else if (str_content.equals("sompo")){ if (disp_lang.equals("ja")) { textview_content = "15本の向日葵(損保ジャパン東郷青児美術館)"; }else if (disp_lang.equals("en")) { textview_content = "Replica of the 4th version(Sompo Japan Museum of Art)"; }else if (disp_lang.equals("fr")) { textview_content = "Vase avec quinze tournesols(Musée d'art Sompo)"; } }else if (str_content.equals("national")){ if (disp_lang.equals("ja")) { textview_content = "15本の向日葵(ナショナルギャラリー)"; }else if (disp_lang.equals("en")) { textview_content = "Fourth version(National Gallery)"; }else if (disp_lang.equals("fr")) { textview_content = "Vase avec quinze tournesols(National Gallery)"; } }else if (str_content.equals("neue")){ if (disp_lang.equals("ja")) { textview_content = "12本の向日葵(ノイエ・ピナコテーク)"; }else if (disp_lang.equals("en")) { textview_content = "Third version(Neue Pinakothek)"; }else if (disp_lang.equals("fr")) { textview_content = "Vase avec douze tournesols(Neue Pinakothek)"; } }else if (str_content.equals("philadelphia")){ if (disp_lang.equals("ja")) { textview_content = "12本の向日葵(フィラディルフィア美術館)"; }else if (disp_lang.equals("en")) { textview_content = "Repetition of the 3rd version(Philadelphia Museum of Art)"; }else if (disp_lang.equals("fr")) { textview_content = "Vase avec douze tournesols(Musée d'art de Philadelphie)"; } }else{ textview_content = ""; } (new Thread(new Runnable() { @Override public void run() { mHandler.post(new Runnable() { @Override public void run() { // Text_View.setText(textview_content); } }); } })).start(); } public void releaseContentView(){ // override this if you need to do some special handling on the component after standard initialization if (_isInitializedCorrectly) { // do here the release of the your UI instances (if customized) } } public void loadScenario(){ ApplicationInfo appInfo = null; PackageManager packMgmr = getApplicationContext().getPackageManager(); try { appInfo = packMgmr.getApplicationInfo(getPackageName(), 0); } catch (NameNotFoundException e) { e.printStackTrace(); throw new RuntimeException("Unable to locate assets, aborting..."); } String dpdfile = appInfo.sourceDir + getSampleScenarioName(); _kddiComponent.loadScenario(dpdfile); } // Set polling rate for loading the media. private final int REPEAT_INTERVAL = 100; private Handler handler = new Handler(); private Runnable runnable = null; // private void reservePlayScenario(){ if (runnable == null){ runnable = new Runnable(){ @Override public void run(){ LoadScenarioStatus status = _kddiComponent.checkLoadScenarioStatus(); if (status == LoadScenarioStatus.CANCEL){ // cancel(appli suspend) }else if (status == LoadScenarioStatus.COMPLETE){ // Ready to play scenario _frameLayout.setVisibility(View.VISIBLE); _kddiComponent.playScenario(); Toast.makeText(xxxx.this, "Preparing........", Toast.LENGTH_LONG).show(); }else if ( status == LoadScenarioStatus.ERROR_NETWORK_UNUSABLE || // faild to load a media becase of no network connection. status == LoadScenarioStatus.ERROR_NETWORK || // faild to load a media becase of network error. status == LoadScenarioStatus.ERROR_SOFTWAREKEY || // faild to load a media becase software key has not be found on server. status == LoadScenarioStatus.ERROR_CONTENT_STOPPED || // faild to load a media becase content has stopped. status == LoadScenarioStatus.ERROR_SERVER || // faild to load a media becase of server error. status == LoadScenarioStatus.ERROR_ETC // faild to load a media becase of another error. ){ // error }else{ handler.postDelayed(this, REPEAT_INTERVAL); } } }; handler.postDelayed(runnable, REPEAT_INTERVAL); } } // private void cancelReservePlayScenario(){ if (handler != null && runnable != null){ handler.removeCallbacks(runnable); runnable = null; } } protected Dialog onCreateDialog(int id) { String end_mes = "Really want to quit the application?"; String yes_str = "Yes"; String no_str = "No"; Dialog dialog; switch(id) { case DIALOG_EXIT: { // AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage( end_mes ) .setCancelable(true) .setPositiveButton(yes_str, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { finish(); }}) .setNegativeButton(no_str, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); }}); dialog = builder.create(); break; } default: dialog = null; } return dialog; } }
【継承クラス】
package com.kddi.satch.wwww; import com.kddi.satch.zzzz.xxxx; import android.os.Bundle; import android.speech.tts.TextToSpeech;//4tts import android.util.Log; import android.widget.Toast; import java.util.Locale; import android.content.Intent; import android.view.Menu; import android.view.MenuItem; public class yyyy extends xxxx { private static final String THIS_CLASS_SHORT_NAME = yyyy.class.getName().replace("com.kddi.satch.wwww.", ""); private static final String THIS_LOGTAG = THIS_CLASS_SHORT_NAME; // Set AR scenario file path. private static final String SCENARIO_NAME = "/assets/Scenario/Scenario_sunflower/vvvv.dpd"; protected String getSampleScenarioName() { return SCENARIO_NAME; } protected String getSampleLogTag() { return THIS_LOGTAG; } private String prev_val = ""; //private static final int CHECK_TTS = 682730935; //menu private static final int MENU_JA = 0; private static final int MENU_EN = 1; private static final int MENU_FR = 2; @Override public void postInitComponent(){ super.postInitComponent(); //4tts if (_isInitializedCorrectly) { _kddiComponent.activateAutoFocusOnDownEvent(true); _kddiComponent.registerCommunicationCallback("setTrackingStatus", this, "setTrackingStatus"); } } // @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //4tts //tts = new TextToSpeech(getApplicationContext(), this); } //4tts public void onInit(int status) { // if (TextToSpeech.SUCCESS == status) { // } else { Log.d("", "Error Init"); } } //add public void setTrackingStatus(String[] arrayOfString) { Log.i("STAT",arrayOfString[0]); if (arrayOfString[0].equals(prev_val)) { Log.i("STAT","repeat"); return; } if (arrayOfString[0].equals("out")) { prev_val = ""; set_text(""); return; } set_text(arrayOfString[0]); if (arrayOfString[0].equals("private")) { if (disp_lang.equals("ja")){ tts.speak("3本のひまわり、アメリカ", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("en")){ tts.speak("Sunflowers, first version,United States", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("fr")){ tts.speak("Vase avec trois tournesols, États-Unis", TextToSpeech.QUEUE_FLUSH, null); } }else if (arrayOfString[0].equals("gogh")){ if (disp_lang.equals("ja")){ tts.speak("15本のひまわり、アムステルダム", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("en")){ tts.speak("Sunflowers, repetition of the 4th version ,Amsterdam, Netherlands", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("fr")){ tts.speak("Vase avec quinze tournesols,Amsterdam, Pays-Bas", TextToSpeech.QUEUE_FLUSH, null); } // }else if (arrayOfString[0].equals("sompo")){ if (disp_lang.equals("ja")){ tts.speak("15本のひまわり、東京", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("en")){ tts.speak("Sunflowers, replica of the 4th version,Tokyo, Japan", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("fr")){ tts.speak("Vase avec quinze tournesols,Tokyo, Japon", TextToSpeech.QUEUE_FLUSH, null); } }else if (arrayOfString[0].equals("national")){ if (disp_lang.equals("ja")){ tts.speak("15本のひまわり、ロンドン", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("en")){ tts.speak("Sunflowers, fourth version,London, England", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("fr")){ tts.speak("Vase avec quinze tournesols ,Londres, Angleterre", TextToSpeech.QUEUE_FLUSH, null); } }else if (arrayOfString[0].equals("neue")){ if (disp_lang.equals("ja")){ tts.speak("12本のひまわり、ミュンヘン", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("en")){ tts.speak("Sunflowers, third version,Munich, Germany", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("fr")){ tts.speak("Vase avec douze tournesols,Munich, Allemagne", TextToSpeech.QUEUE_FLUSH, null); } }else if (arrayOfString[0].equals("philadelphia")){ if (disp_lang.equals("ja")){ tts.speak("12本のひまわり、フィラデルフィア", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("en")){ tts.speak("Sunflowers, repetition of the 3rd version,Philadelphia, United States", TextToSpeech.QUEUE_FLUSH, null); }else if (disp_lang.equals("fr")){ tts.speak("Vase avec douze tournesols ,Philadelphie, États-Unis", TextToSpeech.QUEUE_FLUSH, null); } } prev_val = arrayOfString[0]; } // public boolean onCreateOptionsMenu(Menu menu) { menu.add(0, MENU_JA,0, "日本語").setIcon(R.drawable.ja); menu.add(0, MENU_EN,0, "English").setIcon(R.drawable.en); menu.add(0, MENU_FR,0, "Français").setIcon(R.drawable.fr); return true; } public boolean onOptionsItemSelected(MenuItem item) { switch ( item.getItemId() ) { case MENU_JA: disp_lang = "ja"; check_lang("ja"); return true; case MENU_EN: disp_lang = "en"; check_lang("en"); return true; case MENU_FR: disp_lang = "fr"; check_lang("fr"); return true; } return false; } // public void check_lang(String lang){ if (lang.equals("ja")) { if (tts.isLanguageAvailable(Locale.JAPAN) >= TextToSpeech.LANG_AVAILABLE) { Toast.makeText(yyyy.this, "JP", Toast.LENGTH_LONG).show(); //setLanguage tts.setLanguage(Locale.JAPAN); }else{ if (tts.isLanguageAvailable(Locale.JAPAN) >= TextToSpeech.LANG_MISSING_DATA) { Toast.makeText(yyyy.this, "日本語データをインストールしてください。", Toast.LENGTH_LONG).show(); }else{ Toast.makeText(yyyy.this, "TextToSpeechのエンジンを日本語用に切り替えてください。", Toast.LENGTH_LONG).show(); } } }else if (lang.equals("en")) { if (tts.isLanguageAvailable(Locale.UK) >= TextToSpeech.LANG_AVAILABLE) { Toast.makeText(yyyy.this, "UK", Toast.LENGTH_LONG).show(); //setLanguage tts.setLanguage(Locale.UK); }else{ if (tts.isLanguageAvailable(Locale.UK) >= TextToSpeech.LANG_MISSING_DATA) { Toast.makeText(yyyy.this, "Install TTS English data.", Toast.LENGTH_LONG).show(); }else{ Toast.makeText(yyyy.this, "Change TTS Engine to PicoTTS.", Toast.LENGTH_LONG).show(); } } }else if (lang.equals("fr")) { if (tts.isLanguageAvailable(Locale.FRENCH) >= TextToSpeech.LANG_AVAILABLE) { Toast.makeText(yyyy.this, "FR", Toast.LENGTH_LONG).show(); //setLanguage tts.setLanguage(Locale.FRENCH); }else{ if (tts.isLanguageAvailable(Locale.FRENCH) >= TextToSpeech.LANG_MISSING_DATA) { Toast.makeText(yyyy.this, "Installe les françaises données.", Toast.LENGTH_LONG).show(); }else{ Toast.makeText(yyyy.this, "Changez le Moteur TTS à PicoTTS.", Toast.LENGTH_LONG).show(); } } } } // }
SATCHのシナリオから検出時データを取得して、N2 TTSとPico TTSに喋らせてます。
TOP
インストールと起動
日本語をしゃべるには、日本語の音声合成エンジンが必要です。
KDDIの音声合成エンジン「N2 TTS」のインストールについては、以下を参照
アンドロイドで日本語音声出力(TextToSpeech):音声読み上げ
英・仏語は日本語とは別のTTSエンジンを使います(Pico TTS)。
切り替えは、現状、マニュアルで行います。
N2 TTSがインストールされていれば、N2のアイコンをタップしてメニュから行えます。
設定->音声入出力->テキスト読み上げの設定->エンジン、からも行えます。
英・仏言語データのインストール
Pico TTSの項をタップして、言語データが未インストールなら、タップしてインストールを実行してください。
アプリ(apk)をインストール
ダウンロード後、通知パネルを開いて、完了メッセージの項をタップ。
起動
初回起動時には以下のようなメッセージが出ますが、気にしないでください。「はい」でも「いいえ」でも、どちらでもいいです。
起動後、7~8秒、真っ暗ですが問題ないです。
カメラを絵に向けると、文字を表示して、「しゃべります」。
絵のサイズはある程度大きなものにしてください。あまりに小さいと検出に失敗します。
TOP
トップページ| サイトマップ|