らんらん技術日記

日々の学習メモに

読書感想:Cプログラミング専門課程(藤原博文著)

僕は仕事柄、Cのプログラムを書くことが"たまに"あるのですが・・・
最近、自分のコーディングスキルは低いのではないかと感じてきました。

別に、これまでは十分なレベルだと思っていたわけではないのです。
ただ年相応のレベルというか、年を重ねるうちに、成長するものと思っていました。
予想外だったのは、C(というかプログラム)を書く機会があまりないことです!
たまに書く機会があると、我ながらいつも思うのです

「まるで成長していない・・・」

と。そんなわけで、意識して勉強しようかなと思い、久しぶりにC言語の本を手に取ってみました。


藤原博文 著: Cプログラミング専門課程
www.amazon.co.jp

この本を選んだ理由は、数年前に退社された先輩から頂いた本だからです。
頂いたままお蔵入りさせていたのですが・・・。

感想

初版が1994年です。
発展が著しいコンピュータサイエンス分野にあって、いつの時代の本だよって感じですよね。
そんな訳で僕はお蔵入りさせていたのですが・・・。
しかし今回読んでみて、それは勘違いだったことに気づきました。

この本は今でも通用する、C言語の名著です!

実際、アマゾンの最新レビュー(2019年、2018年)でも高い評価を受けています。
思い出してみると、先輩も「この本は凄くいいよ」って渡してくれました。
当時はその心を解することができず、未熟だったなぁ。。。
本書で触れているパソコンや開発環境は、残念ながら時代遅れです。
しかし、そもそもC言語は、ANSI Cが1989年に制定されて現在に至るまで、文法に大きな変更はありません。
筆者が記したC言語の心の部分は、今でも力強く輝きを放っているように感じました。


さて、僕がこの本を素晴らしいと感じたのは、バグに焦点を当てている点です。
たいていのC入門書は、ポインタや関数等の使い方は解説しますが、誤使用による危険性にはあまり触れていないと思います。
この本では、Cでバグがどのように発生し、プログラムにどのような影響を与えるのかを、執拗に説いています。
参考までに、本の中から一部フレーズを抜粋します。

(スコープについて、p82)
そもそも、C言語に安全なんて言葉はありえません。アセンブラと同レベルの万能で、かつ便利な言葉ですが、同様に危険な言語です。初心者がC言語を使用するのは「気違いに刃物」を持たせるようなものです。

だいたいこんな調子で、全350ページに渡り、Cの危険性について述べているのです。

Cがなぜ危険かというと、コンピュータのメモリの仕組みに直接アクセスできるからです。
ポインタを使ってプログラムの中身を書き換えるとができるし、
キャストによってデータの解釈をいかようにすることもできるし、
動的メモリ領域の管理も好きなようにやれます。
この仕組みはとても便利な反面、かなりやっかいです。

自分では最新の注意を払ってプログラムを書いたとしても、
他人のプログラムによって普通にバグが発生することがあります。
あるいは一見正しく動いているプログラムであっても、
どこかのタイミングでプログラムの一部が破壊されることで、再現困難なバグとなることもあります。

なのでCプログラマとしては、バグの発生原因を知っておく必要がある。
また、他人が使うことを想定して、バグの発生を抑制するコーディングを心掛ける必要がある。

といったことが、本書で学べると思います。
僕自身のことを述べると、
今時はオブジェクト指向の時代やー、
いかに使い回しが効くコードを書くかが重要なんやー、
なんて思っている部分があります。
それはそれで大切だと思うのですが、C言語を扱う以上は、まずは安全なコードであることを優先すべきだなと感じました。

以上です!

追記

急にレビューを書いてみたのは、自分用メモ + 文章を書く練習のためですが・・・
全然内容がまとまらず、自分の文章能力の低さに呆然としました。
今後も何か本を読んだら、挑戦していきたいです。

ゲーム中のセリフをOCRで取得したい part2

前回のあらすじ

オクトパストラベラーを英語でプレイするさなか、ふとある思いを抱く。
「ゲーム画面を見ながら、Google翻訳かけるのって面倒だな・・・」
ゲームなのに面倒、これは深刻な問題である。
ゲームに娯楽の要素が薄くなれば、プレイの中止も検討せざるをえない。
そんな現状を打破すべく、OCR(Tesseract)による自動文字情報取得システムの開発に着手、
苦労の末、プロトタイプ1が完成した!
僕の期待を背負ってプロトタイプ1は始動するも・・・
無残にも、まともに文字情報を得ることはできなかった。

前回の問題点

前回、OCRはそんなに簡単じゃないことが判明しました。
仕方がないので精度向上について考えてみます。
Tesseractのホームページをみていると、役に立ちそうな記事を発見しました。

tesseract-ocr.github.io

記事の主旨を一言でまとめると、
「Tesseractに画像を入力する前に、前処理をやってくれ」
という感じになるでしょうか。
前処理とは、以下のような処理を指すようです。

  • 画像の2値化
  • 白背景に黒文字にする
  • 不要なものを除去する(ページの境界線とか)

確かにそう言われるとなー、前回はスクショをそのままTesseractに放り投げていました。
そりゃあうまく行かない訳だw

ということで、スクショに前処理をかけてみます!!

前処理をかける

素人なりに試行錯誤したところ、精度が安定する方法を見つけることができました。
具体例を紹介します。

〇前処理をかける前

f:id:yukirunrun:20200628212922j:plain
©2018 SQUARE ENIX CO., LTD. All Rights Reserved.

〇前処理をかけた後

f:id:yukirunrun:20200628213029p:plain


もはや全然違う画像ですねw
ここまでやれば認識精度は格段に上がります!
しかしここまでやる必要はあるのか?
素人なので、ホントのところはわかりません。
ただし根拠はあります。
Tesseractはアプリケーションとして、古書のデジタル化を想定しているようです。
つまり一般的な本に対して、精度が良くなるように設計していると推測できます。
ということで、本と同じような状態に近づけると、精度向上につながるはずです!

実はこの状態でも、幾つかの文字は誤認識します。
おそらく、オクトパストラベラーのフォントが日常生活で使われていないからでしょうね。
その問題を解決するにはTesseractの再学習をやる必要がありそうなので、今回はやめておきます。

では、具体的なフローに入っていきます。

領域を特定する

抽出したい文字情報がある場所を、どうにかして抽出します。
どうにかしてというのが、素人が素人たる所以です。
理想的には、体系的かつ流用性のある方法を探るべきですが、そんなことは気にしません。
オクトパストラベラーに特化した方法でやります!

今回の例でいえば、上のスクショにおいて、表示されているメッセージには以下の特徴があります。

  • メッセージの大きさは変化する
  • メッセージの位置は変化する
  • ほぼ一様なベージュ色
  • 四角形のような形をしている(少なくとも丸や三角ではない)

大きさと位置が固定であれば、座標の直打ちで切り抜いていたところです。
仕方がないので、色や形の特徴を使うことにします。
何かいい方法はないかなーとググることしばらく、良いページを見つけました。

axa.biopapyrus.jp

このページの「オブジェクト検出例(バウンディングボックス)」を使うことにします。
この方法をベースに、幾つかコード修正を加えると・・・

