tachiken's blog

開発、プログラミング、その他

Google Vision APIを使って名刺の内容をテキストファイル化

機械学習系のAPIを実装したことがなかったので、これを使って簡単なアプリケーションを 作ってみた。

チュートリアルとサンプル アプリケーション  |  Google Cloud Vision API  |  Google Cloud Platform

手順

まずはGoogle Cloud Platformに移動し、『Google Cloud Vision APIの有効化』を行う。 console.cloud.google.com f:id:tachiken0210:20171030112603p:plain そして『認証情報の作成』からAPIキーを作成し、 f:id:tachiken0210:20171030112645p:plain そこに表示された『自分のAPI キー』をメモする。(※これをアクセストークンとして使う。)

APIの有効化についての詳しい説明は他記事に色々記載がありますので、 そちらをご参考にしていただければとおもいます^^;

さて、テキスト抽出についてですが以下の記事を参考にさせていただきました。 qiita.com

というか、この記事でほとんど名刺のテキスト抽出はやられてますね。。。 ということで私の方ではこれを利用して、

①複数の名刺のテキスト抽出
②名刺ごとにテキストファイルを作成し、そこに名刺の内容を記載する
③テキストファイルのタイトルを会社名に書き換えるという

ということができるスクリプトを書きました。

ソースコード

以下ソースコードになります。

import base64
import requests
import glob
import os

#同一フォルダ内のpngファイル名を抽出
def detect_all_files(access_token=None):
    file_names = [os.path.basename(r) for r in glob.glob("./*.png")]
    texts = []
    for i, file in enumerate(file_names):
        text = detect_text(file,access_token)
        write_text(i,text)
        texts.append(text)
    return texts

#テキストファイルを作成し、ファイル名を株式会社にする
def write_text(i,text):
    text_list = text.split("\n")
    g = open('company_{}.txt'.format(i), 'w')
    for text in text_list:
        if "株"  in text:
            company_name = text
            os.rename('company_{}.txt'.format(i),str(company_name))
        g.write(text)
        g.write("\n")
    return text

#名刺の画像データからテキストを抽出する。
def detect_text(image_file, access_token=None):
    with open(image_file, 'rb') as image:
        base64_image = base64.b64encode(image.read()).decode()

    url = 'https://vision.googleapis.com/v1/images:annotate?key={}'.format(access_token)
    header = {'Content-Type': 'application/json'}
    body = {
        'requests': [{
            'image': {
                'content': base64_image,
            },
            'features': [{
                'type': 'TEXT_DETECTION',
                'maxResults': 5,
            }]
        }]
    }
    response = requests.post(url, headers=header, json=body).json()
    text = response['responses'][0]['textAnnotations'][0]['description'] if len(response['responses'][0]) > 0 else ''
    return text

実行は以下のようにdetect_allk_filesメソッドの引数にAPIキーを入れて実行します。

detect_all_files(access_token='APIキー(アクセストークン)を入力')

処理の流れ

詳細な流れとしては以下のことをしています。
ソースコードのファイルと同一のフォルダに名刺の画像ファイル(png)をおく
f:id:tachiken0210:20171030115614p:plain f:id:tachiken0210:20171030115620p:plain ・globを使ってフォルダ内のpngファイルを全て取得
・それらの画像ファイルからGoogle Vision APIを使ってテキストリストを取得
・テキストリストをsplitメソッドを使って要素に分ける。その中で"株"という文字が入っている要素を会社名とみなし、os.renameによりファイルのタイトルとする。

得られる結果

以上により、以下のように名刺の内容が記載されたテキストファイルが得られます。 f:id:tachiken0210:20171030120801p:plain f:id:tachiken0210:20171030120924p:plain

名刺のタイトルにその人の名前を入れたかったのですが、文字情報から名前と断定することが中々難かしく。。。
文字のサイズ情報が取得できれば名前を断定できるのかな?(名刺は名前が一番サイズ大きいっぽいので)

Pythonエンジニア認定試験に合格しました

本日Pythonエンジニア認定試験を受験し、合格しました。

↓試験についてはコチラ www.pythonic-exam.com

