WonderPlanet Tech Blog

アドバンストテクノロジー部やCTO室のメンバーを中心に最新の技術情報を発信しています。

Zoom Rooms を楽しく・便利に使うノウハウ大公開!

こんにちは、CTO村田です。前回に引き続き Zoom についてです。
今回は、Zoom Rooms をもっと楽しく、便利に使う方法を公開したいと思います。

前回の Zoom に関する記事をご覧になられていない方は、ご一緒にどうぞ!

tech.wonderpla.net

その前に... Zoom Rooms 設定画面の基本的なこと

Zoom Rooms に関する設定は、「ルーム管理」メニューより行います。
その中でも主に使うのは「Zoom Rooms」です。「カレンダー統合」は一度設定すれば、あまり触る事は無いと思います。

f:id:tomo-murata:20171016213853p:plain

そして Zoom Rooms の設定は、全体に反映する設定と、ルームごとの設定があります。

■ 全体に反映する設定

アカウント名の隣にある「アカウント設定」リンクから設定します。

f:id:tomo-murata:20171016114315p:plain

■ ルームごとの設定

「ルーム」タブ内のルーム一覧より、設定したいルームの「編集」ボタンから設定します。

f:id:tomo-murata:20171016114557p:plain

■ 表示言語の変更

設定画面も複数言語に対応し、今では日本語での表示が可能です。
とは言っても、ちょっと日本語がおかし箇所もあり、んんん??? となることも稀に...
そういう時は、 English にして確認した方が良いと思います。難しい英語もないので、個人的には English の方がオススメです。

言語切り換えは、画面右下にいつもあります。

f:id:tomo-murata:20171016214011p:plain

 
それでは、楽しく・便利になる方法を紹介していきます!!
 

ディスプレイの背景画像を変えたい

■ 目的
  • デフォルトの背景画像から別の画像に変えたい。
  • Zoom Rooms のディスプレイを見るだけで、どこの部屋なのか分かりたい。
■ 方法

ワンダープラネットでは、どの会議室なのかディスプレイを見ただけで分かるように背景画像を設定しております。

f:id:tomo-murata:20171016211907j:plain

ルーム編集の「ルームの設定」に「Zoom Roomsの背景画像」という項目があります。

f:id:tomo-murata:20171016112527p:plain

どのような画像を受け付けるかは、要件のところに記載されております。

また「ルームの設定」を見ていると、「ルームアバター」の設定もあります。

f:id:tomo-murata:20171016121522p:plain

こちらもぜひ併せて設定しましょう!
例えば Zoom Client 内の Zoom Rooms にアバターが表示されたりと、随所でルーム名と併せてアバターが表示されるようになります。

f:id:tomo-murata:20171016122021p:plain

ルームの Meeting ID を分かりやすい ID に固定にしたい

■ 目的
  • Meeting ID がコロコロ変わると、ホストルームに入る際にどの Meeting ID で入れば良いか分からない。
  • ランダムな数列の ID は覚えづらい。
■ 方法

ワンダープラネットでは、ある命名規則に則って、各ルームに Meeting ID を設定しております。
ちなみにMeeting ID は、10桁*1の数値 例)123-456-7890 です。 Meeting ID から 例)https://zoom.us/j/1234567890 というURLが発行され、このURLにアクセスするだけでミーティングに入ることもできます。

ただ、命名規則に則って Meeting ID を設定しているとは言え、なかなか覚えるのは厳しいです。
ワンダープラネットでは、Slack コマンドで呼び出せるようにしております。

f:id:tomo-murata:20171016145938p:plain

それでは、Meeting IDの設定と、必ずルームに割り当てた Meeting ID を使う方法です。

ルーム編集の「ルームの設定」に「ルームミーティングID」という項目があります。
こちらに希望の Meeting ID を入力します。

f:id:tomo-murata:20171016150558p:plain

「インスタントミーティングにパーソナルミーティングIDを使用する」にチェックを入れておくと、設定したルームミーティングIDでオンライン会議を開始します。

もっと楽に画面共有をしたい!

■ 目的
  • 画面共有するのに、Zoom Client を立ち上げるのすら面倒です。
  • HDMIケーブルを挿さずに、会議室のディスプレイに映したい。
■ 方法

Zoom Rooms の AirPlay *2機能を活用します。

前回、Zoom Rooms 用の機材として Mac mini を使っていると書きましたが、その理由がコレです!
Zoom Rooms は macOS と組み合わせることで、なんと AirPlay が使えるようになります*3。もうメチャクチャ便利です!!

AirPlay 機能を使うには、Zoom Rooms 用の機材として macOS 搭載の PC を用意するだけです。

では、AirPlayの使い方を見ていきましょう。

その1.macOS からの AirPlay

AirPlayのアイコンをクリックすると、Zoom Rooms のルームがずらっと並びます。今いる会議室を選択します。

f:id:tomo-murata:20171016153810p:plain

そうすると、Zoom Roomsのディスプレイに4桁の数値が現れます。

f:id:tomo-murata:20171016213134j:plain

PCにもパスワードを入力するダイアログが表示されますので入力します。

f:id:tomo-murata:20171016212935p:plain

たったこれだけです!

Zoom Rooms を使ったオンライン会議中であれば、相手にも共有されます。
Zoom Rooms でオンライン会議をしていない時でも AirPlay 可能です。ということは、HDMIなどのケーブルが無くても会議室のディスプレイに表示ができる!ということですね。

macOSの場合は、AirPlay 先を拡張ディスプレイとして扱えます。
PowerPoint や Keynote で作成したプレゼン資料を、ノートを見ながら説明することができます。Zoom Client で画面共有した時は違うところですね。

その2.iOS からの AirPlay

iOS のコントロールセンターから「画面ミラーリング」を選択します。

f:id:tomo-murata:20171016161308p:plain

AirPlay の一覧に Zoom Rooms のルームが並びますので、会議室を選択します。

f:id:tomo-murata:20171016160634p:plain