f:id:yukirunrun:20200628223414j:plain
©2018 SQUARE ENIX CO., LTD. All Rights Reserved.

うまいことメッセージ部を特定することができました!
特定した領域をクロッピングし、その他の部分は破棄します。
参考までに、該当箇所のソースコードはこんな感じ。

    def get_dialogue(self, img_S):
        # img_S is S(Saturation) element of a screenshot in HSV space
        img_bin = cv2.inRange(img_S, 33, 34)
        contours, hierarchy = cv2.findContours(img_bin, cv2.RETR_LIST, cv2.CHAIN_APPROX_NONE)
        for i in range(0, len(contours)):
            if (i == 0):
                max_contour = contours[i]
            else:
                if cv2.contourArea(max_contour) < cv2.contourArea(contours[i]):
                    max_contour = contours[i]
        return max_contour

2値化、ノイズ除去

先ほどと同様に、どうにかしてやります
OCRをかけるにあたってのポイントは、

  • 文字はくっきりと
  • 余分なノイズはなくす

となります。このバランスが結構難しいんですよ。
文字がぼやけると、文字の誤認識になります。
ノイズがあると、ノイズを文字と認識します。

ノイズが残ってしまった例を紹介します。

f:id:yukirunrun:20200628225808p:plain

画像上の方に、謎の横線がいくつか残っていますよね。取り切れなかったノイズです。
これくらいいいじゃんって思うかもしまれせん。
僕はそう思っていました。
しかしTesseractは平気で誤認識してきます!
うまく回避する方法はあるのかもしれませんが、いずれにせよノイズが無いに越したことはありません。

参考までに、僕の実装例を紹介します。
ノイズ除去のため、2値化の範囲をできるだけ絞り、一部モルフォロジー変換を使っています。
また、白背景に黒字になるように色を反転しています。

        #Get the position of a dialogue in the screenshot
        dialogue= self.get_dialogue(img_S)

        #Preprocessing to the dialogue
        x, y, w, h = cv2.boundingRect(dialogue)
        img_crop = img_V[y:y+h, x+3:x+w-50]
        img_bin = cv2.inRange(img_crop, 60, 95)
        img_bin[0:3] = 1
        img_bin[0:40, 400:] = 1
        img_inv = cv2.bitwise_not(img_bin)
        kernel = np.ones((2,0),np.uint8)
        img_inv[0:6] = cv2.morphologyEx(img_inv[0:6], cv2.MORPH_CLOSE, kernel)

プロトタイプ2、始動

そんなこんなで、前処理の仕組みを取り入れたプロタイプ2が完成しました。
メッセージの他に、「尋ねる」「精査する」コマンドにも対応させています。
簡単に結果画面だけ紹介します。

f:id:yukirunrun:20200628232044p:plain

だいぶ実用的なシステムになってきました!
しかし、ところどころ文字の誤認識が見られます(I「アイ」を|「罫線」とか)。
このままだと少し不便なので、Tesseractのチューニング(再学習)が必要かな。
そのためには学習のためのデータが必要なので、とりあえずはプロトタイプ2を運用しつつ、データを集めたいと思います。

以上。

ゲーム中のセリフをOCRで取得したい

オクトパストラベラー(Steam版)絶賛プレイ中です!
以前の記事を挙げてから一ヶ月が経過、8人中5人の2章をクリアしました。

yukirunrun.hatenablog.com

進捗が遅いように見えますが、それは英語でプレイしているせいです。

人に話しかける
→ちょっと何言ってるかわからない
→グーグルで単語を調べる
→グーグルで熟語を調べる
→グーグルで全文翻訳をかける

と繰り返しているので、めちゃくちゃ時間がかかります。数日プレイして、一度も街の外に出ないことはざらです。ゲームとしてのテンポは最悪ですが、それでもオクトパストラベラーは面白いです!
というのも、ただの村人にさえバックグラウンドが用意されていて、

  • 多額の借金を背負って、本当の自分を偽りながらお金を稼ぐ踊り子
  • 貧民あがりで、貴族出身の同僚に対抗意識を抱く兵士
  • 悪そうな人を見つけては、雪玉を投げつける女の子

などなど、色々な人がいます。
単に話しかけるだけでは「ここは始まりの町です」レベルのことしか言わない人でも、深く探ると「宿屋の経営に行き詰っているため、夫にリフォームするように訴えている女性」ということが判明します。
村人にもそれぞれの人生があるんやなぁ、と干渉にひたりながらプレイしています。

OCRで翻訳作業を効率化

※ここからの話はSteamでゲームをしていることが前提になります。

さて、上記のプレイスタイルにあって、面倒に感じていることがあります。
英文の意味を調べるには、ブラウザ上で英文を打つ必要があるということです。

  • 現状の作業
    ゲーム画面を見ながら、英文を打ち、ブラウザに張り付ける

これを、

  • 変更後の作業
    キーを押したら、英文がブラウザに張り付けられる

という風に変更できたら、多少は楽になります。
そこで、以下のようなシステムを考えてみました。

f:id:yukirunrun:20200620233129p:plain

青色の箱はプロセスを、橙色の箱はファイルを表します。
システムの説明をデータの流れる順に行います。

  • SteamでF12キーを押すと、スクリーンショットを保存します(標準機能)。
  • Pythonは、スクリーンショットの保存を確認すると、Tesseractを呼び出します。
  • Tesseractは、OCRを行うためのツールで、スクリーンショット内に含まれる文字情報を取り出します。
  • Pythonは、Tesseractから文字情報を受け取り、整形した後、json形式で保存します。
  • Webサーバはjsonファイルを読み取り、Webページを動的生成します。

・・・といっても、どんなシステムかわかりにくいですよね。
システムの入力と出力だけでも具体的に紹介します。
入力はSteam画面のスクリーンショットです。

f:id:yukirunrun:20200620234828p:plain
©2018 SQUARE ENIX CO., LTD. All Rights Reserved.

出力はブラウザ上の文字になります。

f:id:yukirunrun:20200620235323p:plain

やりたいことは、Steam上のセリフをブラウザ上で表示するだけです。
後はコピペすれば、Google翻訳にかけられるでしょ?

このシステムで肝になるのがOCRと呼ばれる技術です。
OCRとは、Optical character recognitionの略で、日本語では光学文字認識と訳されます。活字の文書をイメージスキャナで画像化し、パソコン上で画像から文字情報を抽出する、といった使い方が起源のようです。スクリーンショットの場合、もはや光学要素はないのですが、OCRを応用することは 可能です。
OCRを実現するためのツールとして、僕はTesseractを採用しました。Googleが開発元で、オープンソースソフトウエア(Apache License 2.0)です。
正直、OCRについても、Tesseractについても、僕は全く詳しくないですw
今回はOCRを試すことができればよいので、今後必要になれば勉強することにします。

プロトタイプ1の開発

プロトタイプ1と銘打って、とりあえずシステム全体として動くものを作ります。

Steamの設定

スクリーンショットの保存方法を変更します。
保存先をプログラムで扱いやすい場所(僕は D:\steam_ss にしました)、保存形式を非圧縮にします。非圧縮にする理由は、圧縮すると画像内の文字が滲んで、OCRの精度に悪影響がありそうだからです(※あくまで素人意見です)。
具体的な手順は、以下ページをご参考に。

