<ruby><cloud vision>ポケカの画像から情報読み取ろうぜ(と思ってた)

高専を卒業しました。 卒業エントリ書くかと思ってたのですが内容まとまらないし、卒業直後で書いてもっていう気がしたので時間をおいて書くかなと思ってます。

ということで、今回はcloud Vision APIを使ってポケモンカードの情報を画像から読み出そうっていうことをした記録です。

目次

  1. やること
  2. 今回使う技術
  3. 詳細な要件
  4. 実際のプログラム
  5. やった結果
  6. その他

1.やること

今回やりたいことの最大の目標として、カードの写真パシャパシャしたら持っているカードの一覧自動で作りたいっていうことが目標です。そのなかでも、今回は画像からカードの情報を取得するところを作ってみます。

2.今回使う技術

今回はgoogleの画像処理系のAPIgoogle Cloud Visionを使います。

cloud.google.com

これは、画像に写っているものを識別したり、画像に写っている文字を読み込んだりできます。 特に、この中のOCR機能を用いて、カードの情報を取得します。

3.詳細な要件

今回はポケモンカードの識別を目標にします。 そのなかでも特に

を撮影して、その画像から、

  • SM"XX"のパックの情報
  • シリーズ内のカードの番号(AA/BB)
  • ポケモンのタイプ
  • ポケモンの名称

を取得します。

4.実際のプログラム

