opencvのインストールログ

環境

インストールログ

基本的に
http://opencv.willowgarage.com/wiki/InstallGuide%20%3A%20Debian
に書いてあるとおり実行しました。
一部インストールするパッケージを変更しています。
IPPやTBBはインストールしていません。pythonラッパーはインストールしました。

sudo aptitude -y install build-essential
sudo aptitude -y install cmake
sudo aptitude -y install pkg-config
sudo aptitude -y install libpng12-0 libpng12-dev libpng++-dev libpng3
sudo aptitude -y install libpnglite-dev libpngwriter0-dev libpngwriter0c2
sudo aptitude -y install zlib1g-dbg zlib1g zlib1g-dev
sudo aptitude -y install libjasper-dev libjasper-runtime libjasper1
sudo aptitude -y install pngtools libtiff4-dev libtiff4 libtiffxx0c2 libtiff-tools
sudo aptitude -y install libjpeg62 libjpeg62-dev libjpeg62-dbg libjpeg-progs
sudo aptitude -y install ffmpeg libavcodec-dev libavcodec52 libavformat52 libavformat-dev
sudo aptitude -y install libgstreamer0.10-0-dbg libgstreamer0.10-0 libgstreamer0.10-dev
sudo aptitude -y install libxine1-ffmpeg libxine-dev libxine1-bin
sudo aptitude -y install libunicap2 libunicap2-dev
sudo aptitude -y install libdc1394-22-dev libdc1394-22 libdc1394-utils
sudo aptitude -y install swig
sudo aptitude -y install libv4l-0 libv4l-dev
sudo aptitude -y install python-numpy


sudo aptitude -y install libpython2.6 python-dev python2.6-dev


sudo aptitude -y install libjpeg-progs libjpeg-dev


sudo aptitude -y install libgstreamer-plugins-base0.10-dev


mkdir ocv
cd ocv/
# sudo aptitude install subversion
svn co https://code.ros.org/svn/opencv/trunk

cd trunk/opencv/
mkdir release
cd release/
cmake -D CMAKE_BUILD_TYPE=RELEASE -D CMAKE_INSTALL_PREFIX=/usr/local -D BUILD_PYTHON_SUPPORT=ON -D BUILD_EXAMPLES=ON ..
make
sudo make install

sudo ldconfig
cd unix-install
pkg-config opencv --libs

動作チェック

C++でのチェック

ダウンロードしたファイルのtrunk/opencv/samples/cpp/ディレクトリのファイルがコンパイルできるかどうか。
コンパイルは、

g++ example.cc `pkg-config opencv --cflags --libs`

など。

pythonラッパーのチェック

pythonでimport cvが成功するかどうか。
ダウンロードしたファイルのtrunk/opencv/samples/python/delaunay.pyがpythonで実行できるかどうか。

見出し語化の高速化

nltkのWordNetLemmatizerを力ずくで高速化した。

環境

Python 2.6.5

コード

# -*- coding: utf-8 -*-

from collections import defaultdict
import nltk
from nltk.corpus import wordnet as _wordnet


_STEMMER = nltk.PorterStemmer().stem
_LEMMATIZATION_POS_PRIORITY = (_wordnet.NOUN, _wordnet.VERB,
                               _wordnet.ADJ, _wordnet.ADV)
_POS_LIST = (_wordnet.ADJ, _wordnet.ADV, _wordnet.NOUN, _wordnet.VERB)


def stem_form(form):
    return _STEMMER(form)


def _detect_pos(form):
    form = form.replace(' ', '_')
    synsets = _wordnet.synsets(form)
    if not synsets:
        return None
    pos = None
    stem = stem_form(form)
    for synset in synsets:
        if stem_form(synset.name[:-5]) == stem:
            pos = synset.pos
            break
    if pos is None:
        pos = synsets[0].pos
    if pos == _wordnet.ADJ_SAT:
        pos = _wordnet.ADJ
    return pos


def lemmatize_with_wordnet(form, pos=None):
    if pos is None:
        pos = _detect_pos(form)
        if not pos:
            return form
    assert(pos in _POS_LIST)
    return nltk.WordNetLemmatizer().lemmatize(form, pos=pos)


def _lemmatize_form_with_wordnet(form, pos_set):
    assert(pos_set)
    if len(pos_set) == 1:
        target_pos = pos_set.copy().pop()
    else:
        target_pos = _detect_pos(form)
        if not target_pos or target_pos not in pos_set:
            for pos in _LEMMATIZATION_POS_PRIORITY:
                if pos in pos_set:
                    target_pos = pos
                    break
    assert(target_pos in _POS_LIST)
    return nltk.WordNetLemmatizer().lemmatize(form, pos=target_pos)