appli-world.jp

Tesseractのセットアップ

インストール&Pythonから呼び出せるようにします。
僕の環境はWindowsなので、以下ページを参考にインストールしました。

Tesseract OCR をWindowsにインストールする方法 | ガンマソフト株式会社

僕はWindows 64bit、v5.0.0α版のインストーラにしました。インストール完了後、システム環境変数を編集し、tesseract.exeにパスを通します。

Pythonコードの作成

ネット上に落ちているコードを集めて、適当に組み合わせます。
なにせPythonも詳しくはないので、とりあえず動けばOKで作ります。
簡単にコードの紹介をすると、

  • watchdogクラスを使って、フォルダの監視を行います。つまり、スクリーンショットが保存されたことを検出し、OCRを行うハンドラを呼び出します。
  • subprocessクラスを使って、Tesseractを実行します。Tesseractの文字情報の出力先はstdoutとし、さらにstdoutをsubprocessの返り値にリダイレクトしています。この仕組みにより、Python内でTesseractによる結果をバイナリデータ列として扱えます。
  • collectionsクラスおよびjsonクラスを使って、文字情報をjson形式で保存します。
  • jsonの保存先は、python実行場所と同一階層にあるocrというフォルダにしています。

という感じでしょうか。コード全体は以下のようになります。

import sys                                                                                                            
import os                                                                                                             
import time                                                                                                           
import subprocess
import json
import collections 
from watchdog.observers import Observer                                                                               
from watchdog.events import PatternMatchingEventHandler                                                               

class screenshotEventHandler(PatternMatchingEventHandler):
    text_index = 0
    def __init__(self, patterns=['*.png'], ignore_patterns=[],
                ignore_directories=True, case_sensitive=False):
        super().__init__(patterns, ignore_patterns, ignore_directories, case_sensitive)

    def on_any_event(self, event):
        if (event.event_type == "modified"):
            print("Chaptured a screenshot No." + str(self.text_index))

            # Read lines from Steam's screenshot using OCR
            mycmd = 'tesseract.exe ' + event.src_path + ' stdout --dpi 96'
            tesseract_out = subprocess.check_output(mycmd, shell=True).decode()
            person = ''
            msg = ''
            for i in tesseract_out.splitlines():
                if len(i) > 3:
                    if (len(person) == 0):
                        person = i 
                    else:
                        msg = msg + i + " "

            # Write lines in JSON format
            lines = collections.OrderedDict()
            lines["person"] = person
            lines["msg"] = msg
            fn = './ocr/lines' + str(self.text_index) + '.json'
            self.text_index += 1
            fw = open(fn, 'w')
            json.dump(lines, fw, indent=4)

if __name__ == "__main__":
    evh = screenshotEventHandler()
    obs = Observer()
    obs.schedule(evh, 'D:/steam_ss', recursive=True)
    print("Start OCR")
    obs.start()
    try:
        while True:
           time.sleep(1)
    except KeyboardInterrupt:
        obs.unschedule_all()
        obs.stop() 
    obs.join() 
    print("Finish OCR")
出力するjsonは以下のようになります。
{
    "person": "Coachman",
    "msg": "If you're planning a journey ..."
}

Webサーバ側のコード

なんのへんてつもないhtml + phpで作ります。
これも素人なのでクオリティはお察し、動けばいいやで作ります。
ちなみにWebサーバはApatche2を使っています。

まずはhtmlから。

<!DOCTYPE html>
<html>
<head>
    <meta charset='utf-8'>
    <title>OCR Test</title>
</head>
<body>
    <h2>Octopath Traveller's dialogue</h2>
    <?php include('./proto1.php'); ?>
    </body>
</html>

ついでphp

<?php
$dir_ocr = "./ocr";
if (is_dir($dir_ocr)) {
    if ($dh = opendir($dir_ocr)) {
        while (($file = readdir($dh)) !== false) {
            $dot_pos = strpos($file, '.json');
            if(strlen($file) >  2) {
                $filepath = $dir_ocr."/".$file;
                $json = file_get_contents($filepath);
                $arr = json_decode($json,true);
                echo "<h3>$arr[person]</h3>\n";
                echo "<p>$arr[msg]</p>\n";
                echo "<br/>\n";
            }
        }
        closedir($dh);
    }
}
?>

プロトタイプ1、始動

一通り仕組みが完成したところで、動かしてみます!
わくわく。

まず、ターミナルソフトでpythonを実行します。
無限ループするので、そのまま放置しておきます(画面上でpython_sl.exeとなっているのは、python.exeを実行すると、Microsoft純正のpythonのダウンロードを勧められるため。僕はAnacondaのpythonシンボリックリンクを張って使っています)。
f:id:yukirunrun:20200621014520p:plain

ゲームをプレイします。
人々との会話を楽しむさなか、英語の意味がわからないタイミングで、F12を押します。

f:id:yukirunrun:20200621014945p:plain
©2018 SQUARE ENIX CO., LTD. All Rights Reserved.

スクリーンショットの保存を検出すると、自動的にOCRを実行し、jsonファイルを生成します。
ターミナル上では、その旨を簡潔に表示します。

f:id:yukirunrun:20200621015217p:plain

適当に4人ほどに試した後、先ほど作成したWebページを開きます。

f:id:yukirunrun:20200621015544p:plain

人々のセリフを正しく取得できてい・・・ないですね。
やっぱ適当に作るとこうなるよなーーーー

今回はとりあえず動いたということで、改善は次回以降にします!
以上。

nRF Sniffer v3を使ってみた

先日、Bluefruit LE SnifferのFWをnRF Sniffer v2からnRF Sniffer v3にアップデートしました。
簡単に使ってみたところ、以下のことがわかりました。

  • Bluetooth5のパケットに対応している
  • BLE Meshのパケットに対応している
  • Bluefruit LE Snifferでは最新スマホの通信を追えない
    • 最新スマホは2M PHYに対応しているため
    • スニファを購入するときは、同対応の確認をお勧めします

総じて言えるのは、アップデートして良かったってことです!
特に最後の「Bluefruit LE Snifferでは最新スマホの通信を追えない」は、1年以上原因がわからず、ようやくの解決となりました。感謝感激です。
以下、簡単に説明を加えようと思うのですが、先に登場人物の紹介を。

f:id:yukirunrun:20200616230404p:plain

左にある基盤剥き出しなのが、今回の主役、Bluefruit LE Snifferです。パソコンに指すと、そこらへんに飛びかうBLEのパケットを盗聴して、その中身をパソコンに報せてくれます。
右にあるUSBメモリみたいなのが、Laird社のBL654です。これでもれっきとしたBLEの開発ツールで、Bluetooth5に対応しています。事前にBLE Meshの機能を入れておいたので、今回はBluetooth5 & BLE Meshデバイスとして動きます。
その他、写真を撮影しているのがiPhone8で、BL654の通信相手になります。

Bluetooth5のパケットに対応している

nRF Sniffer v3ではBluetooth5のパケットを確認できるようです。
全部は確認していませんが、ある程度はカバーしていそうな雰囲気です。
例として、Bluetooth5の新機能「 2M PHY 」で確認します。

