LionでGoogle App Engine用の仮想環境構築

MacOSX Lionでvirtualenv, virtualenvwrapperを使ってGoogle App Engine(GAE)用の仮想環境を構築しようとして少しはまったのでメモ。

環境

  • MacOSX Lion
  • Python2.7.2 (virtualenv起動)
  • Python2.5.6 (仮想環境のinterpretermacにdefaultで入っているもの。/usr/bin/python2.5)

エラーと解決法

mkvirtualenv --python=/usr/bin/python2.5 --no-site-packages gae

と入力したところ、

distutils.errors.DistutilsPlatformError
$MACOSX_DEPLOYMENT_TARGET mismatch: now "10.5" but "10.7" during configure

エラー発生。エラーを吐いている部分のソースコードを読んでみると,

if sys.platform == 'darwin' and g.has_key('MACOSX_DEPLOYMENT_TARGET'):
        cfg_target = g['MACOSX_DEPLOYMENT_TARGET']
        cur_target = os.getenv('MACOSX_DEPLOYMENT_TARGET', '')
        if cur_target == '':
            cur_target = cfg_target
            os.putenv('MACOSX_DEPLOYMENT_TARGET', cfg_target)
        elif map(int, cfg_target.split('.')) > map(int, cur_target.split('.')):
            my_msg = ('$MACOSX_DEPLOYMENT_TARGET mismatch: now "%s" but "%s" during configure'
                % (cur_target, cfg_target))
            raise DistutilsPlatformError(my_msg)

cur_targetはmac環境変数$MACOSX_DEPLOYMENT_TARGETを読んだ値,
cfg_targetは/System/Library/Frameworks/Python.framework/Versions/2.5/lib/python2.5/config/MakefileMACOSX_DEPLOYMENT_TARGETの値らしいんで,.zshrcに,

export MACOSX_DEPLOYMENT_TARGET=10.7

と書いて,sourceしたら無事に環境構築できました。
使ってるOSはLionなので10.7に設定しても問題ないよね...?

PythonでSQLite3を使う

Python2.5以降はsqlite3が標準モジュールとして使えるらしいので、

の問題点の一つである「一度取得した書籍情報を再び取得してしまう」を解決するために、書籍情報を保存するデータベースを生成する。

環境

参考

データベースについては完全な初心者なので、一般知識については、MySQL/PostgreSQLなどデータベースの学習ならDBOnline(移転しました)SQLite入門を見て勉強した。
また、Pythonのsqlite3モジュールの使用法については、http://www.python.jp/doc/2.5/lib/module-sqlite3.htmlSQLite3を使用する - Python Tipsを参考にした。
まだ、勉強し始めたばかりなので上手いデータベースデザインの勘は掴めていないと思うが、

  • メンテナンス性を高く保つ(正規化)
  • アクセス速度を保つ(ディスクアクセス量を出来るだけ減らす)

に注意してアプリケーションにあったデザインを素直に考えれば今のところは大丈夫じゃないかなと思う。普段のプログラミングのクラス設計と似ている部分も多くあるだろう。
というわけで、実際に簡単なサンプルを実装してみた。

データベースの生成

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sqlite3

db = sqlite3.connect('data.db', isolation_level=None)
c = db.cursor()

sql = u"""
create table books (
    title varchar(100),
    price integer,
    id integer
);
"""
c.execute(sql)

sql = u"insert into books values ('python', 2000, 1)"
c.execute(sql)

items = ( ('perl', 3000, 2),
          ('ruby', 1500, 3)
        )
for t in items:
    c.execute(u"insert into books values (?,?,?)", t)

sql = u"select * from books"
c.execute(sql)
for record in c:
    print record[0], record[1], record[2]

db.close()

データベースからレコードの取得

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sqlite3

db = sqlite3.connect('data.db')

c = db.cursor()
sql = u"select * from books"
c.execute(sql)
for record in c:
    print record[0], record[1], record[2]

db.close()

