ニュース

AVRでLEDを点滅させるプログラム

前回の記事で無事にマイコンを経由してLEDを点灯させる事ができた。
その時に利用したプログラムはサンプルをコピペしただけだったので、2つ接続したLEDを交互にランダム点灯させるプログラムに変更したいと思う。

最初にサンプルコードで意味が分からない変数や関数があったので、それを調べることにしてみた。サンプルコードは以下のような感じだ。

#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRD = 1 << 4;           /* make the LED pin an output */
for(;;){
char i;
for(i = 0; i < 10; i++){
_delay_ms(30);  /* max is 262.14 ms / F_CPU in MHz */
}
PORTD ^= 1 << 4;    /* toggle the LED */
}
return 0;               /* never reached */
}

定義済みグローバル変数

DDRDとPORTDにビット演算の結果を代入しているようだけど、まずここが謎だった。

DDRD = 1 << 4;
PORTD ^= 1 << 4;

DDRD変数の中身が気になったので、宣言元である「avr/io.h」を覗いてみたところ、マイコンの種類判定後に「iotn2313.h」をインクルードしているようだ。

#elif defined (__AVR_ATtiny2313__)
#  include <avr/iotn2313.h>

「iotn2313.h」の中にDDRDの宣言があったけどマクロを呼んでいるようだ…。

#define DDRD    _SFR_IO8(0x11)

_SFR_IO8マクロの宣言元は「sfr_defs.h」にあった。どうやらアセンブラとCからの呼び出しでマクロの内容も変化するようだ。

// アセンブラ
#define _SFR_IO8(io_addr) ((io_addr) + __SFR_OFFSET)
// C
#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)

uint8_t型でアクセスできるようで、__SFR_OFFSETは、0x20のアドレスになっているようだ。

AVRのSFRは基本的にメモリ空間にマッピングされているが、低位の64バイトはI/O空間にもマッピングされていて、どちらでもアクセスできる。 しかし、メモリ空間の最初の32バイトには汎用レジスタがマッピングされているのに対し、I/O空間にはそれがないのでアドレスが32バイトずれている。 つまり、メモリ空間で0x20~0x5Fのレジスタが、I/O空間で0x00~0x3Fに見える。 これらのレジスタはIN命令やOUT命令などでアクセスが可能だが、io.h ではレジスタ名がメモリ空間のアドレスで定義されいているので変換が必要になる。 このズレ(0x20)を示すマクロが__SFR_OFFSETで、メモリ空間からI/O空間に変換するためのマクロが_SFR_IO_ADDR。 _SFR_MEM_ADDRというのもあり、こちらは何も変換しない。

このサイトで詳しく解説されていた。
AVR libcを使ってみる you/junkbox

0x11をデータシートで確認してみると、DDRDレジスタ(0x31)となっていた。DDD0〜6とビット位置は同じになっているようだ。
そもそもDDRDレジスタってなんだろって事なんだけど、実は先生に概ね教わっていた。
DDR○は入力・出力を決定するレジスタらしい。この場合、ポートDを入力にする場合は「0」、出力にする場合は「1」に設定するようだ。
同じようにPORTDは、ポートDの出力をHで出す場合は「1」、Lで出す場合は「0」にするらしい。HはHigh、LはLowを表すようで、Hにした場合にプルアップになり、電流が流れるようだ。

これで変数の意味はだいたい分かった。「iotn2313.h」にはこの他にも定義されている変数があるようだ。

関数

続いて関数について調べてみた。今回使用されているのは一つで、_delay_msという関数だ。
調べなくともだいたい分かるが、指定ミリ秒遅延させるものだろう。
気になるのはコメントの「max is 262.14 ms / F_CPU in MHz」というもの。
今回クロック周波数は8MHzに設定していたので、262.14ms / 8Mhz = 32.7675ms になる。最大32msまで設定可能という事だと思う。
32ms以上遅延させたい場合は複数回呼び出す必要があるようで、forループなどで指定回数呼び出す。

for(i = 0; i < 10; i++){
_delay_ms(30);  /* max is 262.14 ms / F_CPU in MHz */
}

30ms * 10 = 300ms(0.3秒遅延させている)

ランダム点滅させるプログラムを組む

お勉強はこのくらいで実際にプログラムを組んでみた。LEDは2つ接続していて、PD3とPD4に接続している。
DDRDのビット3と4を「1(出力)」に設定した。ランダム点滅は、rand関数を呼び出す事で遅延時間を調整する。
定義済み定数・マクロをフル活用するために、指定ビットを立てる_BVマクロ、PD3・PD4定数を利用した。

#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include <stdbool.h>
int main(void) {
DDRD = 0b00011000;
bool flag = false;
for (;;) {
char i;
int r = rand() % 10 + 1;
for(i = 0; i < r; i++) {
_delay_ms(30);
}
PORTD = flag ? _BV(PD3) : _BV(PD4);
flag = !flag;
}
return 0;
}

今回のコードとMakefileはGitHubにも上げてみた。
pontago/avr-LedTest · GitHub

アセンブラで書いてみるのもいいかな。。。
次に作るものは温度計・湿度計になると思うけど、一気にハードルも上るから苦労しそうだ。

AVRでLEDを点滅させてみた

前回LEDを点灯させる所まで何とかできた。と言っても、電池ボックスとLEDを接続しただけ…。
今回はいよいよマイコン(AVR)を使って、LEDをコントロールしてみようと思う。

まず、ライターであるAVRISPmkIIとマイコンを接続しなくてはいけない。パソコンとの接続はUSB経由で接続するだけなので簡単。
AVRISPmkIIのコネクタはISPコネクタ(6ピン)のため、このままだとブレッドボードに接続するのに大変なようだ。
そこで、共エレで販売されている「AVRWRT用ブレッドボードISPケーブル」のコネクタに付け替えた。

色々なサイトを参考にしつつ、マイコンに接続してみた。
続いて、マイコンに書き込むためのソフトをインストールするが、Mac環境だったため、正規のソフト(AVR Studio)が使えないようなので、CrossPack for AVR Developmentを使うことにした。
CrossPack – A Development Environment for Atmel’s AVR Microcontrollers

/usr/local配下にインストールされるようで、/usr/local/CrossPack-AVRというシンボリックリンクが作成される。
CrossPackインストール後に、avr-projectコマンドを実行することで、XCodeのプロジェクトファイルが生成されるようだ。

$ avr-project avr-LedTest

