今日も微速転進

ここではないどこかへ

Google Vision API の手書き文字認識(英語)を試す

経緯

どこかのニュースサイト(英語)でGoogle Nextで発表された新機能として"hand wrtten"(手書き)という文字があったのでググってみたところ、 Google Vision APIのドキュメント(英語の方)にさり気なく"Detecting Handwriting"という記述を発見。

f:id:atuyosi:20180809215901j:plain

下記のリンク先のページで一番下までスクロールすると言語の切り替えボタンがあります。
Optical character recognition (OCR)  |  Cloud Vision API Documentation  |  Google Cloud

文字認識の際に指定するFeature typeは2種類ありますが、手書き文字認識に対応するのはDOCUMENT_TEXT_DETECTION

日本語非対応な上にβ版ですがせっかくなので試してみました。

コードと画像

サンプル画像とJupyter Notebookを以下のリポジトリにおいてみました。興味があればどうぞ。

GitHub - atuyosi/VisionAPI_Trial: Tiny sample for Google's Cloud Vision API

環境構築

GCPのアカウントおよびAPIの有効化はすでに完了済みとします。

Web APIなのでJSONでリクエストを組み立てればい良いのですが、怠惰なのでgoogle-cloud-visionというライブラリを使います。

$ python3 -m venv venv
$ source venv/bin/activate
$ pip install google-cloud-vision google-cloud-core google-cloud-storage

だってJSON、地味に面倒じゃないですか。しかも画像をJSONエンコードしなくていいというメリットもあります。

名前が名前だけにググったときにサクッと意図したページを見つけにくいというのが欠点です。

コード

基本的な使い方

クレデンシャルファイル*1のパスを環境変数を設定しておく。

$ export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your-credential.json

環境に合わせて修正して下さい。

オプション指定なしでVision APIによる文字認識の最低限のスクリプトは以下になります(DOCUMENT_TEXT_DETECTION)。

from google.cloud import storage
from google.protobuf import json_format
from google.cloud import vision_v1p3beta1 as vision

client = vision.ImageAnnotatorClient()

filename = 'sample.jpg'

response = client.document_text_detection(image=open('sample.jpg','rb'))

document_text_detection))text_detection()に変更するとFeature TypeがTEXT_DETECTIONになります。

参考?:a244.hateblo.jp

手書き文字認識のテスト

手書き文字として非常に適当な漢字の画像を用意しました。字が汚くてすいませんって感じですが。

f:id:atuyosi:20180809215817j:plain:w480
※ 画像は縮小済みです。

API側のバージョンは別にv1p3beta1でなくてもいいかも。

通常の文字認識との違いは、手書き用のヒントを指定するかどうか。ドキュメントのサンプルコードによれば以下の2種のいずれか。

  • en-t-i0-handwrit
  • mul-Latn-t-i0-handwrit
    # Language hint codes for handwritten OCR:
    # en-t-i0-handwrit, mul-Latn-t-i0-handwrit
    # Note: Use only one language hint code per request for handwritten OCR.

上記を踏まえたサンプルコード。

filename = 'handwritten.png'

client = vision.ImageAnnotatorClient()
image = vision.types.Image()

# image.source.image_uri = uri  # uri: The path to the file in Google Cloud Storage (gs://...)
image.content = open(filename, 'rb').read()

image_context = vision.types.ImageContext(
        language_hints=['en-t-i0-handwrit']
#    language_hints=['mul-Latn-t-i0-handwrit']
)

response = client.document_text_detection(image, image_context=image_context)

if response.error.code == 0 :
    print(response.text_annotations[0].description)
else:
    print("Error code: {}".format(response.error.code))

認識結果

出力は以下のようになります。今回の画像については'en-t-i0-handwrit'指定しないほうが結果は良好でした……。

手書き用のヒントあり('en-t-i0-handwrit')

Hello, would
Happy Hacking
Thank you
2018 Aug. 10
012345
6289
Q

"World"と感嘆符、それに数字の7、末尾のα、βなど認識できていないという結果。

手書き用のヒントなし

上記のコードを修正してimage_contextオプションを指定しない場合の結果です。

Hello, World,
Happy Hacking
Thank you.
2018 Aug. 10,
012345
6789
ap

これはちょっとどういうことなの……。筆記体*2じゃないとダメなのか。

参考:TEXT_DETECTIONの場合