基本的には、

  1. connect関数で対象のデータベースと接続したConnectionオブジェクトが返る
  2. ConnectionオブジェクトのcursorメソッドでCursorオブジェクトが返る
  3. CursorオブジェクトのexecuteメソッドでSQL文を実行し、結果が返る

これだけ分かっていれば、とりあえずデータベースの操作はできそうだ。
SQLite3を使用する - Python TipsではConnectionオブジェクトに対してテーブルの生成やレコードの追加を行っていたが、http://www.python.jp/doc/2.5/lib/module-sqlite3.htmlではCursorオブジェクトに対して行っている。両者の違いについて調べたところ、

Connection オブジェクトの非標準的なメソッド execute, executemany, executescript を使うことで、 (しばしば余計な) Cursor オブジェクトをわざわざ作り出さずに済むので、 コードをより簡潔に書くことができます。Cursor オブジェクトは暗黙裡に 生成されショートカットメソッドの戻り値として受け取ることができます。この方法を 使えば、 SELECT 文を実行してその結果について反復することが、 Connection オブジェクトに対する呼び出し一つで行なえます。

http://www.python.jp/doc/2.5/lib/node353.html

ということらしい。つまり、Connectionオブジェクトのexecuteメソッドは名無しのCursorオブジェクトを勝手に生成してSQL文を実行してくれるショートカットで、ソースコードの簡略化が目的らしい。そこまで深く考える必要はなかったようだ。
次回は、実際にAmazonのProduct Advertising APIを使ってISBNから書籍情報を取得する - 脳みそHackと組み合わせてみる。複数のテーブルを持つデータベースの作成も行いたい。

AmazonのProduct Advertising APIを使ってISBNから書籍情報を取得する

の続きで、バーコードから読み取ったISBNをAmazonから提供されているAmazonログインに入力して書籍情報を取得する。

Product Advertising APIの使用方法

Representational State Transfer - Wikipediaを用いたWebAPIで、

  1. 認証情報や欲しい情報を取得するための引数をつなげたURLを送信
  2. 帰ってきたxmlから情報を取得

という流れになる。
URLの生成やリクエストの送信を隠蔽したモジュールを公開してくださっている方がいたので、ありがたく使わせていただく。
PythonでAmazon Product Advertising APIを使う - 人工知能に関する断創録

PythonWebカメラからバーコードを読み、取得したISBNからProduct Advertising APIを使って書籍情報の取得

ソースコードのタイトルがだんだん長くなってきましたが、今回も分割せず一本道のソースコードになっております。そろそろリファクタリングした方がいいかな...

import cv
import time
import zbar
import Image
from amazon import Amazon
from BeautifulSoup import BeautifulStoneSoup

cv.namedWindow("camera", 1)
capture = cv.CreateCameraCapture(0)

width = None
height = None

if width is None:
    width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH))
else:
    cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_WIDTH,width)    

if height is None:
    height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))
else:
    cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_HEIGHT,height) 

amazon = Amazon("your_access_key","your_secret_access_key")

while True:
    img = cv.QueryFrame(capture)
    size = cv.GetSize(img)
    gray_img = cv.CreateImage(size, cv.IPL_DEPTH_8U, 1)
    cv.CvtColor(img, gray_img, cv.CV_BGR2GRAY)
    
    scanner = zbar.ImageScanner()
    scanner.parse_config('enable')

    zbar_img = zbar.Image(gray_img.width, gray_img.height, 'Y800', gray_img.tostring())
    scanner.scan(zbar_img)

    for symbol in zbar_img:
        print 'decoded', symbol.type, 'symbol', '"%s"' % symbol.data
        if symbol.type == 'ISBN10':
            xml = amazon.itemLookup(symbol.data, SearchIndex="Books", IdType="ISBN",
                    ResponseGroup="Large,Similarities", ReviewPage="1")
            print amazon.url
            
            soup = BeautifulStoneSoup(xml)
            item = soup.find("item")

            print item.asin
            print item.itemattributes.title
            print item.itemattributes.listprice.amount

    cv.ShowImage("camera", gray_img)
    k = cv.WaitKey(10);
    if k == 'f':
        break