全国のテストセンターで受験することができます。 問題についてなどは守秘義務があるので深くは語れないので、 簡単に書いておこうと思います。

筆者ステータス

自分は普段の業務はプログラミングは一切やらず、趣味で土日に少し触るくらいです。

Python歴は2ヶ月くらいです。

機械学習の勉強で少し使っているくらいです。

出題について

認定教材としては以下の本のみです。
www.oreilly.co.jp

この本の全て内容から出るわけではなく、以下のサイトにあるように 1章から14章までしか出題範囲ではありません。
基礎試験 | 一般社団法人Pythonエンジニア育成推進協会

出題数の比重も異なるので、時間がない方は比重が多い章から勉強していけば 良いのではないかと思います。

学習の仕方

私の試験に際しての学習ですが、2週間前から上の本のみを 勉強しました。

平日1日1時間、休日3時間くらいでしょうか。

合格した感想

結果925/1000という中々の好成績で合格することができました。

上にも書いてあるように認定教材しか勉強しないでも この点数が取れてしまいます。
要するに、、そういうことですwww  試験がこの認定教材に準じている、ということですね。
勉強期間も1週間ぐらいでよかったかも。。

まあたとえ不合格でもすぐ再試験できますし、どのような問題が出るか把握できるので、 勉強不足だったとしてもひとまず受験してみるのはありかもしれません。

Python 機械学習 データを訓練データとテストデータに分ける

例えばarray_dataという506個の14この特徴量をもった numpy arrayがあったとして、それの90パーセントを 訓練データ、残りの10パーセントをテストデータとしたいとする。

こんな時は sklearnのtrain_test_splitメソッドを使うと便利。

from sklearn.model_selection import train_test_split
train_data,test_data =train_test_split(array_data, train_size = 0.9, random_state = 1)

これにより、

train_data (455, 14)
test_data (51, 14)

と、データが2つに分かれる。

Ruby on Rails Google calendar APIを使ってカレンダー情報を取得する。

Google calendar APIを使ってカレンダー情報を取得を試してみた。 以下はAPIインスタンス作成から情報取得までの流れ。

def update_schedule
  client = Google::APIClient.new #APIのインスタンス作成
  client.authorization.access_token = current_user.token #トークンを入れる(もしかしたら不要かも)
  client.authorization.client_id = ENV['GOOGLE_CLIENT_ID'] # Google Developerで取得したclient id を入れる。.envに記載
  client.authorization.client_secret = ENV['GOOGLE_CLIENT_SECRET'] #Google Developerで取得したclient secret を入れる。.envに記載
  client.authorization.refresh_token = current_user.refresh_token #リフレッシュトークンを入れる
  service = client.discovered_api('calendar', 'v3') #APIの種類を指定(google calendarAPIを指定)
  
@responses = client.execute(
  :api_method => service.events.list,
  :parameters => {'calendarId' => 'primary',
    'timeMin'=> (Time.now - 10.months).iso8601,
    'timeMax'=> (Time.now + 1.months).iso8601,
    'maxResults' => 2500},
  :headers => {'Content-Type' => 'application/json'})
#インスタンスを実行する。executeの引数には色々指定でき、取得期間などが指定できる。
 @responseにAPIからのレスポンスがはいる。

  events = []
  @responses.data.items.each do |item|
    events << item
  end
#レスポンスからイベントのデータのみを取ってきてリストに入れる。

python splitの使い方

よくcsvからデータを抜き取る時、元のデータを自分の扱い安い形に 変換したい時がよくあると思う。

その際によく使うsplitメソッド。

例えば以下、

for row in data_reader:
    print(row)
    housing_data.append(split_row)

でrowの中身が

['0.00632  18.00   2.310  0  0.5380  6.5750  65.20  4.0900   1  296.0  15.30 396.90   4.98  24.00']

だったとする。

これだとリストの中身が1つになってしまい、とても扱いにくい。

そんな時splitメソッドを用いると

for row in data_reader:
    split_row = row[0].split()
    print(split_row)
    housing_data.append(split_row)

となりsplit_rowは

['0.00632', '18.00', '2.310', '0', '0.5380', '6.5750', '65.20', '4.0900', '1', '296.0', '15.30', '396.90', '4.98', '24.00']