def _construct_inflected_form_to_lemma_dictionary():
    all_inflected_forms = defaultdict(set)
    for pos, excepted_forms in _wordnet._exception_map.iteritems():
        if pos == _wordnet.ADJ_SAT:
            continue
        for excepted_form in excepted_forms:
            all_inflected_forms[excepted_form].add(pos)

    for pos in _POS_LIST:
        substitutions = _wordnet.MORPHOLOGICAL_SUBSTITUTIONS[pos]
        for lemma in _wordnet.all_lemma_names(pos=pos):
            lemma = lemma.replace('_', ' ')
            all_inflected_forms[lemma].add(pos)

            form = lemma
            if pos == _wordnet.NOUN and form.endswith('ful'):
                suffix = 'ful'
                form = form[:-3]  # len('ful')
            else:
                suffix = ''
            for new_suffix, old_suffix in substitutions:
                if form.endswith(old_suffix) or old_suffix == '':
                    if old_suffix == '':
                        inflected_form = form + new_suffix
                    else:
                        inflected_form = form[:-len(old_suffix)] + new_suffix
                    inflected_form += suffix
                    all_inflected_forms[inflected_form].add(pos)
    inflected_form_to_lemma = {}
    for inflected_form, pos_set in all_inflected_forms.iteritems():
        lemma = _lemmatize_form_with_wordnet(inflected_form, pos_set)
        inflected_form_to_lemma[inflected_form] = lemma.replace('_', ' ')
    return inflected_form_to_lemma


_INFLECTED_FORM_TO_LEMMA = _construct_inflected_form_to_lemma_dictionary()


def lemmatize_with_dict(form):
    try:
        return _INFLECTED_FORM_TO_LEMMA[form.lower().replace(' ', '_')]
    except KeyError:
        pass
    return form.lower()


def _test():
    test_forms = ('media', 'playing', 'player', 'possesses', 'sung', 'became',
                  'begun', 'fallen', 'men', 'buses', 'initial', 'initialization')
    print 'original\tstemming\twordnet_lemmatizer\tlemmatize_with_dict'
    for form in test_forms:
        print ('{0}\t{1}\t{2}\t{3}').format(form,
                                            stem_form(form),
                                            lemmatize_with_wordnet(form),
                                            lemmatize_with_dict(form))


if __name__ == '__main__':
    _test()

'''
original        stemming   wordnet_lemmatizer   lemmatize_with_dict
media           media      medium               medium
playing         play       playing              playing
player          player     player               player
possesses       possess    posse                possess
sung            sung       sung                 sung
became          becam      become               become
begun           begun      begin                begin
fallen          fallen     fall                 fall
men             men        man                  man
buses           buse       bus                  bus
initial         initi      initial              initial
initialization  initi      initialization       initialization
'''


nltkのWordNetLemmatizerの動作は、入力語句に対して以下の手順で見出し語化を行っているようです。

  1. 入力語句が例外リストに載っていないかどうかチェックする。載っていれば別途処理する。
  2. 末尾置換ルールを用いて入力語句の末尾を置換する。
  3. 辞書の見出し語に語句が存在しているかどうかをチェックする。
  4. 末尾置換ルールが無くなるまで手順2.と3.を繰り返す。

手順1.の例外リストとは、主に不規則変化(sing-sang-sungなど)を解決するための手順のようです。
手順2.と手順3.では、あらかじめ用意された末尾置換ルールを使用します。
例えば入力語句が名詞であり、xesで終わっている場合、末尾からxesを取り除き、代わりにxを追加する。その後、置換した単語が辞書の見出し語にあるかどうかをチェックする。例えば入力語句がboxesであるとき、末尾のxesをxに置換しboxとし、その後boxが辞書の見出し語にあるかどうかチェックする。

ソースコードを読む限り、nltkのWordNetLemmatizerは本家WordNetC言語実装と同様の手順で見出し語化を行っているらしい。

末尾の置換、検索、を繰り返しているので実行速度が遅い。
そこで、これら例外リストと置換ルールを使用して、WordNetLemmatizerが処理できる語句をすべて生成する。
それらWordNetLemmatizerが処理できる語句をキーとし、それらの見出し語を値とする辞書(_INFLECTED_FORM_TO_LEMMA)を作成した。
{'boxes': 'box', 'box': 'box', ..., 'media': 'medium', 'medium': 'medium', ...}のような活用形がキーであり、見出し語が値である辞書。

実行速度テスト

http://americannationalcorpus.org/OANC/index.htmlから抽出した16,814,123個の英単語(296,528種類)に対して、見出し語化にかかる時間を測った。

nltkのWordNetLemmatizerによる見出し語化(lemmatize_with_wordnet)では3583秒(およそ1時間)、
nltkのWordNetLemmatizerによる見出し語化(lemmatize_with_wordnet)の入出力をキャッシュした場合では80秒、
今回作成した作成した辞書を使用した見出し語化(lemmatize_with_dict)では20秒、

となった。結局入出力をキャッシュした場合と大差無い結果になった。
しかし、入出力をキャッシュする場合、WordNetに登録されていない単語や固有名詞などが入力されるたび、キャッシュのサイズが大きくなる。
その点、今回作成した辞書のサイズは固定されているので、メモリ消費量の増加などを気にしなくて良い。
今回作成した辞書にも問題点がある。今回作成した辞書では、入力語句から出力が一意に決まるが、通常は一意には決まらない。例えば、betterの見出し語はwellかgoodかは品詞を用いない限り決めにくい。

pythonによる文字列の正規化

テキストマイニングなどを行うためには文書、文、単語などの文字列の正規化が重要です。
単語の大文字小文字の統一、半角全角の統一などをする必要があります。
文字列の正規化のために利用しているpythonコードを以下に書いておきます。
今後増える可能性もあります。

実行環境

Ubuntu 10.04 64ビット
python 2.6.5

