画像認識APIと翻訳APIを使って日本語キャプショニング

Microsoft Computer Vision APIのキャプショニングの性能がイケてるらしい。

日本語キャプションには未対応なので Microsoft Translator API と組み合わせて実験してみました。

f:id:sanshonoki:20170906051557j:plain

Serverless Frameworkを使って画像認識と翻訳のAPIを叩くLambdaファンクションを作成し、それをAPI Gatewayでhttpエンドポイントとして公開。 botからはそのhttpエンドポイントを叩いて結果を投稿します。

いくつか画像を試してみる !!

Webからいくつか画像を拾って試してみます。

競馬編

http://www.geocities.jp/sunday_silence1977_2/photo/06arima/arima06 http://www.geocities.jp/sunday_silence1977_2/photo/06arima/arima06

認識結果「大勢の人々の前に立っている人々の集まり」

確かに、その通り。


http://worldsports-c.com/images/page_img/850072bef46c88f6760738608d72dca6.jpg http://worldsports-c.com/images/page_img/850072bef46c88f6760738608d72dca6.jpg |

認識結果「馬の背に乗っている人々のグループ」

正解 :-)


http://sp.jra.jp/beginner/yosou/img/a-4-3/01.png http://sp.jra.jp/beginner/yosou/img/a-4-3/01.png

認識結果「馬に乗っている男」

惜しい。

アニメ編

続いて子どもがハマっていた仮面ライダーエグゼイド。

http://www.toei.co.jp/tv/ex-aid/story/__icsFiles/afieldfile/2017/03/17/1_1.jpg

認識結果「雪の道にスキーに乗っている人々のグループ」

そうか、ひと昔ふた昔前はスキーウェアも派手だったもんな.. と納得。


http://www.toei.co.jp/release/movie/__icsFiles/afieldfile/2017/08/23/artimgpreview.jpg

認識結果「柵の側にスケートボードに乗っている若い少年」

影がスケートボードか?!

ドラマ編

現在放映中で毎週楽しみにしている月9ドラマ、コードブルーから

http://blogs.c.yimg.jp/res/blog-8f-c7/yukki_na0716/folder/769139/92/9384592/img_4?1266915772 http://blogs.c.yimg.jp/res/blog-8f-c7/yukki_na0716/folder/769139/92/9384592/img_4?1266915772

認識結果「窓の前のテーブルに座っている人々のグループ」

主役の二人もグループと言われてしまうと。。 ╮(•ω•)╭


https://cdn.mdpr.jp/photo/images/2c/099/w700c-ez_220ea4338737fc71234305b81c9a56655ce703ebcd4f7d10.jpg https://cdn.mdpr.jp/photo/images/2c/099/w700c-ez_220ea4338737fc71234305b81c9a56655ce703ebcd4f7d10.jpg

認識結果「部屋に立っている人々のグループ」

確かにその通りです。


https://instagram.com/p/BXt1ua5hGBJ/media/?size=l https://instagram.com/p/BXt1ua5hGBJ/media/?size=l

認識結果「カメラのポーズをとる比嘉真奈美ら」

有名人は名前も認識するようですね。嗚呼、その他になってしまった浅利くん… w

キャプショニングの使いみちについて考えてみる

実験してみての感想はエンジニア目線では機械学習でここまで認識できるのか!という驚きです。 一方でユーザー目線ではこのシュールな文章は一体何に使えるんだろう?とも

そんな中、

面白いかも?? と考えついたのは、

自分を客観視するツール

です。ƪ(•◡•ƪ)"

例えば、感情が高ぶって怒っているとき「中年のオジサンが怒って話している」と機械に客観的に言われれば 冷静になれるのではないでしょうか。。

コード

今回、使ったコードはこちらです。 github.com

APIキー

から取得できます。(無料プランあり)

Microsoft Computer Vision APIMicrosoftアカウント が必要で Microsoft Translator API のほうは Azureアカウント が必要です。

無料の範囲は

となっていてちょっとした実験レベルは無料で十分まかなえます。

MicrosoftアカウントでのMicrosoft Computer Vision API の試用は30日間で有効期限が切れてしまいますが Azureアカウントで CognitiveServiceAPIのインスタンスを立てれば継続して使えるようです。(1分あたり20回のレートリミットの制限もなかったです)

実装で参考にしたページ

Python3.6で sls invoke local すると `Error: spawn python3.6 ENOENT`

サーバレスなアプリケーションを構築するためのツール Serverless Framework で Python3系が使えるようになったということを最近知って

dev.classmethod.jp

の記事を参考に使ってみました。

ただ、