かなりシンプル…XCodeからいじるのも面倒なので、直接ファイルをいじることにした。
firmwareディレクトリにMakefile、main.cの2つのファイルが生成された。大事なのはこの2つのようだ。

Makefileでライターとマイコンの種類を書き換える必要があるようで、以下のように書き換えた。

DEVICE = attiny2313
PROGRAMMER = -c avrispmkII -P usb -p t2313

makeでプログラムのコンパイル、make flashでマイコンにプログラムを転送させるようだ。make fuseでヒューズの書き換えも可能とのこと。
とりあえず、マイコンにプログラムを転送してみようと思い、デフォルトのままmain.cをコンパイルし、make flashを実行してみたがエラーになってしまう。

// firmwareディレクトリで実行
$ make
avr-gcc -Wall -Os -DF_CPU=8000000 -mmcu=attiny2313 -c main.c -o main.o
avr-gcc -Wall -Os -DF_CPU=8000000 -mmcu=attiny2313 -o main.elf main.o
rm -f main.hex
avr-objcopy -j .text -j .data -O ihex main.elf main.hex
avr-size --format=avr --mcu=attiny2313 main.elf
AVR Memory Usage
----------------
Device: attiny2313
Program:      58 bytes (2.8% Full)
(.text + .data + .bootloader)
Data:          0 bytes (0.0% Full)
(.data + .bss + .noinit)
$ make flash
avrdude -c avrispmkII -P usb -p t2313  -U flash:w:main.hex:i
avrdude: stk500v2_command(): command failed
avrdude: stk500v2_program_enable(): bad AVRISPmkII connection status: Target not detected
avrdude: initialization failed, rc=-1
Double check connections and try again, or use -F to override
this check.
avrdude done.  Thank you.
make: *** [flash] Error 1

調べまくった結果、マイコンへの接続の仕方が間違っていたようで、一からコネクタを接続し直してみた。もともと付いていたコネクタと、付け替えたコネクタをよく見比べて繋いでいくと、やはり間違えていた…。
参考サイトを見ながらコネクタの色だけで判断して接続したのがいけなかったようで、しっかり仕様を把握して進めていかなくちゃいけないと反省。
トリパマ Toripama: AVRISPmkII ライタのピンの配列
サンプル・プログラムで学ぶAVRの実践【2/4回目】マイコン書き込み環境の構築 (PIC,78K,R8,HC(S)08/RS08,AVR,MSP430などのマイコン活用)

f:id:happytar0:20120714033630j:plain
こんな感じで接続すると上手くいった。

$ make flash
avrdude -c avrispmkII -P usb -p t2313  -U flash:w:main.hex:i
avrdude: AVR device initialized and ready to accept instructions
Reading | ################################################## | 100% 0.01s
avrdude: Device signature = 0x1e910a
avrdude: NOTE: FLASH memory has been specified, an erase cycle will be performed
To disable this feature, specify the -D option.
avrdude: erasing chip
avrdude: reading input file "main.hex"
avrdude: writing flash (670 bytes):
Writing | ################################################## | 100% 0.28s
avrdude: 670 bytes of flash written
avrdude: verifying flash memory against main.hex:
avrdude: load data flash data from input file main.hex:
avrdude: input file main.hex contains 670 bytes
avrdude: reading on-chip flash data:
Reading | ################################################## | 100% 0.20s
avrdude: verifying ...
avrdude: 670 bytes of flash verified
avrdude: safemode: Fuses OK
avrdude done.  Thank you.

ひゃっほー、悩んだ末に解決した時の嬉しさはたまらない!

肝心のLEDを点灯させるプログラムを書いてみることにした。
マイコンを経由してLEDを接続するにはどうすればいいんだと少し考えたが、PD4(ポート8)にLEDと抵抗を接続することで解決。思ったより簡単だった。
サンプルコードはCrossPackのサイトに書いてあったので、それをコピペすればいいだけだ。

#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRD = 1 << 4;           /* make the LED pin an output */
for(;;){
char i;
for(i = 0; i < 10; i++){
_delay_ms(30);  /* max is 262.14 ms / F_CPU in MHz */
}
PORTD ^= 1 << 4;    /* toggle the LED */
}
return 0;               /* never reached */
}

f:id:happytar0:20120714050150j:plain
LEDが点滅してる…肝心の点滅箇所はいともあっさり出来てしまった。
しかしながら、コピペして終わりというのはあまりにもつまらない。
実際はLEDを2つ接続してランダムに交互に点滅を繰り返す、というプログラムを組んだのだが、謎の変数やら関数が出現しているので、次回に詳しく掘り進めていきたいと思う。

電子工作はじめました

ついに電子工作に手を出してみた。やるやる詐欺をすること1年…はじめるまで長かったです。
三日坊主にならないようにコツコツやっていきたいと思う。

電子工作やらマイコンなんてまったく知らない人間なので、まずはLEDを点灯させてみようという事で以下の部品を購入。

  • プログラマ(ライター?) AVR ISP MKII
  • ブレッドボード EIC-108J
  • AVR ATTINY2313-20PU
  • 3mm赤色LED LT3U31P 250mcd
  • カーボン抵抗 1/4W 200Ω
  • カーボン抵抗 1/4W 150Ω
  • 電池ボックス 単3×3本
  • AVRWRT用ブレッドボードISPケーブル

f:id:happytar0:20120704201002j:plain

まずブレッドボードの使い方を覚えるためにLEDを電池ボックスと接続して点灯させてみた。
さすがにこれが出来なかったらやばいよな…と思いつつ、あっさり点灯して安心した。そしてブレッドボードも案外簡単な仕組みだった。

ブレッドボードに150Ωの抵抗とLED、そして電源を接続しただけ。
必要な抵抗は以下の式で求められるようだ。というかオームの法則ってやつだ。
中学の時に習ったけど勉強に熱心ではなかったのですっかり忘れてしまった…。

R(抵抗) = V(電圧) / A(電流)

LEDのスペックは以下のようだ。

Vf=1.85V〜2.5V If=20mA

そして電圧については、電池ボックスに単三乾電池3本ということなので以下になる。

1.5V x 3本 = 4.5V

これをもとに抵抗を計算してみると、

(4.5V - 1.85V) / 0.02A = 132.5Ω

どうやら132Ωくらいの抵抗でちょうどいいようだ。今回は適当なものがなかったので150Ωと念のために200Ωの抵抗を買ってみた。

