ロボット開発を支えるDevOps

この記事はCyberAgent Developers Advent Calendar 2020の22日目に書くはずだった記事です。大変申し訳ございませんでした!!

あけましておめでとうございました。年1更新みたいになって申し訳ない限りです。。

書くネタはそれなりに溜まってきてるので、今年は全部出し切る!という気持ちでがんばっていこうと思います。


Web系エンジニアからロボット系エンジニアに転身して2年が経ちました。

過去どんな経緯があったのか、ロボットを使って何に取り組んでいるのかについては、この辺の記事をご覧いただけると幸いです。

昨年から今年にかけてはCOVID-19の到来によって一年のうちの大半をリモートワークで過ごしつつ、時にはリアルなフィールドに赴いてロボットを稼働させるということも並行してやってました。

ロボットプロダクト開発から運用までを基本的に遠隔コミュニケーションでこなすというのはなかなか前代未聞でしたが、原状の課題と効率化に向き合ういい機会にもなり、あれこれと工夫を凝らしていく中でロボットを扱う開発ならではのDevOpsといえるような取組みを徐々に始められました。

昨年末に開催された社内カンファレンスにて発表した内容から、一部を抜粋してご紹介していきたいと思います。

ロボットプロダクトの開発プロセス

f:id:kohtaro24:20210209180634p:plain

ざっくりとしてますが、ロボットサービス事業部での開発プロセスの流れを整理してみました。 設計から始まって保守に至るまでのパイプラインの流れこそ一般的だと思いますが、中身はロボットプロダクトならではのユニークな実務が多いですね。デプロイにいたってはほぼ物理的な力仕事で、いい運動になります。

ハイライトされている部分について今回は詳しく紹介していきます。

モーションの作成

ロボットが人とコミュニケーションを行う上で、動きによる表現はとても大切な要素だと考えています。 感情や意図、リアクションの強調など、言語だけでは表現できない部分をカバーすることでより説得力や親しみやすさを演出できます。

基本的には、様々なモーションアセットや制御インタフェースを標準で提供している市販のロボット(PepperやSotaなど)を採用するケースが多いですが、最近では環境やコンセプトに合わせてロボットそのものを自作する機会も出てきました。

大人の事情によりロボットの現物は公開できませんが、複数のサーボモーターを組み合わせて機体を回転・上下させることで簡単な仕草を表現しています。

f:id:kohtaro24:20210202041201p:plain:w700

例えば挨拶の際にはお辞儀を行い、通常発話は縦運動を入れて喋ってる感を出すなど、 セリフのコンテキストに合わせて稼働させるモーターを使い分けています。

f:id:kohtaro24:20210202041730p:plain:w700

基本的には各セリフパターンごとに動きをつけるのですが、自作ロボットとなるとモーションは全て自前で用意するしかないですし、セリフの数に比例してモーション作成の工数が増大していきます。

このモーション作成を効率化するためにhamonicaというツールを作りました。


(大人の事情によりこの動画では架空の3Dモデルを使用しています)


機能としてはMMDBlenderでお馴染みのアニメーションエディタに近いです。任意のキーフレーム(再生位置)ごとの各モーターの回転角度を定義できます。 セリフの音声データを読み込み、音声波形を確認したり試聴しながら動きを決めていくことができます。

また3Dアニメーションでのプレビュー機能を提供しており、作成中のモーションを音声と合わせて確認することができます。

f:id:kohtaro24:20210202145022p:plain

hamonicaはElectron + React.jsで実装しており、プレビュー機能はUnityで作成したものをWebGLビルドして取り込んでいます。

作成したモーションを定義ファイルとしてexportしておき、専用アプリケーションが定義ファイルの内容に基づいて逐一Arduinoに命令することでモーターを稼働させています。

hamonicaがもたらした恩恵には以下のようなものがあります。

実機が手元になくてもモーションが作れる

ロボットの動きを3Dアニメーションでシミュレートできるため、モーション作成工程において実機は不要になりました。

フルリモート下では手元にロボットがない状況も多く、また複数人で作成を分担したいこともあったため、実機なしでモーションが作れるというのは非常に強力でした。

実機での危険な動きを未然に防ぐことができる

バリデーションサポートや3Dアニメーションで動きを確認できることにより、実機に対して負荷のかかるような動きを事前に確認して防ぐことができていました。

定義を使いまわすことで作業を短縮できる

一度作ったモーションを部分的に再利用できるようにすることでモーション一つ一つをフルスクラッチで作る必要がなくなり、作成工数をショートカットできました。

hamonicaは今後もアップデートを施していく予定です。 アニメーションエディタを1から内製するのは投資的なチャレンジでしたが、必要に応じて機能を好きなように拡張できることが大きな強みとなっています。 将来的には音声の加工やミキシングなどもこのエディタ上から行えるようになれば、発話と振る舞いのデザインに特化した唯一無二のツールへと昇華できそうな手応えがあります。

インタラクションの確認

ロボットとのインタラクションシナリオはユーザの行動やコンテキストによって様々なパターンに分岐していきます。

f:id:kohtaro24:20210205154502p:plain:w500

分岐のパターンごとに狙い通りの自然な体験になっていることが重要なので、一通りのフローを繰り返し確認しながら調整していくことになります。 この作業を効率的にすすめる上でインタラクションデバッガー(仮)を用意しました。

  • 現在のインタラクションの再生位置を可視化
  • 任意の位置からインタラクションを再生可能
  • 人物の各種行動イベント(出現や離脱、操作など)を仮想的にトリガーできる
  • 実機なしの場合でも一通りのフローをシミュレーション可能

このツールによって分岐の多いシナリオパターンも効率的に確認できますし、対話モデルを作り込む前のシミュレーション(例えばWoZ法のような)に活かす使いみちもあります。

将来的にはエディタの機能も統合することでGUIプログラミング感覚でインタラクションを構築できるツールに仕上げられるとよいなと考えています。

