外国の方とのZoomリモート会議に参加したよ、多国語会議で共有メモの翻訳を半自動化してみたよ：（Web翻訳サービスとRPA Sikulixを使ってZoomホワイトボード上でやってみました）

最近、ベトナムの方と、私たちのチーム(日本人)とで、日本語やベトナム語が入れ替わり話される会議に参加してきました。

ざっとですが、以下のイメージです。

![current-state-1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/4c363f3d-121f-e318-a359-ce80fb223259.jpeg)


このような形でコミュニケーションをしていましたが、以下のようなうまくいかない面や、色々と改善の余地がありそうなことがありました。

* 議論をしようとすると、なかなか難しい。言葉が通じないことがある。
* 通訳の方が、ベトナムの開発チームの方達に、ベトナム語で説明するのに時間がかかる。そして、我々日本人は何を話しているのかわからないので、ちょっと不安になる。



上記に対し、色々な改善方法があるかと思いますが、、、
今回は、可視化といいますか、目に見えるようにする方面を強化、ということで、
Zoomのホワイトボードに共有メモを日本語でテキストで記載していき、英語に翻訳して表示する、という改善の試作をやってみました。

よい会議のやり方に、ホワイトボードに議論する内容や決定事項を記載して進めるとよい。という話を聞きますので、そういった対応です。


簡単ですが、百聞は一見に如かず、ということで、以下のbefore/afterのイメージを参照ください。

* beforeのイメージ
![rpa_web_translate_before.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/bf723bf1-96b6-52a5-ee58-dcd34aa503b4.jpeg)

* afterのイメージ
![rpa_web_translate_after.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/f3038f07-eb28-b51b-269a-b5181ddb6334.jpeg)



最初は、このようなことをやる価値があるかを検証するため、自分の手作業で、会話の内容から日本語の内容をホワイトボードに列挙して、翻訳ツールで翻訳して英語をホワイトボードに貼り付けてみました。2回ほど。
（私があまり話さなくてよい会議でしたので、比較的やりやすかったです。）

実際にやってみた後、役に立ちそうかどうかをベトナムの方に聞いてみたところ、「いいですね。」か「助かります。」といった返答だったので、
社交辞令かもしれませんが、深く追求せずに半自動化もやってみました。

半自動化までやってみた結果としては、手動でやっていた操作の一部を、自動でやってくれるので、自分自身としても助かりました。会話の内容に集中できます。
ただ、たまに操作ミスが出たりするのは、RPAならでは、という感じかと思います。やはり完全ではないですね。
（私自身の操作や日本語列挙も完全ではないので、RPAと私、お互い様で似たようなものかも）



半自動化のために使ったツールはsikulixです。
使い方は、インターネット上の情報を色々と参考にさせてもらい実施しました。

特に、以下の記事を参考にさせていただきました。
（いつも色々な情報を共有いただいている皆々様に感謝感謝です。お陰様で助かっています。ありがとうございました。）