f:id:happytar0:20120708151644j:plain
無事に点灯した!!全然むずかしい事してないのに嬉しい。
やっぱり形があるものを触って思い通りに動かせると、ソフトをいじるのと違った喜びを感じる。

20mAを越えた電流を流すと壊れると聞いたので、興味本位で抵抗を外してそのまま電源と接続してみた。
f:id:happytar0:20120708151803j:plain
あれ…壊れない。でも弱々しい光り方でなんかおかしい。

とにかく無事?に成功出来てよかった。困った事があれば詳しい先生が居るので心強い。
次はマイコンと接続してLEDの点灯を制御してみたいです。

OpenCVをいじってみた【後編】

前回の記事ではサンプル画像を集めるところまで書いた。
ポジティブサンプルをコツコツ処理するのは面倒なので、opencv_createsamplesコマンドを使って大量のサンプル画像を作ってみた。

opencv_createsamplesを使う

opencv_createsamplesコマンドの使い方は簡単だ。引数なしで実行すると以下のような引数の説明が表示される。

$ opencv_createsamples
Usage: opencv_createsamples
[-info <collection_file_name>]
[-img <image_file_name>]
[-vec <vec_file_name>]
[-bg <background_file_name>]
[-num <number_of_samples = 1000>]
[-bgcolor <background_color = 0>]
[-inv] [-randinv] [-bgthresh <background_color_threshold = 80>]
[-maxidev <max_intensity_deviation = 40>]
[-maxxangle <max_x_rotation_angle = 1.100000>]
[-maxyangle <max_y_rotation_angle = 1.100000>]
[-maxzangle <max_z_rotation_angle = 0.500000>]
[-show [<scale = 4.000000>]]
[-w <sample_width = 24>]
[-h <sample_height = 24>]

「-info」と「-img」はどちらか一方を指定するようで、ネガティブサンプルとポジティブサンプルを組み合わせる場合は「-img」を使用する。
サンプルデータは「-vec」引数で指定したファイルに出力されて、この出力されたサンプルダータをもとに学習処理をおこなう。
「-bg」で指定するネガティブサンプルは、画像ファイルのパスをリスト化したものを指定する。

findコマンドを利用することで簡単に画像ファイルのパスをリスト化することができる。

$ find negative_images/ -name '*.jpg' > negative_images.txt

以下のような引数を指定してサンプルデータを作成した。
「-num」が生成するサンプルデータ数になり、「-bgcolor」は背景とみなす色を指定するようだ。今回は255(白)を指定した。また、「-bgthresh」は背景色とみなす範囲?ということで5を指定してみた。

$ opencv_createsamples -vec positive.vec -img positive_images/1.jpg -bg negative_images.txt -num 7000 -bgcolor 255 -bgthresh 5 -w 24 -h 24

無事にポジティブサンプルの生成に成功したはいいが、これをもとに学習させてみてもいい結果を出すことは出来なかった。
たぶんこの方法でポジティブサンプルを用意する場合、文字など検出対象物として認識しやすいようなものではないとだめなのかもしれない。

地道にポジティブサンプルを集める

やっぱり地道に集めるしかないと思いつつ、なるべく楽に集められないか色々調べてみることにした。
あれこれ調べていると以下のサイトを見つけた。どうやらOpenCVでアニメの顔や目を認識させる、ということをやっているようだ。
anime.udp.jp

ここで公開されている便利な加工ツール「SC」を見つけたので使ってみることにした。ちなみにWindowsアプリのみのようだ。
加工元の画像はネガティブサンプルを集める時と同じように、Yahoo画像検索のAPIを利用して集めた。ひたすらカレーライスだけ切り出すのは正直しんどかった。
そして、色々なカレーライスの画像を見ていくうちに、これを認識させるのはかなり難しいのではないかと思い始めた…。

意外にカレーライスの画像は少なくて、どれも同じような画像が多いので結局800枚ほどしか用意することはできなかった。
これも同じようにopencv_createsamplesコマンドを利用してサンプルデータ化する。先ほどと違うのは、「-img」ではなく、「-info」引数を利用することだ。
「-info」には、「-bg」と同じくリスト化したファイルを指定する。この時に注意したいのは、単純に画像ファイルのパスを指定するだけではなく、「その画像に含まれる検出対象物の数」と「座標」、「大きさ」も書き出す必要があるようだ。

ImageMagickに含まれる「identify」コマンドを使うことで画像の大きさが分かるので、findコマンドと組み合わせて書き出してやればいいようだ。

$ find positive_images/ -name '*.png' -exec identify -format '%i 1 0 0 %w %h' \{\} \; > positive_images.txt
$ opencv_createsamples -vec positive.vec -info positive_images.txt -num 800 -bgcolor 255 -bgthresh 5 -w 24 -h 24

サンプルデータから学習させる

opencv_haartrainingコマンドか、opencv_traincascadeコマンドを利用してサンプルデータを学習させる。opencv_traincascadeコマンドの方が新しいようで、マルチコア対応やLBPにも対応しているのでこちらを利用するのがいいようだ。しかし、学習後に生成されるファイルも新しいフォーマットになるため、opencv_preformanceコマンドで利用することができないようなので注意。

opencv_traincascadeコマンドを引数なしで実行した結果は以下のようになる。