あとは macOS 同様、パスワードを入力ダイアログが表示されますので、ディスプレイに表示されたパスワードを入力します。

サウンドもちゃんと出力され、ゲームではよく見られる激しいエフェクトも遅延無く出力されます。
Zoom Client を経由するよりも軽いのでオススメです。

その3.上記以外からの AirPlay

Windows や Android から AirPlay をする方法を探してみましたが...
Windows の AirMyPC を使う方法しか、今のところ見つかっていないです。

他の AirPlay を実現するツールでは、接続はできるのですが真っ黒な四角が表示されるだけでした。画面共有はできず...

Zoom Rooms の異常を早く検知したい!

■ 目的
  • コントローラーとして使うタブレットがネットワークから切断されたり、バッテリーが無くなったりしている。
  • Zoom Rooms に接続されているスピーカーを外すのは良いけど、元に戻さず外されたままになっている。
  • このような状況になった時、早急に対応できるようにアラートをキャッチしたい。

運用していると Zoom Rooms が使えなくなるような状態が発生してきます...

■ 方法

アラートの設定ができますが、通知先に設定できるのは メールのみ です。
しかし、Slack には Slack でメールを受信する 機能があります。

これらを組み合わせることで、あまりメールを見ない方でも 簡単に Slack で通知を受け取ることができます。

それでは設定していきましょう。

1.ワークスペースにメールアプリを連携させる

Slack の App ディレクトリにアクセスして、 メール アプリを検索します。 email で検索すると最初の方に表示されます。

f:id:tomo-murata:20171016182616p:plain

「Add Configuration」より、通知するチャンネルを選択してメールアプリを追加します。

f:id:tomo-murata:20171016213343p:plain

そうすると、通知用のメールアドレスが発行されます。

f:id:tomo-murata:20171016183143p:plain

2.Zoom Rooms のアラートを有効にし、通知先を設定する

今回は、Zoom Rooms の設定でも「アカウント設定」から行います。
アカウント設定へ遷移すると、「アラートの設定」タブがあります。

f:id:tomo-murata:20171016183452p:plain

あとは、自分が把握したいアラート状況を設定し、最後に通知先メールアドレスとして、Slackで発行したメールアドレスを設定します。

f:id:tomo-murata:20171016183755p:plain

設定した異常状態になると、このように Slack に通知が飛んできます。

f:id:tomo-murata:20171016183943p:plain

これで、わざわざメールを開かなくても、Slackで状況を把握できますね!

■ 注意事項

Slack のメールアプリ追加は、有料プランでしか使えません。
無料プランで同様のことを行いたい場合は、転送用のメールアドレスを発行し、通知したい人数分登録しましょう。

次の会議が迫っていることを通知したい!

■ 目的
  • 会議室を使っている人が時間通りに会議を終わってくれない。
  • コントローラーにスケジュールが表示したい。
■ 方法

カレンダー統合機能を使います。

Zoom Rooms は、今のところ

  • Googleカレンダー
  • Office 365
  • Exchange

と連携できます。

ワンダープラネットでは G Suite を使っているため、今回は Googleカレンダーを使って説明します。

1.G Suite 側で連携専用のアカウントを発行する

連携専用のアカウントを発行します。社員の誰かのアカウントで連携してしまうと、見えてはマズイものが連携されますので止めましょう ^^;

アカウントを作成したら、そのアカウントからカレンダーのリソースを参照できるようにする必要があります。

2.連携専用アカウントでリソースを参照する

連携専用アカウントで G Suite にログインし、Googleカレンダーを開きます。

カレンダー設定 から「カレンダー」タブを開きます。 他のカレンダーのところに、「おすすめのカレンダーを検索 >>」をクリックします。

f:id:tomo-murata:20171016191927p:plain

おすすめカレンダーの中で「その他」タブをクリックします。

f:id:tomo-murata:20171016192059p:plain

この中に入ると、 xxxx のリソース という項目がありますので、クリックすると全リソースが表示されますので、必要なリソースを登録します。

G Suite での設定は以上です。
ここからは、Zoom Rooms での設定となります。

3.Zoom Rooms にカレンダーサービスを追加する

Zoom Rooms の設定メニューより「カレンダー統合」を選択します。
「Googleカレンダー」を選択すると、どのGoogleアカウントで連携するか聞いてきますので、先ほど作成した連携専用Googleアカウントで連携します。
成功するとこのような感じで、表示されます。(追加したては割り当て数はゼロですね)

f:id:tomo-murata:20171016193047p:plain

4.Zoom Rooms のカレンダーに設定する

ルーム編集の「ルームの設定」に「カレンダー(オプション)」という項目があります。
カレンダーサービスを設定すると、リソース一覧が表示されますので、連携したいリソースを設定します。

f:id:tomo-murata:20171016213652p:plain

5.通知するように設定する

ルーム編集の「ミーティング設定」タブに「次のミーティング警告」という項目があります。

f:id:tomo-murata:20171016213543p:plain

こちらを有効にします。

設定は以上です!!

あとは Zoom Rooms を使っている際に次の会議の10分前になると、ディスプレイ上部に以下のメッセージが表示されます。(邪魔にならないように数秒で消えます)

f:id:tomo-murata:20171016225528j:plain

会議室については、Zoom Rooms よりも便利なサービスを使って管理しております。
このサービスについては、改めて紹介させていただきます。

■ 注意事項

この設定を行うと、コントローラーの Meeting List にも表示されます。
限定公開の場合は予定の名称は伏せられますが、一般公開の場合はそのまま表示されますので、登録の際は注意してください。

まとめ

今回掲載した方法の中には、最近になって実現できるようになったものもあります。

ちなみに、社内で一番反応が良かったのは、AirPlay機能です。
Mac、そして iPhone が多いので、AirPlay はとても便利です!

Zoom Rooms は進化し続け、ますます便利になってきております。
新たな発見があり次第、また公開したいと思います。