センシングパラメータの最適化

ロボットを設置する現場においてはさまざまなセンサを活用しています。特にどの現場でもたいてい必須になるのが人物のセンシングで、RealSenseのような三次元カメラなどを使って人物を追跡するアルゴリズムを実装しています。

各センサがイベントを伝搬する上では様々なしきい値を実装しており、人物のセンシングだけでも追跡を行う範囲や精度立ち止まった/離れたとみなすまでの移動距離や時間など様々あります。 センサとロボットの配置環境や周辺の障害物、人の動きの傾向などを考慮してチューニングする要素も多くあり、現地のデプロイ作業と合わせてこれらパラメータの最適化を実施することになります。

f:id:kohtaro24:20210205215641p:plain:w600

実際のチューニング風景はこんな感じです。

f:id:kohtaro24:20210207045541p:plain

この作業は地味ですがとても重要で、曖昧にしてしまうとインタラクション全体の破綻を招きます。 せっかく念入りに組んだセリフやモーションパターンも適切なタイミングで再生されなければ狙い通りの体験にはなりません。 とはいえ現地で活動可能な時間には限りがあり、長時間の滞留は周囲の方々への迷惑になりかねないシチュエーションも多く、この作業は時間との戦いになりがちです。

そこで、短時間で効率的にセンサのチューニングを行うためのGUIコンソールを開発しています。

操作したしきい値は即座に反映され、上で紹介したインタラクションデバッガーと組み合わせることで更新部分をすぐに再確認できます。 人物センシング関連ではRealSenseから取得した映像にガイドをオーバーレイ表示することで現在のしきい値がどう反映されているのかを可視化しています。

これらのサポートによってパラメータチューニングはだいぶ余裕をもって進められるようになりました。 しかしチューニング項目が今後も増えるとその分手間がかかってくるので、プリセットを複数パターン用意するなどのアプローチも今後進めていきたいところです。

システムの死活監視とレポーティング

例によってアプリケーション上で発生した例外はSlackに通知しています。

f:id:kohtaro24:20210208014054p:plain:w500

アプリケーションプロセスの死活状況も監視しており、例えばロボットアプリケーションの異常終了を検知した場合はサイネージ端末などと自動連携してロボットがメンテナンスのため利用できない旨を周囲に告知することもできます。

f:id:kohtaro24:20210210124736p:plain:w500

内部監視だけでなく外形監視も重要です。リアルなフィールドでは電源が抜かれてマシンまるごとシャットダウンしてしまうような事態が起きてもおかしくはありません。

f:id:kohtaro24:20210208030107p:plain

外形監視の方法ですが、アプリケーションの起動/終了ログやインタラクションフローの遷移ログなどをAWSのS3バケットに集積し、Athenaでログ検索基盤を構築、CloudWatch Eventで定期的にログの集計を行っています。

ログが途絶えていたり不自然なログを発見した場合はSlackに通知しており、これによって現地システム側が何も通知を飛ばせず異常終了した場合(停電など)にも気づくことができます。

f:id:kohtaro24:20210209015541p:plain:w500
f:id:kohtaro24:20210209015444p:plain:w500

ログ検索基盤はプロダクトの評価指標の計測にも役立っており、ロボットの利用実績やユーザの滞在時間など様々なメトリクスを一定時間毎にレポーティングしています。

障害発生時の自動アラートコールと通話接続によるサポート

前述の通り障害発生時にはSlack通知を行いますが、メンテナーメンバーが即座に反応できるようにする上で別途アラートコールも実施しています。

f:id:kohtaro24:20210209015805p:plain

この仕組みはTwilioのAPIを用いて自前で構築しており、単純なアラートコールだけに終わらずアラートコールを受け取った人同士の通話をそのまま繋ぐというユニークな機能を搭載しています。

障害に気づいたメンテナーはリモートデスクトップSSH接続等で現地システムに入り、障害の原因や影響範囲を調査し、まず遠隔から対処を試みます。そして原因不明ないしは遠隔からの対処が困難な場合、最終的に現地に向かうことを考えなければなりません。

全員がフルリモート下にいる状況では特に「いま誰がどこまで調査・対応できてるのか?」「現地にいく必要がありそうか?」「現地に行くのは誰か?」など、色々と確認をとりながら進めたほうがいい事柄が多くあり、アラートコールからそのまま通話が始まってくれればスムーズで最高かなと考えてこの仕組みを作りました。

メンテナー同士の作業が衝突して二次災害が起きる可能性を防げるのも大きいですし、障害対応中の孤独感がなくなるのも精神衛生上よかったなと思います。

この仕組みの実現方法は以前にも記事に書いたので興味のある方はご覧ください。

USB接続機器のトラブルシューティング

現地システムは様々な周辺機器によって構成されており、一部の機器はメインマシンとUSBによって接続され、通信や電源の供給などが行なわれています。

f:id:kohtaro24:20210209150513p:plain:w600

USB機器を定常的に使っていると、USBデバイスが突然認識されなくなったり通信できなくなるなどの不具合が起きることがあります。 原因は機器によっても様々ですが、厄介なのはUSBを物理的に抜き差ししない限り復旧できないケースです。

これを解決してくれたのがUSB troubleshooterでした。

メインマシンとUSBデバイスの間にUSB troubleshooterを取り付けておくと、ソフトウェアを使って抜き差しをエミュレートすることができます。

f:id:kohtaro24:20210209175702p:plain

これにより、遠隔地からUSB接続のトラブルを解消できるようになりました。 USBを指し直すためだけに現地に向かうのはなんとしても避けたかったので非常に重宝しています。


今回はロボットプロダクト開発ならではのDevOpsをピックアップして紹介しました。いかがでしたでしょうか。