Usage: opencv_traincascade
-data <cascade_dir_name>
-vec <vec_file_name>
-bg <background_file_name>
[-numPos <number_of_positive_samples = 2000>]
[-numNeg <number_of_negative_samples = 1000>]
[-numStages <number_of_stages = 20>]
[-precalcValBufSize <precalculated_vals_buffer_size_in_Mb = 256>]
[-precalcIdxBufSize <precalculated_idxs_buffer_size_in_Mb = 256>]
[-baseFormatSave]
--cascadeParams--
[-stageType <BOOST(default)>]
[-featureType <{HAAR(default), LBP, HOG}>]
[-w <sampleWidth = 24>]
[-h <sampleHeight = 24>]
--boostParams--
[-bt <{DAB, RAB, LB, GAB(default)}>]
[-minHitRate <min_hit_rate> = 0.995>]
[-maxFalseAlarmRate <max_false_alarm_rate = 0.5>]
[-weightTrimRate <weight_trim_rate = 0.95>]
[-maxDepth <max_depth_of_weak_tree = 1>]
[-maxWeakCount <max_weak_tree_count = 100>]
--haarFeatureParams--
[-mode <BASIC(default) | CORE | ALL
--lbpFeatureParams--
--HOGFeatureParams--

「-data」で指定したパスに分類器が生成される。「-featureType」で学習方法を指定できるが、今回はLBPを利用することにした。

以下のような引数を指定して分類器を作成してみた。
「-numPos」でポジティブサンプルの数を指定するが、バグがあるようできっちり実際の数を指定すると途中で失敗してしまうようだ。実際の数より少なめに指定することで、失敗せずに実行できる。
OpenCV2.4.0でHAAR分類器を学習させるときの問題と対策 (ゆめ技:ゆめみスタッフブログ)

$ opencv_traincascade -data ./data -vec positive.vec -bg negative_images.txt -numPos 750 -numNeg 3000 -numStages 20 -mode ALL -w 24 -h 24 -precalcValBufSize 5000 -precalcIdxBufSize 1000 -featureType LBP

「-data」引数で指定したパスに「cascade.xml」が生成されるので、これが分類器となる。

作った分類器を試してみる

さっそく作った分類器を試してみた。結果からお伝えするとかなり認識率が低い…というかほぼ認識しない。
学習データが少ないのか、やり方が間違っているのか分からない。

OpenCVのサンプルプログラムを利用して作った分類器を試してみた。
引数に認識させたい画像ファイルを指定すると、認識された部分が緑の線で囲われて表示されるものだ。

#include "opencv2/objdetect/objdetect.hpp"
#include "opencv2/highgui/highgui.hpp"
#include <iostream>
#include <stdio.h>
using namespace std;
using namespace cv;
int main(int argc, char** argv) {
Mat image = imread(argv[1]);
String cascadeName = "cascade.xml";
CascadeClassifier cascade;
cascade.load(cascadeName);
vector<Rect> komas;
cascade.detectMultiScale(image, komas);
for (vector<Rect>::iterator it=komas.begin(); it!=komas.end(); ++it) {
rectangle(image, Rect( it->x, it->y, it->width, it->height),
Scalar(0,255,0));
}
cout << "count: " << komas.size() << endl;
namedWindow("result", CV_WINDOW_AUTOSIZE | CV_WINDOW_FREERATIO);
imshow("result", image);
waitKey(0);
return 0;
}

以下のコマンドでコンパイルして実行した。

$ g++ -I/opt/local/include -L/opt/local/lib -lm -lopencv_core -lopencv_highgui -lopencv_imgproc -lopencv_objdetect -g -o sample sample.cpp
$ ./sample test.jpg // 実行

結果はというと…。
f:id:happytar0:20120708193503j:plain
一応成功したっぽいけど、もう少し認識率を上げていかないと使いものにならなそう。というか、何に使い道があるんだろ…。

参考サイト
Haar状特徴に基づくブースト分類器のカスケードを利用する高速物体検知 – penny
Tutorial: OpenCV haartraining (Rapid Object Detection With A Cascade of Boosted Classifiers Based on Haar-like Features) – Naotoshi Seo

OpenCVをいじってみた【前編】

しばらくぶりの更新となってしまった…少しずつでも書いていかなきゃだめですね。
物体認識を試してみたかったので有名なOpenCVを使ってみた記録。

OpenCVのインストール

Mac環境なのでMacPortsを利用して、OpenCVとObjectMakerをインストールした。ObjectMarkerは画像から必要な部分を切り出せるツールらしい。

# port install opencv objectmarker

ちなみにWindows環境にもインストールしてみたが少し面倒だった。
ここのインストールガイドを参考にするといいようだ。
InstallGuide – OpenCV Wiki

まず、OpenCVをコンパイルする環境が何も入っていなかったので、
VisualStudioC++ 2010 Expressってのをインストール。
Microsoft Visual Studio Express

CMakeも必要なようなので、以下のサイトからCMakeをインストール。
CMake – Cross Platform Make

CMakeGUIでOpenCVのパスを指定して、Configureを実行することで、VisualStudioで開けるファイルが生成されるようだ。
コンパイラにVisualStudio2010 Win64を指定したところ、Expressバージョンには64bitのコンパイラが含まれていないようで、Windows SDK 7.1のインストールも必要になった。
この際、マルチコア環境ならばTBBを有効化した方がいいと思う。(「USE TBB: ON」にする)

あとは、VisualStudioでOpenCVのプロジェクトファイルを開いてコンパイルするだけ。

物体検出するための分類器を作る

人間の顔や目などを検出するための分類器は、サンプルとしてもう用意されているようで、それらをそのまま使えばいいだけのようだ。
それだと面白くないので、カレーライスを検出する分類器を作ってみることにした。

分類器を作る流れをおおまかに説明すると以下のようになる。

  1. ネガティブサンプル(検出対象物が写っていない画像)を用意する。3000枚ほどあると効率的らしい。
  2. ポジティブサンプル(検出対象物が写っている画像)を用意する。こちらは7000枚ほどあると効率的とのこと…多いな。
  3. opencv_traincascadeコマンドを使って分類器を作る。

ここで問題になってくるのは学習させるための画像をどうやって集めてくるかだ。
あまりよくないかもしれないが、Google画像検索などを利用すればそれほど苦労しないと思う。
他には、opencv_createsamplesコマンドを利用する方法があるようで、ネガティブサンプルと一つのポジティブサンプル画像を組み合わせることで、ポジティブサンプル画像をたくさん作り出すことができるようだ。

サンプル画像を集める

あまり大きい声では言えないが、画像検索を使ってサンプル画像を集めることにした。
さすがに手作業では面倒なため、簡易的なスクリプトを組んで一気に処理しようと思ってAPIがあるか調べて見ることに。

Google Image Search APIというのを見つけたが、ずいぶん昔に廃止されたようだ。
Custom Search APIというのもあるようだが、こちらは使用用途が違うようだし、一日100リクエスト?までのようでだめっぽい。

Yahooを調べたところ、画像検索用のAPIを公開していた。
検索:画像検索API – Yahoo!デベロッパーネットワーク
通常は一日1000リクエストまでだが、プレミアム会員だと5万リクエストに緩和されるらしい。プレミアム会員であったのでこれを利用することにした。
(他にはFlickrAPIを利用するのもアリだと思う)

“風景 -カレーライス”
こんな感じのクエリを投げれば、カレーライスを含まないネガティブサンプル画像を集めることが出来るはずなので、
作ったスクリプトを回して3000枚ほど用意することができた。

ポジティブサンプルの用意が少しむずかしい。
検出対象物を含んだものを用意するのは同じ手順でいいはずだが、対象物のみを切り出す必要があるはずで、それが非常にめんどくさい…。
こういう時のためにopencv_createsamplesコマンドがあるのか?と思って使ってみることにした。

長くなりそうなので今回はこのへんで終わり。。。次回へつづく。

MT4iのDoS攻撃されるセキュリティホール

MT4iのベータ版である3.1系をのぞく全てのバージョンで、DoS攻撃されうるセキュリティーホールがあるようです。細工されたリクエストを送信するか、もしくは特定の状況下で無限ループしてしまう事が原因で、HTTPプロセスが常駐し続けてしまいます。

原因

記事本文を分割する際に利用する midb_euc 関数の欠陥が原因のようです。分割位置をGETパラメータとして渡すため、「記事本文の文字数より大きい値が渡された時」に分割が終了せずに無限ループに陥ります。なお、記事本文を分割するバイト数として、初期値では「4096」バイトが設定されています。

以下のように sprtbyte に分割位置がカンマ区切りで渡されます。

http://www.example.com/m/mt4i.cgi?mode=individual&eid=1&sprtpage=1&sprtbyte=0,4096

どういう状況でおこりうるか

MT4iはMovableTypeを携帯で見やすいように変換・表示するためのプログラムです。携帯向けに表示するため、記事本文の表示を分割する必要があります。記事本文が分割バイト数ずつ分割表示されるわけですが、それぞれの分割された記事の位置をGETパラメータで制御しているため、後から記事本文を更新した場合に分割位置が一致せずに、無限ループする可能性があります。
クローラーなどが更新前のURLでアクセスしてきて…なんて事もあるかもしれません。

再現方法

sprtbyte パラメータに記事本文より大きい値を渡すことで再現できます。

問題の箇所

midb_euc 関数のコードを抜き出したものです。2系、3.0系ともに同様の関数が使われているようです。
記事本文より大きい開始位置が指定された場合、43行目の while 文内の substr 関数が常に開始位置より小さい値を返すために無限ループし、さらに68行目の while 文内の substr 関数で、開始位置と終了位置が記事本文より大きい場合、常に空文字を返すので無限ループしてしまいます。

sub midb_euc {
my $llen1;
my $llen2;
my $lstr;
my $lstart;
# 先ず正しい開始位置を求めないと
if ($_[1] == 0) {
$lstart = 0;
} else {
$llen1 = $_[1];
$lstr = substr($_[0], 0, $llen1);
$llen2 = MT4i::Func::lenb_euc($lstr);
my $llen3 = $llen1;
while ($_[1] > $llen2) {
$llen3 = $llen1;
$llen3 += $lstr=~s/(\x8E[\xA1-\xDF])/$1/g;                   # 半角カナ数をプラス
$llen3 += ($lstr=~s/(\x8F[\xA1-\xFE][\xA1-\xFE])/$1/g)*2;    # 3バイト文字数*2をプラス
$lstr = substr($_[0], 0, $llen3);
$llen2 = MT4i::Func::lenb_euc($lstr);
}
$llen1 = $llen3;
# 最後の文字が途切れているか判定する
if ($lstr =~ /\x8F$/ || $lstr =~ tr/\x8E\xA1-\xFE// % 2) {
chop $lstr;
$llen1--;
if($lstr =~ /\x8F$/){
$llen1--;
}
}
$lstart = $llen1;
}
# 文字列の切り出し
$llen1 = $_[2];
$lstr = substr($_[0], $lstart, $llen1);
$llen2 = MT4i::Func::lenb_euc($lstr);
my $llen3;
while ($_[2] > $llen2) {
$llen3 = $llen1;
$llen3 += $lstr=~s/(\x8E[\xA1-\xDF])/$1/g;                   # 半角カナ数をプラス
$llen3 += ($lstr=~s/(\x8F[\xA1-\xFE][\xA1-\xFE])/$1/g)*2;    # 3バイト文字数*2をプラス
$lstr = substr($_[0], $lstart, $llen3);
$llen2 = MT4i::Func::lenb_euc($lstr);
}
$llen1 = $llen3;
# 最後の文字が途切れているか判定する
if ($lstr =~ /\x8F$/ || $lstr =~ tr/\x8E\xA1-\xFE// % 2) {
chop $lstr;
if($lstr =~ /\x8F$/){
chop $lstr;
}
}
return $lstr;
}

対応方法

3.1系では問題となっている関数が置き換わっているため、3.1系を利用することで回避できるようですが、まだベータ版なのでそのあたりも考慮する必要がありそうです。

簡単な Func.pl に対するパッチを作ってみました。各 while 文で記事本文より大きい場合と、空文字の場合にループを抜ける処理を追加しただけです。

※2月5日訂正
while 文でループする意味がよく分からなかったので、一度で抜けるように変更しました。

--- Func.pl.org	2011-02-03 16:43:57.000000000 +0900
+++ Func.pl	2011-02-03 16:19:58.000000000 +0900
@@ -46,6 +46,7 @@
$llen3 += ($lstr=~s/(\x8F[\xA1-\xFE][\xA1-\xFE])/$1/g)*2;    # 3バイト文字数*2をプラス
$lstr = substr($_[0], 0, $llen3);
$llen2 = MT4i::Func::lenb_euc($lstr);
+            last;
}
$llen1 = $llen3;
@@ -71,6 +72,7 @@
$llen3 += ($lstr=~s/(\x8F[\xA1-\xFE][\xA1-\xFE])/$1/g)*2;    # 3バイト文字数*2をプラス
$lstr = substr($_[0], $lstart, $llen3);
$llen2 = MT4i::Func::lenb_euc($lstr);
+        last;
}
$llen1 = $llen3;