機械学習で大量のテキストをカテゴリ別に分類してみよう!

こんにちはアドバンストテクノロジー部の@y-matsushitaです。
今回は機械学習を使った取り組みとして、手始めにfastTextを使ったテキストの分類について触れたいと思います。

fasttext.cc

fastTextとはFacebookが提供する単語のベクトル化とテキスト分類をサポートした機械学習ライブラリです。
fastTextという名前の通り動作が軽く早いのが特徴です。試しに使ってみたところ精度も良好で動作も軽かったのでご紹介させていただきます!
今回は試しに様々な情報が入り混じったTwitterの投稿内容を分類して「美容系」「エンタメ系」「暮らし系」情報の3パターンに分類してみます。 なお今回の記事ではPython 3.6.1を使用します。


fastTextを使ってできること

まず最初にfastTextを使った結果をお見せします。
『分類前』が処理前で『分類後』がfastTextを使って分類した結果です。

『分類前』
f:id:y-matsushita:20170929141107p:plain:w600
緑:美容系 , 青:エンタメ系 , 赤:暮らし系

上記のように分類前は色々なジャンルの投稿内容が入り混じった状態です。
これをfastTextで分類すると以下のような形になります。

→『分類後(美容系)』
f:id:y-matsushita:20170929141341p:plain:w600

→『分類後(エンタメ系)』
f:id:y-matsushita:20170929141501p:plain:w600

→『分類後(暮らし系)』
f:id:y-matsushita:20170929141532p:plain:w600

上記のように概ね文章を「美容系」「エンタメ系」「暮らし系」などに分類することができました。 *1
このように文章からカテゴリごとに自動分類したり、スパム的な投稿内容を検知したりもできます。
作成するモデル次第でかなり応用が効きそうな感じです。

また、単語のベクトル表現をつくることで、
SNSユーザの投稿内容からおすすめを紹介するレコメンド機能なども活用事例の代表です。


fastTextをインストール

fastTextはGitHubからダウンロード可能です。
以下のコマンドでダウンロードすると実行できるようになります。

$ git clone https://github.com/facebookresearch/fastText.git
$ cd fastText
$ make
$ pip install cython
$ pip install fasttext

学習用のテキストを用意する

学習用のテキストは過去にTwitterに投稿された「美容系」と「エンタメ系」と「暮らし系」のワードを含むツイートから作成します。

TwitterのAPI Keyを用意

Twitterの内容はAPIから取得します。
TwitterのAPI Keyを持っていない場合は予め用意する必要があります。
Twitter Application Management

MeCabをインストール

日本語は英語とは異なり単語同士がスペースで区切られていないため、分かち書きをする必要があります。
今回はMeCabを利用しました。 mecab-python3

MeCabをインストールしていない場合、以下のコマンドでインストールを行います。

$ brew install mecab mecab-ipadic
$ pip install mecab-python3


投稿の取得からテキスト出力までは以下のコードで行います。
TwitterのAPI Keyと取得したいカテゴリが含まれるキーワードを入力して取得します。

tweet_get.py

import re
import json
import MeCab
from requests_oauthlib import OAuth1Session

CK = "(用意したConsumer Keyを入力)"
CS = "(用意したConsumer Secretを入力)"
AT = "(用意したAccess Tokenを入力)"
AS = "(用意したAccess Token Secretを入力)"

API_URL = "https://api.twitter.com/1.1/search/tweets.json?tweet_mode=extended"
KEYWORD = "芸能 OR アニメ OR 漫画 OR TV OR ゲーム"            #エンタメ系のキーワードを入力
CLASS_LABEL = "__label__1"

def main():
    tweets = get_tweet()                #ツイートを取得
    surfaces = get_surfaces(tweets)     #ツイートを分かち書き
    write_txt(surfaces)                 #ツイートを書き込み

def get_tweet():
    """
    TwitterからKEYWORDに関連するツイートを取得
    """
    params = {'q' : KEYWORD, 'count' : 100}
    twitter = OAuth1Session(CK, CS, AT, AS)
    req = twitter.get(API_URL, params = params)
    results = []
    if req.status_code == 200:
        # JSONをパース
        tweets = json.loads(req.text)
        for tweet in tweets['statuses']:
            results.append(tweet['full_text'])
        return results
    else:
        # エラー
        print ("Error: %d" % req.status_code)

def get_surfaces(contents):
    """
    文書を分かち書きし単語単位に分割
    """
    results = []
    for row in contents:
        content = format_text(row)
        tagger = MeCab.Tagger('')
        tagger.parse('')
        surf = []
        node = tagger.parseToNode(content)
        while node:
            surf.append(node.surface)
            node = node.next
        results.append(surf)
    return results

def write_txt(contents):
    """
    評価モデル用のテキストファイルを作成する
    """
    try:
        if(len(contents) > 0):
            fileNema = CLASS_LABEL + ".txt"
            labelText = CLASS_LABEL + ", "

            f = open(fileNema, 'a')
            for row in contents:
                # 空行区切りの文字列に変換
                spaceTokens = " ".join(row);
                result = labelText + spaceTokens + "\n"
                # 書き込み
                f.write(result)
            f.close()

        print(str(len(contents))+"行を書き込み")

    except Exception as e:
        print("テキストへの書き込みに失敗")
        print(e)