unicode型に変換する

def unicode_ignore_invalid_char(text):
    if isinstance(text, str):
        return text.decode('utf-8', 'ignore')
    return text

変換不能な文字列を無視してstr型からunicode型に変換する。

str型に変換する

def str_ignore_invalid_char(text):
    if isinstance(text, unicode):
        return text.encode('utf-8', 'ignore')
    return text

変換不能な文字列を無視してunicode型からstr型に変換する。

入出力の文字列型を統一する

from functools import wraps

def consistent_texttype(function):
    @wraps(function)
    def _consistent_texttype(*args, **kwargs):
        assert(1 <= len(args))
        input_text = args[0]
        is_unicode = False
        if isinstance(input_text, unicode):
            is_unicode = True
        elif not isinstance(input_text, str):
            is_unicode = isinstance(input_text[0], unicode)  # for collections
        output_text = function(*args, **kwargs)
        if isinstance(output_text, unicode) or isinstance(output_text, str):
            if is_unicode:
                return unicode_ignore_invalid_char(output_text)
            return str_ignore_invalid_char(output_text)
        if is_unicode:
            return map(unicode_ignore_invalid_char, output_text)
        return map(str_ignore_invalid_char, output_text)
    return _consistent_texttype

入力文字列がstr型であるとき出力文字列もstr型にし、入力文字列がunicode型であるとき出力文字列もunicode型にするデコレータ。

unicodeを正規化する

import unicodedata

@consistent_texttype
def normalize_unicode(text, form='NFKC'):
    assert(form in ('NFC', 'NFKC', 'NFD', 'NFKD'))
    unicode_text = unicode_ignore_invalid_char(text)
    normalized_text = unicodedata.normalize(form, unicode_text)
    return normalized_text

半角カタカナを全角カタカナに変換したりする。
例えば㌻をページ、ハンカクカナをハンカクカナに変換する。

HTMLエンティティを変換する

from BeautifulSoup import BeautifulSoup

@consistent_texttype
def unescape_entities_with_beautifulsoup(htmltext, prettify=False):
    soup = BeautifulSoup(htmltext, convertEntities=BeautifulSoup.HTML_ENTITIES)
    if prettify:
        return soup.prettify()
    return soup.__repr__()

BeautifulSoupを利用してHTMLエンティティを変換する。
例えば&gt;を>に変換する。

from BeautifulSoup import BeautifulStoneSoup

@consistent_texttype
def unescape_entities_with_beautifulstonesoup(htmltext, prettify=False):
    soup = BeautifulStoneSoup(htmltext,
                              convertEntities=BeautifulStoneSoup.HTML_ENTITIES)
    if prettify:
        return soup.prettify()
    return soup.__repr__()

BeautifulStoneSoupを利用してHTMLエンティティを変換する。
BeautifulSoupを利用した場合と同じかもしれない。

from htmlentitydefs import name2codepoint
import re

# derived from BeautifulSoup
# __author__ = "Leonard Richardson (leonardr@segfault.org)"
# __version__ = "3.1.0.1"
# __copyright__ = "Copyright (c) 2004-2009 Leonard Richardson"
# __license__ = "New-style BSD"
def _unescape_entity(match):
    x = match.group(1)
    if x in name2codepoint:
        return unichr(name2codepoint[x])
    elif 0 < len(x) and x[0] == '#':
        if 1 < len(x) and x[1] == 'x':
            return unichr(int(x[2:], 16))
        return unichr(int(x[1:]))
    return u'&{0};'.format(x)


@consistent_texttype
def unescape_entities(htmltext):
    unicode_htmltext = unicode_ignore_invalid_char(htmltext)
    unescaped_text = re.sub(u'&(#\d+|#x[0-9a-fA-F]+|\w+);',
                            _unescape_entity, unicode_htmltext)
    assert(isinstance(unescaped_text, unicode))
    return unescaped_text

BeautifulSoupのHTMLエンティティ変換部分を抽出し、少し変更を加えたもの。
HTMLエンティティ変換のためだけにBeautifulSoupを利用するのは高価すぎると考えるときはこちらを利用する。
BeautifulSoupはNew-style BSDライセンスです。

語幹を抽出する(ステミング)

import nltk

@consistent_texttype
def stem_term(term, porter=True):
    if porter:
        return nltk.PorterStemmer().stem(term)
    return nltk.LancasterStemmer().stem(term)

英語用。例えばinitial, initializeをinitiにする。

見出し語化・レンマ化(lemmatization)

import nltk
from nltk.corpus import wordnet

@consistent_texttype
def lemmatize_term(term, pos=None):
    if pos is None:
        synsets = wordnet.synsets(term)
        if not synsets:
            return term
        pos = synsets[0].pos
        if pos == wordnet.ADJ_SAT:
            pos = wordnet.ADJ
    assert(pos in (wordnet.NOUN, wordnet.VERB, wordnet.ADJ, wordnet.ADV))
    return nltk.WordNetLemmatizer().lemmatize(term, pos=pos)

英語用。WordNetを用いて単語の見出し語化を行う。
例えばis, areをbeに、potatosをpotatoにする。
品詞(pos)の指定がなければsynsetsのうち、一番最初に現れる品詞を使用する。

小文字にする

text.lower()

もしくは、

import string

def lower_text(text):
    return string.lower(text)