そもそも2M PHYとは何かを説明すると・・・
Bluetooth4時代は、BLEの通信帯域は1Mbps固定でした。
Wi-Fiに比べると、とてつもなく低い帯域幅です。
もともとBLEは低消費電力が売りですから、1Mbpsで十分でした。
しかし時代の流れがあったんでしょうね。
Bluetooth5では、電波の強度を半分にする代わり、通信帯域を2倍にするモードが追加されました。
これが2M PHYです。
BLEはデフォルトでは1Mbpsで動くのですが、マスタとスレーブの合意が取れれば、2Mbpsで動くようになります。

Bluetoothの仕様書によると、2M PHYの交渉の流れは以下のようになります。

f:id:yukirunrun:20200614155614p:plain

同一の流れをBluefruit LE Snifferで確認してみます。
マスタをiPhone8、スレーブをLaird社のBL654にします。
コネクションを張った直後の通信パケットは以下のようになりました。

f:id:yukirunrun:20200614155707p:plain

ちょうど選択しているパケット(青色)から、2M PHYの交渉がスタートします。 パケットを時間方向に進んでいくと、

  • LL_PHY_REQ
  • (Empty PDU)
  • LL_PHY_RSP
  • LL_PHY_UPDATE_IND

のように、仕様書の流れと一致していることがわかります。
ちなみにPHYの列を見ると、現在は 1M PHY で通信していることがわかります。
交渉が成立すると「LE 1M」 -> 「LE 2M」になると推測できますが、Bluefruit LE Snifferが2M PHYに未対応のため、ここで通信が途切れました。そこらへんの話は後述します。

ついでに、LL_PHY_REQのパケットの中身を確認してみます。

f:id:yukirunrun:20200614160801p:plain

これはiPhone8からBL654へのリクエストです。
要約すると、
「送信には2M PHYを使いたいんや」
「受信にも2M PHYを使いたいんや」
てことを言ってます。

BLE Meshのパケットに対応している

nRF Sniffer v3は、BLE Meshのパケットも解釈してくれます。
とはいっても、ネットワークレイヤまでの話なんですけどね。一応アプリケーションレイヤも見れるのですが、電文は暗号化されているので、中身を解読することはできません。
Nordic社の質問フォーラムを見る限り、現在開発中のようですが・・・ なかなか難しいのかも。
BLE Meshって、暗号化に凄い力を入れていますからね。
メッシュという構造上、ハッカーにさらされやすいので。

それでは、パケットの一例を紹介します。
まずはUnprovisioned Device Beaconから。Bluetooth Meshデバイス(の候補)が、まだMeshネットワークに入っていないことを報せるパケットです。
Bluetooth Meshの仕様書では、以下のように構造を定義しています。

f:id:yukirunrun:20200614165721p:plain

Bluefruit LE SnifferでBL654のパケットを確認します。

f:id:yukirunrun:20200614164659p:plain

パケットのうち、Bluetooth Mesh Beacon部を確認します。
中身に、

  • Type
  • Device UUID
  • OOB :0x0000 = 外部入出力の仕組みなし

が含まれていることがわかります。URI Hashはないですが、これはオプションです。
この状態ではアプリケーションデータが何もないため、暗号化は関係ありません。

続いて、アプリケーションデータを含むパケットの話に進みたいのですが・・・、
専用スマホアプリ(nRF Mesh)の見た目が全然違うんですけど!?
1年前はこんなアプリじゃなかったよな、お前?
全く使い方がわからないw
これは時間がかかりそうなので、また今度にします!!

Bluefruit LE Snifferでは最新スマホの通信を追えない

一年以上謎だった現象です。
iPhone8の通信を追跡しようとすると、最初はうまくいくのですが、途中からばったり通信パケットが見えなくなるのです。
ついに解決することができました!

上で述べたように、iPhone8は、コネクション確立後に2M PHYで通信しようと試みます。
一方でBluefruit LE Snifferの2M PHY対応はというと、搭載しているnRF51822次第になります。
Noridic社のnRF51822のページを覗いてみると・・・
案の定「2M & Coded PHYに対応していません」の記述を発見!

・・・そりゃあ、通信を追えない訳だ。
電気的な特性が違うのだから、意味を解釈できるわけがない。
わかってしまえば当然なんだけどさ。
そんなの簡単に気づくわけないでしょw
メーカ側も多少の親切心を出してくれるとありがたんだけどなー。

ということで、
スニファを買うときは、2M & Coded PHYへの対応を確認しましょう!
同じAdafruit社であれば、Bluefruit nRF52 Featherなら大丈夫かも。


以上、nRF Sniffer v3を使ってみた感想でした!

nRF Snifferをv3にアップデート

久々にNordic Semiconductorのホームページを覗いてみたら、nRF Snifferのメジャーバージョンがv2からv3に更新されていました。 nRF Snifferといえば、過去に僕はBluefruit LE Snifferを購入しています。

www.sengoku.co.jp

購入時のnRF Snifferはv2です。
想定した用途では使えなかったので、ほとんど眠っていますが・・・。
v3では機能面が良くなることを期待しつつ、試しにアップデートしたいと思います!

Adafruit社のページを確認

こういう時は、開発元のホームページを確認するのが一番です。
開発元はAdafruit社ということで、専用ページを見つけました。

https://learn.adafruit.com/introducing-the-adafruit-bluefruit-le-sniffer/using-with-sniffer-v2

以下は、v2→v3にアップデートする際の該当記事を意訳した内容です。

FWアップデートのガイドやチュートリアルは用意してないぞ。
そもそもFWのソースコード持ってないしな。
Nordic社のページを当たってくれや。

なめとんのかwww

自己流でアップデート

仕方がないので、自己流でアップデートしたいと思います。
自己流といいつつも、この世界の「常識」に従っているので、特に変わったことはしていません。
アップデートの流れは、大きく以下の3ステップになります。

  1. 変換基盤を作る
  2. FWアップデート
  3. Wiresharkのセットアップ

「3.Wiresharkのセットアップ」はNordic社のHPに方法がそのまま書いてあります。「1.変換基盤を作る」「2.FWアップデート」は特に決まったやり方はないので、自己流?になります。

変換基盤を作る

パソコンから、基盤上のマイコン(nRF51822)にアクセスする仕組みを作ります。
先に必要な道具を紹介します。

  • Bluefruit LE Sniffer本体
  • ARM Cortex -M0 対応のプログラマ
  • 変換基盤の材料
    • ユニバーサル基盤
    • 配線
    • ピンヘッダ
  • はんだセット、ニッパ、ワイヤストリッパ等の工具

f:id:yukirunrun:20200613184149p:plain

僕はプログラマとしてJ-Link EDUを使いました。
なのでJ-Link EDUを前提に話を進めます。

〇変換基盤を作る
Bluefruit LE Snifferには、SWD(ARMマイコンのデバック用通信規格)を行うためのパッドがあります。
ということで、SWDでプログラムを書き込むことにします。
J-Link EDUのピン配置を参考に、変換基盤を作成します(写真は裏から基盤を見ているので、配置が反転しています)。続いて、配線と Bluefruit LE Snifferをはんだづけします。

f:id:yukirunrun:20200613192242p:plain

f:id:yukirunrun:20200613192335p:plain

〇パソコンと繋げる
J-Link EDUはパソコンと直接USBで接続します。Bluefruit LE Snifferの電源は、適当にUSB給電します。
写真の黒い物体は、ただのモバイル用のUSBバッテリですw