ほとんど内製したツールの話なのでSaaSの活用事例とかを期待してた方々にはなんか申し訳ない感じですが、長期的な投資をしてでもこうしたツール群を開発している背景は「より多くの人が"価値を届けられる"ロボットサービスをデザインできる」ことの実現であり、ロボットをもっと世で活躍させていく上でもこれが重要だと考えています。自分たちはこれらのツールのファーストユーザでもあるという感覚を大事にしています。

社会のライフスタイルの変化も交えながら一層様々なフィールドやテーマでの取り組みが続きますが、しっかりとした実践と検証をサポートできるソリューションを今後も磨き上げていきます。最後までご覧いただきありがとうございました。

input type="date"でplaceholderが効かないので強引に楽な方法で対処する

input type="date" の場合、placeholderを指定しても基本的に反映されないことを知った。

<input type="date" name="birthday" id="birthday" value="" placeholder="誕生日">

f:id:kohtaro24:20200516145110p:plain

f:id:kohtaro24:20200516145150p:plain

「type="date"は維持したいけどplaceholderは使いたい」というのを手っ取り早く実現する方法を模索した結果、

  • デフォルトを input type="text" にしてplaceholderを有効にし、
  • onfocus イベントと onfocusout イベントを使ってfocusが当たっているときだけ input type="date" に切り替える

という荒業があった。

<input type="text" name="birthday" id="birthday" onfocus="this.type='date'" onfocusout="this.type='text'" placeholder="誕生日" style="height: 30px; width: 200px; padding: 10px 10px">

f:id:kohtaro24:20200516145807g:plain

ぶっちゃけplaceholderでなんとかせずlabelタグで良かったなと思ったけど、一応残しとく

SpeeeのWebエンジニアがCyberAgentでロボットエンジニアになった話

この記事は退職者 Advent Calendar 2019の23日目の記事です(遅れましたごめんなさい)。

1年越しのご報告ですが、2018年の12月に株式会社Speeeを退職し、今年の1月から株式会社CyberAgentで新しい仕事を始めました。

Speeeでやってたこと

Speeeには2015年に新卒で入社し、およそ3年半お世話になりました。

Webエンジニアデビュー

Speeeは今でこそRuby界隈で名前を見かけることが多いですが、僕が入社したときはまだPHPJavaの会社でした。

入社前は主にiOSアプリ開発をやってた僕でしたが、なんやかんやでWebエンジニアとしてキャリアをスタートすることになり、 初期は主にPHPで社内向けプロダクトとかWebクローラとかを作って過ごしていました。

たしか入社から4ヶ月後くらいに開発組織改革が始まってメイン開発言語はRubyになり、既存プロダクトも運用フェーズのものを除いてRubyに置き換えていく流れになりました。当時PHPベースで馴染みかけてた自分のWeb開発力は突然のRubyRailsの台頭によって再び弱体化(?)することになりました。

しかも当時は夏季の学生向け就業インターンを盛んに運営していた時期で、僕も通常業務と並行しながらメンターを兼務していました。Rails歴せいぜい数週間の若造がRailsを教えるという、冷や汗の止まらない期間を過ごしたことを覚えています。

ヌリカエ立ち上げ

入社半年後、ライフスタイルメディア事業部(今のデジタルトランスフォーメーション事業本部)に異動し、ヌリカエという外装工事業者紹介サービスの立ち上げに携わりました。

僕にとっては初めてのチーム開発であり、オンプレを脱却してクラウド(AWS)での開発、CI/CDを活用したテストやデプロイの自動化など、チャレンジがてんこ盛りでした。 toCサイト、toBサイト、CRMSFA、請求システムなど、事業にあたって必要となるものはほぼすべて内製で開発しており、業務委託の方を含む4~5人の開発チーム(若干人数変動あり)でやってました。

チーム開発を改善する

ヌリカエのローンチ後も向こう数ヶ月は開発要望がぎっしり詰まっていて、「何から片付けよう」とか「どういう方法で進めよう」とか色々相談事が増えました。

立ち上げ初期の頃は開発プロセスの形もはっきり決まっておらず、その場その場で相談して意思決定する進め方にも効率の悪さを感じていたため、 みんなでアジャイルサムライSCRUM BOOT CAMPを読んで、イテレーションの定義、バックログの管理基準の定義、振り返り(KPT)の周期化など、チームのリズムを作るための取り組みを始めました。別の事業部のチームも巻き込んでKPTに参加し合ったり座談会をやったりすると学びをシェアできてよかったです。

このあたりは色々と語り尽くせぬ学びがありましたが、個人的に強く意識に残っているのは「振り返りの振り返りをしよう」ということでした。改善を生み出しにくい振り返りになってないか、とか。現状のスキームをメタ的に振り返る視点を持てているだけで、形骸化を避け、開発プロセスを良くしようとする作用が自然と働くんじゃないかなという気がします。

コールセンター開発からの"架電芸人"へ

ヌリカエは社内にコールセンターを有しています。 Webサイトから申し込みをしてきたお客様に対して、オペレーターが一度電話をかけて具体的な意思や内容をヒアリングする必要があるためです。

この電話でのオペレーションが中々に過酷で、架けても不在扱いになるパターンが非常に多く、何度か時間をおいて架け直すといったことを繰り返すうちに架電リストは山積みになり、ひたすら電話番号をクリックし続けるオペレータはメンタルを蝕まれる…というようなことが起きていました。

そして「勝手に電話をかけて、相手が応答したときだけ人に繋いでくれるような便利なやつが欲しい」という要望を受けて、自動架電ツールを作りました。当時の現場ではWindows専用のIP-PBXクライアントが使われており、dllを介して発信/切断をトリガできる仕様が提供されていたため、PBXクライアントの発信先を内製CRMと連携して自動制御するWPFアプリケーションをVisual Studioで作りました。Webどこいった。

社内のほとんどがWebをメインとするエンジニアである中、下手にWPFアプリとか作ってスキルセット要件を増やすような真似は将来的にリスク…といった意見もありましたが、それを踏まえても事業の初期フェーズでこれをいち早く実現できた価値は大きかったのではないかなと思います。

