0

opencv-haartrainingの進行状況をtwitterに流すbot

OpenCVをソースからビルドするとhaarlike分類器(顔認識などに使われているやつ)の学習ツールが手に入るんだけど、たくさんのマシンでたくさん学習させているとそれぞれの進行状況をチェックするのが面倒になってくる。

でも、入力した画像ファイルが壊れていると学習が強制終了してしまったり、データがばらつきすぎてて収束しなくてあきらめて終了されたりするので、プロセスが死んでいたらパラメータを直してすぐやり直しをさせたい。学習中は予断を許さない状況が続く。

なので、進行状況を監視してtwitterアカウントshokai_logにpostするbotを作った。
5分間隔でopencv-haartrainingの作業ディレクトリとプロセスが生きているかをチェックする。
学習stageが進む毎に適当に通知し、プロセスが強制終了していた場合は激しくreplyしてくれる。これで安心して寝れる。OpenCV1.0/2.0両方対応。

プロセスが落ちていると教えてくれたり、段階が進む毎にtwitterに投稿したりする。
一見何言ってるのかわかりにくいpostもあるが、「ドドドド」だったらstage4が終わったという意味。
tweet-haartraining.rb
辞書はコード内にある。


第2引数にopencv-haartrainingの-dataオプションで渡した「結果の書き出し先ディレクトリ名」を指定する。第3引数は無しでもいいが、twitter投稿の末尾にメモを付けられる。複数のマシンで実行していてどれの進行状況かわからなくなる時は、マシンの名前を入れておけばいい。
ruby tweet-haartraining.rb /Users/sho/path/to/training/dir/ "Macbook黒"

tweet-haartraining.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'twitter'

# setting
USER = 'your-account'
PASS = 'your-password'
INTERVAL = 300 # sleep sec
YOU = 'shokai' # 時々replyしてくる nilでreplyなし
NOPOST = false # debug用