f:id:yukirunrun:20200613193341p:plain

FWアップデート

ここからはパソコン上での作業になります。
必要なツールの紹介です。

  • nRF Sniffer v3
  • nRF Connect for Desktop
    • Programmer

ツールはNordic社のHPからダウンロードできます。

★nRF-Sniffer
nRF Sniffer for Bluetooth LE - nordicsemi.com

★nRF Connect for Desktop
nRF Connect for Desktop - nordicsemi.com

〇nRF Connect for Desktopのインストール
nRF Connect for DesktopはFW書き込み用のアプリケーションとして利用します。
他の方法でも代替可能ですが、この方法が一番楽かと思います。

NordicのHPから最新版をダウンロードし、インストールしてください。
nRF Connect for Desktopを起動すると、ホーム画面が現れます。
必要に応じて、色々なアプリをインストールできるようです。
今回はProgrammerのインストールのみで十分です(僕は興味本位でBluetooth Low Energyも入れました)。

f:id:yukirunrun:20200613195604p:plain

〇FWを用意する
書き込むFWのイメージは、nRF-Snifferをダウンロードすると、zipファイルの中に入っています。
zipファイルを解凍し、hexフォルダの中から、「sniffer_pca10028_xxxxxxx.hex」を使用します(xxxxxxxは任意の文字です)。
ここで注意です!
pca10028とはNordic社が販売する開発ボードのことです。 Bluefruit LE Snifferとは別製品です。
通常ならば、別製品のFWを使うことはできません。
今回、pca10028のFWを使う理由は、Adafruit社のHPにそれっぽい記述があるからです。
本当に使える確証はありません。
まぁ、おそらく問題ないと思いますw

〇FWを書き込む
パソコンにJ-Link EDUを接続し、先ほどインストールしたProgrammerを開きます。
画面上側のSelect deviceを押して、デバイスと接続します。

f:id:yukirunrun:20200613202432p:plain

バイスが正しく認識されると、画面下側のLogウインドウにその旨のメッセージが流れます。
この状態でFile memory layoutに「sniffer_pca10028_xxxxxxx.hex」をドラッグ&ドロップ、画面右側のWriteを押下します。

f:id:yukirunrun:20200613203356p:plain

ものの数秒のうちに書き込みは完了します。
書き込みに成功すると「Write Procedure finished」のようなログが流れます。

ここまで完了すると一安心です!
僕は今回たまたま一発成功でしたが、いつもはどこかでミスりますw
もし失敗した場合は、まずは「1. 変換基盤を作る」の作業を見直してみてください。

Wiresharkのセットアップ

Nordicが紹介している通りにやればOKです。
僕なりのオリジナリティはないので、省略します。

infocenter.nordicsemi.com

Wiresharkのセットアップがうまくいくと、以下のようにBLEのパケットを確認できます。
f:id:yukirunrun:20200613205419p:plain

以上でアップデートは完了です。
次回、もしv3の新機能があれば紹介したいと思います!

C言語&転置処理でメモリについて考えてみた

{} 最近、パタヘネの第5版を読んでいます!
結構ボリュームはあるのですが、なんとか第5章まで読みました。
名著と謳われているように、ページを進めるほどにコンピュータの奥深さが身にしみます。
もっと早く読めばよかったなぁ。昔の自分は何をやっていたんだ!?
(ブログを読み返す)
そっか、Bilateral Solverをやっていたのか。


・・・一応、Bilateral Solverについて説明します。
Bilateral Solverとは、Googleの研究者が発表した画像処理用の高速ソルバです。
かつて僕は論文を読みながら、C++による実装に取り組んでいました。
僕はこれまでBilateral Solverを知っている人に会ったことはないですw


そうそう、パタヘネの話でしたね。
記憶階層について読んでいると、Bilateral Solver実装時の疑問を、ふと思い出しました。
「行列の転置って処理速度遅いよな・・・」
Bilateral Solverでは10万x10万サイズの行列を扱います。
最終的な解を求めるまでに様々な計算をするのですが、
なぜか行列の転置にかかる時間がめちゃくちゃ長いのです!
ホント謎でした。
当時の僕は、コンピュータの中では不思議な現象が起きる、という結論に至ったのですが・・・

冷静に考えたら、そんな訳はありませんw
今思うと、あの時の現象はキャッシュミスが原因だったのかもしれません。
そうとなれば、さっそく調査だーーーー!

転置のパフォーマンス