実はそんなにたいした事じゃないのかも

共有サーバのほとんどは、長時間実行されているプロセスは強制終了されるものかと思われますので、それほど影響はないのかもしれません。

デタラメ書いてるんじゃねーってことがあれば連絡ください。

ひかりoneギガ得プランのHGW(BL190HW)の性能とその後

ひかりoneのギガ得プランを使い始めて1年が経ちました。実質固定IPという噂でしたが、まさにその通りで一度も変わったことはありません。

一つ問題が出てきたのはルータの性能についてです。ルータについては、このブログに記載してあるようにBL190HWというルータが指定されおり、変更することは一切できません。ひかりoneでは、ルータの事をHGW(ホームゲートウェイ)と呼称しています。

  1. 自分で用意したルータに置き換えることはできない。どうしても使いたい場合は、BL190HWのDMZ機能をブリッジのように使うしかない。
  2. グローバルIPアドレスDHCPを使って払い出される。
  3. MACアドレスとIEEE802.1xを使って認証している。

Webサーバを動作させている状態で、同時接続数300で100Mbpsを超えたあたりからレスポンスがかなり悪くなりました。この状態でルータの管理画面にアクセスすると開くのにも時間がかかっており、やはり負荷が原因だと思いました。

## こんな感じで調べてみた
$ netstat -t | grep "ESTABLISHED" | grep http | wc -l