大文字にする

text.upper()

もしくは、

import string

def upper_text(text):
    return string.upper(text)

先頭のみ大文字にする

text.capitalize()

もしくは、

import string

def capitalize_text(text):
    return string.capitalize(text)

参考文献

入門 自然言語処理

入門 自然言語処理

nkf python インターフェースのインストール

環境

インストール方法

$ mkdir temp  # 作業用ディレクトリの作成
$ cd temp
# http://sourceforge.jp/projects/nkf/ から nkf-2.1.1.tar.gz をダウンロード
temp$ tar zxvf nkf-2.1.1.tar.gz
temp$ cd nkf-2.1.1/

temp/nkf-2.1.1$ wget ftp://city.plala.jp:1221/NkfPython/NKF_python20090602.tgz
temp/nkf-2.1.1$ tar zxvf NKF_python20090602.tgz
temp/nkf-2.1.1$ cd NKF.python/
temp/nkf-2.1.1/NKF.python$ sudo python setup.py install
# Python.h: No such file or directory と表示された場合は、以下を実行する
# sudo aptitude install python-dev
temp/nkf-2.1.1/NKF.python$ cd ../../../
$ sudo rm -rf temp

使用方法

import nkf
flag = '-w'
output = nkf.nkf(flag, input_text)

input_code = nkf.guess(input_text)

Pythonにおける並行処理について

気になったのでPythonのGIL(Global Interpreter Lock)が並行処理にどの程度影響するかについて少し実験しました。

はじめに

まず、「並行」処理と「並列」処理という言葉を区別する必要があります。下記参考文献の「並行コンピュータ技法」によると、

 システムが複数の動作を同時に実行状態(in progress)に保てる機能を備えている場合を並行(concurrent)と言い、
複数の動作を同時に実行できる場合を並列(parallel)と言います。
 重要な概念、違いは「実行状態」という点です。
...中略...
 「並行」は「並列」を含有します。

だそうです。同書によると、1つのCPUコアが2つのスレッドを切り替えながら処理する場合は「並行」処理に含まれるようです。「並列」処理では複数のCPUコアが必須で、複数のスレッドが複数のCPUコアにより同時に実行される事を「並列」処理と言うようです。

目指すべきは「並行」処理ではなく「並列」処理な気がします。

テスト環境

テストコード

1. 逐次処理でのCPU負荷の大きい処理

# sequential_cpu.py


def _cpu_bound_work():
    i = 0
    while i < 100000000:
        i += 1


if __name__ == '__main__':
    for _ in xrange(8):
        _cpu_bound_work()

2. threadingでのCPU負荷の大きい処理

# threading_cpu.py

import threading


def _cpu_bound_work():
    i = 0
    while i < 100000000:
        i += 1


class TestThread(threading.Thread):
    def run(self):
        _cpu_bound_work()


if __name__ == '__main__':
    mainthread = threading.currentThread()
    for _ in xrange(8):
        thread = TestThread()
        thread.start()
    for thread in threading.enumerate():
        if mainthread != thread:
            thread.join()

3. multiprocessingによるCPU負荷の大きい処理

# multiprocessing_cpu.py

import multiprocessing


def _cpu_bound_work():
    i = 0
    while i < 100000000:
        i += 1


class TestProcess(multiprocessing.Process):
    def run(self):
        _cpu_bound_work()


if __name__ == '__main__':
    for _ in xrange(8):
        process = TestProcess()
        process.start()
    for process in multiprocessing.active_children():
        process.join()

4. 逐次処理によるIO待ちの大きい処理

# sequential_io.py

import time


def _io_bound_work():
    time.sleep(10.0)  # to simulate i/o bound work


if __name__ == '__main__':
    for _ in xrange(8):
        _io_bound_work()

5. threadingによるIO待ちの大きい処理

# threading_io.py

import threading
import time


def _io_bound_work():
    time.sleep(10.0)  # to simulate i/o bound work


class TestThread(threading.Thread):
    def run(self):
        _io_bound_work()


if __name__ == '__main__':
    mainthread = threading.currentThread()
    for _ in xrange(8):
        thread = TestThread()
        thread.start()
    for thread in threading.enumerate():
        if mainthread != thread:
            thread.join()

6. multiprocessingによるIO待ちの大きい処理

# multiprocessing_io.py

import multiprocessing
import time


def _io_bound_work():
    time.sleep(10.0)  # to simulate i/o bound work


class TestProcess(multiprocessing.Process):
    def run(self):
        _io_bound_work()


if __name__ == '__main__':
    for _ in xrange(8):
        process = TestProcess()
        process.start()
    for process in multiprocessing.active_children():
        process.join()

テスト結果

1. 逐次処理によるCPU負荷の大きい処理

$ time python sequential_cpu.py
real	0m45.265s
user	0m45.230s
sys	0m0.020s

2. threadingによるCPU負荷の大きい処理

$ time python threading_cpu.py 
real	1m8.033s
user	1m7.420s
sys	0m16.930s

3. multiprocessingによるCPU負荷の大きい処理

$ time python multiprocessing_cpu.py 
real	0m10.969s
user	1m24.960s
sys	0m0.040s

4. 逐次処理によるIO待ちの大きい処理

$ time python sequential_io.py 
real	1m20.095s
user	0m0.010s
sys	0m0.010s

