アセンブリでLEDを点灯させる【前編】

前回の記事で無事にLEDを点灯させることができた。
コードは全てC言語で書いたが、もう一つの方法としてアセンブリで書いてみることにした。
アセンブリなんてコードを見ただけでウンザリしてしまう。

でもやってみる前から諦めてもしょうがない。
この機会に少し調べてみることにした。

開発環境を整える

アセンブリコードをコンパイルするためのコマンドは、最初に導入したCrossPackAVRというものに含まれているavr-asコマンドを使うことができる。
しかし、このコマンドでコンパイルできるアセンブリはGasと言われるもので、
いわゆるよく知られている、アセンブリコードのそれとは違うらしい。

そこで今回は、avraというアセンブラを導入してみることにした。
AVRA Home Page

下記サイトを参考にしながらインストール。
のぅわんべたぁ|AVRアセンブリ

まず、最新版をダウンロード後に展開し、srcディレクトリの中で以下のコマンドを実行してみた。
参考通りに上手くいかず、automakeの部分でコケてしまったので、touchコマンドでエラーが出るファイルを作ってみたところ成功。

$ touch NEWS README AUTHORS ChangeLog
$ aclocal
$ autoconf
$ automake -a
$ ./configure --prefix=/opt/local
$ sudo make install

これで開発するための環境は用意できた。
また、includesディレクトリに.inc拡張子のついたファイル群が入っているようで、マイコンの種類に応じた定義ファイルが格納されているようだ。
レジスタやポートに応じたアドレス、値がマッピングされているようで、初めにインクルードして使う感じだと思う。

LEDを点滅させる

なんと言っても最初はLEDを点滅させるところからだろう。
マイコンにはLEDを2つ接続しているが、手始めに一つ点灯させてみる。

.include "tn2313def.inc"
main:
ldi   r16, 0b00011000
out   DDRD, r16
ldi   r16, 0b00001000
out   PORTD, r16
rjmp  main

なんとこれだけである…実に簡単だ。

まず、.includeで定義ファイルをインクルード。コロンが付く行はラベルなので解説は不要だと思う。
ちなみにドットから始まるものは擬似命令と呼ばれるもので機械語に変換されることはないらしい。
続いて、各命令について調べてみた。

使える命令セットは以下のURLからダウンロード出来る模様。
http://www.avr.jp/user/DS/PDF/AVRinst.pdf

LDI命令は「即値バイト定数を汎用レジスタに取得」と書いてある。
レジスタr16に右オペランドの値を入れると考えればいいと思う。

OUT命令は「汎用レジスタからI/Oレジスタに設定」と書いてあった。
そのままで、I/Oレジスタに汎用レジスタの値を設定するときに使う命令のようだ。

RJMP命令は「PC相対無条件分岐」と書いてある。
要は無条件でこの場所に飛ばすってことみたいだ。今回の場合はmainラベルに飛ばすので、ループさせるということになる。
いわゆるGOTOみたいな感じだろう。

DDRDや「0b00011000」などの値は、前回の記事で説明していたと思うので今回は省く。
上のコードをavraコマンドでコンパイルしてみた。

$ avra main.asm
AVRA: advanced AVR macro assembler Version 1.3.0 Build 1 (8 May 2010)
Copyright (C) 1998-2010. Check out README file for more info
AVRA is an open source assembler for Atmel AVR microcontroller family
It can be used as a replacement of 'AVRASM32.EXE' the original assembler
shipped with AVR Studio. We do not guarantee full compatibility for avra.
AVRA comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of avra under the terms
of the GNU General Public License.
For more information about these matters, see the files named COPYING.
Pass 1...
Pass 2...
done
Used memory blocks:
Code      :  Start = 0x0000, End = 0x0004, Length = 0x0005
Assembly complete with no errors.
Segment usage:
Code      :         5 words (10 bytes)
Data      :         0 bytes
EEPROM    :         0 bytes

こんなメッセージが表示されて成功した。
最初にエラーが出たのだけど、tn2313def.incファイル内にある「#」から始まる行の先頭に「;」を付けたところエラーは消えた。

# vimでこんな感じに処理した
:%s/^#/;#/g

出来上がったmain.hexというファイルを開いてみたところ、かなり小さくてびっくりした。わずか63バイトである…。

:020000020000FC
:0A00000008E101BB08E002BBFBCFE2
:00000001FF

バイナリの転送はavrdudeを使って以下のようなコマンドを実行した。

$ avrdude -c avrispmkII -P usb -p t2313 -U flash:w:main.hex:i

簡単なものだけど一発で成功したので抵抗感も薄らいだ。

アセンブリと聞くだけで拒否反応を示してしまいたくなるが、命令セットを覚えていくことで少しずつ理解が深まっていき、単純なコードながら深さと楽しさがあるように思える。