最初に、不要とは思いますが、行列の転置とはどのような処理かを説明します。
転置前の行列があります。
\begin{pmatrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \end{pmatrix}

転置後はこうなります。 \begin{pmatrix} 1 & 4 & 7\\ 2 & 5 & 8 \\ 3 & 6 & 9 \end{pmatrix}


それでは調査開始です!
本当に転置処理は遅いのか、以下の3パターンの速度を比較してみます。

  1. 行列Aの転置処理
  2. 行列Aと行列Bの加算
  3. 行列Aと行列Bの乗算

そんなの実行環境やプログラムの構造、行列のサイズに依るって??
せやな!!!
今回は調査はあくまで一例ということで。

★使用するパソコンのスペック

  • Macbook Air(13-inch, Mid 2011)
  • CPU:1.7GHz Intel Core i5、32bit、Sandy Bridge?
  • 一次キャッシュ:32kB
  • 二次キャッシュ:256kB
  • 三次キャッシュ:3MB
  • メモリ:4GB

いつの時代のスペックだよって感じですが、普段使いはこれで十分なんだからね!!

★Cプログラム

各処理の実行時間を図るプログラムはこんな感じにしました。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

void transposeMatrix(float **mat_a, int size)
{
    float tmp;
    for (int i = 0; i<size; i++)
        for (int j = i+1; j<size; j++) {
            tmp = mat_a[i][j];
            mat_a[i][j] = mat_a[j][i];
            mat_a[j][i] = tmp;
        }
}

void addMatrix(float **mat_a, float **mat_b, int size)
{
    for (int i = 0; i<size; i++)
        for (int j = 0; j<size; j++)
            mat_a[i][j] = mat_a[i][j] + mat_b[i][j];
}

void multiplyMatrix(float **mat_a, float **mat_b, int size)
{
    for (int i = 0; i<size; i++)
        for (int j = 0; j<size; j++)
            mat_a[i][j] = mat_a[i][j] * mat_b[i][j];
}

int main(int argc, char *argv[])
{
    int size = atoi(argv[1]);
    int mode = atoi(argv[2]);

    float **mat_a, **mat_b;
    int tmp = 0;
    mat_a = (float**)malloc(sizeof(float*) * size);
    for(int i = 0; i<size; i++)
        mat_a[i] = (float*)malloc(sizeof(float) * size);
    mat_b = (float**)malloc(sizeof(float*) * size);
    for(int i = 0; i<size; i++)
        mat_b[i] = (float*)malloc(sizeof(float) * size);


    clock_t start, end;
    double time;
    start = clock();
    switch (mode)
    {
        case 0:transposeMatrix(mat_a, size);
               break;
        case 1:addMatrix(mat_a, mat_b, size);
               break;
        case 2:multiplyMatrix(mat_a, mat_b, size);
               break;
    }
    end = clock();
    time = (double)(end - start)/CLOCKS_PER_SEC;
    printf("%0d %0.6f\n", size, time);
}

シェルの引数を利用して、行列のサイズを10~5000と変えながら、実行時間を計測してみます。
さて、結果は如何に!?
グラフにまとめたので、下にスクロールしてください。




f:id:yukirunrun:20200606234412p:plain
グラフの横軸は行列のサイズ、縦軸が所要時間です。
転置が緑色、加算が水色、乗算が橙色のグラフです。
参考までに、紫色はプログラムとは関係のない2乗曲線です。

グラフから明らかなように、転置処理が最も遅いという結果になりました!
やっぱり昔の記憶は間違っていませんでした。
よかった、よかった。

転置処理が遅い理由

そっかー、転置処理は遅いのかー
よくわかんないけど覚えておこっと。

・・・で終わると、昔の僕と変わりませんw

考察してみたいと思います。
パタヘネで記憶階層について学んだ僕は、この現象の原因をキャッシュミスであると推測しました。

キャッシュは、CPUがアクセス可能な記録素子のうち、特に応答時間が短い素子になります。
もしキャッシュに欲しいデータがある場合は、CPUのデータアクセス時間は短くなります。
もしキャッシュに欲しいデータがない場合は、CPUは代わりに、メインメモリやストレージまでデータを取りに行く必要があります。これはキャッシュミスと呼ばれ、CPUのデータアクセス時間は長くなります。取ってきたデータはキャッシュに格納され、次に同じデータにアクセスする際、アクセス時間は短くなります。
僕も詳しくないので、詳細はwikiや関連本を読んでください。

キャッシュによるアクセス時間短縮の効果は、データの時間的・空間的局所性に依存すると言われています。
その観点で、今回の3つの処理を見てみます。

★加算、乗算のプログラム f:id:yukirunrun:20200607005249p:plain

図のモデルはだいたいのイメージです。厳密なこと言いだすときりがないですが、大筋はあっていると信じています。
キャッシュの割当を以下のようにします。

  • ブロック1:1行目のデータまたは3行目のデータ
  • ブロック2:2行目のデータまたは4行目のデータ

加算、乗算プログラムは、メモリ空間に対して、上から順番にデータにアクセスしています。
キャッシュのデータの入れ替えは、

  • ブロック1:なし→1行目のデータ→3行目のデータ
  • ブロック2:なし→2行目のデータ→4行目のデータ

程度で済みます。
このプログラムは、データの時間的局所性、空間的局所性の両方に優れていると言えます。

★転置のプログラム f:id:yukirunrun:20200607005254p:plain

転置のプログラムは、メモリ空間に対して、ジグザグにデータにアクセスしています。
するとキャッシュのデータの入れ替えは、

  • ブロック1:なし→1行目のデータ→3行目のデータ→1行目のデータ→3行目のデータ→・・・
  • ブロック2:なし→2行目のデータ→4行目のデータ→2行目のデータ→4行目のデータ→・・・

のように何度も発生します。
このプログラムは、データの空間的局所性には優れていますが、時間的局所性には優れていません。



さて、もし上の説明が正しいと仮定するとだ。
キャッシュの容量が不足すると、プログラムの処理時間が遅くなるってことですよね。
実行環境の3次キャッシュの容量は3MByteです。
float型は4Byteなので、
 \sqrt[]{3 * 1024 * 1024 / 4} \simeq 887
行列のサイズが約887x887を超えたあたりから、処理時間が悪化するはずです。
そのあたりのグラフを拡大してみます。

f:id:yukirunrun:20200607012249p:plain

面白いぐらいに、800後半あたりからグラフが乱れているのがわかります。
キャッシュミスが原因である証拠ではないでしょうか!?

ついでにもう一つの謎にも触れておきます。 全体のグラフをもう一度掲載します。

f:id:yukirunrun:20200606234412p:plain

転置のグラフは凸凹してますよね。
行列のサイズが増えると、処理時間は2乗オーダで増えてほしいのですが、なぜでしょうか?
何度もプログラムを実行しても、ほぼほぼ同じ結果になります。
偶然という可能性はなさそうです。

グラフを見ていると、行列のサイズが500増えるごとに似たようなパターンを繰り返しています。
500ということは、一番近い2のべき乗は512・・・
float型なので・・・512 * 4 = 2kByte

この2kByteという数字に何か意味があるのかな・・・?
応急措置として、プログラムを以下のように書き換えます。

mat_a = (float**)malloc(sizeof(float*) * size);
for(int i = 0; i<size; i++)
    mat_a[i] = (float*)malloc(sizeof(float) * 5230);
    //mat_a[i] = (float*)malloc(sizeof(float) * size);

5230である理由は、行列のサイズが4610の時に性能が良かったので、そこに512を足しただけです。
本当はもっといい方法があるはずなので、真似しないように。
これで処理時間を計測してみます。

f:id:yukirunrun:20200607015149p:plain

グラフの緑色が変更前、水色が変更後です。
グラフの凹凸がなくなり、二乗曲線のような特性に近づきました。
予想通りといえば予想通りなんだけど、どうしてなんだろうね・・・?

パタヘネを読んでいると、一番関係ありそうなのはTLB( Translation Lookaside Buffer)です。
いわゆるメモリ仮想化技術における、ページのキャッシュですね。
ただ今回の調査はメモリに対して負荷をかけていないつもりだし、これが原因なのか?
コンピュータに詳しい人、ご意見よろしくお願いします m(._.)m

まとめ

行列の転置処理が遅い理由について、キャッシュミスを前提に調査してみました。
結果は、ほぼほぼ予想通り。
キャッシュミスが性能に直結することは知っていたけど、こんなに性能差が出ることは驚きでした。

しかし得られた結果にはまだ疑問が残ります。
原因は探りたいのですが、簡単にいきそうにありません。
ここは初心に戻って「コンピュータの中では不思議な現象が起きる」という結論にしようかな・・・。

おまけ: 転置処理の高速プログラム

せっかくなので、転置処理を高速で行うプログラムについてググりました。
行列の転置って、普通に使われている処理ですからね。
誰かがいい方法をまとめてくれているはず!

すると案の定、stackover flowに該当する記事を見つけることができました。 stackoverflow.com

このページに記載の方法を使えば、高速な転置プログラムを組めるかも。。。

OCTOPATH TRAVELER 英語版・戦闘台詞 まとめ

オクトパストラベラーで異国を旅する

最近、オクトパストラベラーというゲームにはまっています!
もともと知り合いからお勧めされていたのですが、「英語とJRPG」というブログに出会ったことがプレイの決め手となりました。

Octopath Traveler(オクトパストラベラー) - 英語とJRPGejrpg.hatenablog.com

ブログの中身を要約すると、
・オクトパストラベラーは英語/日本語に対応している
・テキスト量が多いので、英語の勉強におすすめ
・もちろんゲームとしても最高に面白い
て感じでしょうか。

・・・つまりゲームをしながら英語も勉強できる、一石二鳥ってことか!?
はえー、世の中にはうまい話があるもんやね。
ゲームをクリアしてるころには、英語のスキルも格段にアップしてるって寸法だ。
よっっしゃ、そういうことなら早速プレイだ!!!

てのが1月前の話。
確かにゲームとしては最高に面白く、寝る間を惜しんでプレイ中です。
英語の方はあまり上達した気がしません。まだ全員の一章をクリアしたばかりだからかな。
なるほどな、トラベラー達の冒険がいよいよ始まるように、英語の上達もこれからって訳だ。
憎い演出やなー。
まぁ、それは後の楽しみに置いといて。
ここまでプレイして、どうしても気になることが一つあります。

戦闘ボイス(英語)が何を言っているのかわからない

わりとマジで日本語でおkって感じです。
なんというか、トラベラー達が話してる言葉は癖がある気がします。
最初はそのうち耳が慣れると思っていましたが、一向に慣れる気配なし。
かといって英語のセリフをまとめているサイトなんてないし・・・

仕方ないから自分で調べるか、と今回の記事に至る次第です。
てわけで、キャラクター毎によく聞くセリフをまとめてみました!
英語のセリフは海外のwiki掲示板を見たり、自分の耳の感覚を頼りに埋めたので、ミスは多めかも。
比較があるとわかりやすいので、日本語のセリフは以下のブログから拝借しました。

pokemeikyu.blog.shinobi.jp

(2020/10 追記)
オンラインのTOEIC受けてみました。
過去最高点:690点
今回:740点!

勉強方法はというと、ほとんどゲームをしていただけですw
まともに勉強していないし、ダメだろうなと諦めていたのですが・・・
Readingが伸びていました!
Listeningは変化なし、そりゃそうかw
実はオクトパストラベラーの英語は難しすぎて、途中で別ゲームをやっていました(英語テキスト、日本語ボイスのゲーム) 。そのうちオクトパストラベラーに戻る予定です!

Ophilia(オフィーリア)

聖火教のシスターらしく「Flame protect us!」のようなセリフが多いです。

分類 英語 日本語
戦闘開始
通常
I will battle! 行きます
There is no choice. やむを得ません
Flame protect us! 聖火のご加護を
ターン
通常
Well, then それでは
My turn now 行きましょう
I will not be defeated! 負けません
I'll do my best! 頑張ります
攻撃 (擬音のみ)
ブースト Here I go! では、行きます
Prepare yourself! 覚悟してください
防御 No! You will not! させません
アイテム
自分に
Have this これなら
アイテム
仲間に
Take this! これを
回避 You will not 当たりません
さっと
戦闘終了
通常
The Flame guides us true! 聖火の加護に感謝します
I hope I've grown more able 少しは成長できたでしょうか
I am not made for such battles 戦いは、やはり苦手です
Heal Wounds
(回復魔法)
Let your wounds be healed 傷を癒したまえ
Holy Light
(聖なる光)
Oh, Sacred Light! 光よ
Sheltering Light
(守護のヴェール)
Please, protect us! どうか、ご加護を
Luminescence
(光明魔法)
May the Sacred Flame shine forth! 聖火の光よ、輝きたまえ
Heal More
(大回復魔法)
Bring about a miracle of healing! 癒しの奇跡を与えたまえ
Reflective Veil
(反射のヴェール)
I will reflect your barbs 跳ね返します!
Revive
(復活魔法)
In Aelfric's name, be saved! 聖火神の御業で救いたまえ

Cyrus(サイラス)

普段冷静なサイラスの最大ブースト「My focus is unparalleled!」は、意外性があってカッコいいです。
とはいえ、英語だと原則テンション高め。

分類 英語 日本語
戦闘開始
通常
Can't we settle this like gentlemen? やれやれ、争い事は好きではないのだが
Time to teach you a lesson! さあ、授業の時間だ
I've done my research on you 戦いの準備は出来ているよ
ターン
通常
What is the logical course of action? さて、どうしようか
An interesting dilemma… 中々考えがいがあるね
Now, what next? さあ、どう出る?
Hmm ふむ
攻撃 (擬音のみ)
ブースト Now, the true lesson begins! 本気を出そうか!
My focus is unparalleled! 集中していこう
防御 I think not! させるものか!
アイテム
自分に
Let's try this 使っておこう
アイテム
仲間に
How about this これでどうかな?
回避 Upps おっと
Gasp! That was close 危ない危ない
戦闘終了
通常
Not bad, a passing grade 大した強さだ、合格点だよ
You should've studied harder この程度では不合格だな
Thus ends your lesson 授業はこれで終わりだ
Fireball
(火炎魔法)
Oh flames, rage strong! 炎よ、燃えろ
Icewind
(氷結魔法)
Oh ice, pierce them through! 氷よ、切り裂け
Lightning Bolt
(雷撃魔法)
Oh lightning, smite them down! 雷よ、貫け
Analyze
(しらべる)
So much to learn さて、探らせてもらうよ
Fire Storm
(大火炎魔法)
A great inferno to scorth the land! 火炎よ、焼き尽くせ
Blizzard
(大氷結魔法)
A tempest of ice shall rage! 氷嵐よ、巻き起これ
Lightning Blast
(大雷撃魔法)
Nothing will quiet a storm! 雷鳴よ、轟き響け

Tressa(トレサ)

主人公として選択しました。「Ready or not, here I come!」の絶妙な図々しさは、トレサぽくてお気に入りの台詞です。

分類 英語 日本語
戦闘開始
通常
Step right up!(*1) ちゃっちゃと行くわよ!
Ready or not, here I come!(*2) 準備は良い?
Open for business! さあ、開店ね!
戦闘開始
中ボス
Now, Let's get down to business 商売敵、ってとこかしら
ターン
通常
Here I go! 行くよ!
Let's do this! よし!
What to do? さあ、どうする?
My turn, now! あたしの番!
攻撃 How's that? (擬音のみ)
ブースト I won't hold back! ここからよ!
防御 (擬音のみ) (擬音のみ)
アイテム
自分に
This do the trick? よいしょ
アイテム
仲間に
Let me help これを
回避 Nice try! ちっちっちっ
Hahaha! へへん
戦闘終了
通常
Hahaha, how's that? ふふん、どんなもん?
You got more than you bargained for! 商人だからって甘く見たでしょ
Time to close up shop! 今日は店仕舞いよ
Collect
(集金)
Thank you! まいど!
Tradewinds
(風よび)
What a nice breeze 良い風吹いてる
Rest
(ひと休み)
Time for a break ひと休みひと休み
Trade Tempest
(大風よび)
The winds of fortune are howling 幸運の風よ、吹き荒れよ
Donate BP
(BPパサー)
For you! よろしくね
Sidestep
(緊急回避)
Chatch me begin 当たらないよ
Hired Help
(傭兵よび)
Oh, get my money's worth お金に糸目はつけないわ

1:いらっしゃい、こっちへいおいでよ!という意味
2:英語圏のかくれんぼの合図

Ollberic(オルベリク)

海外サイトでも人気のセリフ「My blade is Unbending!」は必聞です。ちなみに剛剣 = Unbendingとして訳されています。

分類 英語 日本語
戦闘開始
通常
Tis a good day for battle(*1) さあ、始めようか
剛剣の騎士、推して参る
Let us fight with honor 良い戦いにしよう
ターン
通常
Now then さて
The battle is truly joy 行くぞ
Victory shall be mine ここからが本番だ
Have at you(*2) 覚悟はいいか
攻撃 Out of my way! (擬音のみ)
ブースト My blade is Unbending! さあ!覚悟はいいか!
防御 I think not! やらせん!
アイテム
自分に
Have a taste of this! 使わせてもらう
アイテム
仲間に
Use this これを使え
回避 その程度か
Too slow 遅い!
戦闘終了
通常
With each battle, I grow stronger. また一つ強くなれた、感謝する
Ah! The thrill of the fight! 良い勝負だった
It ends, here ふぅ、これで終わりか
Level Slash
(横一文字斬り)
I will cut you down! 横一文字斬り
Abide
(ためる)
Huhhhh! はぁああ!
Spearhead
(一番槍)
I shall pierce you through! 一番槍
Incite
(挑発)
Come, If you dare 来るがいい!
Cross Strike
(十文字斬り)
Watch my blade dance 十文字斬り
Stout Wall
(鉄壁)
守りを固める!
Thousand Speares
(千本槍)
Death by a thousand cuts! 千本槍

1:古英語で tis = it isです
2:中世の剣士が使うセリフです

Primrose(プリムロゼ)

なぜかセリフ少なめです。言葉数少ないキャラとはいえ、もっとしゃべってもいいのよ?

分類 英語 日本語
戦闘開始
通常
May I have this dance? さあ、踊りましょう?
プリムロゼ、参ります
かかってらっしゃい
ターン
通常
さあて
踊ろうかしら?
I am ready 出番ね
行くわよ
攻撃 (擬音のみ)
ブースト Hya! Watch me now! 覚悟はいい?
さあ、行くわよ
防御
アイテム
自分に
How about this これで
アイテム
仲間に
Stay strong しっかりね
回避 ふっ
戦闘終了
通常
ごめんね、素人さんとは踊らないの
アンコール?遠慮するわ
The show is over あら、相手が悪かったようね
Lions Dance
(獅子の舞)
The Lion Dance! 獅子の舞
Moonlight Waltz
(月夜の詩)
The Moonlight Waltz 月夜の闇
Peacock Strut
(孔雀の舞)
The Peacock Strut 孔雀の舞
Mole Dance
土竜の舞)
The Mole Dance 土竜の舞
Night Ode
(闇夜の詩)
Night Ode, bring your shade 闇夜の帳、災いを祓え
Panther Dance
(黒豹の舞)
The Panther Dance 黒豹の舞
Bewildering Grace
(摩訶不思議の舞)
さあ、どうなるかしらね?