5. threadingによるIO待ちの大きい処理

$ time python threading_io.py 
real	0m10.029s
user	0m0.020s
sys	0m0.000s

6. multiprocessingによるIO待ちの大きい処理

$ time python multiprocessing_io.py 
real	0m10.035s
user	0m0.020s
sys	0m0.010s

まとめ

1,4の逐次処理が遅いのは当然として、2のthreadingモジュールを使用してCPU負荷の大きい処理を行った場合の実行速度がかなり遅いです。
pythonのGILの影響で、並列処理ができていない事が原因なのでしょう。3のmultiprocessingモジュールを使用した場合はGILを回避できるようです。

並列処理できないthreadingモジュールを使う意味はあるのでしょうか。少なくとも5のように、IO待ち時間が長い処理を複数回行う場合はthreadingモジュールを使用する意味はあるようです。webページのクローラなどには向いているようです。

やはりthreadingモジュールよりもmultiprocessingモジュールを使用した方がいい気がする。(もしくはos.forkを使用するか)

ALAssetsLibraryについて

 以下は私がWebや書籍から集めた情報や、私が試行錯誤した経験により、ALAssetsLibraryについてまとめたものです。
そのため、間違いが含まれている可能性があります。何かしらの間違いを見つけた方はご指摘願います。

はじめに

 ALAssetsLibraryとはiOS4で追加された、iPhone/iPad/iPodの写真/映像フォルダにアクセスするためのフレームワークです。
 私はiOS4がリリースされた頃からiOSプログラミングを始めたため、iOS3以前については良く知りませんが、iOS3以前ではファイルパスを指定し、写真/映像フォルダに直接アクセスしていたようです。しかし現在はファイルに直接アクセスするとAppleの審査が通らないようです。そのため現在iPhone/iPad/iPodの写真・映像フォルダにアクセスするためにはALAssetsLibraryを使用しないといけません。
 ALAssetsLibraryを使用すると画像データのEXIFデータなども取得でき、便利です。
 ALAssetsLibraryがリリースされてから半年程度経ちますが、ALAssetsLibraryに関するサンプルコードやチュートリアルなどはかなり少ないです。そのため今回ALAssetsLibraryについてまとめることにしました。

サンプルコード

MYAssetsAccessor.h

#import <UIKit/UIKit.h>


@class ALAssetsLibrary;


@interface MYAssetsAccessor : NSObject {
 @private
  ALAssetsLibrary *assetsLibrary_;
}


- (void)loadUrlsWithCallbackTarget:(id)anObject
                    callbackMethod:(SEL)aSelector;
- (NSDictionary *)metadataOfAssetIdentifiedByUrl:(NSURL *)url;
- (UIImage *)thumbnailOfAssetsIdentifiedByUrl:(NSURL *)url;
- (UIImage *)fullResolutionImageOfAssetsIdentifiedByUrl:(NSURL *)url;


+ (void)addObserverForAssetsLibraryChange:(id)anObserver
                                 selector:(SEL)aSelector;
+ (void)removeObserverForAssetsLibraryChange:(id)anObserver;


// to understand how the enumeration works.
- (void)enumerationTest_;

@end


MYAssetsAccessor.m

#import <AssetsLibrary/AssetsLibrary.h>  // require AssetsLibrary.framework
#import "MYAssetsAccessor.h"


@implementation MYAssetsAccessor

- (id)init {
  assetsLibrary_ = [[ALAssetsLibrary alloc] init];
  return self;
}


- (void)dealloc {
  [assetsLibrary_ release];
  [super dealloc];
}


- (void)loadUrlsWithCallbackTarget:(id)anObject
                    callbackMethod:(SEL)aSelector {
  id callbackTarget = [anObject retain];
  SEL callbackMethod = aSelector;
  
  NSMutableArray *urls = [[NSMutableArray alloc] init];
  
  void (^assetResultBlock)(ALAsset *, NSUInteger, BOOL *) =
  ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
    if (!*stop && asset) {
      ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation];
      [urls addObject:[defaultRepresentation url]];
    }
  };
	
  void (^resultBlock)(ALAssetsGroup *, BOOL *) =
  ^(ALAssetsGroup *group, BOOL *stop) {
    if (*stop || !group) {
      [callbackTarget performSelector:callbackMethod withObject:urls];
    } else {
      [group setAssetsFilter:[ALAssetsFilter allAssets]];
      [group enumerateAssetsUsingBlock:assetResultBlock];
    }
  };
  
  void (^failureBlock)(NSError *) =
  ^(NSError *error) {
    NSLog(@"exception in enumerating assets. %@", error);
    [callbackTarget performSelector:callbackMethod withObject:urls];
  };
  
  ALAssetsGroupType targetGroupType = ALAssetsGroupAll;
  [assetsLibrary_ enumerateGroupsWithTypes:targetGroupType
                                usingBlock:resultBlock
                              failureBlock:failureBlock];
  
  [urls release];
  [callbackTarget release];
}


- (NSDictionary *)metadataOfAssetIdentifiedByUrl:(NSURL *)url {
  __block NSDictionary *metadata = nil;
  [assetsLibrary_ assetForURL:url
                  resultBlock:^(ALAsset *asset) {
                    ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation];
                    metadata = [[defaultRepresentation metadata] copy];
                  }
                 failureBlock:^(NSError *error) {
                   NSLog(@"exception in accessing assets by url. %@", error);                      
                 }];
  return [metadata autorelease];
}


