本棚の写真から本のリストを構築したい

バーコードを読み取って本のリストを作るiPhoneアプリはあるらしいのだが,本というものは背表紙が見える状態で整理されていることが多いのだし,本棚の写真を撮るだけでリスト作れたら便利だなーと思いまして.で,画像処理全くわからないんだけど我流でOpenCVを使いながら試み中.一応そのものズバリなタイトルな英語論文(Building book inventories using smartphones)はあって,それを参考にキーワードを探りながら画像処理の知識を蓄えている.

本のタイトルのあたりは角が多いのでコーナー検出をすればそこにコーナーが集中する,ということと,直線の検出をすると平行な線がたくさん出る,ということを使えばいいんじゃないかと思っている(後者は論文でも使っている).

今の処理の流れは

  1. 元画像に対してコーナー検出を行なって角の座標を得る(コーナー検出アルゴリズムはEigenValueがよさそうという結果を得ている)
  2. 元画像に対してCannyエッジ検出を行った画像を得る
  3. エッジ検出を行った画像に対して,角の座標付近を黒で塗りつぶす処理を行う(文字が消えるのを期待する)
  4. HoughLinesで直線検出を行う(これで背表紙ごとに分割できるはず…うまくいけば)

比較的うまくいっている例が以下となる:

トランジスタ技術みたいな太い本はうまくいくのだが,細い本や暗い本,角が丸みを帯びた本はダメ.スマートフォンだとどうしても解像度が低いし,ズームができない(つまり斜め方向から撮影する本が多くなって角が判別しにくくなる)のでより条件が悪くなる.遠くから解像度の高いカメラで撮影しないと使い物にならなさそう.

背表紙ごとの分割をクリアした所で,OCRと名寄せの困難が待ち受けてるので,ハードルはかなり高そう.

ついに林檎の軍門に降る

インターネットの人に公開する用の電話番号が欲しくなったので050 plusとかいうサーヴィスのためにiPod touchを買った.

良い点

  • 解像度の高いディスプレイと良質なフォントがとてもQoLに影響することが分かった.
  • 家計簿アプリのレシート撮影して解析する機能が楽しい.