しかし、ルータが認証の役割も担っているため、そう簡単に交換することはできません。

上述した通り、認証にはMACアドレスとIEEE802.1xが使われています。MACアドレスの偽装については比較的簡単なのですが、IEEE802.1x認証をクリアするのは現状厳しいようです。IEEE802.1x認証でシリアル番号も送信している?
IEEE802.1x認証は、24時間に一度確認しているようなので、一度認証されれば他のルータに置き換える事はできますが、24時間後に切断されます。

どうしようかと考えていた所、もう一台ルータをレンタルする方法がある事を発見しました。
電話サービスの追加など、ホームゲートウェイを複数台ご利用になる場合 | auひかり(auの光ファイバーサービス)

合計2台までのようですが、これでとりあえず問題は解決できそうです。ちなみに2台目のルータにもグローバルIPアドレスが割り当てられます。

  1. HGWは合計2台までしかレンタルできない。
  2. 電話サービスを二つ契約する必要がある。
  3. 2台目のルータにもグローバルIPアドレスがもう一つ払い出される。

もう一台レンタルするためには、電話サービスを二つ契約する必要があるため、525円+472円が月額費用に加算される点は注意が必要です。申込手数料が650円くらいかかりますが、サポートセンターに電話経由で申し込み、三日くらいで新しいルータが到着しました。

現在2台のルータで運用していますが、レスポンスも改善したようです。

HyperMacのバッテリーセルを交換してみた

先日バッテリーリフレッシュさんにお願いしたHyperMacのMBP-060が戻ってきました。

15日に注文して、17日には作業完了のメールが着ていました。到着したのが18日という事を考えるとかなり早いです。
かかった金額は11890円と送料の740円だけでしたので、海外から直接購入するよりずいぶん安くなります。

f:id:happytar0:20100918105400j:image
段ボール箱に入って返送されてきました。

f:id:happytar0:20100918105500j:image
しっかり緩衝材で梱包されていました。

f:id:happytar0:20100918105600j:image
二つ入っていると思ったら、見積りをお願いしたMBP-150でした。

交換したバッテリーは無事に使うことができたのか

このバッテリーセル交換サービスは、動作保証までしていないため、本当に動くのかどうかは使ってみるまで分かりません。

f:id:happytar0:20100918105700j:image
端子がある側面にシールを剥がした跡がありましたので、恐らくここから開腹したのでしょう。通常ならまったく気にならないレベルでした。

さっそくACアダプタを通して充電してみる事にしました。

f:id:happytar0:20100919172100j:image
無事に充電のLEDが点灯してほっとしました。

MacBookProに接続した所、充電中のアイコンになり正常に充電されました。うれしい誤算としては、充電中にあれだけ熱くなっていたHyperMacが、ほとんど熱くならなくなった事です。
長時間使ってみた所、交換前と同じように熱くなりました…

バッテリーリフレッシュサービスは使えるのか

今回利用した限りだと、とても満足できる結果だったので今後も利用してみたいと思いました。もちろん、メリット・デメリットがありますので、デメリットが許容できる範囲内であれば、利用してみてもいいのではないでしょうか。

メリット

  1. 純正品を海外から購入するよりも安い
  2. バッテリーセルが日本製になる(純正品は中国製だと思う)
  3. バッテリーの容量がアップする
  4. バッテリーセルの交換作業も国内なので安心できる

デメリット

  1. 動作が保証されていない
  2. 既にHyperMacを持っている必要がある(当たり前)
  3. 交換するHyperMacを送らないといけない

※バッテリーリフレッシュサービスを利用する際は自己責任でお願いします

今回利用させていただいたバッテリーリフレッシュさんはこちらからどうぞ。
http://www.batt.jp/

Mac専用バッテリーHyperMacを復活させる

HyperMacの寿命はどのくらいなのか

1年ほど前に購入したHyperMacですが、フル充電しているにも関わらず1分くらいでバッテリーが無くなってしまうようになりました。
そんなに使用回数は多くなかった気がします。少なくとも1000回は使っていないでしょう。
使用中は、火傷しそうなくらい熱くなったりしたので色々気になる所はありました。

こんな記事もあるようです。
http://blog.goo.ne.jp/portaledge/e/a3b352cf4e20f7d16ad37d548e5b9809

しかし、Macで使用出来る外部バッテリーはほとんどありません。自分が知っている限り、まともに使えるのはHyperMacくらいなのではないでしょうか。
ちなみにMBP-060とMBP-150を同じ時期に購入しましたが、偶然なのかどちらも使えなくなってしまいました。

まともな外部バッテリーが出てこないのはなぜか

どうしてまともな外部バッテリーが出てこないのか、それはMac独自の電源コネクタ「MagSafe」によるものです。Apple社は、この「MagSafe」の特許を取得しており、他社に対してライセンスする気がないようです。

ではなぜ、HyperMacは「MagSafe」を使用できているのか。Apple社からライセンスを受けている訳ではありません。純正品のMagSafe電源アダプタを加工し、流用することで、ライセンスの問題をクリアしているという事のようです。

中国企業らしいアイディアではありますが、ついにApple社から目をつけられたようです。
Apple、外部バッテリーメーカーを訴える|KODAWARISAN

今後、HyperMacはどうなってしまうのか心配ですが、現在持っているHyperMacを大事に使っていきたい所です。純正の外部バッテリーが出てくれれば、それが一番いいような気もしますが・・・。

外部バッテリーの寿命を延ばすには

大事に使っていきたい所ですが、所詮バッテリーは消耗品なので、時間が経てば使えなくなってしまうのは当然です。ではどうすれば、バッテリーの寿命を少しでも延ばす事ができるのでしょうか。

まず自分の反省を踏まえ、どうしてこんなに早くバッテリーがダメになってしまったのかを考えてみました。

  1. 日差しが入ってくるような室温が高い場所に放置していた。
  2. すぐ使えるように充電コネクタを挿し、いつもフル充電状態にしていた。
  3. 実は1000回近く使っていた。
  4. バッテリーセルが中国クオリティ。