def format_text(text):
    '''
    ツイートから不要な情報を削除
    '''
    text=re.sub(r'https?://[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text=re.sub(r'@[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text=re.sub(r'&[\w/:%#\$&\?\(\)~\.=\+\-…]+', "", text)
    text=re.sub(';', "", text)
    text=re.sub('RT', "", text)
    text=re.sub('\n', " ", text)
    return text

if __name__ == '__main__':
    main()

取得後のテキストのフォーマットは以下の様に、見出しに__label__*と分類用のラベルを付けています。
この場合、__label__1に「美容系」、__label__2に「エンタメ系」、__label__3に「暮らし系」を設定しています。 分類ごとにCLASS_LABELとキーワードを変えて実行します。

__label__1,美容 室 の カラー と 自宅 で の カラー リング の 違い と は 。 。 。
__label__1,はちみつ に は 、 優れ た 保 湿 力 が あり ます 。 美容 に 優れ た 効果 が あり ます 。
__label__2,【 名 台詞 】 「 あきらめ たら そこ で 試合 終了 です よ … ? 」 某 人気 漫画 より
__label__2,主 に アニメ の 絵 や 漫画 を 描い たり 、 ブログ など の 活動 を し て い ます 。 pixiv で 漫画 を 投稿 し 、 ブログ で 発表 を 繰り返し て ます 良けれ ば 話しかけ て ください
__label__3,うち の ペット の 犬 が 食後 に 家 の 中 で 穴 を 掘ろ う と する ん だ けど 、 どうして だろ う 。
__label__3,もうすぐ 修学旅行 ! 高校 生活 の ひとつ で ある 行事 楽しん で き ま ー す

今回は約3000件*3の投稿内容を用意しました。
上記のコードでは一度に100件までしか取得できないため、
何回か時間を置いて繰り返し実行してデータをためてください。

取得直後のテキストはカテゴリごとにバラバラの状態なので、CATコマンドで結合させましょう。

$ cat __label__1.txt __label__2.txt __label__3.txt > model.txt

テキストからモデルを生成する

取得した「model.txt」からfastTextのモデルを生成します。
生成は以下のコードで行います。変換自体は1行で完結します。

learning.py

import sys
import fasttext as ft

argvs = sys.argv
input_file = argvs[1]
output_file = argvs[2]

classifier = ft.supervised(input_file, output_file)

input_fileに学習用のテキストファイル名、 output_fileに生成後のモデル名を設定します。

$ python learning.py model.txt model

生成が終わるとmodel.binというモデルが出力されます。


文章の分類

fastTextにモデルと新たに判断したいテキストを渡すと、 判定結果の分類とその判定が下される確率が取得できます。
判定結果が入力した文章に近いほど確率の値が高くなります。

prediction.py

import sys
import fasttext as ft
import MeCab

class predict:

    def __init__(self):
        # モデル読み込み
        self.classifier = ft.load_model('model.bin')

    def get_surfaces(self, content):
        """
        文書を分かち書き
        """
        tagger = MeCab.Tagger('')
        tagger.parse('')
        surfaces = []
        node = tagger.parseToNode(content)

        while node:
            surfaces.append(node.surface)
            node = node.next

        return surfaces


    def tweet_class(self, content):
        """
        ツイートを解析して分類を行う
        """
        words = " ".join(self.get_surfaces(content))
        estimate = self.classifier.predict_proba([words], k=3)[0][0]

        if estimate[0] == "__label__1,":
            print('美容系', estimate[1])
        elif estimate[0] == "__label__2,":
            print('エンタメ系', estimate[1])
        elif estimate[0] == "__label__3,":
            print('暮らし系', estimate[1])


if __name__ == '__main__':
    pre = predict()
    pre.tweet_class("".join(sys.argv[1:]))

実行結果

以下のようにコマンド入力し最後に判定するテキスト内容を貼り付けて実行します。

$ python prediction.py "判定するテキスト"

試しにTwitterから学習に使用していない適当なツイートで判定を行います。

python prediction.py "化粧水・乳液・美容液がひとつになった基礎化粧品が本日発売開始"
美容系 0.998047
python prediction.py "10月○日より劇場版公開予定!東京で舞台挨拶が行われました"
エンタメ系 0.654297
python prediction.py "お買い得な生鮮食品を毎日お届け。格安情報はこちら。"
暮らし系 0.8125

結構いい感じに分類してくれている気がします。
また取得した投稿3000件*3を学習用、100件*3を検証用に別で用意し実験したところ、 96.333%の精度で分類できました。*2


分類が難しい点

試していく中で分類がうまくいってないところがあったのでご紹介します。

のび太「あったかいふとんでぐっすりねる。こんな楽しいことがほかにあるか。」
(エンタメ系 0.443359 暮らし系 0.439453 美容系 0.115234)
エンタメ系も入ってますが、かなり暮らし系寄りですね。
登場人物のセリフとかになると分類はかなり難しくなりそうです。
またこのセリフにはありませんが健康などについて触れると、美容系も高くなってきます。

メジャーリーグでも活躍した某野球選手が美容整形をカミングアウト!スタジオは大騒然!
(美容系 0.996094 エンタメ系 1.95313e-08 暮らし系 1.95313e-08)
こちらは美容整形という部分に大きく反応して美容系に寄ってしまってます。
学習に使用したテキスト次第で特定のキーワードが強くなりすぎてしまうのかもしれません。


まとめ

fastTextを使って文章を「美容系」、「エンタメ系」、「暮らし系」に分類しました。
文章の分類だけでなくネガポジ判定や特定の単語に似たワードを抽出するなどにも使えるので、活用の幅は多そうです。
思いの外、簡単に実装できたのでチャレンジしてみてはいかがでしょうか!

*1:公開されているとはいえ一般の方のツイートを使うのは抵抗があったため記事内のツイートは一部架空のものを使用しています。

*2:学習用データと検証用のデータの取得方法が同じ場合での結果なので、実際に運用した場合の精度はもっと下がると思われます。

Core MLを利用した機械学習とVisionでの画像認識

先日リリースされたiOS 11でCore MLとVisionフレームワークが新たに追加されました。
これはWWDC 2017で発表されたもので、Core MLはiOS上で機械学習の学習モデルを利用するための仕組みで、Visionは画像認識に関する機能を集めたフレームワークです。
これらの機能でどんなことが出来るのか紹介します。

Core MLでできること

学習モデルをiOS上で取り扱うための仕組みです。
学習モデルに何かしらの判断をさせたいデータを渡し、その結果を受け取るための橋渡しをしてくれます。
クラウドやサーバー上ではなく、iOS端末内で機械学習モデルを利用します。
あらかじめ用意した学習モデルを端末内で利用するもので、学習モデルの再学習をするものではありません。

学習モデルをiOSアプリで利用するには

coremltools」というPythonで動作するツールが用意されています。
Xcode上で学習モデルを利用するには「.mlmodel」という形式に変換する必要があり、coremltoolsでその変換を行います。

coremltoolsが対応している学習モデルの種類

Model Family Supported Packages
Neural NetworksKeras (1.2.2, 2.0.4+)
Caffe 1.0
Tree EnsemblesXGboost (0.6)
scikit-learn 0.18.1
Generalized Linear Modelsscikit-learn (0.18.1)
Support Vector MachineslibSVM (3.22)
scikit-learn (0.18.1)
Feature Engineeringscikit-learn (0.18.1)
Pipelinesscikit-learn (0.18.1)
coremltoolsの環境構築

大まかな手順は以下の通りです。

  1. virtualenvでPythonが動く環境用意する。
  2. pip install -U coremltoolsでツールをインストールする。
  3. coremltoolsを利用するために必要な下記のツールをpipでインストールする。
    ・numpy (1.12.1+)
    ・protobuf (3.1.0+)
    ・Keras (1.2.2+, 2.0.4+)
    ・Tensorflow (1.0.x, 1.1.x)
    ・Xgboost (0.6+)
    ・scikit-learn (0.15+)
    ・libSVM

詳細なインストール手順はcoremltoosのドキュメントを参照してください。

変換の例(.h5 → .mlmodel)

TensorFlowとKerasで作成した犬と猫の画像をどちらか判別する学習モデル「model.h5」をXcodeで扱える形式「convertedmodel.mlmodel」 に変換する例です。
以下の様なPythonコードをターミナルで実行します。
.mlmodelに変換されたファイルをXcodeのプロジェクト内に取り込むことができます。

from keras import backend as K
from keras.datasets import mnist
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import Dense, Flatten, Convolution2D, MaxPooling2D
from keras.optimizers import Adadelta
import keras

import coremltools
path = 'model.h5'

# kerasで作成した学習モデルの変換をするメソッド。
# 入力を画像としています。入力した画像が”dog”と”cat”の2種類のラベルで分類されます。
coreml_model = coremltools.converters.keras.convert(
    path, 
    image_input_names='image', 
    is_bgr = True, 
    image_scale = 1.0, 
    predicted_feature_name = "label", 
    class_labels = ['dog', 'cat'])

# 変換後のファイルを出力する
coreml_model.save('convertedmodel.mlmodel')

coremltoolsのドキュメント
https://developer.apple.com/documentation/coreml/converting_trained_models_to_core_ml

Visionでできること

主に画像認識の機能を備えたフレームワークです。
機能としては、以下になります。

  • Face Detection and Recognition(顔の検出)
  • Machine Learning Image Analysis(機械学習での画像分析)
  • Barcode Detection(バーコード認識)
  • Image Alignment Analysis(画像の位置合わせ)
  • Text Detection(文字認識)
  • Horizon Detection(地平線認識)
  • Object Detection and Tracking(物体の移動トラッキング)

それぞれどういった事ができるのか、説明します。

Face Detection and Recognition(顔の検出)

カメラやイメージピッカーで選択した写真の中から人の顔を検出し位置・範囲を特定します。
ランドマーク(目、鼻、口)の位置の検出も可能です。 f:id:HidehikoKondo:20170928192044p:plain f:id:HidehikoKondo:20170928192100p:plain

Machine Learning Image Analysis(機械学習での画像分析)

別途作成したラベル付き画像を学習させたモデルに、画像を認識させ、モデルの学習内容に応じて分類します。
この記事の後半で実装例を紹介します。

Barcode Detection(バーコード認識)

QRコードなどバーコードの読み込みます。 AVFoundationフレームワークにバーコードを読み込む機能はありましたが、同時に複数のバーコードを認識できるなどの違いがあります。

Image Alignment Analysis(画像の位置合わせ)

アフィン変換、ホモグラフィック変換、画像レジストレーションなどの画像処理をしてくれるクラスです。
写真をつなぎ合わせてパノラマ写真の様な画像の作成などができます。
f:id:HidehikoKondo:20170928192340p:plain

Text Detection(文字認識)

画像内の文字を認識し、その位置と範囲の特定をします。
1文字毎と文節毎で位置と範囲の特定ができる。日本語でも認識することが可能です。
※認識した文字が「A」とか「あ」であるとか、OCRの様な機能ではありません。それを実現するには追加で実装が必要となります。 f:id:HidehikoKondo:20170928191958p:plain

Horizon Detection(地平線認識)

風景などの写真でどのくらい傾いているのかを取得できます。
取得した値を使うことで、傾いている写真を回転させて、地平線に対して水平になるように補正できます。 f:id:HidehikoKondo:20171002162340p:plain

Object Detection and Tracking(物体の移動トラッキング)

カメラで撮影している被写体の移動を検知し、位置をトラッキングします。 f:id:HidehikoKondo:20170928192401p:plain

Machine Learning Image Analysisの利用例

ここからは「Machine Learning Image Analysis」の実装例を説明します。
XcodeでVGG16学習モデルを取り込み、iOSアプリで写真に写っている物体が何であるかの認識をさせてみましょう。

動作環境
  • iOS11をインストールしたiOS端末
  • Xcode 9
  • Swift 4.0
  • VGG16学習モデル(.mlmodelに変換したもの)
学習モデルの入手

coremltoolsを使うことで、自分で作成した学習モデルを扱うこともできますが、
今回はお手軽にAppleが公開している.mlmodelに変換済みの学習モデルを用います。
下記のURLからVGG16の学習モデルをダウンロードしてください。
https://developer.apple.com/machine-learning/ f:id:HidehikoKondo:20170929125652p:plain

学習モデルの取り込み

ダウンロードした「VGG16.mlmodel」をXcodeにドラッグ&ドロップします。
Target Membershipのチェックボックスを忘れずにチェックしておきます。
Model Evaluation Parametersの欄を見ると、学習モデルの入力が画像で、出力は文字列でラベルが返ってくることがわかります。
f:id:HidehikoKondo:20170929142843p:plain

コード
import UIKit
import CoreML
import Vision
import ImageIO


class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    @IBOutlet weak var cameraView: UIImageView!
    @IBOutlet weak var resultLabel: UILabel!

    var inputImage: CIImage!

    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }

    func imagePickerController(_ imagePicker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        self.resultLabel.text = "Analyzing Image…"
        if let pickedImage = info[UIImagePickerControllerOriginalImage] as? UIImage {
            self.cameraView.contentMode = .scaleAspectFit
            self.cameraView.image = pickedImage
        }
        imagePicker.dismiss(animated: true, completion: {
            guard let uiImage = info[UIImagePickerControllerOriginalImage] as? UIImage
                else { fatalError("no image from image picker") }
            guard let ciImage = CIImage(image: uiImage)
                else { fatalError("can't create CIImage from UIImage") }
            let orientation = CGImagePropertyOrientation(rawValue: UInt32(uiImage.imageOrientation.rawValue))
            self.inputImage = ciImage.oriented(forExifOrientation: Int32(orientation!.rawValue))

            //リクエストハンドラの作成。ここでカメラで撮影した画像を渡します。
            let handler = VNImageRequestHandler(ciImage: self.inputImage)
            do {
                try handler.perform([self.classificationRequest_vgg])
            } catch {
                print(error)
            }
        })
    }

    //VGG16学習モデルで画像を認識させるリクエストの作成
    lazy var classificationRequest_vgg: VNCoreMLRequest = {
        do {
            var model: VNCoreMLModel? = nil
            model = try! VNCoreMLModel(for: VGG16().model)
            return VNCoreMLRequest(model: model!, completionHandler: self.handleClassification)
        } catch {
            fatalError("can't load Vision ML model: \(error)")
        }
    }()

    //学習モデルが持っているラベルに応じて分類した結果の表示。分類結果の1個目(一番confidenceが高いもの)を表示します。
    //分類結果はVNClassificationObservation型のオブジェクトで返ってきます。
    func handleClassification(request: VNRequest, error: Error?) {
        guard let observations = request.results as? [VNClassificationObservation]
            else { fatalError("unexpected result type from VNCoreMLRequest") }
        guard let best = observations.first
            else { fatalError("can't get best result") }

        DispatchQueue.main.async {
            var classification: String = (best.identifier);
            self.resultLabel.text = "Classification: \"\(classification)\" Confidence: \(best.confidence)"
        }
    }

    @IBAction func openCamera(_ sender: Any) {
        let sourceType:UIImagePickerControllerSourceType = UIImagePickerControllerSourceType.camera
        if UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.camera){
            let cameraPicker = UIImagePickerController()
            cameraPicker.sourceType = sourceType
            cameraPicker.delegate = self
            self.present(cameraPicker, animated: true, completion: nil)
        } else {
            print("error")
        }
    }

    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion: nil)
    }
}