結果的に架電ツールはサービス運営に欠かせない武器として受け入れられ、オペレーションの最適化を経て、晴れてWebベースの架電ツールにリプレイスするところまで繋げられました。 ありがたいことに、これがキッカケで9期下半期のベストテクノロジー賞(よっぽどの時だけ発表される技術部門MVP的な賞。ミュウ並のレア率)を受賞することもできました。

現在のヌリカエの架電ツールは、電話APIであるTwilioをベースとしてReact+Redux構成のSPAに置き換わり、架けすぎ防止機能やユーザセグメント別発信機能などを備え、今なお進化を続けています。 一連の活動を通じて社内からは"架電芸人"という称号をいただき、Twilioコミュニティでも事例紹介登壇をさせてもらったご縁でTwilio Championを拝命するに至りました。

架電芸人、転職へ

上記以外でもイエウール手伝ったり、チームOKR大臣やったり色々あるんですが、まあそんな感じの前職で(話長くなってしまいました)、転職の機会は割と唐突に訪れました。 転職先はCyberAgentのロボットサービス事業部 R&Dセンターで、主な仕事はロボットを活用した接客サービスの研究開発です。

なぜそこ!?とはよく聞かれました。実際、この流れでロボットに行くとは誰も思わないでしょう。 まあそれはその通りで…大学院時代にHCI(Human Computer Interaction)の研究をやってたことが直接的には関係してます。

ロボットサービス事業部との出会いは割と偶然の積み重ねで、一度会って話をしたあとから転職を具体的に考え始めました。

もともと、コンピュータと人の距離を近づける機会を作る仕事に自分も関わりたいという思いがありました。 Speee時代に一番気持ちを込めることができたコールセンター開発も、人とコンピューターの協調作業を発展させていたことに他ならなかったり。そうやってSpeeeで培ったいろいろな知識や技能を引っ提げて、HCI研究の社会実装を目指す事業に飛び込めるのはとても魅力的でした。あと今思えば「全く新しい環境にチャレンジしたい」という単純な欲求も芽生えていたのかもしれません。

そんなこんなで今年1月に転職を果たし、今や最初の1年間を終えようとしているところです。 ロボットサービス開発での1年間のダイジェストはこちらの記事でご覧いただけます。

Speeeの方々には大変お世話になりました。最後も大所帯で見送っていただいたり、今も変わらず飲みに誘ってくれたり、結婚式に駆けつけていただいたり、ほんとありがたい限りです🙏

エモくなってしまいましたが、クリスマスなので許してください。

ロボット接客システム開発の裏側の話

この記事は CyberAgent Developers Advent Calendar 201920日目の記事です。

なんやかんやあり、今僕はCyberAgentという会社のロボットサービス事業部 R&Dセンターというところにいます。

コミュニケーションロボットをはじめとする物理対話エージェントを活用したサービスの研究開発などをやっていて、これまで以下のような実証実験に携わっていました。

これらの経験を通じて得られたコミュニケーションロボットサービス開発の知見(苦労?)について、今回はいくつかピックアップしてご紹介できればと思います。

  • システム構成の話
  • モックアップ開発の話
  • 運用の話
  • 人物センシングの話

システム構成の話

前提として、ロボットを取り巻くシステム構成は、設置する環境や提供したいUXによって大きく変化するのが当然と言っていいでしょう。 これはあくまで一例ですが、とある商業施設にて案内ロボットを設置した際の構成です。

f:id:kohtaro24:20191219154310p:plain

タッチディスプレイで案内コンテンツを表示し、案内役である横のロボットが口頭で情報提供をします。 周囲の人の動きをセンサ(深度カメラ)で読み取り、近くで立ち止まっている人に集客担当ロボットが自ら声をかけるといったこともやっています。

f:id:kohtaro24:20191219154605p:plain

このシステムの母体(Main App)はElectronで構成されたデスクトップアプリケーションです。Electronはマルチプラットフォームで動作するアプリケーションを開発できる上、ReactJSやTypeScriptなどWebのプラクティスにあやかることができるので助かっています。

ディスプレイコンテンツの表示、人物検知スクリプトの起動および検知情報の取得、ロボットの発話/モーションの制御などをMain Appが一挙に担っています。各モジュール同士の通信は主にHTTP/HTTPSになるため、施設内にLAN環境を構築しています。

そして、ロボットが対応する案内コンテンツなどはクラウド上のCMSで管理しているため、遠隔からコンテンツを更新することが可能です。

f:id:kohtaro24:20191219160605p:plain

モックアップ開発の話

当たり前ですが、接客ロボットはお客様にベストな接客体験を提供しなければなりません。 ディスプレイやセンサなどの周辺機器と連携した効果的なアプローチや情報提供を始め、 言葉の選び方、身振りのとり方、間の空け方といった対話術についても検討の余地があります。要はUXデザインがサービスの命運を大きく分けます。

そのため、開発プロセスにおいてはUX設計→プロトタイプ開発→デモ(フィードバック会)のサイクルを高速に回していきたいところです。

しかし、技術的な検証やシステムのインテグレーションなど、全てをまともに設計/実装していると1回あたりの作業コストが大きくなり、このサイクルが中々回りません。UXの設計方針が大きく変わったりすると、フルスクラッチでの作り直しを余儀なくされることもあります。 f:id:kohtaro24:20191219164430p:plain

そこで最近、プロトタイプの前段階としてモックアップを作成するプロセスに変えてみました。

ハリボテを用意して黒子役の人が裏でゴニョゴニョすれば、開発しなくても体験を確認できるじゃん!ってやつですね(雑)。 Webの開発でペーパープロトタイピングとかがあるように、ロボットにおいてもWizard of Oz法などといったデザインの手法があったりするので、その辺が参考になります。

f:id:kohtaro24:20191219165739p:plain