良くない点

  • バイブレーション機能が無いので音をなるべく出さずに着信に気づく方法がない.やむを得ず常時イヤホンをつけている.
  • 050 plusの留守電機能がちと鬱陶しい(録音終了時に#を押してもう一回1#押さないといけない)

イヤホンのひもが本体とくっついてるのが嫌なので,片耳につけるタイプのBluetoothヘッドセット試そうかと思ってる.
追記:050plus公式に「Bluetoothには対応しておりません」書かれてた…どうしたものか

Ocsigen/EliomでWebアプリを作る (フォームの設置編)

今回書いた全ソースコードはgithubのリポジトリのtut3ディレクトリにあります.

前回はローカルの手書きHTMLからサービスに向ってPOSTリクエストを投げましたが,今回はリクエストを投げるHTMLをWebアプリで出すようにします.その前の準備として前回のままだと不都合があるので,main_serviceを 1. サービスを受け持つ部分 2. ページを出力する部分 の2つに分割します.

let main_service =
  Eliom_services.service
    ~path:["index"]
    ~get_params:unit
    ()
 
let comment_service =
  Eliom_services.post_service
    ~fallback:main_service
    ~post_params:(string "name" ** string "comment" ** string "submit")
    ()
 
let main_page =
  register
    ~service:main_service
    (fun () () -> (* HTML出力部.あとでフォームを出力するように置き換える *) Lwt.return
        (html (head (title (pcdata "")) [])
              (body [p [pcdata "hogehoge"]])))

つまり,register_serviceというのはserviceとregisterを一挙にやってくれていたわけです.

次にHTML出力部をフォームを出力するように置き換えます.

	 let f =
	 (Eliom_output.Html5.post_form comment_service
				       (fun (name, (comment, submit)) ->
					    [p [pcdata "name: ";
					    string_input ~input_type:`Text ~name:name ();
					    pcdata "comment: ";
					    string_input ~input_type:`Text ~name:comment ();
					    string_input ~input_type:`Submit ~name:submit ~value:"Post" ()]]) ()) in
	 return (html (head (title (pcdata "")) [])
                               (body [f]))

Eliom_output.Html5.post_formの最初の引数でどのサービスに向ってリクエストを投げるかを指定し,次の引数でPOSTリクエストのパラメータからどのようなフォームを作るかを指定します.ここでcomment_serviceを参照する必要があったので,serviceとregisterを分割しました.いやlet rec使えば分割しないでも行けたんですけど,ややこしくなるので.

string_inputはEliom_output.Html5.string_inputのことです.まあそんなに難しくないと思います.

こうすることにより,/indexにアクセスするとフォームが表示されるようになります.

Ocsigen/EliomでWebアプリを作る (POSTリクエストの処理編)

今回書いた全ソースコードはgithubのリポジトリのtut2ディレクトリにあります.

POSTを処理するサービスを記述するには,POST単体だけではダメで,そのサービスのURIがGETリクエストされた時の処理も書かないといけません.
EliomではEliom_services.post_service関数の~fallbackにGETを処理するサービスを指定します.~post_paramsにはGETのときと同様にパラメータを記述します.

let comment_service =
  Eliom_services.post_service
    ~fallback:main_service
    ~post_params:(string "name" ** string "comment" ** string "submit")
    ()

サービスでどんなページを表示するかはGETのときと似たような感じです(今回は関数が2つに分割されてるけど).ページの内容を返す関数の第二引数の記述に注意.どうやら(param1, (param2, (param3, …)))と書かないといけないようで.

let comment_page = Eliom_output.Html5.register
    ~service:comment_service
    (fun () (name, (comment, _)) ->
      let message = name ^ "さんのコメント: " ^ comment
      in
      Lwt.return
        (html (head (title (pcdata "")) [])
              (body [p [pcdata message]])));

テスト用のHTMLが以下.

<html>
<head><title>post</title></head>
<body>
<form action="http://localhost:8080/index" method="post">
name: <input type="text" name="name"><br>
comment: <input type="text" name="comment"><br>
<input type="submit" name="submit" value="Submit">
</form>
<body>
</html>

これに適当に入力してsubmitすると以下のようになるはず.

なお,この場合,Eliom_services.post_serviceの~post_paramsにはちゃんとstring “submit”を入れておかないと「パラメータが合わねえよ」と怒られます.パラメータの過不足は許容されないっぽい?
追記: Eliom_parameters.optを使って,opt (string “submit”)と書けばオプショナルなパラメータを作れるようなので不足する分にはよさそうです.

Ocsigen/EliomでWebアプリを作る (とにかく何か表示する編)

まえがき:ここのサンプルはgithubのplus7/ocsigen_tutのtut1/に置いてありますので適宜cloneしてください.

Webアプリはとにもかくにも何かを表示しないといけないことになっているらしいので.とりあえずHello worldしましょう.

ocsigenserverを動かすには設定ファイルが必要なのは前のエントリで環境構築していればわかると思いますが,いったい何が書いてあるんでしょうか.下記のXMLが私が簡略化してみたものです.

<!-- -*- Mode: Xml -*- -->
<ocsigen>
 
  <server>
 
    <port>8080</port>
 
    <logdir>./log</logdir>
    <datadir>./data</datadir>
    <user>ahya</user><!--www-data-->
    <group>ahya</group><!--www-data-->
 
    <commandpipe>/tmp/ocsigen_command</commandpipe>
 
    <charset>utf-8</charset>
 
    <findlib path="/usr/local/lib/ocaml/3.12.1"/>
 
    <extension findlib-package="ocsigenserver.ext.staticmod"/>
    <extension findlib-package="ocsigenserver.ext.ocsipersist-sqlite">
      <database file="./db/file"/>
    </extension>
    <extension findlib-package="eliom.server"/>
 
    <!-- Inclusion of all external configuration files matching *.conf
         from the directory 'dir' (in alphabetical order): -->
    <extconf dir="/usr/local/etc/ocsigenserver/conf.d" />
 
    <host charset="utf-8" hostfilter="*">
      <site path="">
<eliom module="./byoki.cmo" />
      </site>
    </host>
 
  </server>
</ocsigen>

<user>と<group>には自分のユーザー名入れてください.本格運用するときにはApacheのようにwww-dataとか.<logdir><datadir>はその名の通りのものです.mkdirしておいてください.

<commandpipe>はocsigenserverを制御するときに使う名前付きパイプのpathみたいです.再起動無しにモジュールをアップデートしたい時等に使うとか(Updating sites without shutting down the serverのところを参照).

<findlib>にはOCamlのライブラリへのPathが書かれています.普通にインストールしていれば変更の必要はないでしょう.

<extension>の列はocsigenserver内で有効にする拡張機能の一覧です.eliom.serverは古い資料だとocsigen.ext.eliomになってたりするので気をつけましょう.あとeliomはocsipersist-sqliteに依存してるっぽいです.これ書かないとエラーでて死にます.dbディレクトリもmkdirしておいてください.

<extconf>は私もよくわかってないのでそのままにしました.

<host>以下の<site>が核心部分です.<eliom>のmodule属性でどのモジュールに基づいてWebアプリを動かすかを指定します.<site>のpath属性ではどこのディレクトリ以下にこのWebアプリを配置するかを記述します.ここでは何も書いていないのでhttp://localhost:8080/直下でアクセスできます.例えば”exampleapp”と書けばhttp://localhost:8080/exampleapp/となります.

次にWebアプリ本体です.

open Lwt
open Eliom_pervasives
open HTML5.M
open Eliom_services
open Eliom_parameters
open Eliom_output.Html5
 
let main_service =
  register_service ~path:["index"] ~get_params:unit
    (fun () () -> return (html (head (title (pcdata "")) [])
                               (body [h1 [pcdata "Hello work!"]])))

register_serviceはEliom_output.Html5.register_serviceのことです.~pathにはこのサービスがどのpathを受け持つかを書きます.この例だとhttp:/localhost:8080/indexにアクセスするとこのサービスが表示されることになります.~get_paramsにはどのようなGETリクエストのパラメータを取るかを書きます.今回は何も取らないのでEliom_parameters.unitが指定されています.なにかパラメータを取りたい時には~get_params:(Eliom_parameters.int “i”)と書きます.こいつらにチルダがついてるのはOCamlのラベルを使っているからですね.使ってるの初めて見た….最後の関数はどんな内容を返すかの関数です.第一引数にGETのパラメータ,第二引数にPOSTのパラメータが渡されるらしい.今回は何もとらないので()になっています.htmlとかheadとかはHTML5.Mの型で,文字通りhtmlの要素を表しています.
このソースをocamlfind ocamlc -thread -package eliom.server -c byoki.mlでコンパイルした後,ocsigenserver -c kowai.confでサーバを起動すれば,http://localhost:8080/indexにアクセスすると味気のな~いページが表示されるはずです.

Ocsigen/EliomでWebアプリを作る (環境構築編)

日本語情報は3, 4年前くらいには出てるみたいなんですけど最近なさげなので検証も兼ねて作業記録つけますよ.作業環境はDebian sid.

今回は環境構築ということであんまり詳しくは書きませんので適宜リンク先を読んでください.

Ocsigen bundleのインストール

Ocsigen bundleを説明に従ってインストールする.ただしここの記述,抜けがあって,libgdbm-devが追加で必要だったので,必要なパッケージをapt-getするときには気をつけよう.

なお,Ocsigen bundleにはすでにEliomも付属しているので追加で何かする必要はない.

ocsigenserverの動作チェック

root で ocsigenserver または /etc/init.d/ocsigenserver start すれば80番ポートでデフォルトのHTTPサーバが起動するのでブラウザから確認する.It works! と出ているはず(ocsigen server単体のマニュアルより

次はもうちょっと詳しめのチュートリアルのソースコードと設定ファイルをコピペして,My first pageだけでも動かして満足してみよう.とりあえずここまで動けば遊べる態勢になっているはずだ.

次回に続く,かも?

ディレクトリの配置

最近どうやってhome以下のディレクトリ切ってるかのメモ.

  • usr  makeする系の小物ソフトやライブラリ
  • opt  unzipしたほうが早い系のでかいソフト
  • proj  他人と作業する前提のファイル類
  • repo  公的なソフトウェアのリポジトリから引っ張ってきたのがここ
  • work  雑多な作業用のディレクトリ
  • tmp  一時作業
  • vm  仮想マシンの類

いろいろなカオスを経てこんな感じにすることが増えてきたと思う.

ボツ原稿

0101000111001110101001001110011100111100001001000000100001010100
1001001111011101111110110001100001000110001010001110111010111110
1100010110110010101011011111011100110101110011011000111011100111
1101000110110000001101011011010000110100111100011000111000001110
0100010001010001011100011110101101110101110000001001001111100011
1100011111010100100111000001011010101001111000111111101101001110
1100100001100100000101011000111100001010010111110100101011010101
1001101010110001011111010110010011101011100100010110011111101111
0110011110111111010111000110001001110010110100001010100001011010
1010000101011101100100000010000110001100111100101110101111101110
1011001000110101001101001111110000111001001111011110001111010110
1101101110011111011011011011100001010010101110100011101100010011
1111110111010100101010010100101111110010111110011111001100000011
1100100001101110010010001001010010000100111000110000001011101010
0100110000010110111001100001010110011001000010110010111111011101
101111001010010110010111110001