storyboardでは撮影した写真用のUIImageView・結果表示のラベル・カメラ希望のボタンを配置して、IBOutletとIBActionの接続をします。

f:id:HidehikoKondo:20170929150243p:plain

カメラでAppleのMagic Mouseを撮影してみました。
ちゃんとマウスとして認識されていますね。

f:id:HidehikoKondo:20170929150937p:plain

まとめ

このようにCore MLで学習モデルをiOSアプリで扱うことが出来るようになりました。
iOSアプリで学習モデルと扱うのにサーバーとの通信が必要なくなったことで、モバイル環境でも利用しやすくなります。
また、Visionフレームワークで今までは困難だった画像認識も手軽に行うことが出来るようになります。
iOSアプリで学習モデルが扱えることや、画像認識の機能を駆使することで、新たなアプリのアイデアやサービスが出てくるのではないでしょうか?
今後もモバイル向けに提供される新機能の動向を見ていかねばと思います。
次回以降の記事で顔認識などの機能の実装例も紹介できればと思っています。
お楽しみに。

ワンダープラネットにおける Zoom の導入背景と使い方

こんにちは、CTO村田です。
初回はどうしてもオンライン会議サービスの Zoom について書きたいので、書きます! zoom.us

長くなるので、3回に分けて書きます。

  1. Zoom の導入背景と簡単な使い方
    → Zoom ってなに? Zoom 聞いたことあるけど実際どうなの?という方向け
  2. Zoom をもっと楽しく、便利に使う方法
    → Zoom 使っているけど、もっと便利に使いたい方向け
  3. Zoom を便利に使う方法の実装方法
    → ここは主にソースコードが並びますので、完全にエンジニア向け