となり、扱いやすい形となる。

pythonで強化学習を試す

強化学習が巷でブームになっておりますが、私もpython 初心者ながらこれについて手探りながら勉強しています。

その中でとても参考になったのが以下のHironsanさんの記事。

qiita.com

強化学習についての説明はとてもわかりやすかったのですが、実際の記事に記載してあるコードや引用元のUC Berkeleyの中身をみてもメソッドの一部が欠けていたので、自分でコード補完して動かしてみました。 コードは以下、

import random
import operator, math, random, copy, sys,  os.path, bisect
import pandas as pd


def print_table(table, header=None, sep=' ', numfmt='%g'):

    justs = [if_(isnumber(x), 'rjust', 'ljust') for x in table[0]]
    if header:
        table = [header] + table
    table = [[if_(isnumber(x), lambda: numfmt % x, x)  for x in row]
             for row in table]
    maxlen = lambda seq: max(map(len, seq))
    sizes = map(maxlen, zip(*[map(str, row) for row in table]))

def argmin(seq, fn):

    best = seq[0]; best_score = fn(best)
    for x in seq:
        x_score = fn(x)
        if x_score < best_score:
            best, best_score = x, x_score
    return best

def argmax(seq, fn):

    return argmin(seq, lambda x: -fn(x))

def vector_add(a, b):

    return tuple(map(operator.add, a, b))

orientations = [(1,0), (0, 1), (-1, 0), (0, -1)]

def turn_right(orientation):
    return orientations[orientations.index(orientation)-1]

def turn_left(orientation):
    return orientations[(orientations.index(orientation)+1) % len(orientations)]

import random


class MDP:

    def __init__(self, init, actlist, terminals, gamma=.9):
        self.init = init
        self.actlist = actlist
        self.terminals = terminals
        if not (0 <= gamma < 1):
            raise ValueError("An MDP must have 0 <= gamma < 1")
        self.gamma = gamma
        self.states = set()
        self.reward = {}

    def R(self, state):

        return self.reward[state]

    def T(self, state, action):

        raise NotImplementedError

    def actions(self, state):

        if state in self.terminals:
            return [None]
        else:
            return self.actlist


class GridMDP(MDP):

    def __init__(self, grid, terminals, init=(0, 0), gamma=.9):
        grid.reverse()  # because we want row 0 on bottom, not on top
        MDP.__init__(self, init, actlist=orientations,
                     terminals=terminals, gamma=gamma)
        self.grid = grid
        self.rows = len(grid)
        self.cols = len(grid[0])
        for x in range(self.cols):
            for y in range(self.rows):
                self.reward[x, y] = grid[y][x]
                if grid[y][x] is not None:
                    self.states.add((x, y))

    def T(self, state, action):
        if action is None:
            return [(0.0, state)]
        else:
            return [(0.8, self.go(state, action)),
                    (0.1, self.go(state, turn_right(action))),
                    (0.1, self.go(state, turn_left(action)))]

    def go(self, state, direction):

        state1 = vector_add(state, direction)
        return state1 if state1 in self.states else state

    def to_grid(self, mapping):

        return list(reversed([[mapping.get((x, y), None)
                               for x in range(self.cols)]
                              for y in range(self.rows)]))

    def to_arrows(self, policy,U):
        arrows_result={}
        chars = {
            (1, 0): '>', (0, 1): '^', (-1, 0): '<', (0, -1): 'v', None: '.'}
        for(s,a)in policy.items():
            if policy[s] == None:
                arrows_result[s] = U[s]
            else:
                 arrows_result[s]=chars[a] 
        
        return self.to_grid(arrows_result)

# ______________________________________________________________________________


sequential_decision_environment = GridMDP([[-0.04, -0.04, -0.04, +1],
                                           [-0.04, None,  -0.04, -1],
                                           [-0.04, -0.04, -0.04, -0.04]],
                                          terminals=[(3, 2), (3, 1)])

# ______________________________________________________________________________

# ______________________________________________________________________________