前回から追加した部分は、

  • Amazonインスタンスを生成してitemLookupメソッドにISBNを渡す
  • 帰ってきたxmlをBeautifulSoupでパースして情報を取得・表示

現時点での問題点としては、

  1. 同じ書籍でもISBNが取得されれば10msごとにProduct Advertising APIを叩いてしまう
    • 保存用のdictionary型を生成して同じ書籍は検索しないようにする
    • dictionary型をpickleする方法とsqliteを使う方法があるけどどちらの方がいいのかな?要素数が変動する場合もあるのでdictionary型の方が楽そうだけど...
  2. 柄のある背景にバーコードが印刷されている場合に認識率が悪い
    • グレースケールではなく背景の柄を消すような二値化処理が出来れば認識率は上がるはず
  3. ソースコードが醜い

上記の問題点を修正しつつ、次回は、mechanizeと絡めていきたい。

MacVim-KaoriYaでcolorschemeの設定

.vimrcでcolorschemeを設定していたが、何故かうまく反映されなかった。正確に言うと、MacVimを起動してからsource .vimrcをしないと反映されなかった。

.vimrcの最終行にcolorschemeの設定を書いてみたりいろいろ悩んだ挙句、MacVim-KaoriYaのReadmeを読み返してみると、

GUIに関係しない設定、たとえば tabstop, shiftwidth, expandtab, wrap, showmatch などの設定はホームフォルダ/.vimrcにて設定することをオススメします。
GUIに関する設定、たとえば columns, lines, guioptions, transparency, antialias, colorscheme などの設定は ホームフォルダ/.gvimrcにて設定することをオススメします。
MacVim.app/Contents/Resources/vim/vimrc
ホームフォルダ/.vimrc または _vimrc
MacVim.app/Contents/Resources/vim/gvimrc
ホームフォルダ/.gvimrc または _gvimrc
の順番で読み込まれます。

http://code.google.com/p/macvim-kaoriya/wiki/Readme

.gvimrcを作成してcolorschemeを設定しvimを起動するとあっけなく成功。灯台下暗し。

ちなみに使用しているcolorschemeはVim color scheme: Wombat | Not Really A Blog

OpenCVとZBarを使ってPythonでバーコードリーダを作る

PythonからOpenCV 2.2を使う(MacOSX SnowLeopard + homebrew) - 脳みそHackの続きで、ZBar bar code readerと組み合わせてバーコードリーダを作る。

環境

ZBarのインストール

homebrewを用いてZBarをインストールする.

brew install zbar

その後,ZBarのpythonラッパーをpipを用いてインストールする.コンパイルにPIL(Python Image Library)を使用するので、先にPILをインストールする必要がある。

pip install PIL
pip install zbar

また、ZBarの実行にはnumpyも必要なので、numpyもインストールする。
しかし、pipやeasy_installでnumpyをインストールしてZBarを使ったコードを実行しようとすると、

RuntimeError: module compiled against ABI version 2000000 but this version of numpy is 1000009
Traceback (most recent call last):
  File "cv_gray.py", line 1, in <module>
    import cv
ImportError: numpy.core.multiarray failed to import

というエラーメッセージが出てしまう。pipやeasy_installで入るnumpyのバージョンは1.6.0だが、どうやら、ZBarはnumpy2.0.0を使いたいらしい...。
MacOSX SnowLeopard用のnumpy2.0.0インストーラがなかなか見つからない中、http://stronginference.com/scipy-superpack/を見つけたので、インストールしてみる。実行...うまくいった!

virtualenv

virtualenv使ってる人は、共通のsite-packagesから仮想環境のsite-packagesにnumpy-2.0.0.dev_f72c605_20110113-py2.6-macosx-10.6-universal.eggをコピーして、仮想環境上で、

easy_install numpy

という手順を踏むと、仮想環境上でnumpy2.0.0が使えるようになります。
もっとスマートな解決法があれば誰か教えてください。