今回は「Zoom の導入背景と簡単な使い方」です。

「ちょっとZoom良いですか?」

ワンダープラネットでは、オンライン会議はかなりライトに行われています。

特にゲーム開発においては、Slackや資料を通じた文字や絵だけでは伝わらないニュアンスも多く、名古屋本社と東京オフィスを跨ぐ場合にはオンライン会議を用いて相手に伝えています。
伝わりにくいなと思ったら Slack で「ちょっと Zoom 良いですか?」と誰もが気軽に行います。
場合によっては、名古屋本社と東京オフィスを常時接続しているモニターの前に立って「〇〇さん、いますかー?」ではじまります。

最初は仕様確認などのやり取りが多かったですが、今では

  • 全体会議
  • 面接、面談
  • 外部の方を交えた会議

など、多岐にわたってオンライン会議が利用されております。

という訳で、ワンダープラネットにおいては無くてはならないオンライン会議。なぜ Zoom を選んだのか...

Zoom は「簡単」「高品質」「安定」

Zoom を使う前からオンライン会議は行われていましたが、以下の問題がありました。

  • 途中で声が途切れる
  • 画面共有が上手く反映されない
  • 会議が突然終わる

結果、会議自体が思うように行かず、無駄な待ち時間の発生や参加者のイライラなどに繋がりました。
会議の内容以前に、良い会議とは言い難いですよね。