Alfyn(アーフェン)

地方なまりのある兄ちゃんです。やんちゃなのはいいけど、First Aidの「That'll do 'er」は何度聞いてもわからなかった・・・。

分類 英語 日本語
戦闘開始
通常
Let's see what you got. いっちょかますかぁ
Wanna tango, han? 良いぜ、来な!
You won't feel a thing! なめんなよ!
ターン
通常
Here we go! 行くぜ
All right! さてと
Here goes nothing よっしゃあ!
Bring it on! かかってきな
攻撃 (擬音のみ)
ブースト Let's get down to work! やってやるぜ!
All right! よっしゃあ!
防御
アイテム
自分に
Here gose nothing よっ!
アイテム
仲間に
Try this おらよ!
回避 へへん
あぁらよっと
戦闘終了
通常
Ha! Try again! 出直してきな!
That's what I'm talking about! ざっとこんなもんよ
First Aid
(応急手当)
That'll do 'er(*1) よしっと
Icicle
(氷柱)
Cool off! ひんやりするぜー
Rehabilitate
(健全化)
I got just what you need こんなもんかな
Amputation
(大切断)
(かけ声のみ) 大切断
Empoison
(毒処方)
Pick your poison!(*2) こいつは効くぜぇ
Vivify
(復活手当)
That's what I'm here for! お代?いらねえよ
Last Stand
(死中活撃断)
This calls for surgery! 死中活撃断