モックアップでのレビューを支援するため、裏方の操作に特化したツールを作ってみたら便利でした。

f:id:kohtaro24:20191219182937p:plain

運用の話

ロボットを使ったサービスを提供するにあたって欠かせない要素が、現場での運用のフォローです。

盗難防止などの観点から、ほぼ全ての機材は毎日の営業終了後に収納が必要で、翌朝には再設営することになります。 開発者が現場に常駐し続けるわけにもいかないので、基本的にはこの作業を施設側従業員の方にお願いすることになります。

現場の方々に設営/撤収のオペレーションを回してもらう上でも、様々な工夫が必要です。

  • 全ての機材の設置/片付け/トラブルシューティングマニュアルを用意
    • それぞれが数十ページにわたる超大作になりがち
  • 機材の設置位置、接続ポートなどの全てにマーキングを施す(いわゆるバミリ)
  • 各ソフトウェアの起動操作の簡便化
  • 稼働状況などを遠隔地からモニタリング

機材の設営において一番難所となるのがセンサ(深度カメラ)の設置です。 高所に設置しなければならない上、アングルや画角の調整がシビアなので、この緻密な調整を現場の方々に毎日依頼するのはかなりの負担になってしまいます。

考えた末に、オリジナルのセンサ設置台を作ったこともありました。センサの土台が着脱可能になっており、こうすることで角度を維持したままセンサだけを収納できます。

東急ハンズの木材コーナーには大変お世話になりました。

f:id:kohtaro24:20191220152413p:plainf:id:kohtaro24:20191220152804p:plain

あと、Electronアプリケーションの起動時には全ての機材との疎通チェックを行い、人物検知スクリプトなどの周辺モジュールはElectronのサブプロセスとして実行しています。 この起動シーケンスにて何らかの異常が認められた場合は、エラー情報を可視化し、マニュアルの確認へと誘導しています。 f:id:kohtaro24:20191220145236p:plain

1ヶ月程度の実証実験の中だけでも色々なリスクが想定されましたが、いざ長期運用となった場合は可用性、保守性、耐久性など更に視野を広げて対策を考える必要がありそうです。

人物センシングの話

接客ロボットは接客機会を作らないことにはどうしようもないので、お客様に自ら声かけをするなど、アテンションの効果を高めることが重要です。

これをやるにあたり、人物の検出や追跡、滞留/離脱の判定が技術的には重要な要素となってきます。

人物の追跡

人物を検出するにあたっては、IntelのRealSenseという深度カメラを使うことが多いです。 このカメラからRGB映像と深度映像(奥行き)を毎フレームごとに取得し、画像処理を通じてRGB映像から人物を探します。深度映像の情報と組み合わせることで人物の3次元座標を作ることができます。

f:id:kohtaro24:20191220163501p:plain

次に必要な処理として、この人物が前のフレームにもいた人物であるかを推定する必要があります。 直近20フレームくらいの間に取得した人物の座標を保持しておき、DBSCANクラスタリングを使って座標密度のクラスタを作っています。

過去の座標には人物ID(人物ごとに付与した一意なID)が紐付いており、最新フレームの人物座標が属するクラスタ内で、最も出現している人物(のID)を最新座標と同一人物とみなします。下画像の場合だと最新座標はID:1となります。

f:id:kohtaro24:20191220170450p:plain

過去座標のサンプルが少ないうちはクラスタが作れず全て外れ値になってしまう(別人とみなされてしまう)ので、外れ値に対してもユークリッド距離を使った前フレーム座標との比較判定を入れることで追跡精度を補完しています。

f:id:kohtaro24:20191220162134p:plain

人物の滞留/離脱

人物の追跡ができてしまえば、時間と人物座標の推移に基づいて滞留と離脱を判定することができます。 この判定を用いることで、立ち止まった相手に声をかける相手が立ち去ったタイミングで接客をやめるといったことを実現できます。

f:id:kohtaro24:20191220173945p:plain

とはいったものの、この判定ロジックは人物の検出や追跡が成功しているという前提に依存しています。 現実的な問題として、後ろや横を向かれたり体が見切れてしまったりすると、人物検出精度は落ちる傾向があります。 万人に対して十分な画角やアングルを確保するのは中々骨の折れる作業です…

f:id:kohtaro24:20191220174647p:plain

目の前にいるにも関わらず離脱判定が暴発し接客が打ち切られてしまえば、その人の体験は最悪なものとなってしまうでしょう。

これを防ぐ方法の一つとして、ソナー(音波)や赤外線などを通じて得られるアナログ情報を併用するという手法があります。

例えばPepperは足元にソナーがついているので、付近の物体との距離を逐一監視することができます。 手前に人が立っている状態であれば距離に大きな変化が発生するため、より確実な精度で滞留や離脱の判定を行うことができます。

f:id:kohtaro24:20191220182221p:plain

「じゃあ全部ソナーで良くね?」ってなるかもしれませんが、 カメラはカメラで広範囲を見渡せるし、画像処理を併用すれば物体の特徴を区別したり追跡できるので、アテンションなどには幅広い使い道があるでしょう。 それぞれのセンシング手法も適材適所があるので、どのように組み合わせ、使い分けていくかを考えるのはとても面白そうです。

f:id:kohtaro24:20191220184229p:plain

おわり

コミュニケーションロボットを使ったサービス開発の裏側について、さくっとご紹介しました。 「楽しそう!」とか「大変そう!」とか思ってもらえれば幸いです。

僕も去年まではごく普通の(ちょっとだけ電話に詳しい)Webアプリケーションエンジニアだったので、センシングやったり画像処理やったりDIYやったりと新鮮で面白いことにチャレンジできました。

改めて振り返ると触った技術も多岐に渡っていて、この仕事はまさに総合格闘技だなぁと思います。ニッチな領域ゆえ手探り状態が続いており、今回の知見もまたすぐに置き換わるかもしれません。 とりあえず音声認識とか人物認識とかで詳しい方がいたら是非繋がりたいのでご連絡ください!