そのような状況の中で出会ったのが Zoom です。
試した結果、以下のポイントの評価し、採用に至りました。

  • 簡単: 参加者の招待が簡単、画面共有が簡単で遅延も少ない
  • 高品質: 音声と映像がクリア
  • 安定: 音声や映像が途切れることはなく、アプリの挙動も安定
  • Mac と Windows の両方に対応

もともと「不安定なオンライン会議を何とかしたい!」から はじまりましたので、まず普通に会話できるようになっただけでも満足でした。
それに加えて、画面共有や参加者の招待、サービスそのものの利用が簡単だったのも大きいです。

ここからは、どのように Zoom を利用しているのか、導入初期と現在に分けて書いていきます。

Zoom を導入した頃の使い方

導入初期の頃は、名古屋本社と東京オフィスの間で以下のように使うことが多かったです。

  • 仕様確認などのメンバー同士(1 対 1)の会議
  • 定例的な会議

この頃は参加者も多くはなかったので、以下の構成で Zoom を利用していました。

多少円滑になるように会議室には「カメラ」「マイク/スピーカー」「有線 LAN」を繋いだ USB ハブを置きました。
そして会議のたびに、各自の PC をその USB ハブとディスプレイに繋いではじめるというスタイルです。
Skypeをする感覚と同じですね!

Zoom Client の注意点

無料版と有料版があります、 無料版は、1対1のミーティングでは時間制限がありませんが、3名以上(グループミーティング)で行う場合は40分を経過すると切断されます。
よくグループミーティングのホストをするメンバー*1には、 Pro 版を購入していました。 Plans and Pricing - Zoom

Zoom Client を使ったオンライン会議のはじめ方

※ 今回は、アカウントの作成・ログインやアプリのダウンロードは省略します。

1.Zoom Client を起動
現在では日本語にも対応しているので、アプリの操作がより簡単になったと思います。
f:id:tomo-murata:20170925154219p:plain

2.「ビデオありで開始」または「ビデオなしで開始」で開始
相手の表情が見えた方が話しやすいので、通常「ビデオありで開始」しています。

開始直後にこのようなダイアログが表示されることもありますが、音声を使う場合は「コンピューターでオーディオに参加」を選択で問題ありません。毎回選択が面倒な場合は、ダイアログ下のチェックボックスにチェックしてください。
f:id:tomo-murata:20170925154536p:plain

3.参加者を招待するURLを取得

こちらの「招待」から行います。
f:id:tomo-murata:20170925154931p:plain

ダイアログが表示されますので、「URLのコピー」を選択するとクリップボードにコピーされます。
f:id:tomo-murata:20170925155215p:plain

4. 参加者にURLを送りつけ共有
コピーしたURLをSlackで共有しましょう! f:id:tomo-murata:20170925155623p:plain

5. 参加者はURLをクリック
URLをクリックするとブラウザが立ち上がり、Zoom Client がインストールされていれば Zoom Client が起動し対象の会議に入ります。インストールされていない場合は、対象の会議に入れるように導いてくれます。

Zoom と Slack を連携させている場合はもっと簡単!

Zoom は Slack との連携が可能です。 Setting Up Slack Integration – Zoom Help Center

連携させると /zoom という Slash Commands が追加されます。
f:id:tomo-murata:20170925160824p:plain

/zoom を実行すると、Slackに以下の内容が出力されます。
参加者は「Click here to join」をクリックするだけで済んじゃいます。

f:id:tomo-murata:20170925160921p:plain

コマンドで済むあたりが、エンジニアからしたら超便利ですね!!

しかし、Zoom Client の限界...

ただ、この運用にも限界がやってきました。
何が発生したかと言いますと...

  • 人によっては Zoom 開始までの準備にもたつく
  • オンライン会議が不安定になり、音声や画像の質が下がるときがある

各自のPCを使っていたことが、主な原因です。

基本的に開発者のPCは、重いアプリを複数起動しています。
Unity、Xcode、Photoshop、Mayaなどなど...

どのオンライン会議サービスが良いかを色々調査した結果、Zoom が新しいサービスをリリースしていた事に気づきました。

それが、 Zoom Rooms です。

現在の Zoom の使い方

現在では、 Zoom Rooms を中心としたオンライン会議になっております。
全会議室に Zoom Rooms を導入しておりますので、 Zoom Rooms を使いたくても使えないという状況は発生しません!

Zoom Rooms とは

カンファレンスタイプの Zoom です。 zoom.us

料金は、1ルームにつき月額 $49.00 です。
他社のオンライン会議サービスと比較しても、お手軽な金額です。

Zoom Rooms を利用するには Zoom Rooms 用に機材を用意する必要があります。

  • カメラ
  • マイク/スピーカー
  • タブレット
  • Mac/PC

タブレット!? と思うかもしれませんが、このタブレットの存在が良いんですよ!!

なぜなら、Zoom Rooms はタブレットを使って操作するからです!タブレットの操作例です。

会議をはじめるとき
相手の会議室を選択し「Meet Now」をタップするだけで、オンライン会議がはじまります。*2
f:id:tomo-murata:20170925223250p:plain

会議をはじめたあと
ミュートやビデオ、カメラの操作までできます。もちろん会議の終了も!
f:id:tomo-murata:20170925223558p:plain

タブレット上で行う操作は、すごくシンプルです。
オンライン会議前の準備にもたつくという事も、これならありません。

ちなみに参考までに、ワンダープラネットでは タブレットと Mac/PC は、これらを選択しております。

  • タブレット:中古の iPad mini
  • Mac/PC:Mac mini(256GBのSSDにカスタマイズ)

Apple信者と言われそうですが、 Mac mini にすることで便利な機能が使えます。*3

