この記事はGoogle Cloud Platform その2 Advent Calendar17日目の記事です。
こんにちは。卒論に苦しむ大学生です。
突然ですが、mgramをご存知ですか?
105個の質問に答えることで、性格診断ができるサービスです。ちょっと前に流行りましたよね
「今度の同窓会で、自分と似てる性格の人とお喋りできたら楽しそうだえ」
そう思ったのが、この物語のはじまりでした。
作成物
Go で書いたプログラムに、mgram診断のURLを引数として渡すと・・・
診断結果をGoogle Spread Sheetに記述してくれます。
本当はwebアプリにして、
自分のURLを入力→性格が近い人を表示
という形にしたかったのですが・・・。
ごめんなさい。学歴を得るための修行(*1)で忙しく、開発ができませんでした。
以下の記事では、今回使用した
- Google Cloud Vision API
- Google Apps Script
- Go
- goquery
のプログラムを晒していこうと思います。
最終的な全コードはgithubにおいておきます。
Google Cloud Vision APIで画像からテキストを抽出する編
Cloud Vision APIとは
Google Cloud上で使える、機械学習を使った画像分析のサービスです。画像から物体情報を抽出したり、テキストを抽出する、といったことがREST API形式でリクエストを投げるだけで簡単に実現できます。
公式サイトでは、ブラウザから画像を上げることでCloud Visionをお試しできるので、気軽に使ってみませう!
料金については、テキスト検出や顔検出などのそれぞれの機能が一ヶ月1,000回まで無料で、その後は1,000回あたり$1.50となっています。詳しくは公式サイトを確認してください。
また、公式ドキュメントも充実しており、APIを使用するためのクライアントライブラリ(ベータ版)もC#,Go,Java,Nodejs,PHP,Python,Ruby向けに用意されている上に、各言語ごとのサンプルコードとAPIドキュメントも公開されているので、割とハードル低めにつかうことができました。
今回は、Goのクライアントライブラリを使ってCloud Vision APIを利用しました。ライブラリを使うための手順は、前述した公式ドキュメントを参照するのが確実なので、割愛!
mgramのwebページから画像などを抜き取る
mgarmの診断URLから、ユーザーの名前と画像のURLを抜き取ります。いわゆるスクレイピングです。
個人的に何度か使ったことのあるgoqueryを使用しました。jQueryぽくセレクタを指定することができます。
コードは以下の通り。
// 標準出力からURLを受け取る
mgramURL := os.Args[1]
doc, err := goquery.NewDocument(mgramURL)
if err != nil {
fmt.Print("url scarapping failed")
}
// セレクタを指定してURLなど取得
imgURL, _ := doc.Find(".image-frame > img").Attr("src")
userName := doc.Find(".introSection > .sectionHeading").Text()
//「〇〇の診断結果」を「〇〇」にする(=名前だけ抽出)ため、テキスト末尾5文字をさようなら
userName = string([]rune(userName)[0 : utf8.RuneCountInString(userName)-5])
これで、診断画像のURLと、名前を取得できました。
画像をCloud Vision APIに投げてみる
先程のコードで画像のURLが取得できているので、今度はAPIにリクエストを飛ばす部分です。
といっても、公式のサンプルコードがあれば簡単に投げれてしまう
サンプルコードに少し変更加えたものがこちらです。
ctx := context.Background()
client, err := vision.NewImageAnnotatorClient(ctx)
if err != nil {
return nil, err
}
//先程取得したmgram診断画像のURLを使う
image, err := vision.NewImageFromURI(imgURL)
texts, err := client.DetectTexts(ctx, image, nil, 1000)
if err != nil {
fmt.Println("error is occured")
log.Fatal(err)
return nil, err
}
if len(texts) == 0 {
fmt.Println("No text found.")
return nil, nil
}
// 取得したテキストを標準出力で表示
for _, annotation := range texts {
fmt.Println(annotation.Description)
}
//はじめの要素nに全てのテキストが改行区切りで入っているので配列にする
textCandidate := strings.Split(texts[0].Description, "\n")
//取得したテキストには不要な文字列も入っているので、性格因子の部分だけ抽出
for _, value := range textCandidate {
fmt.Println(value)
if strings.HasPrefix(value, "#") && len(value) > 0 && !checkRegexp(`[A-Za-z]`, value) {
personalities = append(personalities, string([]rune(value)[1:]))
}
}
fmt.Println("vision api successed")
公式のサンプルコードからの変更点は、
- URLから画像を取得する部分
- mgramの性格因子だけ抜き取る部分
です。
しかし、ここで問題点!
mgramの診断画像では、文字の背景色がランダム(?)に違い、一部の背景色ではCloudVisionがうまく文字を認識してくれない事案が発生しました
そこで、診断画像を加工しました!
テキスト抽出のしやすい画像に加工する
加工した画像はこんな感じ。
before | after |
---|---|
|
画像の2値化。これならテキストをちゃんと読み取ってくれるでしょう。
(ついでに画像中央のいらない文字を消し去りました。)
こんな加工も、Goなら標準ライブラリで完結しちゃいます!すごー
コードは以下の通りです。
//mgramの画像を読取る
response, err := http.Get(imgURL)
defer response.Body.Close()
srcImg, _, err := image.Decode(response.Body)
if err != nil {
return
}
// 加工後の画像の入れ物を作る
srcBounds := srcImg.Bounds()
//書き出し用のイメージを作る
dest := image.NewRGBA(srcBounds)
// 1ピクセルごとに編集していきます
for v := srcBounds.Min.Y; v < srcBounds.Max.Y; v++ {
for h := srcBounds.Min.X; h < srcBounds.Max.X; h++ {
//該当ピクセルの色を取得してグレースケールに変換
c := color.GrayModel.Convert(srcImg.At(h, v))
gray, _ := c.(color.Gray)
// しきい値で白と黒に二値化
if gray.Y > 250 {
gray.Y = 255
} else {
gray.Y = 0
}
// 2値化した色を書き込む
dest.Set(h, v, gray)
//画像中央部分を消す
if srcBounds.Max.Y/3 < v && v < srcBounds.Max.Y*2/3 && srcBounds.Max.X/3 < h && h < srcBounds.Max.X*2/3 {
dest.Set(h, v, color.RGBA{255, 0, 0, 0})
}
}
}
outfile, _ := os.Create("temp.png")
defer outfile.Close()
png.Encode(outfile, dest)
難しいことはしておらず、1ピクセルごとに色を変換しているだけです。
これをCloud Visionに投げれば、大丈夫だろう。
GASでスプレッドシートに登録する編
Google Apps Script とは
Google Apps Script(GAS) は、Googleが提供するサービス(Google Spread SheetやGoogle Documentsなど)と連携したアプリを作るためのプラットフォームです。
(公式サイト)
これを使えばGoogleのツールを使っていろいろできるようです。
今回はスプレッドシートに紐づけてGASを使う!
とりあえずを動かしてみる
とりあえず、GETでアクセスすると反応してくれるコードを書きます。
新しいスプレッドシートを作り、「ツール」メニューから「スクリプトエディタ」を開きます。
エディタに、以下のコードを貼り付けます。
function doGet(e) {
return ContentService.createTextOutput("hello...");
}
そして、公開メニューから、「ウェブアプリケーションとして導入」を選択し、
アプリケーションにアクセスできるユーザーを「全員(匿名ユーザー含む)」に設定。
その後導入ボタンを押すと、承認が求められるので、承認します。
画面に従ってページ遷移すると、注意されますが、詳細リンクを開いて移動します。
公開します。
これで準備完了。
GETパラメータをスプレッドシートに登録する
GASの方で、GETパラメータをスプレッドシートに書き込むようにします。
スクリプトエディタから、以下のようにコードを変更します。
function doGet(e) {
var rowData = {};
if (e.parameter == undefined) {
//パラメータ不良の場合はundefinedで返す
var getvalue = "has no parameter"
rowData.value = getvalue;
var result = JSON.stringify(rowData);
return ContentService.createTextOutput(result);
}else{
var id = '自分のスプレッドシートのid';
var sheet = SpreadsheetApp.openById(id).getSheetByName("書き込みたいシート名");
var array = [ e.parameter.name,e.parameter.mgramURL, e.parameter.p1 , e.parameter.p2 , e.parameter.p3 , e.parameter.p4, e.parameter.p5, e.parameter.p6, e.parameter.p7, e.parameter.p8 ];
//シートに配列を書き込み
sheet.appendRow(array);
//書き込み終わったらOKを返す
var getvalue = "ok"
rowData.value = getvalue;
var result = JSON.stringify(rowData);
return ContentService.createTextOutput(result);
}
}
idやシート名は、適宜自分のものに変える必要があります。
idについては、スプレッドシートのURLがhttps://docs.google.com/spreadsheets/d/「id」
になっているので、
そこからコピペしましょう!
コードを変更したら、先程と同じように公開をします。
コードを更新するためには、プロジェクトバージョンを「新規作成」にする必要があります。
更新した上で、「在のウェブ アプリケーションの URL」にパラメータ(例えば、?name=テスト&p1=テスト)をつけてアクセスするとシートにパラメータの値が入力されているはずです。
Cloud Vision APIの結果をスプレッドシートに書き込む
残すは、Cloud Vision APIの結果をスプレッドシートに書き込むよう連携するだけです。
といってもやることは、
文字化けしないようにパラメータをエンコードしてhttpリクエストを飛ばす
だけです。
//パラメータを作る
params := "?name="
params += url.QueryEscape(name)
params += "&mgramURL="
params += url.QueryEscape(mgramURL)
for key, value := range personalities {
params += "&p"
params += strconv.Itoa((key + 1))
params += "="
params += url.QueryEscape(value)
}
// GETリクエスト飛ばす
resp, error := http.Get(sheetURL + params)
if error != nil {
fmt.Println("errror occured")
return error
}
defer resp.Body.Close()
これで必要な処理は下記終わりました。
あとは、これまでのコードを連携させるだけです。
連携したコードはgithubをみてください!
おわりに
感想
実装してみて、
- CloudVisionで単純な画像で文字認識させたい場合、画像を加工してから投げるのがよさそう
- Goを使えば簡単な画像加工はすぐにできて楽しい
- スプレッドシートにデータを保存する方法として、パラメータを使うのは簡単でよい
- Goらしい書き方とか全く知らないので、そろそろちゃんと勉強したい
といった気持ち。
今回くらい単純な機能であれば、CloudVisonもGASも難なく使うことができました。
さくっとアプリを作りたいときに使えそうです。
しかしやっぱり、webアプリにしたい。
修行(*1)が厳しい中、完成するかはわかりませんが、完成したらまたQiitaに書きたいと思います。
参考
https://qiita.com/roana0229/items/fbff1a03d127dac7bdc4
https://qiita.com/takanakahiko/items/e8123ee6b565c6ee8d8e
https://twinbird-htn.hatenablog.com/entry/2017/04/23/001743
*1) 卒論の執筆をして、大学を卒業するために頑張る行為