MotoJapan's Tech-Memo

技術めも

【python】Yahoo Web APIでバーコード情報(JAN)から商品名を読み出す(数列→商品情報) 【お家IT#18】

本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次

前回は画像からバーコード情報(数列)を取得した。
今回はこの情報から、商品情報を取得する。

取得方法のアイディア

実際このバーコード情報から商品名を求める方法はいくつかある。

今回はWebAPIを中心に話を進めるが折角なのでスクレイピングも少しみてみる。

1.スクレイピングする方法

例えば、JANコード「9000009984074」に対応するのは、お馴染み「クリスタルカイザー」という飲料水。
これを検索にかけてみるとこうなる。

BeautifulSoup4 をインストール
$ apt-cache search beautifulsoup
$ sudo apt-get install python3-bs4
実装

python3.x系で実装するとこんな感じ

import urllib.request
from bs4 import BeautifulSoup

headers = {
        "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:47.0) Gecko/20100101 Firefox/47.0",
        }
request = urllib.request.Request(url='https://www.google.co.jp/search?q=9000009984074+jan', headers=headers)
response = urllib.request.urlopen(request)
html = BeautifulSoup(response)
print(html)
結果

こんな感じで欲しいワード「クリスタルカイザー」は出ているのでこれを頑張って引っ張り出せばできなくもないかも。
f:id:motojapan:20171026022432p:plain
形態素解析してBoWとかでまず頑張るとか、でも食品名は無限に増え続ける固有名詞と考えるとそれだけで自然言語のタスクとして楽しそう。(今回はやらないが)

2.WebAPIで検索する方法(種類)

脱線したがこちらが本筋。
WebAPIでバーコード情報から商品名を求められるメジャーどころは下記。

Product Advertising API (Amazon Web Services)
楽天商品検索API (Rakuten Developers)
ショッピング商品検索(Yahoo! Developer Networks)

Yahoo! Developer Networksを使ってみる

今回はまず手軽にやってみたいので、Yahoo! APIにした。

さらに、Yahoo! Developer Networksを使うモチベーションは、Yahoo!が提供する地図や気象情報などの多種多様なサービスがWebAPIから扱えるという点もあったので、今回登録してみた。

次の前提は把握していたほうが話がスムーズ。

  • Yahoo! Developer Networksは、ディベロッパー(アカウント)登録が必要である
  • 「アプリケーション」という単位で管理している
  • 1アカウント当たり、10個までのアプリケーションを登録できる
  • クエリ数は、アプリケーション通算の総カウントで計算される
  • API別に上限がある

登録

ディベロッパー登録

まずディベロッパー登録を下記から行う。
Yahoo! JAPAN ID登録 - Yahoo! JAPAN
gmailでも登録できる。

アプリケーション登録

「アプリケーション管理」から「新しいアプリケーションを開発」を選択。
f:id:motojapan:20171029012051p:plain

アプリケーションの種類を選択し、他の入力情報を埋める。
f:id:motojapan:20171029012238p:plain
「クライアントサイド」を選択する場合はこんな感じ。

クライアントID取得

アプリケーションを登録後、「デベロッパーネットワークトップ」>「アプリケーションの管理」>「アプリケーションの詳細」から、「Client ID」をメモする。
f:id:motojapan:20171029012729p:plain
ここでメモしたIDを、とする。

インストール & 実装

必要モジュールをインストール

$ pip install beautifulsoup4 

単なるWebAPIとして扱えるので「スクレイピングする方法」と似たように簡単だが今度はpython2.7系で実装するとこんな感じ。

import urllib
from bs4 import BeautifulSoup

// code : バーコード情報
def code_to_product_info(code):
    print('*********** start product_info ************')
    start_time = time.time()

    product_info = None
    client_id = '<client_id>'

    url = 'http://shopping.yahooapis.jp/ShoppingWebService/V1/itemSearch?appid={0}&jan={1}'.format(client_id, code)
    response = urllib.urlopen(url).read()
    soup = BeautifulSoup(response)
    res = soup.find_all('name') // nameタグを取得
    //ここから超雑
    if len(res) > 0:
        product_info = res[0]

    print 'proc_time {0:f} [ms] '.format((time.time() - start_time) * 1000)
    return product_info
    print('*********** end product_info ************')

結果

タグで取り出してあげるとこんな感じで欲しいデータが取れた。

クリスタルガイザー ミネラルウォーター 500ml×48本 並行輸入品 代引不可

f:id:motojapan:20171028180346p:plain

処理速度は50-150[ms]程度。(通信状況によって揺らぐ)
以上。