webカメラで取得した画像中のバーコードをデコードする

Hello Absurd World! Webカメラを使ったバーコードスキャン on pythonを参考に作った。

import cv
import time
import zbar
import Image

cv.namedWindow("camera", 1)
capture = cv.CreateCameraCapture(0)

width = None
height = None

if width is None:
    width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH))
else:
	cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_WIDTH,width)    

if height is None:
	height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))
else:
	cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_HEIGHT,height) 

while True:
    img = cv.QueryFrame(capture)
    size = cv.GetSize(img)
    gray_img = cv.CreateImage(size, cv.IPL_DEPTH_8U, 1)
    cv.CvtColor(img, gray_img, cv.CV_BGR2GRAY)
    
    scanner = zbar.ImageScanner()
    scanner.parse_config('enable')

    zbar_img = zbar.Image(gray_img.width, gray_img.height, 'Y800', gray_img.tostring())
    scanner.scan(zbar_img)

    for symbol in zbar_img:
        print 'decoded', symbol.type, 'symbol', '"%s"' % symbol.data

    cv.ShowImage("camera", gray_img)
    k = cv.WaitKey(10);
    if k == 'f':
        break
  1. webカメラから画像を取得する
  2. グレースケールに変換
  3. Zbar.Image形式に変換
  4. 画像をスキャン
  5. デコード結果を表示

という処理をループしている。
ZBarライブラリ内にもvideoの入力を扱う関数が用意されているが、iSight(Macに標準で搭載されているwebカメラ)の指定方法が分からなかった。/dev/にiSightを示すドライバが見当たらないので、iSightは通常のusb接続のwebカメラとは扱い方が異なるようだ。OpenCVではcv.CreateCameraCaptureで引数0をとるとiSightからの画像を取得できる。このあたりよく分かっていないので要勉強。
次は、PythonでAmazon Product Advertising APIを使う - 人工知能に関する断創録を参考にバーコードから書籍情報を取得する。

PythonからOpenCV 2.2を使う(MacOSX SnowLeopard + homebrew)

Pythonを使って画像処理をやってみたいなと思っていたところ、こんな記事を発見。

Starting with OpenCV release 2.2, OpenCV will have completed it's new Python interface to cover all the C and C++ functions directly using numpy arrays.

http://opencv.willowgarage.com/wiki/PythonInterface

早速、PythonからOpenCV 2.2を使ってみた。

環境

インストール

macportsヤメてhomebrewにしました。でopencvをsnow leopardに入れる方法 - ( ꒪⌓꒪) ゆるよろ日記を参考に、homebrewを使ってOpenCVをインストール。後はOpenCVPythonバインディング(動的ライブラリ)であるcv.soをPYTHONPATHの通った場所にコピーすれば、PythonからOpenCVが使えるようになっているはず。
Macに初めから入っているPythonを使う場合の手順は以下のようになります。

brew install opencv
cp /usr/local/Cellar/opencv/2.2/lib/python2.6/site-packages/cv.so /Library/Python/2.6/site-packages/

webカメラで取得した画像をそのまま表示

とりあえず簡単なデモを試してみたいなとググッてみると、ちょうどいいサンプルを発見。
http://www.betasix.net/opencv-2-2-python-examples/

import cv
import time

cv.NamedWindow("camera", 1)
capture = cv.CreateCameraCapture(0)

width = None #leave None for auto-detection
height = None #leave None for auto-detection

if width is None:
    width = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_WIDTH))
else:
	cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_WIDTH,width)    

if height is None:
	height = int(cv.GetCaptureProperty(capture, cv.CV_CAP_PROP_FRAME_HEIGHT))
else:
	cv.SetCaptureProperty(capture,cv.CV_CAP_PROP_FRAME_HEIGHT,height) 

while True:
    img = cv.QueryFrame(capture)
    cv.ShowImage("camera", img)
    k = cv.WaitKey(10);
    if k == 'f':
        break

動いた!
次は、ZBar bar code readerと絡ませて遊んでみたい。