sls invoke local とローカルでファンクションを実行すると、

Error: spawn python3.6 ENOENT

が出ちゃう.. ╰(゚x゚​)╯

デプロイした remote で実行すると問題なく動くのだが気持ち悪い…

Webを検索すると同じエラーが出ている人がいたのでこれを参考にやってみる

https://doruby.jp/users/nakamatsu/entries/%60sls-invoke-local%60-%E3%81%99%E3%82%8B%E3%81%A8%60Error–spawn-python3-6-ENOENT%60%E3%81%A8%E8%A8%80%E3%82%8F%E3%82%8C%E3%81%9F

が、これでもうまくいかない… (◞‸◟)

解決方法

結局うまくいった方法はこれでした。

$ brew install python3   # pyenvで3.6をインストールしてもNG。system環境に3.6をインストールする
$ pyenv local system  # パッケージをインストールするときはsystem環境のpythonにして行う
$ pip3 install requests  # pyenvのsystem環境で必要なパッケージ(この場合は requests)をインストール

この3ステップでエラーが出なくなりました。(2番目のステップが抜けてました)

スッキリです。(•̀ᴗ•́)و

ちなみに、パッケージがすでにインストールされていてファンクションを実行するだけのときは pyenv で system環境 にしなくても実行できるようです。

ステッドラーのシャープペンシル

4歳の息子がひらがなの練習をしているとき、たまたま 普通の鉛筆ではなくステッドラーシャープペンシル を使うことがあった。

そしたら、普通の鉛筆だとグー握りしてしまうのにこのシャープペンシルだと自然とちゃんと握れて、またすごく書きやすそうではありませんか ( ☉་☉ )

ステッドラー シャープペンシル 1.3mm 771 https://www.amazon.co.jp/dp/B001OUU32A

1.3mmの太芯シャープペンシル

【特長】・1.3mmと太い芯のシャープペンシルの為、軽い筆圧で筆記することができ安定感があります。

・エルゴノミックコンセプトに基づく三角軸と直径16mmの太軸、 スベリ止め加工の施されたグリップゾーンが特徴です。 ノック部には繰り出し式の字消しを装着しております。

もちろん、自分では書きやすいと思って 1000円 を投じ使っていたわけですが、小さい子どもがサクサク文字を書き出したのを目の当たりにし その価値は十分あったなと実感しました ( •ᴗ•)

同時に、子どもは嘘をつけない正直なユーザー であることを改めて実感

CordovaによるReact.jsアプリのネイティブアプリ化でハマったところ

React.jsで作ったスライドショーアプリをCordovaでネイティブアプリ化しました。ƪ(•◡•ƪ)

f:id:sanshonoki:20170815204337p:plain:w200

github.com

いくつかハマった点があったのでまとめておきます。

なお、アプリ化の手順に関しては以下のサイトが大変参考になりました。♪(・ω・)ノ

qiita.com

Cordovaの導入やコマンドの使い方に関してはこのあたり

ハマった点… ><

react-routerを使ったときに起動ページのパスが違う

対策

各環境ごとに起動ページのパスが違うのでルーティングを複数用意する必要があります。

起動ページのパス
React.jsアプリ /
cordova serve ios /ios/www/index.html
cordova emulate ios /Users/xxx/Library/Developer/CoreSimulator/De…25B7D/CordovaReactSlickExample.app/www/index.html

なので、react-routerを使うときは

<Router>
    <Route exact path="/" component={App} /> <!-- Reactのwebアプリでの確認用 -->
    <Route path="*/index.html" component={App} /> <!-- cordovaアプリ用 -->
</Router>

のように2種類の初期ページ表示用のルーティングをもっておく必要がありました。

public/以下のassetsへのアクセス

これに関連して、React.jsアプリのpublic以下のassetsへの参照も少し工夫する必要がありました。

  1. 初期ページを起動時パス取得用のダミーページ(Homeコンポーネント)にする

     <Router>
         <Route exact path="/" component={Home} />
         <Route path="*/index.html" component={Home} />
     </Router>
    
  2. ダミーページで起動時のパスを取得し、Globalな state に保存する

     class Home extends React.Component {
         constructor(props) {
             super(props)
         }
    
         componentDidMount() {
             // 起動時のパスを取得する
             const root = path.dirname(this.props.location.pathname)
    
             // stateに保存するアクションを呼ぶ
             this.props.setRootPath(root)
         }
    
         render() {
             // 実際の初期ページへリダイレクトする
             return (
                 <Redirect to="/login" />
             )
         }
     }
    
     const mapDispatchToProps = dispatch => {
         return {
             setRootPath: (path) => dispatch(setRoot(path)) // 起動時パスを保存するアクション
         }
     }
     export default connect(null, mapDispatchToProps)(Home)
    
  3. assetsにアクセスするときは起動時パスを考慮した形で行う

    const root = getState().home.root // 起動時のパスをstateから取得
    const url = `${root === '/' ? '' : root}/images/slick/pictures.json`
    fetch(url)
    