1:何かを治療したときに使う、やや地方色がある言い方
2:悪い選択肢しかないけどどっちを取る?、という意味

Therion(テリオン)

特にSteal SPの「I appreciate it, really!」は性格の悪さが滲みでていい感じですw

分類 英語 日本語
戦闘開始
通常
また面倒事か
Come on, I've got better things to do 始めるか
I'll make this quick やれやれ
ターン
通常
My turn 行くぞ
Outta my way はぁ
どけ
フッ
攻撃 Try this! そこだ
ブースト Let's do this! 頃合いか
I'm ready, are you? 覚悟はいいな
防御 No, you don't! させるか
アイテム
自分に
I'll take this よっと
アイテム
仲間に
here ほらよ
回避 ふっ
Over here! 何処見てるんだ?
戦闘終了
通常
Where's the nearest tavern? 酒でも飲みたい気分だ
Don't start things you can'f finish はぁ、厄介事は御免だ
And that's that. 終わりだ
Steal
(盗む)
What's yours is mine いただきだ
Wildfire
(鬼火)
Don't play with fire 炎を味わえ
HP Thief
(ライフスティーダガー
You're too kind ライフスティーダガー
Shackle Foe
(コウモリ)
Hah, weak そいつはなまくらか?
Armor Corrosive
(フクロウ)
I see through your defenses 守りががら空きだ
Steal SP
(マジックスティーダガー
I appreciate it, really! マジックスティーダガー
Share SP
(SPパサー)
Keep it up! 諦めるな

H'annit(ハンイット)

ハンイットの話し方は中世の英語なので、ネイティブスピーカーも困惑するらしいです。
英語素人がわかるわけない!でもそこにシビれる憧れるぅ!

分類 英語 日本語
戦闘開始
通常
Now, letten us hunten. 行くぞ
Comen if thou daren! 立ち塞がるなら狩るだけだ
Letten us beginen! 始めよう
ターン
通常
What next? わたしの出番だな
Letten my arrow flyen true 狙い打つ……!
Letten the hunt beginen どう動く?
Standest thou against me? Then, be hunted! かかってこい
攻撃 (擬音のみ)
ブースト Holden back nothing! 全力だ!
Comen! 行くぞ!
防御 ふっ
アイテム
自分に
使わせてもらう
アイテム
仲間に
使え
回避 見えている!
I will not be thy prey! 当たるものか
戦闘終了
通常
I honor thee, my quarry 相手にも敬意を払おう
The hunt endeth 終わりだな
Thou didst fight with courage. 良い戦いだった
Rain of Arrows
さみだれ矢)
さみだれ
True Strike
会心の矢)
Aimen carefully, shooten true 狙い撃つ!
Thunderbird
雷鳥
Lightning, done my bidding! 友なる雷よ
Leghold Trap
ねんちゃく糸)
足を止めさせてもらう
Mercy Strike
(すん止め)
命までは奪わない
Arrowstorm
(どしゃぶり矢)
どしゃぶり矢
Take Aim
(ターゲット)
よく狙え

最後に

こうやってまとめてみると、ボイスを全然聞き取れない理由がわかりました。
こいつら癖ありすぎでしょ!!!
現地の文化がわからないと厳しすぎるって・・・。

とはいえ、雰囲気だけでも英語ボイスは味があっていいですよ。
普段日本語でプレイしている方も、試しに英語ボイスに変えてみてはいかかがでしょうか。