sequential_decision_environment2 = GridMDP([[-1, -0.04, -0.04, -0.04,+1],
                                           [-0.04, -0.04,  None,-0.04, -0.04],
                                           [-0.04, None,  -0.04, -0.04,0.04],
                                           [-0.04, -0.04, -0.04,-0.04, -0.04]],
                                          terminals=[(4, 3), (0, 3)])

# ______________________________________________________________________________


def value_iteration(mdp, epsilon=0.001):

    U1 = {s: 0 for s in mdp.states}
    R, T, gamma = mdp.R, mdp.T, mdp.gamma
    while True:
        U = U1.copy()
        delta = 0
        for s in mdp.states:
            U1[s] = R(s) + gamma * max([sum([p * U[s1] 
                                             for (p, s1) in T(s, a)])
                                        for a in mdp.actions(s)])
            delta = max(delta, abs(U1[s] - U[s]))
        if delta < epsilon * (1 - gamma) / gamma:
            return U


def best_policy(mdp, U):

    pi = {}
    for s in mdp.states:
        pi[s] = argmax(mdp.actions(s), lambda a: expected_utility(a, s, U, mdp))
    return pi, U

def expected_utility(a, s, U, mdp):

    return sum([p * U[s1] for (p, s1) in mdp.T(s, a)])

# ______________________________________________________________________________


def policy_iteration(mdp):

    U = {s: 0 for s in mdp.states}
    pi = {s: random.choice(mdp.actions(s)) for s in mdp.states}
    while True:
        U = policy_evaluation(pi, U, mdp)
        unchanged = True
        for s in mdp.states:
            a = argmax(mdp.actions(s), key=lambda a: expected_utility(a, s, U, mdp))
            if a != pi[s]:
                pi[s] = a
                unchanged = False
        if unchanged:
            return pi[f:id:tachiken0210:20170529202347p:plain]


def policy_evaluation(pi, U, mdp, k=20):

    R, T, gamma = mdp.R, mdp.T, mdp.gamma
    for i in range(k):
        for s in mdp.states:
            U[s] = R(s) + gamma * sum([p * U[s1] for (p, s1) in T(s, pi[s])])
    return U


pi ,U= best_policy(sequential_decision_environment, value_iteration(sequential_decision_environment, .01))
pd.DataFrame(sequential_decision_environment.to_arrows(pi,U))

実行すると結果は以下のように

f:id:tachiken0210:20170529202334p:plain

行と列や中身を変更

f:id:tachiken0210:20170529202347p:plain

初心者が3ヶ月でアプリを作った話 〜その1、きっかけ〜

普段の仕事ではハードウェアに関わる仕事をしている最中、「やっぱりプログラミングくらいできなきゃな」と思い、独学で始めたプログラミング。

独学でプログラミングを勉強した人にはわかると思うが、エラーの意味などがわからないとドツボにはまりありえないほど多くの時間を費やすことが多く、要領よく勉強できませんでした。

 

そんな時に出会い、入校したDIVE INTO CODEというプログラミングスクール。

こちらはRuby On Railsを中心にテキストベースで学んでいくスタイルのプログラミングスクールです。

 

このDIVE INTO CODEの内容や感想は別に記載するとして、このスクールでの一つのイベントである「Demoday」という自分の考えたアプリを3ヶ月くらいでプロトタイピングし発表するというイベントに出た時のことを(備忘録としても)書こうと思います。

 

作ったアプリは「Googleカレンダーと連携した工数管理アプリケーション」で、具体的にはGoogleカレンダーの情報を「タグ」という概念によりプロジェクトごとにカテゴライズしてくれるアプリです。

簡単なフローとしては

  1. google omniauthによるログイン

    github.com

    (ユーザー情報取得)

  2. google api clientによるカレンダー情報取得
    github.com

  3. カレンダー情報をタグによって分類する。

    github.com

  4. lazy high charts によりグラフ化する。

    github.com

 

簡単にいうとこんな感じの流れです。

 

肝心の出来はというと、自分が当初考えていた機能は初めて3ヶ月で大体実装できました。最初は何もわからず大変だったが。。。

 

次からどんな機能を追加していったかやどんなコードを書いていったかなど、(初心者ながら)書かせていただこうと考えております。

 

それでは(^ ^)