Zoom Rooms を使ったオンライン会議のはじめ方

ワンダープラネットでは、2パターンの Zoom Rooms の開始方法があります。

さきほどのタブレットの操作例で出したのは、Invite(招待)方式と呼んでおります。
招待先の部屋に誰か人がいる場合に用います。招待された部屋は、Answer(応答)/Decline(拒否)をするまで呼び出し音が鳴り続けるので注意です。
f:id:tomo-murata:20170925230334p:plain

もう1つがJoin方式です。
ホストから招待するのではなく、立ち上がっているホストに乗り込むイメージです。
この方式は、相手の部屋に人がいるかどうか分からない場合に用います。 f:id:tomo-murata:20170925231402p:plain

Join方式で会議をはじめるとき
ホスト側: 単に「Meet Now」をタップするだけで、立ち上げておく。
f:id:tomo-murata:20170925231726p:plain

ゲスト側: 「Join」メニューから、入りたいMeeting IDを入力し「Join」をタップする。
Zoom における Meeting は、必ず11桁の ID を持っています。Zoom Client、Zoom Rooms に限らず持っています。
f:id:tomo-murata:20170925232134p:plain

PCにある資料を共有して映したいとき
ここで、Zoom Client の登場です。 各自のPCにインストールした Zoom Client から Zoom Rooms のホストにJoinします。
あとは画面共有をするのみ!ここは以前から慣れていた操作がそのまま使えます。

f:id:tomo-murata:20170927135455p:plain

ちょっとした工夫

Meeting ID がたびたび出てきますが、このIDが毎回異なるとZoom Rooms からホストへの Join やZoom Client からの画面共有時が大変です。「IDは何ですかー?」と毎回確認しなければなりません。

ワンダープラネットではIDを確認しなくても済むように、各Room の Meeting ID はある命名規則に沿って設定してます。そして「Meet Now」した時は、設定した Meeting ID でルームを作成するように設定しております。

とは言え、全部屋のMeeting IDを全員が覚えているかとそうではありません...

Slack に 部屋名の Slash Commands を作成し、実行するとMeeting ID と URL を知らせてくれるものを作成しました。URL をクリックすると、Meeting に入った状態で Zoom Client が立ち上がるので、画面共有時に大変便利です!

f:id:tomo-murata:20170925233746p:plain

まとめ

Zoom Client も良かったですが、Zoom Rooms に変えたことで 安定性・利便性が格段に上がり快適になりました。
ワンダープラネットでは無くてはならいサービスです!

Zoom Rooms について書かれたものは少ないので、この記事をきっかけに知っていただけると嬉しいです。

ざっと使い方も混ぜて書きましたが、ほんの一例です。より便利な使い方も書きますので、お待ちください!

*1:ホストのみ Pro 版を購入していれば大丈夫です。

*2:ワンダープラネットの会議室は、全て名古屋メシに由来します:笑

*3:Mac mini にすることで、どう便利なのかは次回書きます。

Tech Blog としてリニューアル!

リニューアル後、1エントリー目を担当するCTO村田です。

今回は、ブログのリニューアルにまつわる内容を書きたいと思います。

Tech Blog までの変遷

ワンダープラネットのTech系ブログは「エンジニアブログ」ではじまり、「デベロッパーブログ」としてリニューアル、
そして今回の「Tech Blog」へのリニューアルという変遷を経ています。

今まで『誰をターゲットに、どのような内容を発信していたのか』は、以下になります。

エンジニアブログ
  • 発信者
    • 全エンジニア
  • 対象
    • エンジニア
  • 内容
    • エンジニアが開発中に困り調査 / 解決したこと
    • 新しい技術の調査内容
デベロッパーブログ
  • 発信者
    • エンジニア、デザイナー、プランナー、QA、CS
  • 対象
    • モバイルゲーム開発に携わっている方
    • モバイルゲーム開発に興味を持たれている方
  • 内容
    • モバイルゲーム開発に関する技術情報
    • モバイルゲーム開発での取り組みや改善内容

デベロッパーブログにしたことで発生した問題

デベロッパーブログにしたことで発信する内容の幅は広がりましたが、以下の問題も発生しました。

  • 寄稿者を広げすぎた結果、記事のレベルがまちまちになってしまった。
  • エンジニアブログの時に比べて技術情報の発信が減った結果、創業当初の「技術の会社」感が薄れた。

こういった背景があり、今回「テックブログ」としてリニューアルしました。

Tech Blog で変わったこと

「Tech Blog」の概要は、以下のとおりです。

  • 発信者
    • アドバンストテクノロジー部のメンバー
    • CTO室のメンバー
  • 対象
    • エンジニア
  • 内容
    • 研究開発で利用している技術的な内容
    • 他社サービスの利用事例
    • その他ゲーム開発におけるテクニカルな内容

Tech Blog では、その名の通り “技術” に寄せた内容にしました(戻しました)。

発信者も以前のエンジニアブログの時から少し変わり、CTO管掌の部署とチームのメンバーにしました*1
それぞれの部署、チームがどんな構成かと言いますと、

  • アドバンストテクノロジー部
    会社のプロダクトに対して新しい技術で付加価値を提供するR&Dチームと、最新サービスを活用し社内の働き方や業務フローを改善するInternal IT*2 チームが所属する部署。
  • CTO室*3
    エンジニアを横串でサポートし、会社全体の技術的な駆け込み寺となる少数精鋭のエンジニアチーム。

以上のメンバーが Tech Blog を通して、技術的なこと、最新サービスの活用方法を毎週発信していきます!

エンジニアにとっては読み応えがあり、毎週の更新が楽しみにしていただける内容をCTOが責任を持って届けて参りますので、これからもチェックしてください!!

*1:もちろん上記以外からの立候補も受け付けています。

*2:よく言われる社内SEです。一般的な社内SE業務も担当します。

*3:CTO室については、別途活動内容を記載できればと思います。