* [無料RPA「sikuli」とは？業務自動化・働き方改革へ３つの導入メリットとデメリット](https://auto-worker.com/blog/?p=101#)
* [SikuliXでRPAをやるために覚えたこと](https://qiita.com/hirohiro77/items/a91f16662091618f2794)
* [SikuliはRPAとして使えるのか？【結論：条件付きで使えます】](http://marukentokyo.jp/2019/05/12/sikuli_rpa/)
* [Table of Contents &mdash; Sikuli X 1.0 documentation](http://doc.sikuli.org/toc.html)

sikulixは、RPAでも無償で利用できるので、敷居が低くてよいですね。
レコーディング機能はないので、Pythonでプログラミングする必要はありますが、レコーディングできるようになれば、もっと敷居が低くなりそうです。


今回の内容の前提として、以下がありますので、ご注意ください。

 * 構成
  * PC
     * OS: Windows 10 64bit
     * SW
         * [Sikulix](http://sikulix.com/)（RPA）2.0.5-win
         * [Amazon Corretto](https://aws.amazon.com/jp/corretto/)（Java、Sikulixが依存）11.0.13.8
         * [Google Chrome](https://www.google.com/intl/ja_jp/chrome/) 96.0.4664.45
 * 2021年10月下旬～11月中旬までに調べたり動作確認した内容です。


では、以下の各章の流れでやったことを順に紹介させてもらいます。
まずは手動でやってみて、次に自動化してみる、という流れです。



#1. 手動で翻訳を実行した内容

Zoomのホワイトボードを使って会議内容を共有しました。

以下が操作の流れになります。

+ 会議開始前の事前準備
  + ZoomClientアプリでZoomミーティングに接続
  + Zoomのホワイトボードを共有
  + ChromeブラウザでWeb翻訳を開き、日本語から英語に翻訳をするよう設定
    + 翻訳は社内のWeb翻訳サービスを活用しました。（この部分は、社内情報のため公開できません。ご了承ください。）
+ 会議中
  + 会議内容をテキストで追加
  + タイミングよいときにテキストをWeb翻訳で翻訳し、翻訳した内容を右側に追加
  + 上記を繰り返す


やってみた感触としては以下になります。

* ちょっと大変
  * 会話の聞き取り、内容を日本語でタイピングする、タイピングした日本語をWeb翻訳して英語を張り付けるのが、ちょっと忙しい
  * たまに、聞き逃したり、会話の内容がわからなくことがある
* 日本語のテキストの共有や、翻訳の共有もリアルタイムより遅い（数秒～数十秒）
  * 議論が難航した際の補助的な支援程度
  * 会議終わった後の議事メモになる程度



以下は、参考までに、細かい内容の補足です。

* Zoomホワイトボードについて
  * Zoomの画面共有の機能で、ホワイトボードを共有します。
     * 共有したユーザは、テキストの追加から移動・編集と色々できます。
     * ただ、他のユーザは、「コメントを付ける」機能で、テキストやハートのアイコン等を追加できますが、追加されたテキスト等のオブジェクトを移動や編集はできませんでした。
共有しているユーザからホワイトボードの画像が送られてきているような感じです。

という感じで、共有していない他のユーザは、既存のテキストを選択してコピーすることができません。翻訳するテキストのコピーペーストができません。
なので、ホワイトボードを共有しているユーザが、既存のテキストを翻訳する必要がありました。



* 翻訳ツールについて
  * 社内のWeb翻訳を活用
     * ChromeブラウザでWeb翻訳を開いて、日本語から英語に翻訳する設定をしておきました。
  * 将来の参考
     * 今後は、翻訳API等を活用して音声データをそのまま翻訳させたいですが、色々な翻訳APIがあるので、向いている翻訳ツールを活用するのがよさそうです。以下の話を聞いたことがありますので、状況に応じて選べばよさそうですね。
         * ベトナム語は、Azureが良い。
         * 英語は、AzureやGoogleも同じぐらい。
         * 日本語は、GoogleもAzureも同じぐらい。（ただ、インターネット上ではGoogleの方がよい、という話もありました。）



#2. sikulixで翻訳を自動化

上記の手動操作をRPAのsikulixで半自動化しました。

自動化でなく、半自動化というのは、完全な自動化ではないからです。
sikulixの開始ボタンを押すと、日本語が英語に翻訳されていく感じです。無限ループさせると自動化になりそうですが、予期せぬ変な挙動になるかもしれません。


試作するにあたり、工夫した点は以下になります。

* 翻訳するテキストの検出
  * 翻訳するテキストには、行頭に赤い★★（星マーク２つ）を付ける。
     * 書きかけの最中に翻訳されるといけないので、テキストを書き終えた最後に★★を付ける。
     * 英語に翻訳したテキストや、まだ翻訳したくない書きかけのテキスト等、sikulixによる検出対象を区別できる。
  * 翻訳後に、翻訳対象と翻訳結果の★★を削除する。（再度翻訳されないために）
* 負荷の低減や処理の速度を速める。
  * できるだけ画像処理の範囲を小さくする。Regionの範囲を狭くする。
     * 動作前に、Regionを設定。
        * Zoomホワイトボードや、Zoomホワイトボードの操作ツール（選択やテキスト）のみにする。
        * Web翻訳のウィンドウ範囲のみにする。
* 翻訳したテキストのホワイトボード上の配置位置
  * 翻訳対象のテキストの場所から、右に500ピクセル移動した場所に配置する。
     * 逆に翻訳対象のテキストは、ホワイトボード上の左側に記載しておく。


今回、特に気を付けたのは、処理速度です。
会議中の話している最中に結果を表示してほしかったので、RPAでどこまでいけるかわかりませんが、できるだけ早く動いてほしいと思いました。

画面全体や広い範囲を対象にすると、検出やボタンクリック等の操作が汎用的になりますが、遅くなります。
なので、固定の場所で、しかも狭い範囲内を対象にさせました。
（RPAでこういうやり方もあるのかと、やりながら気づくことができました。もっといろいろなユースケースで使えそうな感触を持ちました。）



#3. sikulixのコード

参考までに、今回試作したコードが以下になりました。
最初にこれから実装することをコメントアウト内に列挙しています。

コード自体は、まだまだ入門者であること、お試しなので最低限動くことを優先、実験的コードやTODOも記載したまま、といった内容ですので、ご了承ください。


<div>
<pre><code>
# *****　最初に検討した流れ start　*****
# zoomホワイトボード　テキスト　抽出
# 検出対象を記号で入れておく
# 　例：赤い★
#"translate_target_icon_1.png"
# 処理の流れ
# Zoomホワイトボード内の対象をサーチ
# 結果の閾値以上の対象を順に以下を処理
# 　Zoomのホワイトボードの選択"zoom_whiteboard_tool_select_icon.png"をクリック(クリック後は青と黄"zoom_whiteboard_tool_select_on_icon.png"になる)
# 　検出対象までマウス移動
# 　ダブルクリック
# 　Ctrl+aですべてを選択
# 　Ctrl+c、または、コピー
# 　ChromeのWeb翻訳をアクティブに（事前に開いておく）
# 　　Sikulixで探してクリック、または、PowerShellで実行
# 　翻訳入力欄にペースト
# 　翻訳結果をコピー
# 　Zoomホワイトボードにコピーする
# 　　Zoomのホワイトボードのテキスト"zoom_whiteboard_tool_text_icon.png"をクリック(クリック後は青と黄"zoom_whiteboard_tool_text_on_icon.png"になる)
# 　　検出対象と同じ高さ位置(または横位置)で、一定のオフセット位置移動
# 　　マウスをクリックしてテキスト入力
# 　　コピーして貼り付ける
# 　今回の検出対象を削除
# 　　今回の検出対象の位置に移動
# 　　クリックしてテキストを選択
# 　　ダブルクリックして検出対象を選択
# 　　
# *****　最初に検討した流れ end　*****


# Zoomホワイトボードのルール
# 翻訳させたい場合、行頭の先頭にテキストで文字を記載
#   最後に先頭に検出対象を入れることを推奨
# 検出対象が検出されるように、他の文字や図形がかぶらないようにする

# sikulix doc http://doc.sikuli.org/toc.html

# ex: init layout "init_layout_1_nec.png""init_layout_2_nec.png"
region_white_board = Region(2057,60,1645,952) # TODO init set white board region
region_zoom_white_board_control = Region(1927,1014,110,43) # TODO init set white board control(select and text) region
region_web_translate_windows = Region(1925,124,1260,765) # TODO init set web translate region
click_icon_offset_x = 50
click_icon_offset_y = 0
past_text_offset_x = 500
past_text_offset_y = 0
delete_target_translated_mark_offset_x = 20
delete_target_translated_mark_offset_y = 0

print(region_white_board)
# ex: R[1927,4 1196x700]@S(1)


check_icon_second = 0.2
wait_translate_second = 0.5
wait_display_change = 0.5

def click_zoom_white_board_select():
    is_check_select_icon = region_zoom_white_board_control.exists("zoom_whiteboard_tool_select_icon.png", check_icon_second) # TODO 後で固定位置(初期に読み込み)に変える
    if is_check_select_icon != None:
        #region_zoom_white_board_control.hover(is_check_select_icon)
        region_zoom_white_board_control.click(is_check_select_icon)

def click_zoom_white_board_text():
    is_check_select_icon = region_zoom_white_board_control.exists("zoom_whiteboard_tool_text_icon.png", check_icon_second) # TODO 後で固定位置(初期に読み込み)に変える
    if is_check_select_icon != None:
        #region_zoom_white_board_control.hover(is_check_select_icon)
        region_zoom_white_board_control.click(is_check_select_icon)

# web translate icon load
match_web_translate_icon = region_web_translate_windows.find("web_translate_icon.png")
print(match_web_translate_icon)
click(match_web_translate_icon)
match_web_translate_do_icon = region_web_translate_windows.find("web_translate_do_button.png")
print(match_web_translate_do_icon)
#click(match_web_translate_do_icon)
match_web_translate_delete_icon = region_web_translate_windows.find("web_translate_input_delete_button.png")
print(match_web_translate_delete_icon)
#click(match_web_translate_delete_icon)
match_web_translate_input_textarea = region_web_translate_windows.find("web_translate_input_textarea.png")
print(match_web_translate_input_textarea)
#click(match_web_translate_input_textarea)
match_web_translate_output_textarea = region_web_translate_windows.find("web_translate_output_textarea.png")
print(match_web_translate_output_textarea)
#click(match_web_translate_output_textarea)



# Zoom icon load
match_zoom_icon = region_white_board.find("zoom_icon.png")
print(match_zoom_icon)
match_zoom_icon_right = match_zoom_icon.getTarget().offset(click_icon_offset_x, click_icon_offset_y)
print(match_zoom_icon_right)
click(match_zoom_icon_right)

# 画面切り替えを待つ
sleep(wait_display_change)

def do_translate():
    #match_list = region_white_board.findAll("translate_target_icon_1.png") # 一文字だと検出漏れが発生した
    match_list = region_white_board.findAll("translate_target_icon_2.png")
    print(match_list)
    # ex: org.sikuli.script.Finder@1f96e5d0
    for match in match_list:
        print(match)
        # ex: M[1972,90 19x19]@S(1) S:1.00 C:1981,99 [-1 msec]
        # 対象の位置に移動してテキストをコピー
        match_location = match.getTarget()
        print(match_zoom_icon_right)
        # ex: L[1981,99]@S(1)
        hover(match_location) # move mouse point
        doubleClick(match_location)
        type("a", KeyModifier.CTRL)
        type("c", KeyModifier.CTRL)

        # web翻訳で変換し、結果をコピー
        click(match_web_translate_icon)
        click(match_web_translate_delete_icon)
        hover(match_web_translate_input_textarea)
        click(match_web_translate_input_textarea)
        type("v", KeyModifier.CTRL)
        click(match_web_translate_do_icon)
        sleep(wait_translate_second)
        click(match_web_translate_output_textarea)
        type("a", KeyModifier.CTRL)
        type("c", KeyModifier.CTRL)

        # 翻訳をZoomに張り付ける
        click(match_zoom_icon_right)
        click_zoom_white_board_text()
        past_location = match.offset(past_text_offset_x, past_text_offset_y)
        print(past_location)
        hover(past_location)
        click(past_location)
        type("v", KeyModifier.CTRL)

        # 翻訳を終えたら検出対象を消す
        click_zoom_white_board_select()
        # コピー先を消す
        # コピー先の座標だと行指定となるので、オフセット移動させる（ペースト先付近をfind()させた方が良い？）
        past_location_target_mark = past_location.offset(delete_target_translated_mark_offset_x, delete_target_translated_mark_offset_y)
        doubleClick(past_location_target_mark)
        doubleClick(past_location_target_mark)
        type(Key.DELETE)
        # コピー元を消す
        click_zoom_white_board_select()
        hover(match_location) # move mouse point
        doubleClick(match_location)
        doubleClick(match_location)
        type(Key.DELETE)

if True:
    do_translate()
else:
    loop_count = 0
    while True:
        do_translate()
        sleep(1.0)
        loop_count += 1
        print("loop count: " + str(loop_count))


</code></pre>
</div>

上記コード内の画像とファイル名のリスト

* translate_target_icon_1.png
  * ![translate_target_icon_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/11a48994-e258-458a-b05c-a5a60a8bf417.png)
* translate_target_icon_2.png
  * ![translate_target_icon_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/9513128f-27bc-6dc2-53e1-5034edd85bfe.png)
* zoom_icon.png
  * ![zoom_icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/b3bbaa1d-98e8-f80c-3cb8-42a4c3046ce4.png)
* zoom_whiteboard_tool_select_icon.png
  * ![zoom_whiteboard_tool_select_icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/d20a1276-a7c7-2152-e740-eba9d9d7f053.png)
* zoom_whiteboard_tool_select_on_icon.png
  * ![zoom_whiteboard_tool_select_on_icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/6f25a68c-3bb6-ef9a-a112-43395489bcc3.png)
* zoom_whiteboard_tool_text_icon.png
  * ![zoom_whiteboard_tool_text_icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/e123db89-1739-8085-6825-fe5b571033c1.png)
* zoom_whiteboard_tool_text_on_icon.png
  * ![zoom_whiteboard_tool_text_on_icon.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/7eb3210b-a3ef-c9bf-e4e7-e6499bd529a2.png)
* 以下の画像は、社内情報があるため公開できません。ご了承ください。
  * init_layout_1_nec.png
     * 初期の画面レイアウトのイメージ例１です。説明用のためプログラムでは使っていません。
  * init_layout_2_nec.png
     * 初期の画面レイアウトのイメージ例２です。説明用のためプログラムでは使っていません。
  * web_translate_do_button.png
     * 翻訳を実行するボタンです。
  * web_translate_icon.png
     * 翻訳サービスのアイコンです。
  * web_translate_input_delete_button.png
     * 翻訳用の入力テキストをクリアするボタンです。
  * web_translate_input_textarea.png
     * 翻訳用の入力テキストの領域です。
  * web_translate_output_textarea.png
     * 翻訳後の出力テキストの領域です。


一部公開していないので、そのまま使われる方はおられないかと思いますが、もし、上記のプログラムを流用等で動作させる場合、以下の変数は再設定してください。
（別のPCに、上記のコードをsikulixに読み込ませたら、そのPCのディスプレイの解像度の領域外だとうまく起動しないことがあったので、ご注意ください。）
デフォルトだと私が動作させているFullHD(1920/1080)のデュアルディスプレイ(左右に並べる設定)の右側のディスプレイ側となっています。

* region_white_board
  * Zoomホワイトボードの領域となるよう、Region設定ください。
* region_zoom_white_board_control
  * Zoomホワイトボードの操作ツールの選択とテキストが入った領域となるよう、Region設定ください。
* region_web_translate_windows
  * Web翻訳のウィンドウの領域となるよう、Region設定ください。

参考までに、私の方で動かした初期画面は、以下の配置にするようにしていました。
Web翻訳サービス（Zoomホワイトボードの左側）は公開できないので、黒塗りしています。
![init_layout_1_public.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/943678/0ae28cc7-8ff0-dec8-b1b0-1863ebe4636d.png)


以下の点がポイントになります。

* Zoomのホワイトボードはデフォルト位置のまま
* Zoomのホワイトボードの操作ツールは、左下に異動
* ChromeのWeb翻訳は左側で、Zoomホワイドボードより下（RPAが操作できるように、アクティブになってもZoomホワイトボードのアイコンが隠れないように）



あと、Zoomのホワイトボードの操作ツールのアイコンで、「選択」や「テキスト」の日本語までは入れていないです。
英語版だと、「Select」や「Text」になるので、言語環境の差により使えなくなるからです。
現状、検出範囲のRegionをかなり狭くしていることもあり、アイコンだけで十分安定して検出できると思えています。



#3. さいごに

手動で試して、簡単なツールを試作して検証し、また次の(改造版か新規)を試す、という流れをやろうとしていまして、今回のが最初の1つ目になります。

現場で実情を体験し、簡単にできる範囲で試作し、現場で試してみて、また改善していく。ということが新鮮です。
（いつもは、作るばかりで使う機会が少なかったりしてきたもので。。。）

あと、個人的に、自動化させることは好きなので、sikulixの開発は楽しい方です。

sikulixのコーディング言語であるPythonの経験もあったので、比較的、楽に動くものが作成できました。
これからも、色々試作を試していきたいと思います。



今回、試作して、動かしてわかったこととしては、リアルタイム性に難がありましたので、今後改善していきたいと思います。（RPAそのものもありますが、ワークフローの一連の操作的にも）
ただ、全く使い物にならないわけでもなさそうな感触なので、ユースケースに合わせて活用したいです。

今後できたらよいかも、と思ったことの一つに、
sikulixやPowerShell等を活用して、自動でRegionを狭い範囲に設定するようなツールがあってもよいかな、と思いました。
（PowerShellで、開いているプロセスのウィンドウ幅を取得することができるので、連携できるかもと）
そうすれば、今回のsikulixの初期設定で領域のRegionを設定する手間も必要がなくなり、
また、色々な環境に合わせて、ある程度快適に動作するよう最適化もできるようになるかもしれません。



では、最後までお読みいただきありがとうございました。
何かの参考になれば幸いです。