LoginSignup
5
3

More than 5 years have passed since last update.

Cloud Vision,GAS,mgramで高校の同窓会を盛り上げたかった

Last updated at Posted at 2018-12-16

この記事はGoogle Cloud Platform その2 Advent Calendar17日目の記事です。

こんにちは。卒論に苦しむ大学生です。

突然ですが、mgramをご存知ですか?

105個の質問に答えることで、性格診断ができるサービスです。ちょっと前に流行りましたよね

「今度の同窓会で、自分と似てる性格の人とお喋りできたら楽しそうだえ」

そう思ったのが、この物語のはじまりでした。

作成物

Go で書いたプログラムに、mgram診断のURLを引数として渡すと・・・
mgram.gif

診断結果をGoogle Spread Sheetに記述してくれます。
スクリーンショット 2018-12-17 1.45.38.png

本当は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ドキュメントも公開されているので、割とハードル低めにつかうことができました。:smile:

今回は、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にリクエストを飛ばす部分です。
といっても、公式のサンプルコードがあれば簡単に投げれてしまう:man::baseball:
サンプルコードに少し変更加えたものがこちらです。

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がうまく文字を認識してくれない事案が発生しました:sleeping:

そこで、診断画像を加工しました!:smile:

テキスト抽出のしやすい画像に加工する

加工した画像はこんな感じ。

before after
spice.png out.png

画像の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...");
}

そして、公開メニューから、「ウェブアプリケーションとして導入」を選択し、
アプリケーションにアクセスできるユーザーを「全員(匿名ユーザー含む)」に設定。
その後導入ボタンを押すと、承認が求められるので、承認します。
画面に従ってページ遷移すると、注意されますが、詳細リンクを開いて移動します。
ログイン_-_Google_アカウント.png

公開します。

これで準備完了。

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) 卒論の執筆をして、大学を卒業するために頑張る行為

5
3
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3