障害アラートコール×多人数通話接続という仕組みを思いついたのでサーバレスで実現した

この記事はSpeee Advent Calendar 2018かつTwilio Advent Calendar 2018の5日目の記事です。一石二鳥という魂胆です。

エンジニア界隈で定番のAdventCalendarですが、私はAdventCalendarの由来がお菓子であることを知らず、先日アンジャッシュ的なことをかましてしまいました。

妻:アドベントカレンダーあげたら〇〇ちゃん(友人のお子さん)喜ぶかな〜
自分:え、AdventCalendarってなにか知ってるの?てか流石に〇〇ちゃんには難しすぎない?
妻:もう少し子供向けのがいいかな?
自分:いや子供向けっていうか、そもそもどんな記事書くつもりなの?
妻:え?
自分:え?

はじめに

Webサービスはいかなる努力を費やしたとしても死ぬときは死にます。

昨今のWebサービスでは、死活監視ツールを入れてサーバの生存状況やら異常やらを検知できるようにしているケースも多いと思います。

しかし異常を検知できたとしても、すぐ人が気づいて対処できるかどうかは別問題です。例えば深夜の時間帯や外出中など、そもそも気づくことが困難なシチュエーションがあります。

ある程度の規模の開発チームであれば、必ず誰か待機者を立てたりエスカレーションフローを組むなどしてカバーできるかもしれません。しかし、せいぜい2~3人の小規模チームだとそれは難しいです。

個人的にありそうなネックを列挙していきます。

  • 障害に気づきにくい
    • 他所に気を取られてると反応が遅れることも多いかと思います
  • 行き違いによって二次災害が起こる恐怖
    • 他の誰かがすでに対応開始しているかもしれない
    • 操作が衝突することは避けたい
  • 個人での対処の判断が難しいケースもある
    • 他メンバーに相談したいのにレスが帰ってこない
  • コストかけたくない
    • PagerDutyとかあるけどそこまでお金払いたくない
    • OSSでもwakerとかあるけど監視サーバの保守もあまりしたくない
    • 固定費はあんまりかけたくない、でも障害には気づきたいんや。。!(ジレンマ)

ということで、すごくお手軽にこれらの問題を解決できそうな仕組みを考えました。

そして思いついたのが 障害アラートコール×多人数通話×サーバレス という組み合わせです。

アラートコールによる通知

f:id:kohtaro24:20181206013850p:plain

電話発信で障害を伝える仕組みは近年よく採用されてるように思えます。プッシュ通知よりも強力ですからね。

多人数通話接続で障害対応をサポート

f:id:kohtaro24:20181206020443p:plain

せっかく電話で受けるなら、通知で終わるんじゃなく電話の機能をもっと有効活用したいじゃないですか。 電話に出た人全員の通話をそのまま繋げてしまえば、コミュニケーション取りながら対応できるので作業の衝突が防げたり、声を掛け合いながら安心して作業を行えそうです。

サーバレスでコストカット

いつ起きるか分からない障害の通知のために常時サーバを動かしておくのは、可能なら避けたいところです。 これらの仕組みをサーバレスで実現できれば、固定費がまるまる浮くわけですね。


作ってみた

f:id:kohtaro24:20181206024214p:plain

なるべく簡単に色んなアプリケーションと連携できるように、このようなWebhookを作りました。 このWebhookを実行するだけでアラートコールが発信されます。 GETパラメータで発信対象の電話番号リスト応答時に最初に流れる音声ガイドの内容を指定できるようになっており、アラートの種類に合わせて柔軟に内容を変更できます。

実際に動いている様子がこちらです。 アラートコールを受け取ったら最初に音声ガイドが流れ、その後応答者同士の通話が開始しています。

いかがでしょう?ちょっと使ってみたくなりませんか!?

これらの仕組みは全てTwilio上で構築されており、サーバレスの実現にあたりTwilio Functionsを使用し、通話接続にはConferenceという機能を使用しています。着信からの通話接続の仕組みについては去年作ったこれが大体ベースです。

以下で作り方を解説していきます。ものの30分であっという間に実現できてしまうのでぜひ試してみてください。

発信用電話番号を購入する

Twilioコンソールにログインし、電話番号の購入ページから適当に050番号を探して購入します。

050番号は毎月108円の維持費がかかります(これだけは固定費としてかかってしまう)。

f:id:kohtaro24:20181206031223p:plain

Functionsを作成する

Functionsを使ってサーバレスアプリケーションを作っていきます。

  • Functionsについて
    • Twilioコンソール上でjavascriptを書いてボタンをポチるだけでAPIエンドポイントが作れる
    • 静的ファイルのホスティングを提供するTwilio Assetsと組み合わせることができる
    • AWSAPI GatewayとLambdaのラッパーのようなものだと思えばいい

AWSAPI Gateway×Lambdaを使って自前で構築する場合と比較すると、このあたりが嬉しいところですかね。

  • GUI上でさくっと作ってデプロイできる(一応CLIから操作するためのツールキットもあります)
  • Twilio関連ライブラリがそのまま呼び出せる
  • Twilioに関するクレデンシャル情報の設定を省略できる
  • Twilioのプライベートネックワークからのリクエストのみを受け付けるように制限かけたりできる

ではまずTwilio Functionsの設定画面を開きます。

Enable ACCOUNT_SID and AUTH_TOKEN がONになってることを確認し、なってなければONにしてSaveします。

f:id:kohtaro24:20181206043020p:plain

次にTwilio Functionsの作成画面を開きます。

f:id:kohtaro24:20181206035113p:plain

今回は二つのFunctionを用意します。

  • Function(1): Webhookリクエストを受けて指定の電話番号に発信する
  • Function(2): 発信に応答があった場合にコールバックされ、応答者を電話会議につなぐ

テンプレートを聞かれるのでBlankを選択して作成。