さすがに購入して1年程度で毎日使うような事もしませんでしたので、3.はないでしょう。4.については、中国企業なので、中国製のバッテリーセルを使っていてもおかしくありません。中国製と日本製のものでどこまで品質に差があるのかというのも、素人の僕としては分かりません。

バッテリーの保存環境や使い方によって寿命に変化があるのかが一番気になる所ですが、下記のような衝撃的なサイトを発見しました。

リチウムイオンバッテリー考

「この(リチウムイオン電池の)劣化は満充電で保存すると激しくなり、また保存温度が高いほど劣化が早くすすみます。リチウムイオン電池を長期保存するときにはできるだけ冷暗所に保存し、あまり充電しない状態で保存することをお勧めします。」

長期保管
 例えば、iBookラボを学校で管理していて、夏休み中は使わないなど、ノートブックコンピュータをしばらく使う予定がない場合、アップルではバッテリーを50%充電した状態で保管しておくことをお勧めします。

 もし、バッテリーが完全にゼロになった状態でノートブックコンピュータを保管すると、深い放電状態となり、充電ができなくなってしまう可能性があります。

 逆に、フル充電の状態で長期保管すると、バッテリー容量が失われる、つまり、バッテリーの耐用年数が短くなる可能性があるのです。それを防ぐには、PowerBookiBookのバッテリーを取り出して、適温範囲内で保管しましょう。

自分の使っていた環境と合致します。一番衝撃的だったのは「満充電で保存すると劣化が激しくなる」という記述です。外部バッテリーという性質上、常にフル充電状態にして保存している人は多いと思いますので、一番気をつけなくてはいけない所でしょう。

どうすれば少しでも寿命が延ばすことができるかをまとめると

  1. 温度が高い場所で保存せず、涼しい冷暗所で保存する。
  2. フル充電状態にして放置しない。
  3. 使用頻度に間がある場合は、50%程度充電し使用する際にフル充電するようにする。
  4. 過放電を防止するために長期間使用していなかった時は、たまに充電してあげる。

色々気を使わなくてはいけないようで少したいへんそうですね・・・。

バッテリーの寿命を復活させる方法はあるのか

どうすれば寿命を延ばせるかという観点で書いてきましたが、自分のバッテリーは既にお亡くなりになっているので、今さら寿命がどうとか言っても仕方ありません。
円高なのでまた海外から買ってもいいのですが、「バッテリーセルが中国クオリティ」という不安要素を排除することはできません。
そこでバッテリーを復活させる事ができないか調べた所、バッテリーセルを交換することで復活させる事ができるようです。

そもそもバッテリーセルとは何なのか

自分も詳しくないので分かりませんが調べた感じだと、セルというのは電池の事であり、バッテリーとはこのセルの集合体という事のようです。
要するにセルを交換するという事は、バッテリー内部の電池を新しい物に交換するという事になります。
電池なら自分で替えられるのではないか、と思う人もいるかもしれません。バッテリーの内部は若干複雑になっており、リチウムイオンの場合、充電・放電に対して制御を必要とし、制御基板と繋がっているようです。
自己責任で替えておられる方がいるようでしたが、とても怖くて自分で交換する事はできませんでした。

バッテリーセルを購入する場合、数千円程度で購入できるようです。
※自分で交換をする事を推奨しているわけではありません。自分で交換する場合は、必ず自己責任でおこなってください。
ロワジャパン バッテリーバンク  デジカメ携帯パソコンバッテリー・充電器

バッテリーセルを交換してみる事に

そこで今回はバッテリーリフレッシュサービスを利用する事にしました。バッテリーリフレッシュサービスとは、バッテリーのセルを新しい物に交換してくれるサービスで、ベイサンという所が最大手のようです。
ソフマップなどでも受付代行をおこなっているようです。

今回は利用したのはベイサンではなく、バッテリーリフレッシュという別の業者にお願いしてみる事にしました。
バッテリーリフレッシュ・セル交換の専門店

当然HyperMacは一覧に載っておりませんでしたので、見積りをお願いしてみることに。掲載されているバッテリーを見てみると、純正品と大差がない物もありましたので、海外から購入するより安ければいいなという感じでした。その後、ベイサンでも交換可能か問い合わせた所、対応不可との事でした。

実際に見積りをお願いしてみた結果を踏まえてまとめてみました。

直接購入(Cableなし$-30.95) バッテリーリフレッシュ ベイサン
MBP-060 14,365円 11,890円 対応不可
MBP-150 31,365円 29,890円 対応不可

※$1=85円で計算

送料などを考慮するとバッテリーセルを交換した方が安く済むようです。また、バッテリーリフレッシュさんでセルを交換した場合、元の単セルあたり2200mAhに対して、日本製のセルになり2600mAhと容量までアップします。

このサービスで一点注意しないといけないのは、「バッテリーセル」のみを交換するサービスだという事です。動作確認や保証はおこなっておらず、動作を保証してくれるわけではないという事に注意しなくてはいけません。
※リフレッシュサービスにお願いする際も必ず自己責任でおこなってください。

HyperMacのmAhとは

HyperMacのバッテリー容量(mAh)ですが、公式サイトを調べても一切記載されていません。おそらく、充電する端末ごとに使われる電圧が違うため記載されていないのかもしれません。
ここでふと気づいたのですが、外部バッテリーでよく書いてある「8000mAh」などの記載は、電圧ごとに変わるのであって、これはアテにならないと今ごろ気づきました。多くの場合は、3.7V換算での値を表記しているみたいです。

以下のような計算で求められるようです。
Wh x 1000 / V = mAh

とりあえず、3.7V換算で計算してみました。

Energizer XP18000 68Whくらい? 約18000mAh
MBP-060 60Wh 約16000mAh
MBP-100 100Wh 約27000mAh
MBP-150 150Wh 約40000mAh
MBP-222 222Wh 約60000mAh

こうして見てみると、HyperMacはすごいバッテリー容量です。
ちなみにMacBookPro13インチで、システムプロファイラの「電源」から確認した所、12494mV(12Vくらい?)で、4951mAhと表示されていました。MBP-060の公称では、1.0倍長く使えるとの事なので合っているのか計算してみました。

60Wh x 1000 / 12V = 5000mAh

4951mAhと近い数字が出たので計算方法は間違っていないようです。このような感じで、使用する端末ごとに使われる電圧を調べて計算してみると、どのくらいバッテリーが持つのか計算することもできるでしょう。