def post(message)
  return if !message
  message = "@#{YOU} #{message}" if rand(3)<1 if !(message =~ /@#{YOU}/) && rand(2)<1
  puts message + "\t" + Time.now.to_s
  return if NOPOST
  httpAuth = Twitter::HTTPAuth.new(USER, PASS)
  tw = Twitter::Base.new(httpAuth)
  tw.update(message)
end

if ARGV.size < 1
  puts '結果が出力されるディレクトリへのパスが必要です。メモも付けられます(オプション)'
  puts 'e.g. ruby tweet-haartraining.rb /path/to/haar/training/dir/ "研究室の学習用パソコン"'
  exit 1
end

puts path = ARGV.shift
memo = ARGV.shift || ""
dir_path = path
if path =~ /\/$/
  dir_path = path
  xml_path = path[0...path.size-1]+'.xml'
else
  dir_path = path+'/'
  xml_path = path+'.xml'
end


if stage_p = Dir.glob(dir_path+'*').delete_if{|i| File::ftype(i) != 'directory'}.map{|i| i.split(/\//).last.to_i}.max
  post "ステージ#{stage_p}から開始" + " " + memo
else
  messages = ["開始。",
              "はじめ",
              "起床",
              "おきた",
              "start",
              "スタートしました",
              "hello world",
              "hello work",
              "はじめますわっ",
              "スタンバイレディ セタップ"]
  post messages[rand(messages.size)] + " " + memo
end


while true do
  sleep INTERVAL
  stage = Dir.glob(dir_path+'*').delete_if{|i| File::ftype(i) != 'directory'}.map{|i| i.split(/\//).last.to_i}.max
  if File.exists? xml_path
    messages = ["全行程完了(ステージ#{stage})。お疲れ様でした。",
                "全部オワタ(#{stage})",
                "修了しました",
                "寝る。#{stage}時に起きる。",
                "終わったので、#{stage}時に帰ります",
                "全段階完了しました。データを回収し、電源を落としてください(#{xml_path.split(/\//).last})",
                "全ステージ完了しました(#{xml_path.split(/\//).last})",
                "ⓢⓤⓨⓐⓡⓘ"]
    post "@#{YOU} " + messages[rand(messages.size)] + " " + memo
    exit 0
  end
  if nil == `ps aux | grep opencv-haartraining`.split(/[\r\n]/).delete_if{|m|m=~/grep opencv-haartraining/}.first
    messages = ["#{stage}段階目まで来たけど異常終了したかも",
                "落ちてる",
                "ERROR! haartraining is not working. please restart \(^o^)/",
                "異常終了",
                "異常です",
                "動いてないっぽい・・・",
                "死んだかも",
                "だめっぽい・・",
                "おい、異常終了してるぞ",
                "冒 険 の 書 (#{stage}) は 消 え ま し た",
                "おお、死んでしまうとは情けない",
                "\(^o^)/"*stage,
                "ピッコロの気が消えた",
                "なん・・だと・・",
                "#{stage}面でピチュった"]
    post "@#{YOU} " + messages[rand(messages.size)] + " " + memo
    sleep INTERVAL*2
    next
  end
  next if stage == stage_p or stage == nil
  stage_p = stage
  messages = ["#{stage}段階目まで進みました",
              "バリバリです(stage#{stage})",
              "ばっちりですわっ",
              "------ここまで読んだ(#{stage})------",
              "がんばってます(#{stage})",
              "stage #{stage}",
              "ステージ#{stage}なう",
              "now finished stage#{stage}.",
              "よし!ステージ#{stage}まで終わった!!!",
              "うわ"+"あ"*stage,
              "ド"*stage,
              "ゴ"*stage,
              "ゴ"+"ー"*stage,
              "もりもり",
              "ふむふむなるほど"+"・"*stage,
              "頭が"*stage+"おかしくなりそうだ",
              "もういや",
              "無理"*stage,
              "ズザ"+"ー"*stage,
              "帰りたい",
              "まだ#{stage}段階目だ",
              "もうstage#{stage}まで終わった。超はやい",
              "もうstage#{stage}まで終わった",
              "stage#{stage}まで終わった",
              "stage#{stage}まで終わったし",
              "ククク・・遂に#{stage}界までまで昇ってきたか・・・",
              "ⓢⓤⓨⓐ"*stage]
  
  post messages[rand(messages.size)] + " " + memo
end

0

bootcampで入れたWindowsのパーティションサイズを小さくして、そのぶんMac側を大きくしたらWindows起動しなくなった

追記:これやったらwindows起動しなくなったのでやらないほうがいい・・


Macbook ProにbootcampでWindows XPをインストールしてあるんだけど、最初にインストールした時にWindowsに容量を与えすぎていてMac側が足りなくなってきたのでパーティションサイズを変更した。

Windowsを20GB減らして、その分Mac OSXを20GB増やした。


■準備



■Windowsのパーティションを小さくする
Mac付属のDisk Utilityではfat32パーティションのサイズ変更はできないみたいだけど、
Ubuntuのディスクを入れてCDからブートして(インストールしないで)パーティションエディタというのが上のメニューにあるから起動する。

Macのbootcampアシスタントでwindowsを入れたとすると、画面上で左側にはMacのパーティションがあり、右側にWindowsが入っている。
パーティション・エディタ(gparted)でWindowsのパーティションを小さくして、右に詰める。かなり時間がかかる。

ディスクに変更を書き込む。これで20GB空いた。


■Macのパーティションを大きくする
本来、HFS+でフォーマットしてあるMacのパーティションはMacに入っているDisk Utilityでサイズ変更できるはずなんだけどパーティションマップがエラーを報告しているとかでできなかった。

また、gpartedでもリサイズできなかったので、小一時間悩んだ。


思い切ってバックアップは取ってあるのでMacのパーティションを消す。
gpartedではMacのHFS+でフォーマットできないので、空いた所にフォーマット無しのパーティションを作る。

変更を書き込んでUbuntuは終了させる。


■Macをバックアップから復活させる
Macのインストールディスク(LeopardかSnowLeopard)を入れてalt押しながらMacbookPro起動。

Macのパーティションを消したからかボリューム名がズレて、見た目CDなのにWindowsと書かれたアイコンができるので、それを起動。
Leopardのインストール画面になる。

インストールはせずに、画面の上のメニューからDisk Utilityを起動して、さっき作った未フォーマットのパーティションをHFS+にする。

さらにTimeCapsuleとLANケーブルで直接接続し、「バックアップからシステムを復元」でMac OSXを復活させる
Snow LeopardからLeopardに戻した – 橋本詳解

8時間ぐらい待つ。ばっちり20GB増えてた。



そういえば検索するとWinCloneというソフトを使えと書いてあるページがいくつか出てくるが、WinCloneは最新版の公開が停止されていて、ひとつ古いバージョンを使ったら完全にパーティションサイズが同じでないと書き戻しができなかった。

0

Inspiron Mini9に外付けUSB RAID HDDをつけてファイルサーバー化した

Ubuntuが入ってるDellのInspiron Mini9を回収してきたので、IO DATAの外付けRAID HDDをつけてファイルサーバーにした。
もともとうちではGLANTANKを使ってたんだけど、部屋の掃除をしたら急にファンの音が気になるようになったので、もう3年以上使ってるし入れ替える事にした。
inspironは4GBのSSDでファンレス。外付けHDDもファンレスなのでとても静かになった。


I-O DATA ミラーリング/ストライピング RAID対応USBハードディスク 2TB HDC2-U2.0
アイ・オー・データ (2008-06-20)
売り上げランキング: 675
おすすめ度の平均: 4.0
5 Cost-Efficient & Convenient!
4 結構いいww
5 順調です
5 ミラーでのフォーマットが時間がかかる
4 動画専門で使っています


■ほしい機能
ただのNASではなく
  • 音が静か
  • RAID1でミラーリングしてデータが消えないようにしたい
  • 家のdynamic dnsを更新
  • 家の外からもe-mobileでsshでログインできたり、mercurialやgitのリポジトリを置ける
  • rsyncで大学にある巨大なファイルをダウンロードさせておく作業を任せられる
  • sambaでwindowsのファイル共有にも参加する
  • 念のためモニタとキーボードも付いていて、ネットワークが壊れている時も操作しやすい
  • Inspiron Mini9と外付けHDDどちらが壊れても迅速に入れ替え・復旧できる構成にしたい
  • apacheとhgweb.cgiを動かしてmercurialのリポジトリビューアを置きたい
  • 巨大ファイルのやりとりのためにapacheは動かしておきたい


■HDC2-U2.0の設定
まずRAID1に設定し直す。後でやると中身がフォーマットされるので。

パソコンに接続しないで、ACアダプタだけ刺す。
電源モードスイッチをONに切り替える。起動する。

裏にあるRAID SETUPボタンを3秒押し続けると電源LEDが点滅しはじめる。
前側のFUNCボタンを押す毎に電源LEDの色が変わる。

青:RAID0 ストライピング
紫:RAID1 ミラーリング
緑:マルチディスクモード
なので、紫にする。裏側のRAID SETUPボタンを押して設定終了。

パソコンに接続して容量が1TBに減っている事を確認。これでミラーリングモードになった


■HDC2-U2.0のフォーマット
inspiron mini9に接続して起動。
sudo apt-get install gparted
which gparted
sudo gparted
gpartedでHDDのパーティションをフォーマットする。ubuntuをインストールする時に出てくるツール。

全部ext3でフォーマットした。しばらく待つ。


■HDC2-U2.0をマウントする
マウントポイントを作っておく
mkdir /home/sho/ex


/dev/sdb1としてHDC2-U2.0が認識されていたので
/etc/fstab を編集
/dev/sda2		/			ext3 defaults	0 0
proc /proc proc defaults 0 0
/dev/sdb1 /home/sho/ex ext3 defaults 0 0
自分のホームディレクトリの下に置く。

マウントする
sudo mount -a

これで/home/sho/ex/ 以下はRAIDで守られる。


■sambaのインストールと設定
sudo apt-get install samba

/etc/samba/smb.conf を編集
[global]
workgroup = MSHOME

coding system = UTF-8
doc charset = cp932
unix charset = UTF-8
wins support = yes

[ex]
path = /home/sho/ex
comment = ex
browseable = yes
read only = no
create mask = 0755
directory mask = 0755

sambaからはHDC2-U2.0の中以外は見える必要が無いので、[homes]とかは ; で全部コメントアウトした。


samba再起動
sudo /etc/init.d/samba restart


■Dynamic DNSの更新
mydns.jpを使っているんだけど、ここはDiCEとかを使わなくてもBasic認証で更新できる。

crontab -e で設定追加
*/10 * * * * wget --spider http://mydns-username:password@www.mydns.jp/login.html
10分おきに更新する。


■その他
あとはまあ普通のLinuxなので、mercurialとかgitとかapacheをインストールしておしまい。
SSDの容量を稼ぐために、Inspiron Mini9に入ってたゲームやGIMPやEvolutionメールやOpenOfficeを全部消した。

0

cvCalcOpticalFlowBM

オプティカルフローのサンプルコードをちょっといじって、カメラからの入力を表示してみた。
ブロックマッチングによるオプティカルフローを使った。

できたソースコード、Makefile


narupeko opticalflownarupeko opticalflownarupeko opticalflownarupeko opticalflownarupeko opticalflow

image.cpp

#include <cv.h>
#include <highgui.h>
#include <cxcore.h>
#include <ctype.h>
#include <stdio.h>
#include <iostream>
#include <boost/format.hpp>

using namespace std;
using namespace boost;

void detect_flow(IplImage *img, IplImage *img_p, IplImage *dst);

int main(int argc, char* argv[]) {
IplImage *img = NULL;
CvCapture *capture = NULL;
capture = cvCreateCameraCapture(0);
//capture = cvCaptureFromAVI("test.avi");
if(capture == NULL){
cerr << "capture device not found!!" << endl;
return -1;
}

CvSize size = cvSize(240, 180);
IplImage *img_resized = cvCreateImage(size, IPL_DEPTH_8U, 3);
IplImage *img_gray = cvCreateImage(size, IPL_DEPTH_8U, 1);
IplImage *img_gray_p = cvCreateImage(size, IPL_DEPTH_8U, 1);
IplImage *img_dst = cvCreateImage(size, IPL_DEPTH_8U, 3);

char winNameCapture[] = "Capture";
cvNamedWindow(winNameCapture, CV_WINDOW_AUTOSIZE);

while (1) {
img = cvQueryFrame(capture);
cvResize(img, img_resized);
cvCvtColor(img_resized, img_gray, CV_BGR2GRAY);
cvCopy(img_resized, img_dst);
detect_flow(img_gray, img_gray_p, img_dst);
cvShowImage(winNameCapture, img_dst);
cvCopy(img_gray, img_gray_p);
if (cvWaitKey(10) == 'q') break;
}

cvReleaseCapture(&capture);
cvDestroyWindow(winNameCapture);

return 0;
}

void detect_flow(IplImage *src_img1, IplImage *src_img2, IplImage *dst_img){
int i, j, dx, dy, rows, cols;
int block_size = 10;
int shift_size = 1;
CvMat *velx, *vely;
CvSize block = cvSize(block_size, block_size);
CvSize shift = cvSize(shift_size, shift_size);
CvSize max_range = cvSize(50, 50);

rows = int(ceil (double (src_img1->height) / block_size));
cols = int(ceil (double (src_img1->width) / block_size));
velx = cvCreateMat(rows, cols, CV_32FC1);
vely = cvCreateMat(rows, cols, CV_32FC1);
cvSetZero(velx);
cvSetZero(vely);

cvCalcOpticalFlowBM(src_img1, src_img2, block, shift, max_range, 0, velx, vely);

for (i = 0; i < velx->width; i++) {
for (j = 0; j < vely->height; j++) {
dx = (int)cvGetReal2D(velx, j, i);
dy = (int)cvGetReal2D(vely, j, i);
cvLine(dst_img, cvPoint(i * block_size, j * block_size),
cvPoint(i * block_size + dx, j * block_size + dy), CV_RGB(255, 0, 0), 1, CV_AA, 0);
}
}
}


Makefile
SRC = image.cpp
DST = image

prefix=/opt/local
INCPATH=$(prefix)/include
LIBPATH=$(prefix)/lib

OPT= -lcv -lcvaux -lcxcore -lhighgui

CC=g++ -O

CFLAGS= -I$(INCPATH)/opencv
LDFLAGS=-L. -L$(LIBPATH)


all:
$(CC) $(SRC) -o $(DST) $(CFLAGS) $(LDFLAGS) $(OPT)

2

Monoで.NETアプリをLinux/Macで動かす

Monoはオープンソースの.NET Framework互換ランタイム。俺はUbuntuやMac OSXで自分の作ったWindowsアプリを動かすのに使っている。

Ubuntuのaptでインストールできるバージョンは2.1、Macのmacportsでは2.4がインストールできるが、Macだけはmono-projectからdmgダウンロードしてきてインストールして使ってる。


ちゃんとGUIも再現される。下はWindowsとVMWare上のUbuntuで同じアプリを起動したところ
C# – UDP/IPでチャットアプリより)
UDPの勉強


■アプリを起動する

mono App.exe
で起動する。DLLなども問題なく読み込まれる。


■Monoで動くように実装するポイント
Mono2.xは.NET Framework2.0と3.0の間ぐらいまで実装されているらしい。
  • DirectXは動かない、というか無い
  • Win32APIももちろん無いので使わない
  • 設定はレジストリではなく.configファイルに保存しよう
  • .NET3.0以降で増えた関数型言語由来の機能はあまり使わない方がいい。ラムダ式とか。
  • delegateやthreadは使える
  • Genericsのマイナーなメソッドは使わない。普通にAddとかRemoveとか使う分にはいいが、高階関数を使ってそうな機能(ソートとか)はあまり触らない方がいい
  • ディレクトリの区切り文字は \ ではなく Path.DirectorySeparatorChar を使う
  • 起動ディレクトリの取得は Application.StartupPath ではなく
    System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
    を使うと、System.Windows.Formsを読み込まなくても済む
基本的にWindowsネイティブな機能を使わないようにしていれば、Generics関連のメソッドが無い以外で困ったことは多分ない。


■開発の手順
  1. WindowsのVisualStudioで開発する。
  2. Monoで動くか確かめるために、VMWareでUbuntuを起動する。
  3. Dokan SSHFSでUbuntuのディスクをマウントする
  4. 適当にコードを書く。Windowsで問題なく動くところまで確認する。
  5. マウントしたディスクにRealSyncで転送する
  6. Ubuntuでも動かしてみる。デバッグの方法がよくわからない。特にMonoで実装されていない関数を実行してしまった時は原因特定に時間がかかってしまう・・・
  7. Ubuntuのapt版Monoで動けばmacportsのMonoでも動く。バージョンが高いので。