Fetch API cannot load file:///android_asset/www/xx/xxx.json. URL scheme “file” is not supported のエラーが出た(Android

対策

fetchは file:// をサポートしてないらしく XMLHttpRequest を使って記述します。

https://github.com/github/fetch/pull/92#issuecomment-140665932

function fetchLocal(url) {
  return new Promise(function(resolve, reject) {
    var xhr = new XMLHttpRequest
    xhr.onload = function() {
      resolve(new Response(xhr.responseText, {status: xhr.status}))
    }
    xhr.onerror = function() {
      reject(new TypeError('Local request failed'))
    }
    xhr.open('GET', url)
    xhr.send(null)
  })
}

fetchをfetchLocalに置き換えればok。

このエラーはiOSのときには出ませんがfetchLocal置き換えの副作用はありません。

Unable to post message to https://www.youtube.com. Recipient has origin file://.のエラーが出た

対策

config.xml に以下を追加

<allow-navigation href="https://*youtube.com/*" />

https://cordova.apache.org/docs/en/latest/reference/cordova-plugin-whitelist/

モバイルでのYouTube埋め込み動画の自動再生

先日、React.jsの練習Webアプリ(スライドショー)でBGM機能としてYouTube埋め込み動画の自動再生を実装しました。

自動再生は以下のように埋め込みURLにautoplay=1のパラメータをつけることで実現できます。

<iframe width="854" height="480" src="https://www.youtube.com/embed/B2fPYlGKdXM?autoplay=1" frameborder="0" allowfullscreen></iframe>

が、モバイル端末では自動再生されないことが判明… 。