- (UIImage *)thumbnailOfAssetsIdentifiedByUrl:(NSURL *)url {
  __block UIImage *thumbnail = nil;
  [assetsLibrary_ assetForURL:url
                  resultBlock:^(ALAsset *asset) {
                    thumbnail = [[UIImage alloc] initWithCGImage:[asset thumbnail]];
                  }
                 failureBlock:^(NSError *error) {
                   NSLog(@"exception in accessing assets by url. %@", error);                      
                 }];
  return [thumbnail autorelease];
}


- (UIImage *)fullResolutionImageOfAssetsIdentifiedByUrl:(NSURL *)url {
  __block UIImage *fullResolutionImage = nil;
  [assetsLibrary_ assetForURL:url
                  resultBlock:^(ALAsset *asset) {
                    ALAssetRepresentation *defaultRepresentation = [asset defaultRepresentation];
                    fullResolutionImage = [[UIImage alloc] initWithCGImage:[defaultRepresentation fullResolutionImage]];
                  }
                 failureBlock:^(NSError *error) {
                   NSLog(@"exception in accessing assets by url. %@", error);                      
                 }];
  return [fullResolutionImage autorelease];
}


+ (void)addObserverForAssetsLibraryChange:(id)anObserver
                                 selector:(SEL)aSelector {
  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
  [defaultCenter addObserver:anObserver
                    selector:aSelector
                        name:ALAssetsLibraryChangedNotification
                      object:nil];
}


+ (void)removeObserverForAssetsLibraryChange:(id)anObserver {
  NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
  [defaultCenter removeObserver:anObserver
                           name:ALAssetsLibraryChangedNotification
                         object:nil];
}


// to understand how the enumeration works.
- (void)enumerationTest_ {
  void (^assetResultBlock)(ALAsset *, NSUInteger, BOOL *) =
  ^(ALAsset *asset, NSUInteger index, BOOL *stop) {
    if (*stop) {
      NSLog(@"stopped enumerating assets.");
    } else if (!asset) {
      NSLog(@"the end of the group.");
    } else {
      NSDictionary *urls = [asset valueForProperty:ALAssetPropertyURLs];
      NSLog(@"%@", urls);
    }
  };
	
  void (^resultBlock)(ALAssetsGroup *, BOOL *) =
  ^(ALAssetsGroup *group, BOOL *stop) {
    if (*stop) {
      NSLog(@"stopped enumerating groups.");
    } else if (!group) {
      NSLog(@"the end of enumation.");
    } else {
      NSString *groupName = [group valueForProperty:ALAssetsGroupPropertyName];
      
      ALAssetsFilter *photosFilter = [ALAssetsFilter allPhotos];
      [group setAssetsFilter:photosFilter];
      NSInteger numberOfPhotos = [group numberOfAssets];
      NSLog(@"%d photos in %@", numberOfPhotos, groupName);
      [group enumerateAssetsUsingBlock:assetResultBlock];
      
      ALAssetsFilter *videosFilter = [ALAssetsFilter allVideos];
      [group setAssetsFilter:videosFilter];
      NSInteger numberOfVideos = [group numberOfAssets];
      NSLog(@"%d videos in %@", numberOfVideos, groupName);
      [group enumerateAssetsUsingBlock:assetResultBlock];
      
      ALAssetsFilter *allFilter = [ALAssetsFilter allAssets];
      [group setAssetsFilter:allFilter];
      NSInteger numberOfAssets = [group numberOfAssets];
      NSLog(@"%d assets in %@", numberOfAssets, groupName);
      [group enumerateAssetsUsingBlock:assetResultBlock];
    }
  };
  
  void (^failureBlock)(NSError *) =
  ^(NSError *error) {
    NSLog(@"exception in enumerating assets. %@", error);
  };
	
  // ALAssetsGroupType targetGroupType = ALAssetsGroupLibrary |
  //   ALAssetsGroupAlbum |
  //   ALAssetsGroupEvent |
  //   ALAssetsGroupFaces |
  //   ALAssetsGroupSavedPhotos;
  ALAssetsGroupType targetGroupType = ALAssetsGroupAll;
  [assetsLibrary_ enumerateGroupsWithTypes:targetGroupType
                                usingBlock:resultBlock
                              failureBlock:failureBlock];
  
  // the result will be:
  // 2011-04-28 18:38:40.939 MyProject[869:707] 4 photos in Photo Library
  // 2011-04-28 18:38:40.964 MyProject[869:707] {
  //   "public.jpeg" = "assets-library://asset/asset.jpg?id=104&ext=jpg";
  // }
  // ...
  // 2011-04-28 18:38:40.989 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:40.993 MyProject[869:707] 0 videos in Photo Library
  // 2011-04-28 18:38:40.997 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.003 MyProject[869:707] 4 assets in Photo Library
  // 2011-04-28 18:38:41.008 MyProject[869:707] {
  //   "public.jpeg" = "assets-library://asset/asset.jpg?id=104&ext=jpg";
  // }
  // ...
  // 2011-04-28 18:38:41.030 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.039 MyProject[869:707] 25 photos in Camera Roll
  // 2011-04-28 18:38:41.053 MyProject[869:707] {
  //   "public.jpeg" = "assets-library://asset/asset.JPG?id=1000000021&ext=JPG";
  // }
  // ...
  // 2011-04-28 18:38:41.273 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.277 MyProject[869:707] 4 videos in Camera Roll
  // 2011-04-28 18:38:41.285 MyProject[869:707] {
  //   "com.apple.quicktime-movie" = "assets-library://asset/asset.MOV?id=1000000004&ext=MOV";
  // }
  // ...
  // 2011-04-28 18:38:41.314 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.319 MyProject[869:707] 29 assets in Camera Roll
  // 2011-04-28 18:38:41.327 MyProject[869:707] {
  //   "public.jpeg" = "assets-library://asset/asset.JPG?id=1000000021&ext=JPG";
  // }
  // ...
  // 2011-04-28 18:38:41.569 MyProject[869:707] the end of the group.
  // 2011-04-28 18:38:41.573 MyProject[869:707] the end of enumation.
}