f:id:kohtaro24:20181206035215p:plain

作成されたら、このように書き換えます。

f:id:kohtaro24:20181206040730p:plain

以下がFunction(1)の処理内容です。

GETパラメータとして二つのパラメータを受け取ります。

  • telsパラメータ:+81から始まる発信対象の番号をカンマ区切りで入れる
  • messageパラメータ:応答時に読み上げる音声ガイドのテキストを入れる
exports.handler = function(context, event, callback) {
    const twilioClient = context.getTwilioClient();
    // telsパラメータ内の電話番号に対して順番に発信
    event.tels.split(',').forEach(function(tel){
        twilioClient.calls.create({
            // 応答があったらFunction(2)をコールバックする。
            url: 'https://' + context.DOMAIN_NAME +'/join_conference?message=' + encodeURIComponent(event.message),
            from: '+815012345678', // 購入済みのTwilio番号(発信番号として通知される)。自分が購入した電話番号に書き換えること
            to: tel,
        }, function(err, result) {
            console.log('Created message using callback');
            console.log(result.sid);
            callback();
        });
    });
};

Saveを押したら即デプロイされます。

f:id:kohtaro24:20181206042551p:plain

これでアラートコールを実行するWebhookが出来ました。

f:id:kohtaro24:20181206050046p:plain

次に応答者を通話接続するためのFunction(2)を作成します。 (1)と同様にBlankを選択して作成したら、このように書き換えます。

f:id:kohtaro24:20181206044901p:plain

以下がFunction(2)の処理内容です。

GETパラメータとして一つのパラメータを受け取ります。

  • messageパラメータ:応答後に読み上げる音声ガイドのテキストを入れる(Function(1)から渡される)
exports.handler = function(context, event, callback) {
    let twiml = new Twilio.twiml.VoiceResponse();
    let conferenceName = 'AlertRoom'  
    twiml.say({ language: 'ja-JP' }, "エマージェンシー、エマージェンシー、" + event.message) // メッセージを読み上げる
    twiml.dial().conference(conferenceName); // AlertRoomという電話会議室に招待。最初の一人は保留音が鳴った状態で待機になる。
    // twiml.dial().conference({ waitUrl: 'https://hogehoge/assets/保留BGM.mp3' }, conferenceName); // 保留音として任意のBGMを流すこともできる
    callback(null, twiml);
};

Saveをお忘れなく。

これで発信の応答者を電話会議につなぐ仕組みができました。

f:id:kohtaro24:20181206050510p:plain

なんとこれで完成です! Function(1)のエンドポイントにGETパラメータをつけてアクセスしてみましょう。ちゃんと設定できていれば電話がかかってくることでしょう。

https://#{割り当てられたサブドメ}.twil.io/alert?tels=#{発信したい電話番号(+81が必要)をカンマ区切りで}&message=hoge

あとは任意の監視ツールにて特定のアラートを拾った際に、このURLにリクエストをHookすればOKです。

利用してるSaaSがメールでの通知にしか対応してない場合などは、Sendgridとかとうまく連携すると良さそうです。

まとめ

  • 障害に気づきにくい

    → 電話鳴らして気づかせよう

  • 行き違いによって二次災害が起こる恐怖

    → メンバー全員の通話を繋いでしまえ!

  • 個人での対処の判断が難しいケースもある

    → メンバー全員の通話を以下略

  • コストかけたくない

    → サーバレス化で固定費は毎月108円(番号維持費)。障害が起きない限り通話料は発生しない!

    → Functionsは毎月1万アクセスまで無料なので、障害が1万回起きなければ無料

    → エンドポイント一個作っとけばアプリケーション側はWebhook指定するだけ!

電話ってすごいな〜

いかがでしたでしょうか。お金と人がいればこんな問題はあまり気にならないかもしれませんが、小さいチームには需要あるんじゃないかということで記事にしてみました。

もしよかったら試してみてください。ご覧頂きありがとうございました。

TwilioでAmazon Pollyが使えるようになったので早速試した

  • TwilioではTwiMLのSay動詞を使って通話相手に対するTTS(Text To Speech)を手軽に実行できる
  • 日本語も読み上げてくれるがかなり棒読みなので実運用で使うには厳しさがある
  • そのため読み上げ品質を妥協できない場合は他サービスの音声合成APIなどを使って独自に音声読み上げを実現するケースも多かった
  • 先日のTwilioBlogによるとSay動詞が利用するTTSプロバイダとしてAmazon Pollyが選択可能になったらしい

やってみよう

TTSプロバイダをAmazon Pollyに変更する

YOUR DEFAULT PROVIDORをAmazon Pollyに設定

f:id:kohtaro24:20180809134751p:plain

間もなく変更が完了する

f:id:kohtaro24:20180809134724p:plain

なお、Amazon Pollyにプロバイダを変更すると100文字読み上げあたり0.0008ドルのコストが発生することには注意(デフォルトは無料)。

Sayで音声を流してみる

こんな感じの音声を流してみた

<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say language="ja-JP">お電話ありがとうございます。現在、電話に対応できるオペレーターがおりません。恐れ入りますが、改めてお掛け直しください。</Say>
</Response>

Before(Pollyじゃない方)

おかけなお⤴しください

after(Pollyの方)

棒読み感はまだありますが明らかにPollyの方がいいですね。

個人的にはとりあえずPollyに変更しとこうかなと思いました。 記事によるとPollyのSSMLもSay動詞内に書けるようなので、色々調整はできそうです。  

朝起きれない問題に本気で向き合う目覚ましサービスを作った

この記事はTwilio AdventCalendar 2017かつSpeee AdventCalendar 2017の21日目の記事です。

昨日の記事はこちら

朝起きれていますか?

朝早く起きれると、自由な時間が増えて色々ハッピーになります。

朝起きれない問題

早起きが得なことは分かっているのですが、早起きは大体いつも失敗してしまいます。 その原因を考えてみます。