ここからは実際のプログラムです。 今回はrubyを使って書きました。 理由としては、今後web appとしてrailsのアプリ作れたらいいかもとおもったからです。(いいかもと思ったなのでやるとは言っていません

ということで実際のプログラムです。 まず、前提として、cloud visionのサイトにアクセスしてAPIキーを取得します。 今回は作成したコードをgithubにあげて管理しようと思っていたので、APIキー等は全てINIファイルに保管して、実施のプログラムには書かないようにします。 このINIファイルを用いて情報を取得するgemにinifileというものがあります。 今回はそれを用いて設定情報を管理します。 まずは使う設定ファイルは以下の感じです

f:id:poolbooyer:20190331134605p:plain
iniファイル
このファイルから情報を読み込む処理は以下のようになります。
f:id:poolbooyer:20190331134819p:plain
iniファイルを読み込む処理
4行目、6行目でAPIキー等の情報を取得して、8行目にリクエストのURLをAPIキーを含めて作成します。 これをもとにして、実際のリクエストを行います。 このリクエストの作り方は以下のページを参考に作りました。

qiita.com

gemもあるらしいのですが、環境に導入できず、困った結果、この記事にたどり着き、試したところで来たのでこの記事を参考に作成しています。 ほぼ共通しているのですが、画像のファイル名をコマンドライン引数で受け取るようにし、画像の解析をINIファイルから取得した形式で行なうように修正しています。

その上で、取得した情報をJSONでパースします。 パースするデータがこんな感じですって言って見せたかったんですが、カードの情報を全部乗せるのは著作権的にもマズそうなんで、やめときます。
その読み込んだデータをもとにして、情報を切り出します。 この先全般著作権に関わりそうなので書くのは控えますが、include?を使って情報を探して、結果を探し出します。 実際にやってみた詳細はもう少し作成が進んだら追記します。 とりあえず今回はこれで

<JavaScript>同じclassのフォームをidと関連付けて取る

忙しい時期をとりあえず超えたので遊びで作ってみるお

目次

  1. やること
  2. 新規で使うこと
  3. 実際のコード

1.やること

今回はおんなじクラスのformから情報をとってきます。 取得する時に、同じクラスのフォームにそれぞれでidを割り振っているので値とidを関連付けて取得します。 読み込んだデータは(今回は特に何もせず)出力だけします。

2. 新規で使うこと

今回は新規で使うことととしては発火させるトリガーをonclickからeventlistenerにしました。 eventlistenerを用いた理由としてはhtml側で発火させるタイミングいじるのもめんどくさくなる(?)のでjs側で全部調整したいと思ったからです。

queryselecter使ってみようかと思ったんですけどidとclassで混乱しそうだったのでやめました

3. 実際のコード

今回はcodepenを使ってみます。 まずは全体の完成物から

formにテキストを入力してpushを押すと、フォームのid+フォームの内容が出てきます。

See the Pen 同classでIDとvalueを関連付けて保存 by Ryo NIioka (@poolbooyer) on CodePen.

まずは今回の大きいところ
イベントを聞くところ

const target = document.getElementById("push");
target.addEventListener('click', view_data, false);

ターゲットになるelementにaddEventListenerを追加します。 ターゲットを指定してその時に使いたいイベントを選択して発火させれます。今回はボタンを選択してポタンがクリックされたら発火します。 次、発火させる内容 今回は情報を読み出す関数と表示する関数を呼び出してるんでそこの話は飛ばします。
実際に情報を呼び出す関数

function get_elements(classname) {
  //取得するクラスの要素数を確認
  var list_len = document.getElementsByClassName(classname).length;
  //取得した値を保持するarray
  var json=[];
  //要素数だけ実施
  for (var i = 0; i < list_len; i++) {
    //対象の要素を1つずつ取得
    input = document.getElementsByClassName(classname)[i];
    //IDと値を取得し、配列に追加
    var add={id:input.getAttribute("id"),value:input.value};
    json.push(add);
  }
  //取得した情報を返す
  return json;
}

やってることとしては配列に対して取得した情報のオブジェクトを追加している形になります。追加結果を返します。
表示する部分はこんな感じ

//取得した情報を表示する関数
function add_data(data){
  //表示用のリストを作成
  var text="<dl>";
  //取得したフォームの個数分実行
  for (var i=0;i<data.length;i++){
    //リストの要素を追加
    text=text+"<dt>id:"+data[i].id+"</dt><dd>value:"+data[i].value+"</dd>";
  }
  //タグを閉じる
  text=text+"</dl>";
  //表示エリアを取得
  var area=document.getElementById("output_area");
  //表示する
  area.innerHTML=text;
}

使いみちもなく作りましたがトリガの勉強になったので良かったです。 (とつぜんのまとめ)

<ruby>kmlつく〜ろ〜

今回は

poolbooyer.hatenablog.jp

の続きです。 前回生成した情報をもとにしてgoogle earthに表示できるようにします。

目次

  1. やること
  2. 使う技術
  3. 具体的なプログラムの構成
  4. 実際のソースコード
  5. その他

1.やること

やることはこんな感じ - Pigで作ったデータを読み込む - 読み込んだデータを年ごとに分ける - それぞれのデータをkml形式に吐き出し という感じです。

2.使う技術

今回はREXMLというものを使います。
REXML
普通にパッケージとして入っているので特に入手のためにアレヤコレヤする必要はありません。

require 'rexml/document'

で使えます。

3. 具体的なプログラムの構成

3.1 そ入力データの確認

今回扱う元のデータはこんな感じです。

f:id:poolbooyer:20190130140355p:plain
入力として使うデータ
(1行目は説明用に書いてますが実際のデータでは消しています) この中のLat(緯度)とLong(経度)に対応する位置にピンを立てます。

3.2 全体の流れ

このプログラムの流れはこんな感じ

  • pigで生成されたデータの読み込み
  • 読み込んだデータの分割(年ごとに配列に振り分け)
  • 配列の各要素ごとに出力を行う

3.3 kmlってそもそもなんだ

kmlの説明をふっとばしてたんでここで説明します。 kmlとはXMLの一種でgoogle earth上で読み込むことで地図上にピンや線を配置することができるようになるものです。 今回はピン表示のみの実現を目指します。

今回作成したいkmlのサンプルはこんな感じ

f:id:poolbooyer:20190130172444p:plain
生成するkmlの例
あんまり詳しく書きませんが今回使うタグの説明はこんな感じ

タグ 説明
Placemark このタグの中に個々のピンの情報を記述
name 位置の名称を記述(ピンのところに表示される)
description ピンのバルーンに表示される内容を記述(html対応)
styleUrl ピンのスタイルを記述したところのURLを記載
Point 位置に関する記述をする
coordinates 位置の情報を経度、緯度、高度の順で記述

スタイルについてはもうちょっと調べて別で記事かくと思います。

4. 実際のソースコード

今回も各部分に分割して書いていきます。

4.1 データの読み込み処理

まずは元になるデータを読み込みます 読み込み方法は普通です。読み込んだデータは改行コード,タブ空白単位で配列に保存します。

コードはこんな感じ

def read_data()
    #データを読み込み
    read_data = []
    File.open("data/output/high/part-r-00000", mode = "rt"){|f|
        read_data = f.read
    }
    #読み込んだデータを改行コードで配列に格納
    read_data=read_data.split("\n")
    line_data=[]
    #配列をtab空白単位で分割
    read_data.each do |line|
        line_data.push(line.split("\t"))
    end
    return line_data
end

特に当たり障りのない普通のファイル読み込みですね。

4.2 データの分割

今のままだと、一行単位で分割されただけになっているので各行のデータをカンマ区切りで分割していく必要があります。 そのための処理を行っていきます。

#データの分割
def divideData(full_data)
    #読み込んだ情報を,単位で分割
    split_data=[]
    full_data.each do |line|
        line.each do |cel|
            split_data.push(cel.split(","))
        end
    end
    return split_data
end

これまた特に当たり障りのない処理ですね。

4.3 情報を年単位でまとめる

このままだと情報が11年分まとまりもなく同じ配列に収まっている状態でいただけないのでそこを解決します。 年単位で情報を分割します。 どうやって分割しようかと思ったのですが、三次元の配列に保存しました。

コード的には三次元ですが、図で書くとこんな感じのイメージです

f:id:poolbooyer:20190130231353p:plain
取り扱うデータのイメージ図

def split_by_year(data)
    #年ごとに配列に一時保存
    stack=[]
    for num in 0..10 do
        stack[num]=[]
    end
    #年ごとに振り分け
    data.each do |line|
        if line[5]=="2008"then
            stack[0].push(line)
        elsif line[5]=="2009" then
            stack[1].push(line)
        elsif line[5]=="2010" then
            stack[2].push(line)
        elsif line[5]=="2011" then
            stack[3].push(line)
        elsif line[5]=="2012" then
            stack[4].push(line)
        elsif line[5]=="2013" then
            stack[5].push(line)
        elsif line[5]=="2014" then
            stack[6].push(line)
        elsif line[5]=="2015" then
            stack[7].push(line)
        elsif line[5]=="2016" then
            stack[8].push(line)
        elsif line[5]=="2017" then
            stack[9].push(line)
        elsif line[5]=="2018" then
            stack[10].push(line)
        end
    end
    return stack
end

4.4 まとめた情報を出力する

さあ、本編(?)です。
今回のいちばん重要な部分です。kml出力処理を行います。
kmlで出力する時の流れとしては

  • オブジェクトを作る
  • 作ったオブジェクトにclass,内容を追加(add_attribute,add_text)
  • 親のオブジェクトに追加(add_element)
  • 全体をファイル出力

といった流れです。 実際のプログラムは下のとおりです。

#kml出力を行う
def create_kml(data)
    #元のノード(コメント)を生成
    doc = REXML::Document.new
    doc << REXML::XMLDecl.new('1.0', 'UTF-8')

    # ルートノードを作成
    kml = REXML::Element.new('kml')
    kml.add_attribute('xmlns','http://www.opengis.net/kml/2.2')
    doc.add_element(kml)
    
    #ドキュメントノードを作成
    root = REXML::Element.new('Document')
    # ルートノードの下に子ノードを追加
    data.each do |line|
        #台風番号を名称に格納
        name = REXML::Element.new('name')
        name.add_text(line[0])

        #親ノードに名称を追加
        placemark = REXML::Element.new('Placemark')
        placemark.add_element(name)

        #説明を追加
        description = REXML::Element.new('description')
        title="<h1>"+line[5]+""+line[0][-2..-1]+"号</h1>"
        date="<dl><dt>date</dt><dd>"+line[5]+"/"+line[6]+"/"+line[7]+" "+line[8]+"時</dd></dl>"
        info="<dl><dt>気圧</dt><dd>"+line[1]+"</dd><dt>データ数</dt><dd>"+line[4]+"</dd></dl>"
        text=title+date+info
        description.add_text(text)
        
        #親ノードに名称を追加
        placemark.add_element(description)

        #スタイル読み込み用タグを追加
        styleurl= REXML::Element.new('styleUrl')
        stylepath="style.kml#"+line[5]
        styleurl.add_text(stylepath)
        placemark.add_element(styleurl)
        #位置情報を格納
        point = REXML::Element.new('Point')
        coordinates = REXML::Element.new('coordinates')
        coordinates.add_text(line[3]+","+line[2]+",0.")
        
        #親ノードに位置情報を追加
        point.add_element(coordinates)
        placemark.add_element(point)
        root.add_element(placemark)
    end
    #元ノードに追加
    kml.add_element(root)
    #ファイルに出力
    #各年のファイル名を作成
    fname="kml/high/"+data[0][5]+".kml"
    File.open(fname, 'w') do |file|
        doc.write(file, indent=2)
    end
end

ファイルのパスのところはちょっと変えてます。 これを使うことでkmlで出力できます。 これで実際に出力されたkmlはこんな感じ

f:id:poolbooyer:20190131000848p:plain
出力されるkml

5. その他

生成されたデータをgoogle earthで読み込むとこうなります。

f:id:poolbooyer:20190131000936p:plain
google earthで読み込んでみたよ