ここまでで、画像から商品名(食品名)を検索するモジュールができた。
次回からは、OCRを用いて画像から賞味期限を取得するモジュールの話を進めたい。

【python】zbarでバーコードを読み取る(画像→数列) 【お家IT#17】

本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次

前回まででフロントエンド・バックエンドの整備が終わった。
以降は、画像処理部分を進める。

f:id:motojapan:20171026004212p:plain

今回は食品名(商品名)を取得するために、バーコード情報の読み取りをしたい。

バーコードとは

ふと、そもそもバーコードが何かをちゃんとわかっていなかったので調べる。

「黒いバーと空白の組み合わせで、数字、文字、記号を読み込めるデータ形式であり、一般的に縞模様を光学系認識機器で読み取る。
一般に普及している縞模様の一次元バーコードや、QRコードに体表される二次元バーコードをよく目にしている。
今回は食品名を取得したいので、今回は一次元バーコードを中心に考える。

バーコードの詳細は次が参考になる。
バーコードとは?|基礎知識|自動認識|デンソーウェーブ

バーコード種類は下記。

  • 統一商品コード(ユニークコード)
  • インストアコード(任意コード)

特にユニークコードで、日本で使われているのはJANコード

JANコード桁数は下記。

  • 標準タイプ(13桁)
    • 先頭7桁 : JANメーカーコード
    • 先頭9桁 : 国番号2桁 + JANメーカーコード7桁
  • 短縮タイプ(8桁)

JANコード構成要素は下記。

また、特に気になるのは、両サイドにあるマージンと呼ばれる空白は、十分な領域が必要である。<ナローバー(最も小さい黒縞)の10倍>

バーコードを読み込む方法は2つ思いつく。

  • バーコードリーダーといった専用機器で赤外線反射から読み取る
  • カメラなどの汎用機器で画像から読み取る(今回はこっち)

まぁこんな感じのバーコードリーダーでの読み取りでもよい。

ただ、それはあまりにつまらないし、「自宅で手軽に使う」というポリシーから外れるのでので今回は画像処理ベースで調べてみた。(スマホ一台で済ませたいし。)

結果的にpython上で動くスタンドアローンな画像処理ライブラリ、「zbar」を使用してみる。

環境

今まで通りRaspberry piを使用しているので、下記の通り。

zbarインストール

かなり簡単

//インストール
$ sudo apt-get install libzbar-dev  
$ sudo apt-get install python-zbar
...

//確認
$ python
import zbar
//error無しでOK

インストール時のトラブルシューティング

「error: command 'arm-linux-gnueabihf-gcc' failed with exit status 1」がインストール時に出る場合

下記インストール時にこんなエラーが出ることがある。

$ pip install zbar

対策 : module不足なので追加

$ sudo apt-get install python-dev

「segmentation fault」が実行時に出る場合

確認時(実行時)にこんなエラーが出ることがある。

$ python
import zbar
//segmentation fault

対策 : pipで入れたzbarがconflictしているようなのでpipはアンインストール、apt-getでインストール

$ pip uninstall zbar 
$ sudo apt-get install python-zbar

実装例

//img : cv2 image(numpy)
def convert_image_to_code(img):
    print('=========== proc start =============')
    start_time = time.time()
    #init module
    scanner = zbar.ImageScanner()
    scanner.parse_config('enable') 

    #image to gray
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    h = gray.shape[0]
    w = gray.shape[1]
    zimage = zbar.Image(w, h, 'Y800', gray.tostring())

    #scan
    scanner.scan(zimage)

    result = []

    for symbol in zimage:
        print('Type: {0} |  Data: {1}'.format(symbol.type, symbol.data))
        result.append({'type':symbol.type, 'data':symbol.data})

    print 'proc_time {0:f} [ms] '.format((time.time() - start_time) * 1000)
    print('=========== proc end ===========')

    return result

結果

入力画像は★に注意して、実際にいくつか試してみるといい感じの結果となった。



平面紙印刷

レトレクパック印刷

曲面印刷

画像全体







撮影領域







結果

4970231640022

4902204411599

4902106232209

400 x 60 の画像に対して、処理速度は20-40[ms]程度。
曲面ぐにゃぐにゃ面でも割りとちゃんと認識できる。

以上。
次回はこのバーコードの数列から、食品名を取得する。

【Javascript】非同期実行を実装し、ボタンを押したフィードバックを全画面に行う 【お家IT#16】

本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次

前回までで、javascript/CSSを用いて撮影画面を作成した。
最後にUIの完成度を高めたいので撮影ボタンを押したらバックグラウンド全体が青くチカっとフラッシュするようにしたい。

