※別メディアで2019.12.17に公開した記事の移植です。
こんにちは、のびすけです。
沖縄オープンデイズ2019(#ood2019)のハッカソンで、バス路線検索のアプリケーションを作ろうとしていたチームがありました。
沖縄を観光中の観光客が、バス停の名前を入力すると路線検索をしてくれる
といったアプリケーションを作ろうとしていたチームがあったのですが、 それ以前の課題(後述)がありそうだなぁと思ったので、ちょっと僕なりにアプローチしてみました。
実はハッカソン中に作ってはいたのですが、なかなかまとめられず今に至る……
遅れてすみません。(Node.jsアドベントカレンダーの皆様)
沖縄の地名、読めない。
「斎場御嶽」。皆さん読めますか? 僕には読めませんでした。てか読み方聞いて衝撃でした。
他にも勢理客
や保栄茂
なども手強いです。
そうなんです、 バス停を発見してバス路線を調べようとしても、そもそも読み方が分からない問題があります。
町で見かけたとき、調べるのに時間がかかりすぎる。
元のテキストがあってコピペできる状況なら、ググればすぐ読み方は分かりますよね。
ただ、バス停や駅などの外にいる状況でこれらの字面を見たとして、パッと検索出来るでしょうか?
「斎場御嶽」の場合、僕だったら
- 「さいとう」と入力
- 「斎藤」に変換
- 藤の字を削除して「斎」だけ残す
- 次の文字も1~3を繰り返して作る
- 1~4を繰り返して最終的にググって読み仮名を知る
みたいな感じになるかなと思いました。
これは バス停や駅などの一刻を争う場面ではかなりのタイムロスなのでは無いでしょうか?
ということで、この問題を解決していきたいなと思います。
LINE BRAIN OCRで何とかならないか
ちょうどLINE BRAIN OCRのAPIがベータリリースされていたのを思い出したので使えないか試してみました。
DEMOページでとりあえず手書きの「斎場御嶽」を読み込ませたら、けっこう汚い字でもそれっぽく読んでくれました。
https://www.instagram.com/p/B57Yq7ijJg2/
これで、 街で見かけた地名などの写真を投げるとテキストで返してくれるLINE BOTが作れそうですね。
OCR APIをNode.jsから扱ってみる
ということで実装です。LINE BRAIN OCR APIをNode.jsから触ってみました。一応、ここだけを切り出してQiitaにもメモしてあります。
現時点だとLINE DEV DAY 2019に参加して登録したがドキュメントにアクセスできる模様です。
(もしかしたらそれ以外のルートでアクセスできる人もいるかもしれませんが)
LINE OCRのサンプルリクエスト(ドキュメントは一般公開されてないっぽい)に以下のようなコードがあり、これをNode.jsで再現してみました。
curl -X POST https://ocr-devday19.linebrain.ai/v1/detection \
-H 'X-ClovaOCR-Service-ID: PMqTDgBsucfsyvi7pJEsbIxMIUeNQWDg' \
-H "Content-Type: multipart/form-data" \
-F "image=@./sample.jpg" \
-F scaling=false
axiosでのヘッダーの指定方法でform-dataが提供してくれる、form.getHeaders()
を使う必要がある点で若干ハマったのでNode.jsからmultipart/form-dataで画像を送りたい人は気をつけてください。
'use strict';
const fs = require('fs');
const FormData = require('form-data');
const axios = require('axios');
const url = `https://hogehoge.com/hogehoge`; //ポスト先のエンドポイントURL
const imagePath = `./public/image.png`; //画像のパス
const file = fs.createReadStream(imagePath);
const form = new FormData();
form.append('image', file);
const config = {
headers: {
'X-HOGEHOGE-HEADER': 'xxxxxxx', //APIごとのヘッダーなど
...form.getHeaders(),
}
}
axios.post(url, form, config)
.then(res => console.log(res.data)) //成功時
.catch(err => console.log(err)); //失敗時
LINE OCRのNode.js向け非公式SDK
ここまでの実装内容などをまとめて、すごく雑な非公式SDKを作ってみました。
使い方も中身もシンプルです。
$ npm i @n0bisuke/lineocr
'use strict';
const Brain = require('@n0bisuke/lineocr');
const ocr = new Brain(`サービスID`);
const IMAGE_PATH = `./sample.png`; // 画像パス
//RECOGNITION - 文字認識のみを行います。もしくは、文字領域の検出と認識を順に行います。
const options = {
entrance: 'detection', //optional: Default value: recognition
};
ocr.recognition(IMAGE_PATH, options)
.then(res => console.log(res.data)) //成功時
.catch(err => console.log(err.response)); //失敗時
こんな雰囲気で使えるので、試せる方(β版にアクセス出来る方は限られてそうですが)は使ってみてください。
斎場御嶽というテキストは解釈されたが……
テキストは解釈されました。 ただ、読み方は分からないですよね。
このテキストをGoogleに投げるくらいでもだいぶ良さそうですが、せっかくなら読み方も教えてくれるBOTを作ってみたいなと思い、APIやWebサイトに投げて回答を得るチャレンジです。
ちなみに読み方答え合わせ
だいぶ読み方を引っ張ってしまいましたが、それぞれ斎場御嶽(せいふぁーうたき)、勢理客(じっちゃく)、保栄茂(びん)
と読みます。
初見だとほぼ無理ゲーですね笑
脱線しますが、他にも調べてて出てきて気になったのは金武(きん)
とかは武の字の読みどこに消えたんだ感ありましたね(笑)
うちのスクールの1階に北谷食堂(ちゃたん食堂)っていう居酒屋が入ってて良く授業終わりに行くんですけど、いつも北谷(ちゃたん)って何だ?って思ってた謎が解けました。
沖縄の地名だったんですね(笑)読めない訳ですね。
……とまぁ。脱線しましたが、この読み方や地名の解釈は出来るのでしょうか。
1人目の挑戦者: Gooラボ ひらがな化 API
Gooが提供しているAPIです。 漢字を入れるとひらがな化して返してくれます。
さて彼は沖縄の地名を読めるのでしょうか……
斎場御嶽=さいじょうみたけ まぁ確かにそうなのかもしれないですが……
勢理客=せりきゃく まぁ確かに……
沖縄の地名の読みにGooラボは勝てませんでした。
2人目の挑戦者: Wikipedia (MediaWiki API)
有名な地名であればWikipediaにも載っているということで、この箇所を抜き出してみます。
Wikipediaのページにはだいたいひらながの読み方が書いてあるので、この箇所を抜き出します。
WikipediaのAPIにリクエストを投げて帰ってきた本文から正規表現で抜き出します。
'use strict';
const axios = require('axios');
// const KEYWORD = `斎場御嶽`;
const KEYWORD = `豊見城`;
// const KEYWORD = `勢理客`;
// const KEYWORD = `保栄茂`;
const WIKIPEDIA_API_URL = `http://ja.wikipedia.org/w/api.php?format=json&action=query&prop=revisions&rvprop=content&titles=${encodeURIComponent(KEYWORD)}`
axios.get(WIKIPEDIA_API_URL)
.then(res => {
const pages = res.data.query.pages;
const page_id = Object.keys(pages)[0]; //Wikipedia PageID
const page_content = pages[page_id].revisions; //Wikipedia
const body = page_content[0]['*'];
// console.log(body);
const regexp = new RegExp(`'''${KEYWORD}'''((.*?))は`);
const yomigana_all = body.match(regexp)[1]; // みぐすく・とみしろ など
const yomigana = yomigana_all.match(/[ぁ-ゞー]+/)[0]; //ひらがな以外が出てきたら区切り文字として最初の読みを返す
console.log(yomigana)
}) //成功時
.catch(err => console.log(err)); //失敗時
$ node wikipedia.js
とみぐすく
結果はこんな感じで、 豊見城=とみぐすく、 斎場御嶽=せいふぁーうたきなどいい感じに読み仮名を取得できました。
ただ、当たり前なのですが、 Wikipediaにページが存在しない地名には対応できず、今回試した中だと保栄茂の読み方は分かりませんでした。
3人目の挑戦者: Weblio
Weblioは優秀で、結構多くの地名に対応してくれています。
例えば斎場御嶽だとこんな感じのページになります。
WeblioはAPIが見当たらなかったのでスクレイピングしてみます。
'use strict';
const axios = require('axios');
// const KEYWORD = `斎場御獄`;
// const KEYWORD = `斎場御嶽`;
// const KEYWORD = `豊見城`;
// const KEYWORD = `勢理客`;
// const KEYWORD = `保栄茂`;
const KEYWORD = `世富慶`;
const WEBLIO_URL = `https://www.weblio.jp/content/${encodeURIComponent(KEYWORD)}`
axios.get(WEBLIO_URL)
.then(res => {
const pages = res.data;
let body = pages.match(/<title>(.*?)<\/title>/)[1];
let yomigana = (body.match(/\((.*?)\)/)) ? body.match(/\((.*?)\)/)[1] : '';
if(yomigana === ''){
body = pages.match(/name="description" content="(.*?)"/)[1];
yomigana = body.match(/ふりがな:(.*?)種別/)[1];
}
console.log(body);
console.log(`[${yomigana}]`);
}) //成功時
.catch(err => console.log(err)); //失敗時
Wikipedia同様に、情報があればうまくいきました。
世富慶で調べるとWeblioにも検索結果が存在しなく。
世冨慶で調べると結果が出てくるという惜しい結果に。 ググると世富慶
も世冨慶
も両方出てきますが、Weblio内でエイリアスが貼られている訳では無さそうなので惜しいところまで来てるけど結果は読み込め無さそうです。
結果出来上がったBOT
ということで結構惜しいですよね。一つのAPIやサイトの情報だけでは厳しく、複数のWebサイトなどの情報を駆使して団体戦でやっと戦えるといったところでしょうか。 沖縄強い。
とりあえずはWikipediaに投げる -> Weblioに投げる -> Gooに投げる
みたいな順番で処理を書いてBOTにしてみています。
ということであくまでも現時点版ですが、こんな感じの挙動になっています。
Wikipediaのみ
↓
Wikipedia+Weblioも導入
けっこう読み込んでくれるのも増えてきて実際に使えそうな雰囲気も出てきました。
ただやはりOCRの時点で読み込みが出来ないとそもそもWikipediaなどに投げても違う結果になりますよね。
僕が書く豊見城(とみぐすく)は、何回やってもキビしいですね……
結果とまとめ
沖縄強い。
沖縄の地名の難しさに勝つのは難易度がとても高い…… という結果です。
複数の辞書やWebサイトなどの情報を元に団体戦で挑まないといけないですね……
あと、僕が豊見城を書くと毎回(何回か書いて試した)「曲見」と認識されてしまうんですけど、これは僕の字が汚いの問題なのかLINE BRAINの精度の問題なのか気になる
ところですね。
とりあえず、まだまだ課題があることが分かったので、今後も挑んで行きたいと思います。(たぶん気持ちだけ)
沖縄の皆さんからのコメントお待ちしております。それでは!