長くなってしまいそうなので、次の記事で2つのLEDを一定間隔で切り替えて点灯させる、ということをやってみたいと思う。

今回書いたコードもGitHubに上げてみた。
pontago/avr-LedTest-asm · GitHub

参考サイト
アセンブラなんて簡単じゃないか(1/3) − @IT MONOist
解説 AVRアセンブラ講座 (1)|freeml byGMO

AVRで温度計を作るために考える

作りたいと思ってから1ヶ月ほどが経過した。
時間を空けすぎるのはよくないので、作るのに何が必要か、どうやって作るのかを調べることにした。

温度を測るには温度計センサー(LM60BIZ)なるものがあるようで、
こいつを使えば温度によって電圧を返してくれるらしい。
比較的難易度は低そうだ。

実は湿度計も同時に作ろうと思ったのだけど、
安く入手できるHS-15Pを使うとなると、
湿度によって変動するインピーダンスを調べて対数演算が必要らしい。
あほの俺には無理だ。

TDKが出しているCHK-GSSというのを使うと、
電圧の変化で処理できる上に、温度も分かるらしい。俺でも出来そう。
しかし2000円くらいするので尻込みしてしまった。
今回は温度計のみで絞ることにした。

ACアダプタを使う

今まで乾電池から電源を取っていたけど、ACアダプタを使うことにした。
スイッチングACアダプター12V1A(NP12-1S120)を使う予定だ。
12Vの電圧を5Vまで降圧させる必要があるので、3端子レギュレータ(LM7805CV)を使う。

ノイズや発振を防ぐために、前後にコンデンサをかませる必要があるようだ。
0.33μF50Vと0.1μF50Vを使うことにした。

ブレッドボードでも簡単に使えるようになる、
ブレッドボード用DCジャックDIP化キットというものを使う。

7セグLEDを使う

計測した温度を表示するために7セグLEDを利用する。
3桁表示できるC-533Sを使うことにした。

各セグメントに対してマイコンの各ポート接続してしまうと、
ポート数が足りなくなってしまうので、トランジスタで電流を増幅させダイナミック点灯させる。

トランジスタは、2SC1815GRで60V150mAまで増幅出来るようだ。

各セグメント(8本)に対して、10mA程度流せば十分なようなので、
10mA * 8本 = 80mAが必要な計算だ。余裕をもって100mAとした。

トランジスタとマイコンの間には必要以上に電流が流れないように、電流制限抵抗というものが必要らしい。

このトランジスタの電流増幅率(hFE)は100で、
トランジスタを通すことによって0.7Vほど電圧降下がすることを考慮して、以下ように計算した。
(5V – 0.7V) * 100hFE / 100mA = 4.3kΩ
4.3kΩに近い4.7kΩの抵抗を使うこととした。

トランジスタのコレクタとベース間には安定化させるために抵抗が必要らしいので、
10kΩの抵抗を使うことにした。
あとはエミッタと7セグLEDのコモンに接続すればいいはずだ。

続いて、各セグメントとマイコンのポートに接続する。
この時に抵抗も挟む必要があるので、7セグLEDの順方向電圧の1.8Vを差し引いて、抵抗値を以下のように計算した。
(5V – 0.7V – 1.8V) / 0.01A = 250Ω
近い240Ωの抵抗を使うこととした。

必要なもの

これでだいたいの流れを調べたので、必要なものをピックアップする。
マイコンはATTinyしか持っていなかったので、ATMegaも買うことにした。
あとこれから必要になるであろうテスターと、いくつか抵抗も追加で買う。

部品名 型番 値段
AVRマイコン ATMEGA328P-PU 250円
ポケット・デジタルマルチメータ(テスタ)[周波数+容量][オートレンジ] P-10 1000円
7セグメントLED表示器 超高輝度赤色3文字(3桁)(カソードコモン) C-533SR 200円
高精度IC温度センサ LM60BIZ 100円
ブレッドボード用DCジャックDIP化キット 100円
トランジスタ 2SC1815GR 200円
3端子レギュレータ[5V1A] LM7805CV 100円
絶縁型ラジアルリードタイプ積層セラミックコンデンサー0.33μF50V 100円
絶縁型ラジアルリードタイプ積層セラミックコンデンサー0.1μF50V 100円
カーボン抵抗(炭素皮膜抵抗)1/4W 10kΩ 100円
カーボン抵抗(炭素皮膜抵抗)1/4W 4.7kΩ 100円
カーボン抵抗(炭素皮膜抵抗)1/4W 240Ω 100円
カーボン抵抗(炭素皮膜抵抗)1/4W 1kΩ 100円

マイコン先生に確認してみたところ、これで大丈夫らしい。
チェックが入らなかったのは嬉しい。
注文したらいよいよ組み立てることになるからワクワクだ。

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/