そもそもの問題

ボタンを押したら、バックグラウンドの色をチカッっとしたい。
Windowsのメッセージで言うところのWM_LBUTTONDOWN と WM_LBUTTONUP のイベントが取れればよかったのだが、これに相当する関数が見つからなかったので、
onClick時に非同期メッセージを飛ばしてbackgroundカラーを変更することにした。
(ダサいのでいい方法あったら教えてください)

非同期にする方法

いろいろ調べたが、この資料がわかりやすい。
JavaScriptの同期、非同期、コールバック、プロミス辺りを整理してみる - Qiita

実装

setTimeoutに非同期実行したい関数を設定する。
背景色の変更は、body.classNameに所望のレイアウトを指定することで可能。

//**************** css ***********************

<style type="text/css">
...
.background_color_theme{
    background: #ffffff; /*デフォルトの背景色*/
}
.background_color_bt_pressed_theme{
    background: #0000ff; /*フラッシュ用の青色*/
}
...
</style>

//************ javascript ********************

<script type="text/javascript">

function take_picture() {
    //前回までに追記
    //(base64で画像を送信する実装)
    ...
    setTimeout(function(){body.className = "background_color_theme"}, 60); // 60 [msec]後に元のテーマに戻る
    body.className = "background_color_bt_pressed_theme"; 
}

</script>

以上。
次回からは画像処理の話をしたい。

【CSS】接頭句の違い "#"と"." 【お家IT#15】

本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次

前回は、ボタンの透過度を設定した。
今回は、基礎的な話でなんとなく使っていたCSSの接頭句の違いを書く。


前々回のコードをコピーしたのが下記。

"."で開始するstyleは、classに対応
"#"で開始するstyleは、idに対応

classで記載しておくと同じstyleを適用できる。

//**************** html **********************

…
    <div class="l2">
        <canvas id="camera_record_box"></canvas>
    </div>
…

//**************** css ***********************


<style type="text/css">.l2 {
    top: 0px;   /*動的に変更されるので適当*/
    left: 0px;  /*動的に変更されるので適当*/
    width: 400px; /*撮影枠サイズ*/ 
    height: 60px; /*撮影枠サイズ*/
    position: absolute; /*absoluteに設定*/
    padding: 0em 0em;
}
#camera_record_box{
    position: absolute; /*absoluteに設定*/
    border: 1px solid red; /*赤い撮影枠*/
    padding: 0em 0em;
}</style>

以上。
次回でフロントエンド(webアプリ系統)の話は終えたい。
最後は簡単に非同期処理について、以降は画像処理の話をしたい。

【CSS】半透明(透明度)の設定方法 【お家IT#14】

本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次


前回は、レイアウトの重ね合わせを実装した。
今回は、ボタンの透過度を設定したい。

完成イメージ

前回からの続きだが、ボタンが透過して下レイヤーの映像をスルーするようにしたい。
f:id:motojapan:20171029201448p:plain

実装

opacityで設定することができる。
前回までに追記。

.my_btn{
    …
    background: #668ad8;
    opacity: 0.8; /*0-1の範囲で不透明度を設定*/
    …
}

結果

f:id:motojapan:20171029200413j:plain

以上。

次回はなんとなく使っていたCSSの接頭句についてまとめる。

【Javascript / CSS】divタグでcanvas等のレイアウトを重ねる方法 【お家IT#13】


本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次

前回は、カメラ撮影枠を作成するために、カメラリソースの切り替えによるVideo要素のサイズ変化を検出した。
今回は、実際にカメラ撮影枠を作るためのレイアウトの重ね合わせ方法についてまとめる。

完成イメージ

こんなものを作りたい。
f:id:motojapan:20171029201525p:plain
カメラのプレビュー上に、撮影領域と撮影ボタンを配置したい。

基本方針

  • 基本的な枠組みは、HTMLのdiv要素CSSでレイアウトの重ね合わせ。
  • 動的なレイアウトプロパティの変更は、JavascriptからCSSの設定を変更。

div要素で重ねわせ方法と実装

ポイント

重ねわせで重要なのはCSSのposition。
下記の設定を行う。

外側のparent position: relativeに設定。
内側のl1~l3 特定の位置に置きたい場合、position: absoluteに設定。

特に今回は、

  • l1は、video要素があり、動画ストリームが確定すると自動的にサイズが決まる
  • l2は、l1のサイズのサイズに応じて、動的にサイズが決まる

となる。

実装

動的にCSSプロパティを操作する場合、2つの方法がある(style / setAttribute)
実装は下記の通り。