目覚ましアラームは次第に効能が悪くなる

朝起きるためにスマホ等でアラームをセットするわけですが、セットしたアラーム音も毎日聞いてると慣れてきて、次第に聞き流せるようになってしまいます。 そうならないように ものすごく嫌な音 をセットしてみたりするのですが、あまりにも嫌すぎて無意識の間にアラームを止めてしまうことが多々ありました。

逆説的には、 日々新鮮で楽しい出来事 が朝の目覚めには必要なのかもしれません。

「全部自己責任だから」という甘え

早起きはいつも失敗するものの、 出社が間に合わなくなるまで寝てしまうケース はほぼありません。 つまり、 自分以外の存在に対して影響が及ぶ状況下 では起きれるのです。

結局のところ、「早く起きれなかった分には誰も困らない」という甘えが裏にあるのではないでしょうか。

どうすれば朝起きれるのか

問題点の逆説から、以下の二つを抑えれば早起きの成功率が上がりそうです。

  1. 起きる度に新鮮で楽しい出来事が発生すること
  2. 自分以外の存在に対して影響が及ぶ状況下であること

上記を実現する最もシンプルな方法は 人に起こしてもらう ことです。

コミュニケーションを取ることで意識が覚醒する

起こしてくれる人がいるということはつまり、対話が発生します。対話は更新され続けるものであるため、常に新鮮な刺激とストレスの解放を得ることができます。

起こしてくれる誰かがいるという状況が気持ちを動かす

起こしてくれる人がいる手前、流石に無視するわけにはいきません。自分が起きずにいることがその人の損害になってしまいます。

起こす側の問題

上記から、起こしてくれる人の存在が非常に重要だということが分かりました。

しかし、起こす側の人にも解消すべき問題があります。

それは 起こす人が先に起きていないといけない という前提条件です。 すなわち、この条件を満たす上では 起こす人を起こす人 が必要であり、以下この構造は無限に続きます。

f:id:kohtaro24:20171221025628p:plain

この無限階層問題を脱出する上では 互いが互いを起こし合う という仕掛けを実現する必要があります。 f:id:kohtaro24:20171221031021p:plain

遠隔地にいる人を起こさなければならない時、最もよく使われるであろうツールはなんでしょう?

そう、 電話 です。

つまり お互いの電話に自動発信できる仕組み があればいいんです。 f:id:kohtaro24:20171222013832p:plain

お互いの発信によってお互いが目覚め、更に会話することによって意識が覚醒する。あわよくばそのまま二人の予定も立てられる。これは起きれそうです。 f:id:kohtaro24:20171222014717p:plain

作ってみた

  • 目覚ましの予約

目覚ましを利用するために予約を行います。 予約は以下のようなフローで行えます。

  1. 指定の電話番号に発信する
  2. 音声案内に従い、起こして欲しい日時と一緒に起きたい人の電話番号を伝える
  3. 寝る
  • 予約時刻のコール

予約した日時になると2人の電話が着信し、お互いが着信に応答した時点で通話がはじまります。

仕組みについて

この機能の大半はTwilioを用いることで実現しています。 バックエンドのアプリケーションとしてRuby on Rails、スケジューラとしてSidekiqを利用しました。

目覚まし予約の仕組み

Twilioの電話番号に着信が来るとRailsがイベントを受け取り、命令をTwiMLで返しています。 f:id:kohtaro24:20171222023201p:plain

このケースでは、Sayによる音声案内、Gatherによる音声テキスト化をTwiMLから利用しています。

Gatherの結果で得られたテキストから、rails側で正規表現なり駆使して必要なパラメータを抽出し、Sidekiqにスケジュール登録します。

f:id:kohtaro24:20171222023223p:plain

同時発信の仕組み

予約された日時になると、SidekiqがTwilio REST APIに電話番号への発信をリクエストすることで、Twilio経由で対象者への発信が行われます。

f:id:kohtaro24:20171222023750p:plain

Twilioの発信に対して応答があった際、Railsがイベントを受け取り、命令をTwiMLで返します。

f:id:kohtaro24:20171222024200p:plain

このケースでは、Dial, Conferenceによる電話会議の機能を利用しており、発信への応答者を随時Conference Room(会議室)に招待します。

f:id:kohtaro24:20171222024411p:plain

これにより、 2人が着信に応答したタイミングで通話が開始される 仕組みを実現できます。

だいぶざっくりした説明になってしまったのですが、詳細が気になる方はソースコードこちらにあるので御覧ください。

この目覚ましの特徴

① アプリもサイトも不要

予約機能も電話一つで利用できるようにしたので、ぶっちゃけ家電だったりガラケーでも問題なく利用できます。 そしてhtmlもcssjavascriptも書かないで済むので開発者が楽です。

② 予約者以外は朝起きて電話に出るだけでいい

親しい人と一緒のサービスを利用したいとなった場合、多くのSNSなどではアプリを入れる必要があったりアカウントを作ったり友達申請したりと、利用を開始できるまでの障壁がいくつもあります。この目覚ましにおいては、予約者が相手の番号を知ってるだけで利用条件が満たされます。

(全然知らない人の電話番号を登録するようなイタズラもできてしまうので、もし公に運営するならSMS認証挟んだりなどの対策が必要だと思います)

③ 最大250人で一緒に目覚ましできる

Conferenceの最大キャパシティが250人なので、その気になれば250人で同時に目覚ましすることも可能です。

(とても通話なんかできたもんじゃないと思いますが)

いかがでしょうか

この目覚まし、めざましくないですか?

正直、ConferenceやGatherを使ってみたいという気持ちだけが先行して、テーマ自体は完全にネタのつもりだったのですが、中々面白いものが形になったなと思います。

Twilioはまだまだ面白い使い方ができそうなので、引き続き試していこう。

おまけ

某モンスターの鳴き声で音当てゲーム的なものも作ってみました