どうやら仕様のようです。(´Д`。)

YouTube Player API Reference for iframe Embeds  |  YouTube IFrame Player API  |  Google Developers

ChromeSafari などのモバイル ブラウザでは、HTML5 <video> 要素を再生するには、ユーザーの操作(プレーヤーをタップするなど)による起動が必要です。以下は、Apple のドキュメントの抜粋です。

「警告: ユーザーが費用負担する携帯電話ネットワーク経由で要求していないダウンロードを防止するために、iOSSafari では組み込みメディアを自動再生できません。必ずユーザーが自分で再生します。」

この制限があるため、autoplay、playVideo()、loadVideoById() などの関数およびパラメータはすべてのモバイル環境では動作しません。

結論

結論から書きましょう。

  • 音声をミュートした状態ではモバイル端末でも自動再生は可能
  • 音声再生ありだとモバイル端末では自動再生はできない

ということになります。

モバイル端末での埋め込み動画の自動再生

この記事に書いてあるように無音の状態であれば自動再生は可能です。

qiita.com

ただし、player.unMute()した時点で残念ながら自動再生はストップしてしまいます。

再生ボタンをそれとなく表示する

ならばユーザーに再生ボタンを押してもらうしかありません。

最初にトライしたのは以下の記事に書いてあるiframe埋め込み動画にオーバーレイして再生ボタンを表示させる方法です。

Youtube Iframe API not working for mobile devices? - Stack Overflow

  <style>
    iframe#player {
      position: absolute;
      left: 0x;
      top: 0x;
      width: 200px;
      height: 100px;
    }

    #play_button {
      position: absolute;
      left: 0px;
      top: 0px;
      opacity 0;
      width: 200px;
      height: 100px;
      pointer-events: none;
    }
  </style>

のように自前の再生ボタンをiframe埋め込み動画と同じ位置に同じサイズでオーバーレイします。 pointer-events: none;によって再生ボタン上でのクリックイベントはiframe埋め込み動画の要素上で受け取れ、動画を再生できます。

オーバーレイした状態 オーバーレイしない状態(参考)
f:id:sanshonoki:20170814051612p:plain:w100 f:id:sanshonoki:20170814051622p:plain:w100

クリックした場所によって挙動が違う..?!

自前の再生ボタンのクリックした場所によって挙動が違うという現象に出くわしました..。

  • 想定通りにインラインで動画が再生され、オーディオも流れる
  • なぜか新しいタブを開いてフルスクリーンで動画が再生される

調べてみると、

f:id:sanshonoki:20170814054505j:plain

曲名の部分が https://www.youtube.com/watch?v=... へのリンクとなっており、ここをクリックすると新しいタブで開いてしまいました。

ユーザーがボタンのどのエリアをクリックするかは分かりません。。

なので、このままではNGです。

showinfo=0を指定すると曲名のリンクは非表示にできますが代わりにYouTubeロゴのブランディングが表示されてこれがhttps://www.youtube.com/watch?v=...へのリンクになって同じことになります。。

f:id:sanshonoki:20170814085851j:plain

Playerのパラメータを以下のようにチューニングするとPC閲覧時にはYouTubeロゴを非表示にできましたがモバイル閲覧ではYouTubeログは表示されてしまいます..。(>д<;)

playerVars: {
    showinfo: 0,
    controls: 2,
    modestbranding: 0,
    playsinline: 1,
},

最終的に行き着いた方法

YouTubeの再生ボタンをそのまま利用することにしました。

具体的には表示サイズをYouTubeの埋め込み動画の再生ボタンを同じ大きさにすることで再生ボタンのエリアのみが露出するようにしました。

const opts = {
  width: '40', // same size of play button
  height: '30', // same size of play button
  playerVars: {
    showinfo: 0,
    controls: 2,
    modestbranding: 0,
    playsinline: 1,
  },
}

この設定にすることでモバイル端末での表示は以下のようになります。

f:id:sanshonoki:20170814090934j:plain

この状態だとボタンのどこをクリックしても新しいタブで再生することなくinlineで動画(音声)が再生されます。:-)

ただ、iOSiPhoneの場合は動画再生は全画面表示となるらしくこの方法でもインラインでのBGM再生はできませんでした.. (iOS10からはインライン再生できるようです)

React.jsでBGMつきスライドショーを作る

React.jsの練習としてスライドショーアプリを作ってみました。

React Slideshow Sample f:id:sanshonoki:20170801050429p:plain

単なるスライドショーでは面白くないのでBGMをYouTubeから検索して流せるようにしました。

検索ボックスに曲名などのキーワードを入れてボタンをクリックすればYouTubeから検索してそれを自動再生するようにしてあります。お気に入りである AIさんの Story をテキストボックスの初期値に入れています。:-)

他にも

あたり今回のサンプル写真のテーマ(子どもの成長)にハマるかなと思っています。 他にもハマる曲があったらぜひ教えてください

コードはこちらにあり、public/images/slick/ 以下に好きな写真を置けばその写真を使ってスライドショーができるので興味ある方はご覧ください

github.com

BGM検索&再生

やっていることはシンプルです。

  1. YouTubeでキーワード検索する (GET https://www.youtube.com/results?search_query=...
  2. 検索結果のhtmlから曲のId(data-context-item-id)とタイトルを抜き出す
  3. iframeで表示する(https://www.youtube.com/embed/{曲Id}?autoplay=1

React.js側からYouTubeページを取得しようとすると No 'Access-Control-Allow-Origin' header is present on the requested resourceエラーが出てアクセスできないのでYouTube検索はRails側でやっています。

スライドショー

スライドショー機能は React対応した react-slick があったのでそれを使いましたが表示で少し苦戦したのでメモ

写真が真ん中に表示されず左寄せになってしまう

以下の設定をCSSに加えることにより中央に表示されるようになりました。 これで正しいやり方なのかはよく分かりません..

  div.slick-initialized div.slick-slide {
    display: flex;
    justify-content: center;
  }
左右のArrowが表示されない

slick-theme.cssでなぜか color: transparent になってました。 なのでCSSを上書きすると表示されるようになりました。

  .slick-next:before, .slick-prev:before {
    color: gray;
  }

会議と打ち合わせ

最近、「博報堂のすごい打ち合わせ」という本を読みました。

f:id:sanshonoki:20170714041701j:plain https://www.amazon.co.jp/dp/4797391340

「5割雑談でも最高の結論を導き出す博報堂の打ち合わせ術」というそそる内容で始まります。。

面白い内容で通勤の帰りの電車の中で一気に読破してしまったのですがその中でもああ、そうかと思って頭に一番残ったのが「会議」と「打ち合わせ」は違うというものです。

  • 会議: 情報の共有(報告、連絡、相談)のために行われる
  • 打ち合わせ: 考えやアイデアを出し合い、積み上げていくために行われる

自分は「会議」よりも「打ち合わせ」していきたいです。

そして、それを頭に焼き付けるために今回、それぞれに別名(ラベル)をつけてみました。

  • 会議とは・・・「予定調和」である
  • 打ち合わせとは・・・「真剣勝負」である

これを見ると「打ち合わせ」したくなりませんか?w

さぁ、真剣勝負しましょう♪