ドキュメントにある通り、手書きは非対応のようです。

language_hitに'en-t-i0-handwrit'をセットしてもエラーにはなりませんが、出力は変わりませんでした。

print(response.full_text_annotation.text)
Hells, world
Thank yo
208 Aug
6s

明らかに手書きはダメダメ。

これまでTEXT_DETECTIONDOCUMENT_TEXT_DETECTION`に明確な差があるのかよくわからない状況でしたが、ここにきて差別化を図ってきたということでしょうか。

個人的にはTEXT_DETECTIONの方を手書き対応させる方が名称の点では自然だと思った。 どちらかというとDOCUMENT_TEXT_DETECTIONは名前からして(フォーマルな)文書イメージからの文字認識という印象だったのでちょっと意外。

TEXT_DETECTIONなら情景画像認識でも違和感ないし、壁の落書きのような手書き文字に対応するのは違和感がない。

Googleの技術陣の考えることはよくわかりません。

そのほか

上記のコードは違う書き方もできます。

filename = 'handwritten.png'

client = vision.ImageAnnotatorClient()

response_d = client.annotate_image({
    'image': {'content':  open(filename, 'rb').read() },
    'features': [{'type': vision.enums.Feature.Type.DOCUMENT_TEXT_DETECTION}],
    'image_context': { 'language_hints': ['en-t-i0-handwrit']},
})

中途半端にJSON風味。

ドキュメントへのリンク

結論

ちょっとテスト用の画像が残念だったのかなと反省しています。

TEXT_DETECTIONは手書き文字を受け付けないが)DOCUMENT_TEXT_DETECTIONは手書き文字(英語)を認識できる。

現状は英語とラテン文字のみ手書き文字認識対応。他の言語にも将来対応するんでしょうけど。

正直に言うと、はじめからデジタルで入力すればいいのに、というのが私の見解です。

なお、Microsoftの文字認識APIもいつのまにかアップデートしていて、そっちも英語は手書きに対応しているらしいのでそのうち試す予定。

*1:APIキーを記載したJSONファイル

*2:筆記体は覚えてないです

書評:『逆引きPython標準ライブラリ』

図書館にあったので借りてみました。読んだというより何が書いてあるのか眺めたという感じですが。

結論:微妙。

はっきり言っておすすめしません。3,000円出せるなら他の本が良いでしょう。

読み始めて最初のうちは役に立つと思いますが、すぐに物足りなくなると思います。

概要

逆引きPython標準ライブラリ 目的別の基本レシピ180+! (impress top gear)

逆引きPython標準ライブラリ 目的別の基本レシピ180+! (impress top gear)

あまりPythonでコードを書かない人が書いているという印象を受けた。

対象となっているPythonのバージョンは3.6.3。

対象読者は「Pythonの文法を知っている人」と記載がある。

オライリーの「入門Python3」のそれぞれの後半部分をダイジェスト化して3で割ったような本*1

標準ライブラリ限定なので必然的にNumpyやPandasの機能は紹介されていない。

購入してよかったと思うユーザーは限られると思和ざるを得ない。

説明そのものは基本的に丁寧な文章で読みやすいです。ただ、関数なりメソッドの使い方が丁寧に紹介されている箇所とそうでない箇所の差が激しいので入門書読んだだけのレベルでは混乱しかねないという印象です。

そのままサクッとコピーできるようなレシピ集ではありません。

内容について

標準ライブラリというより基本データ型に対する解説が多いので非常に中途半端な感じになっている。 全体的に駄目なポイントをいくつか。

  • 目次の「やりたいこと」のレベルが雑(粒度が細かすぎる)
  • ページレイアウト、特に各ページの見出しがいまいち(インポートするモジュール名がぱっとみてわからない)
  • 入門書に書いてあるレベルの事柄にページを使いすぎ
  • 索引がしょぼい

ページ数の配分も疑問。第1章から第3章はまともなPythonの解説書なら書いてあるレベル。

些細なことではあるけど関数とメソッドの違いは意識して欲しいと思った。

以下、各章別の疑問点。

Introduction

Pythonの文法を知っている人が対象読者と明記しているのにPythonのインストール方法を解説している。コンセプトがぶれ過ぎではないか。

  • pip freezeをインストール済みパッケージの表示コマンドとして紹介している(普通はpip list
  • 標準ライブラリを対象にしているのにpipコマンドやAnacondaの解説をしている

Atomエディタの解説は悪くない。

第1章〜第3章

まともなPythonの入門書なら書いてあるレベルの内容*2

逆引きスタイルの書籍にするからこういう初歩的な内容にページを割く必要が出てくるのではないか。

文字コードの話が出てこないのは残念。ユニコードのコードポイントと文字の変換のトピックがあるのに……。

参考:7.2. codecs --- codec レジストリと基底クラス — Python 3.6.6 ドキュメント

ついでにいうと改行コードの変換の話も無い。

新しい機能である正規表現matchオブジェクトのインデックス表記(Ruby風)の記載もない。

第4章、第5章

ファイル操作と時刻、数学関数、乱数など。入門書によっては解説がないので有益ではある。

pathlibも紹介してほしかった。

第6章

普通の入門書でネットワークがらみの機能解説はないので役に立つかも。ただし、オライリーの「[入門Python3](https://amzn.to/2ONjl6f9」には解説があったはず。

実際問題としてはこういうトピックを扱うなら標準ライブラリにこだわらずにFlaskの初歩を解説してくれたほうがありがたい。

エキスパートPythonプログラミング改訂2版

エキスパートPythonプログラミング改訂2版

第7章

タートルグラフィックスのためのモジュールの使い方を「逆引き」で知りたい状況はちょっと想像がつかない。

ここにページを割くなら第8章を充実させた方が良かったのでは。

なお、TkInterの解説はオライリーの本にも載っている。

実践 Python 3

実践 Python 3

第8章

わずか18ページ……。

ここでは、知っておくと便利なクラス、関数などをランダムに取り上げます。

流石にランダムは困るよ、ランダムは。カテゴリにとらわれないといいたいんだろうけど。

それとこの章の組版は疑問。他の章とこの章はレイアウトが違うのだから組版も変えてほしかった。 モジュールの一部の機能だけ紹介する内容なのだから、まずモジュール名なりクラス名を知りたい。そこでメソッドの名前なりクラスのコンストラクタをデカデカと強調されてもありがたくない。

argparseは解説少なすぎ。

まとめ

パラパラと眺めた結果、他の本の良さを再認識しました。

プログラム未経験で「プログラミング未経験者向けのPython入門書」を読んだだけの状態なら役に立つかも。

グーグル検索なりQiitaの記事から必要な情報を読み取れるレベルなら無駄。標準ライブラリに限定されているせいで中途半端。

やや古いけど、冒頭で紹介したオライリーPython本のほうが買ってから永く間役に立つと思う。

*1:2ではなく3

*2:購入した入門書の索引がちゃんとしていればこの本を買う必要はない

PythonのWebスクレイピング解説書の比較メモ

全部買ったわけではないですが、購入検討ように比較したメモ。

初歩的なレベルであればブログやQiitaに記載されている記事でなんとかなる場合が多いですが、クローラーを作成してがっつりやるなら一冊買って持っておいたほうがいいはず。

意識したポイント

残念ながら網羅できていないです。

  • Javascript対策の有無
  • ログインの必要なページへの対処方法が記載されているか
  • Selenium WebDriver
  • XpathCSSによる要素の絞り込みの解説
  • サンプルコードのライセンス
  • 環境構築の方法と解説

現在発売されている本で紹介されているPhantom.jsは開発が終了しているので非推奨。また、基本的にどの本もコマンド操作によるスクレイピング方法も紹介している。

オライリー

PythonによるWebスクレイピング

PythonによるWebスクレイピング

出版社サイト:O'Reilly Japan - PythonによるWebスクレイピング

オライリースクレイピング本。サンプルコードについての扱いは他のオライリー本と同様、自由にして良い。

基本的に対象のPythonのバージョンは3系。Scrapyのサンプルのみ2.7。

  • BeautifulSoup
  • Scrapy (python 2.7)
  • Phantom.js
  • TesseractでCAPTCHAを突破する事例(不完全?)
  • クッキー、入力フォーム対策
  • Xpathはコラムで解説
  • スクレイピングについての倫理に関する説明あり(+付録も)
  • pdfniner3kが紹介されているが、個人的にはpdfminer.sixを推奨。

240ページ強と薄い。ちょっと古いのが難点。付録にPythonの文法解説あり。

Pythonクローリング&スクレイピング

出版社サイト:Pythonクローリング&スクレイピング ―データ収集・解析のための実践開発ガイド―:書籍案内|技術評論社

環境構築の解説は最も丁寧。

動作環境はPython 3.5 (macOS)/ Python 3.4 (Ubuntu)。 簡単なPythonの文法の解説あり。

著作権などの注意はこちらが詳しい。CSSセレクタおよびXPathについての解説はないが、解説なしにサンプル内で使用されている。

Pythonによるクローラー&スクレイピング入門』

出版社サイト:Pythonによるクローラー&スクレイピング入門 設計・開発から収集データの解析・運用まで(加藤勝也 横山裕季)|翔泳社の本

明らかにプログラマ、エンジニア向け。

特にあまりネットで記事にならないような、業務での運用向けのノウハウが知りたい人が対象。

環境としてMacを前提にしている。Pythonによる並列処理の勉強にも役に立つ。

Pythonのバージョンは3.6。Homebrew+pyenvでインストールすることを前提にしている*1

並列処理、データ収集とその後の活用に重点をおいている本。簡単なPythonの解説あり。

  • ジョブキューCelery)
  • Web(django, flask)
  • chromedriver
  • Scrapy
  • Slack連携
  • 最後発で内容が比較的新しい

クローラーの作成だけでなく運用までフォローしているところがポイント。

少し変わったトピックとしては自作スクリプトへのScrapyの組み込み方法が紹介されている。

MacユーザーでなくてもLinuxが使えるなら問題なく読めるはず。

書いてないこと

  • ログイン処理の必要なページへの対処
  • セレクタの解説
  • BeautifulSoup

Pythonによるスクレイピング&機械学習 開発テクニック』

スクレイピングの本としては中途半端。

  • BeautifulSoup4がメイン
  • XPathによる要素の絞り込み方法は紹介されていない。
  • Cookie対策については対策が紹介されている
  • 環境構築方法の解説がいい加減(Docker etc.)

書評:a244.hateblo.jp

おすすめしない。

補足

Selenium WebDriverに関してはPython経由で使うよりJavaのライブラリの方が有利かも。ブラウザ側の変更への対応がJavaのライブラリが一番早いので。

selenide.org

どの本もXPathCSSセレクタの解説はあまり詳しくない。ブラウザの開発ツールで取得した値そのままでは上手くいかないときがあるので注意が必要。

まとめ

技術評論社の本が一番バランスがいい。スクレイピング自体の知識があって業務でがっつりやろうとしているなら翔泳社『Pythonによるクローラー&スクレイピング入門』

技術評論社の本と翔泳社の本はどちらも特色があって甲乙つけがたい。あとはライブラリの公式ドキュメント。

*1:Homebrewはセットアップ済みという前提

今週のふりかえり(2018年8月第1週)

f:id:atuyosi:20180806150107j:plain:w480

体重を増やそうと思うときに限って体重が増えない……。ジムに通うとかしないとダメかな。

調子があがってきたかと思ったけど見事に暑さにやれられてペースダウン。

某コンテスト用のラズパイの部品が届いたのでがんばりましょう。

続きを読む

Amazon Advertise API 関連メモ書き

AmazonProduct Advertising APIに関するメモ書き。備忘録なので非常に雑です。

APIの概要

Amazonの商品データベースから各種情報をXMLで取得できるAPI

商品の詳細や在庫情報など、取得できる情報は幅広い。

商品データベースに関する操作とカートに関する操作がある。

この記事では商品データベースに関するAPIのみ。

  • Search(検索)
    • ItemSearch:キーワード(またはASINなどのID)でItem(商品情報)検索
  • Lookup(特定の項目の参照)
    • BrowseNodeLookup:ブラウズノード(カテゴリあるいは出品者、メーカー)について情報を得る
    • ItemLookup:ASINやISBNのようなIDでItemを検索
    • SimilarityLookup:類似商品を探す

公式ドキュメント

Welcome - Product Advertising API

2013年8月1日付けが最新。

Amazon.co.jpではなくdocs.aws.amazon.comのドキュメントをたどる。

使用するには

  1. まずAmazon アソシエイトに登録する
  2. APIを有効化し、APIキーを入手する

  3. Amazon associateのアカウントを作る(買い物用のアカウントとは別にした方が無難)

  4. アクセスキーの取得

Amazon Product Advertising API関連リンク集

注意事項

目的外の使用に対して非常に厳しくなっているので注意が必要。

Product Advertising API (PA-API) の利用ガイドライン

月に最低一回、「(改変なしの)APIからのレスポンスに含まれるURL」から売上が発生していれば最低1秒1回の呼び出しは可能。

APIからのレスポンスに含まれるURLを改変無しで使用しろ、というのはかなり厳しい気がする。

転売用の価格調査用のDB系のとばっちりということなんだろう。

APIに指定するパラメータについて

すべての国で共通

カテゴリのリスト

SearchIndexを使う場合に必要。

Python用ライブラリ

bottlenoseというライブラリがあるのでこれを使う。

以下はAPIの挙動の確認に作ったもの。キーワード検索結果を取得するサンプル。

APIからのレスポンスそのものはXMLで保存している。後半部分で結果をパースしてPythonのリストにしている。 APIキーはdotenv経由で環境変数から。

#! /usr/bin/env python3
# coding: utf-8

import bottlenose

from bs4 import BeautifulSoup

from retry import retry
import os
import sys
import pathlib
from time import sleep
from dotenv import load_dotenv
from string import Template
from datetime import datetime

# [GitHub - theskumar/python-dotenv: Get and set values in your .env file in local and production servers.](https://github.com/theskumar/python-dotenv)
# [GitHub - lionheart/bottlenose: A Python wrapper for the Amazon Product Advertising API.](https://github.com/lionheart/bottlenose)



load_dotenv('./django_project/.env')

AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY')
AWS_ASSOCIATE_TAG = os.environ.get('AWS_ASSOCIATE_TAG')

amazon = bottlenose.Amazon(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_ASSOCIATE_TAG, Region='JP')

response_group = 'Images, ItemAttributes' # response_groupに何も指定しないと画像のURLは取得できない?

response_text = None

cached = False

if cached :


    path = pathlib.Path("./dump7.xml")

    with path.open('rt') as fp:
        response_text = fp.read()

else :

    for i in range(10) :
        try:
            response = amazon.ItemSearch(
                Keywords="フルメタル・パニック", SearchIndex="All",
                ResponseGroup=response_group
            )
            response_text = response.decode('utf-8')
            break
        except:
            print("got 503 error, retry!")
            sleep(3)
    else:

        sys.exit(-1)


    # path = pathlib.Path("./dump7.xml")
    filename = "./dump_" + datetime.today().strftime("%Y-%m-%d-%H%M%S") + ".xml"
    path = pathlib.Path(filename)

    with path.open( mode="wt") as op :
        op.write(response_text)

soup =  BeautifulSoup(response_text,'lxml') # lxmlを使う場合はタグ名はすべて小文字。

itemList = soup.find_all("item")

item_data = list()
for item in itemList :
    print(item.name)

    author_list = [node.text for node in item.find_all('author')]

    creator_list = list()

    for tag in item.find_all('creator'):
        role = tag['role']
        text = tag.text

        creator_list.append((text, role))

    # publisher or manufacture studi or author ? どれか
    temp = {
        'title' : item.find('title').text,
        'detail_url' : item.find('detailpageurl').text,
        'asin' : item.find('asin').text,

        'manufacturer' : item.find('manufacturer').text if item.find('manufacturer') else None, # Kindle版はManufacture なし?

        'authors' : author_list,
        'creators' : creator_list,

        'small_image_url' : item.find('smallimage').find('url').text if item.find('smallimage') else None,
        'medium_image_url': item.find('mediumimage').find('url').text if item.find('mediumimage') else None
    }

    if item.find('format') :
           temp['format'] = item.find('format').text
    if item.find('binding') :
            temp['binding'] = item.find('binding').text

    print(temp)

    item_data.append(temp)

# author, creatorは複数の可能性がある。

print(itemList)

print(item_data)

書籍や動画以外は画像の縦横比がバラバラだったりするので注意が必要。

参考情報

Response Groups関連

商品に関するXML属性

下記のリンクから"Ancestry: ItemAttributes"という項目を確認する。

Response Elements - Product Advertising API

参考URL

検索時の制約

「Amazon API」の使い方![最終回]-実際に作ってみよう- | HPcode

カテゴリ別のランキング

BrowseNodeを指定するらしい。

システム管理者さんの憂鬱 : [C#] Amazon Product Advertising API を使用する

その他

広告