バッテリーリフレッシュしたバッテリーはちゃんと使うことができるのか

まだ交換したバッテリーが届いていないため、ちゃんと使うことができるかは分かりませんが、届き次第またレポしたいと思います。HyperMacのこれからが分からない状況なので、少しでも寿命を延ばせるように使ったり、使えなくなってしまったらどうすれば良いかを考えるいい機会になりました。
HyperMacを使っている人は参考にしてみてはどうでしょうか。
※バッテリーセルを交換する場合は、くれぐれも自己責任でお願いします。

実際に交換してみた記事はこちらから。
HyperMacのバッテリーセルを交換してみた – フタなしカンヅメ

tarボールソースからインストールしたソフトを管理する「Graft」を使ってみる

ソースインストールした物を管理するツール「Graft」をインストールして使ってみたので、記録として残しておきます。

インストールと言っても簡単で、配布元サイトのドキュメント通りに実行すれば問題ありません。
配布元サイト
graft.html – peters

$ wget -O graft-2.4.tar.gz "http://peters.gormand.com.au/Home/tools/graft/graft-2.4.tar.gz?attredirects=0"
$ tar zxvf graft-2.4.tar.gz
$ cd graft-2.4
## ここでエラーがでます。
$ make -f Makefile.dist
######################################################
#                                                    #
#       You'll now need to modify the Makefile       #
#      variables to suit your local conditions.      #
#                                                    #
######################################################
make: *** [Makefile] Error 1
## エラーが出るので、Makefileを編集する必要があるようです。

Makefile

このPACKAGEDIRがパッケージ管理用のディレクトリになり、ここにソースからインストールします。
PACKAGEDIR	= /pkgs
TARGETDIRは、通常インストールしたい場所、つまり/usr/localなどを指定します。
# TARGETDIR	= /pkgs
TARGETDIR	= /usr/local
Perlコマンドの場所を指定します。/pkgs/bin/perl となっていますが、ほとんどの場合、/usr/bin/perl に設置されていると思いますので変更します。
# PERL		= /pkgs/bin/perl
PERL		= /usr/bin/perl

あらためてビルドします。

$ make clean
$ make
# sudo make install
## graftがインストール出来ていることを確認。
$ ls /pkgs
graft-2.4
## graft自身を/usr/local/bin配下にインストール
# /pkgs/graft-2.4/bin/graft -i graft-2.4
## シンボリックリンクされていることがわかる
$ ls -l /usr/local/bin
lrwxrwxrwx  1 root root 25 7月 30日 09:23 /usr/local/bin/graft -> /pkgs/graft-2.4/bin/graft

これでインストールは無事に完了しましたので、さっそく使ってみます。
今回は、試しにrubyをインストールしてみました。

$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.8/ruby-1.8.7-p299.tar.gz
$ tar zxvf ruby-1.8.7-p299.tar.gz
$ cd ruby-1.8.7-p299
$ ./configure --prefix=/pkgs/ruby-1.8.7-p299 && make
# make install
## 無事にインストール出来たことを確認
$ ls /pkgs
graft-2.4  ruby-1.8.7-p299
## graftコマンドでインストール
# graft -i ruby-1.8.7-p299
$ ls -l /usr/local/bin
lrwxrwxrwx  1 root root 30 7月 30日 09:27 /usr/local/bin/ruby -> /pkgs/ruby-1.8.7-p299/bin/ruby

rubyをアンインストールしてみる。

# graft -d ruby-1.8.7-p299
EMPTY        /usr/local/lib/ruby/1.8/bigdecimal/ is now empty. Delete manually if necessary.
EMPTY        /usr/local/lib/ruby/1.8/cgi/session/ is now empty. Delete manually if necessary.
...

一部ディレクトリが残ってしまっていますが、-Dオプションで実行することで、空のディレクトリも削除してくれるようです。

アップデートを想定して、ruby1.9系を入れてみることにしました。
アンインストール後に、インストールしても問題ないかと思いますが、今回はPruneという機能を使ってみます。これは既存のファイル群との衝突を回避するために、末尾に.prunedを付けたファイル名にリネームしてくれるものです。

## まず1.8.7をインストール
# graft -i ruby-1.8.7-p299
$ /usr/local/bin/ruby -v
ruby 1.8.7 (2010-06-23 patchlevel 299) [i686-linux]
## 1.9.1をダウンロード、インストール
$ wget ftp://ftp.ruby-lang.org/pub/ruby/1.9/ruby-1.9.1-p429.tar.gz
$ tar zxvf ruby-1.9.1-p429.tar.gz
$ cd ruby-1.9.1-p429
$ ./configure --prefix=/pkgs/ruby-1.9.1-p429 && make
# make install
## graftコマンドでrubyを1.8.7から1.9.1にアップデートします
## アンインストール前に新しいバージョンの物をインストールしようとすると、衝突としてエラーが表示されます
# graft -i ruby-1.9.1-p429
CONFLICT     /usr/local/bin/erb is linked to something other than /pkgs/ruby-1.9.1-p429/bin/erb (/usr/local/bin/erb -> /pkgs/ruby-1.8.7-p299/bin/erb)
# graftコマンドを-pオプションで実行してみると、ファイル名の後ろに.prunedとリネームされているのがわかる
# graft -p ruby-1.8.7-p299
$ ls -l /usr/local/bin
lrwxrwxrwx  1 root root 30 7月 30日 09:39 /usr/local/bin/ruby.pruned -> /pkgs/ruby-1.8.7-p299/bin/ruby
# あらためてインストール
$ graft -i ruby-1.9.1-p429
## 1.9.1がインストールできていることを確認
$ ls -l /usr/local/bin
lrwxrwxrwx  1 root root 30 7月 30日 10:02 /usr/local/bin/ruby -> /pkgs/ruby-1.9.1-p429/bin/ruby

いくつか気になった点がありました。

  • 衝突回避のためにリネームされた.prunedファイルを消せない。

 (-pオプションで実行させる度に、上書きされるようなので問題なし?)

  • 設定ファイルも一緒に上書きされそうなので、.graft-exclude

や.graft-includeなどを使って、必要なものだけインストールするように調整する必要があるかもしれません。(未検証)

非常にシンプルに出来ていて簡単に使うことが出来るので、ごちゃごちゃとした余計な機能はいらない、という人にとってはオススメです。
あとは、ソースからインストールしたものが/pkgsに溜まっていくので、前のバージョンにすぐに切り替えたりといった事もできます。