@end


AppDelegate_iPhone.h

#import <UIKit/UIKit.h>


@class MYAssetsAccessor;


@interface AppDelegate_iPhone : NSObject <UIApplicationDelegate> {
  UIWindow *window_;
  MYAssetsAccessor *myAssetsAccessor_;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;

@end


AppDelegate_iPhone.m

#import "AppDelegate_iPhone.h"
#import "MYAssetsAccessor.h"


@interface AppDelegate_iPhone(Private)

- (void)assetLibraryDidChange_:(NSNotification *)aNotification;
- (void)assetUrlsDidLoad_:(NSArray *)urls;

@end


@implementation AppDelegate_iPhone
@synthesize window = window_;

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    
  [self.window makeKeyAndVisible];
  myAssetsAccessor_ = [[MYAssetsAccessor alloc] init];
  [MYAssetsAccessor addObserverForAssetsLibraryChange:self
                                             selector:@selector(assetLibraryDidChange_:)];
  [myAssetsAccessor_ enumerationTest_];
  [myAssetsAccessor_ loadUrlsWithCallbackTarget:self
                                 callbackMethod:@selector(assetUrlsDidLoad_:)];
  return YES;
}


- (void)applicationWillTerminate:(UIApplication *)application {
  [MYAssetsAccessor removeObserverForAssetsLibraryChange:self];
}


- (void)dealloc {
  [myAssetsAccessor_ release];
  [window_ release];
  [super dealloc];
}

@end


@implementation AppDelegate_iPhone(Private)

- (void)assetLibraryDidChange_:(NSNotification *)aNotification {
  NSLog(@"asset library changed. %@", aNotification);
  // TODO reload urls
}


- (void)assetUrlsDidLoad_:(NSArray *)urls {
  NSLog(@"%d urls, %@", [urls count], urls);
  if ([urls count] == 0) {
    return;
  }
  NSURL *url = [urls objectAtIndex:30];
  NSDictionary *metadata = [myAssetsAccessor_ metadataOfAssetIdentifiedByUrl:url];
  NSLog(@"metadata of %@ is %@", url, metadata);
  UIImage *thumbnail = [myAssetsAccessor_ thumbnailOfAssetsIdentifiedByUrl:url];
  NSLog(@"thumbnail of %@ is %@", url, thumbnail);
  UIImage *fullResolutionImage = [myAssetsAccessor_ fullResolutionImageOfAssetsIdentifiedByUrl:url];
  NSLog(@"full resolution image of %@ is %@", url, fullResolutionImage);
}

@end

サンプルコード概要

 MYAssetsAccessorはALAssetsLibraryを利用した、画像/映像フォルダへアクセスするためのクラスです。AppDelegate_iPhoneはMYAssetsAccessorを利用したコードです。

 MYAssetsAccessorのloadUrlsWithCallbackTarget:callbackMethod:で画像/映像フォルダ中の画像/映像を表すURLの配列を取得します。メソッド中のenumerateGroupsWithTypes:usingBlock:failureBlock:は非同期で実行されるため、URLの取得が終了するもしくは何らかのエラーが生じた場合、指定したcallbackメソッドを呼び出します。なお、ブロック内で*stop = YES;とすると、走査を終了させることもできます。
 URLの一覧が取得できれば後はメタデータや、サムネイルを取得したりといろいろできます。

 URLは画像/映像フォルダに何らかの変更が生じた場合、変更される事があるので、ALAssetsLibraryChangeNotificationを監視しておきます。

 MYAssetsAccessorには動作確認用のenumerationTest_メソッドを用意しています。画像/映像フォルダを走査して、URL等をログに書き出します。


サンプルコードはMITライセンスです。
 MYAssetsAccessor.h 直
 MYAssetsAccessor.m 直
 AppDelegate_iPhone.h 直
 AppDelegate_iPhone.m 直

注意点

ALAssetsLibraryのインスタンスのメソッドはメインスレッドで呼ぶ方が良い

 ALAssetsLibraryのインスタンスのメソッドを初めて呼び出すと、次のようなアラートが表示されます。

 メインスレッドで呼び出した場合は上記のアラートが表示されます。
 しかし、メインスレッド以外で呼び出した場合は上記のアラートが表示されず、ユーザの入力待ち状態のままになり、ALAssetsLibraryに関する処理が一向に進まなくなります。
 なお、上記のアラートでユーザがDon't Allowを選択すると写真フォルダにアクセスできず、failureBlockが呼び出されます。