//**************** html **********************

<div class="parent" width="720px">
    <div class="l1">
        <video id="camera" width="720px" autoplay></video>
    </div>
    <div class="l2">
        <canvas id="camera_record_box"></canvas>
    </div>
    <div class="l3">
        <a id="take_picture" href="#" class="my_btn">TAKE PICTURE</a>
    </div>
</div>

//**************** css ***********************

<style type="text/css">
.parent {
    width: 720px;
    height: 0px;
    position: relative; /*relativeに設定*/
    border: 3px solid black;
    padding: 0em 0em;
    margin:0;
}
.l1 { /*前回の話の通り、動画ストリームが確定してから自動的にサイズが決まる*/
    top: 0px;
    left: 0px;
    width: 720px; 
    padding: 0em 0em;
}
.l2 {
    top: 0px;   /*動的に変更されるので適当*/
    left: 0px;  /*動的に変更されるので適当*/
    width: 400px; /*撮影枠サイズ*/ 
    height: 60px; /*撮影枠サイズ*/
    position: absolute; /*absoluteに設定*/
    padding: 0em 0em;
}
.l3 {
    bottom: 0px; /*下端に配置*/
    position: absolute; /*absoluteに設定*/
    padding: 0em 0em;
}
#camera_record_box{
    position: absolute; /*absoluteに設定*/
    border: 1px solid red; /*赤い撮影枠*/
    padding: 0em 0em;
}
</style>

//************ javascript ********************

//関数でCSSの設定を操作する
//(前回に追記)

<script type="text/javascript">
function notify_change_size() {
    video_layout_width  = video.clientWidth
    video_layout_height = video.clientHeight

    var element_parent = document.getElementsByClassName('parent');
    var element_l2  = document.getElementsByClassName('l2');
    var element_box = document.getElementById('camera_record_box');

    box_width  = 400 //box size
    box_height = 60  //box size

    left = (video_layout_width - box_width) / 2
    top = (video_layout_height - box_height) / 2

    //parentへの設定
    for(var i=0, l=element_parent.length; i<l; i++){
        element_parent[i].style.width   =  String(video_layout_width)  + "px";
        element_parent[i].style.height  =  String(video_layout_height) + "px";
    }

    //l2への設定
    for(var i=0, l=element_l2.length; i<l; i++){
        //今回はi=0のみ
        element_l2[i].style.left =  String(left)  + "px";
        element_l2[i].style.top  =  String(top)   + "px";
        element_l2[i].style.width   =  String(box_width)  + "px";
        element_l2[i].style.height  =  String(box_height) + "px";
    }

    //camera_record_boxへの設定
    element_box.setAttribute("width",  String(box_width)  + "px");
    element_box.setAttribute("height", String(box_height) + "px");
}

videoElement.onresize = notify_change_size;

</script>

結果

上手くいった。
f:id:motojapan:20171029200239j:plain


以上。

次回はボタンを透過したい。

【Javascript】カメラ起動前後のvideoタグのサイズ変化を検出する 【お家IT#12】


本件の実装の一部  
motojapan.hateblo.jp

前回の続き  
motojapan.hateblo.jp

目次

前回は、UserMediaのカメラリソースを切り替えるなどの話をした。
今回は、カメラリソースの切り替えによるVideo要素のサイズ変化を検出したい。
例えばインカムからアウトカムに切り替えるとき、解像度が異なると、Video要素のサイズが変わる。
f:id:motojapan:20171024003838p:plain

もう少しわかりやすく具体例を出して説明する。

具体例:Video要素のプレビューの中央に撮影領域を作りたい

例えば、下図のようにVideo要素のプレビューの中央に撮影領域を作りたい。(赤い枠線部分)
f:id:motojapan:20171023005112p:plain
これを作る場合、少なくともVideo要素のサイズ変化を検出してレイアウト確定時のサイズを求める必要がある。

実装:Video要素のサイズ変化を検出、確定時のサイズを取得

実は知っていればめちゃくちゃ簡単

//**************** html **********************

<video id="camera" width="720px" autoplay></video>

//************ javascript ********************

//動的に取得する関数
<script type="text/javascript">
function notify_change_size() {
    var video = document.getElementById('camera');
    video_layout_width  = video.clientWidth
    video_layout_height = video.clientHeight
}

var videoElement = document.querySelector('video#camera');
//onresizeメッセージのコールバック関数を設定する
videoElement.onresize = notify_change_size;
</script>

以上。
次回はこの具体例を少し掘り下げてどうやってVideo要素でのプレビュー上に撮影領域を作るか考える。