ALAssetsLibraryのインスタンスのメモリ消費量は写真/映像フォルダ内のコンテンツ量に応じて増える

 画像フォルダに10枚しか写真がない場合と、画像フォルダに10,000枚の写真がある場合とでは、ALAssetsLibraryのインスタンスのメモリ消費量がかなり違います。検索用インデックスでも作っているのでしょうか。
 画像/映像フォルダ内のコンテンツ量が増えるにつれ、アプリケーションで使用できるメモリ量が少なくなってしまいます。動作保障コンテンツ数などを決める必要があるのかもしれません。iPhone/iPad/iPodのデフォルトの写真ビューアも画像が80,000枚程度保存されていると強制終了するようになりますし。

ALAssetsLibraryChangeNotificationはバックグラウンド状態では受信できない

 1. 対象のアプリケーションをバックグラウンド状態にする
 2. 他のアプリケーションを起動/フォアグラウンド状態にする
 3. 他のアプリケーションで画像/映像フォルダに変更が生じる(新たな画像を追加する、など)
 4. 他のアプリケーションを終了/バックグラウンド状態にする
 5. 対象のアプリケーションをフォアグラウンド状態にする
 上記のようなアプリケーションの切り替えなどを行った場合、対象のアプリケーションがALAssetsLibraryChangeNotificationを受信するのは5のタイミングです。3の画像/映像フォルダに変更が生じた際ではなく、5の対象のアプリケーションがフォアグラウンド状態になったときです。

iTunesの同期ではALAssetsLibraryChangeNotificationは発生しない

 iTunesの同期により画像/映像フォルダに変更が生じても、ALAssetsLibraryChangeNotificationは発生しません。

iTunesで同期すると画像のEXIFは消失する

 PCからiTunes経由でiPhone/iPad/iPodに画像を転送した場合、転送元のPC上の画像にEXIFが付属していたとしても、iPhone/iPad/iPodに転送される際に削除されるようです。
 下記参考文献のiOS4プログラミングブックの中でも次のように述べられています。

PC上のオリジナル画像がExif情報付きだったとしても、同期するタイミングで情報が削られているようです。

 ただし、iPhone/iPad/iPodのカメラで撮った写真に関しては、iTuneの同期でEXIFは削除されません。おそらくApple独自のメタデータ形式などがあるのでしょう。

iTunesで同期すると画像はJpegとして保存される

 ALAssetsLibraryと直接関係はないですが、iTunesで同期すると、bmppngjpegに変換されてiPhone/iPad/iPod上に保存されるようです。

画像/映像の読み込みと新規書き込みのみ可能

 ALAssetsLibraryでは、画像/映像の読み込みと新規書き込みのみが可能で、削除や修正/上書きなどはできないようです。

参考

公式リファレンス

ALAssetsLibrary Class Reference
 Apple公式のリファレンスです。

読み込み

iOS 4の新機能13選&AssetsLibraryで作る画像ビューア (2/4):SDKで始めるiPad/iPhoneアプリ開発の勘所(終) - @IT
 ALAssetsLibraryを使用した画像ビューアの作成方法。ソースコードのダウンロードもできます。
iPhoneの画像からEXIF,GPSデータを取得する ~ guess what?
 GPSデータの読み込みなど。

書籍

iOS4プログラミングブック

iOS4プログラミングブック

 iOS4で追加された機能に焦点をあてた本。多くはありませんがALAssetsLibraryについての記述があります。
私が知っている限り、ALAssetsLibraryについて書かれている日本語の本はこれだけです。

docx, xlsx, pptxファイルからテキストをXMLとして抽出する

MS Office 2007以降のファイル(.docx, .xlsx, .pptx)はZip圧縮されたXMLファイルなので、ファイルからのテキスト抽出などが簡単にできます。


.pptxファイルをZipファイルとして展開すると上記のようになります。
各スライドがXMLファイルとして保存されています。
.docx, .xlsx, .pptxはそれぞれディレクトリ構造が若干違います。
読み取り、もしくは書き込みパスワードが設定されている場合はZipファイルとして解凍はできないようです。

import os.path
import zipfile


def _extract_xmls_from_msxml_base(filepath, prefix):
    xmls = []
    zf = None
    try:
        zf = zipfile.ZipFile(filepath, 'r')
        for name in zf.namelist():
            if name.startswith(prefix):
                xmls.append(zf.read(name))
    except zipfile.BadZipfile, e:
        # for locked files
        print e
    finally:
        if zf is not None:
            zf.close()
    return xmls


def extract_xmls(filepath, ext=None):
    if ext is None:
        root, ext = os.path.splitext(filepath)
    ext = ext.lower()
    prefix = None
    if ext == '.pptx':
        prefix = 'ppt/slides/slide'
    elif ext == '.xlsx':
        prefix = 'xl/worksheets/sheet'
    elif ext == '.docx':
        prefix = 'word/document'

    if prefix is not None:
        return _extract_xmls_from_msxml_base(filepath, prefix)
    return None


def _test():
    print extract_xmls('test.pptx')
    

if __name__ == '__main__':
    _test()