プログラミングの最近のブログ記事



一昨日急にid:ranha先生に「いっしょにやらない?」と誘われたのでホイホイついていくと、案の定そこにはラムダ計算な世界がアッー!...というのは冗談として、まったくその方面の知識のない僕が混ざることで、見事に足を引っ張りまくることになりました(´・ω・`)


まあそれはそれとして、とりあえず学んだことをまとめておきます。


PCFとは何か


Programming language for Computable Functions。直訳すると、計算可能関数のためのプログラミング言語。わかるような、わからないような...。


参考文献の一部を抜粋してみます。


プログラミング言語の意味論(※PDF)



PCF は型付きラムダ計算を基盤とした単純な文法を持つプログラミング言語である。


(中略)


PCF は自然数とその上の高階関数の計算に特化しており、自然数の加減と(再帰) 関数の生成や呼び出しといった機構を備えている。一方で現実のプログラミング言語が備える実用的な機能、例えばファイル入出力、画面表示、ネットワークといった機器の操作や、オブジェクトやポインタ、配列といった機構は備えていない。



大雑把にいうとたぶんいわゆる関数型言語、なイメージでいいのかな(漠然としすぎ


ラムダ計算とは何か


tnzkはラムダ計算についてほとんど理解していないので、これについて先に教えてもらうことに(これが(時間的な意味で)ドツボにはまる結果になるわけですが...)。


関数とは何か

これは偶然、解析の授業でやったことが思い出されたので併記しておくのですが、関数とは何かという話。


ある集合Rを実数の集合への写像とするとき、この対応付けを関数と呼んで、f(x) = yみたいに表すとのことです。単純に言うと数と数の対応関係に他ならない。


数学よりも先にプログラミングで関数という概念に触れた人間としては、やはり「引数をほげほげして結果を返すもの」といったようなイメージがあるので、単純な対応付け(即座に結果が返ってくる)と考えるのはどうも違和感を覚えてしまいますが、あくまで対応関係でしかない、というのが一般的な解釈だそうです(その対応付けの詳細を記述するために数式を用いるのであって、普遍的にその対応関係が理解されうるなら数式さえ必要なくらいの勢いで)。


ただWikipediaを見ると、上記の理解はあくまで「現代的解釈」だそうで、昔の人(オイラーさんだとからしいです)はそれとはまた違った考え方を持っていたみたいです。


ラムダ計算における関数

普通、数学では関数をf(x) = x + 2とか表すわけですが、これが意味するのは「値xを関数fに引数として渡すと、それに対して値yが得られる」といった事実を記述しているので、別に表記方法に対した重要性はなく、別な表記方法を考えることもできるらしいです。


で、ラムダ計算では、上の関数を次のように書きます:



λx.x + 2

基本的にはこれだけです。λのあとに引数が並んで、「.」のあとに関数の内容(?)を記述するわけですね。


体系的に調べたわけではないですが、らんはさんなどの喋りを聞いているとどうやら「.」以降の記述を「ボディ:Body」と呼ぶようです。これに対して「λx」のほうを「ヘッド/ヘッダ」と呼ぶのかどうかは定かではないです。


チャーチ数

ラムダ計算の狙いは、「なるべく小さな規則のセットですべての計算可能な関数が表現できるようにする」というのがあるようです。CPUでいうところのRISCを目指すもので、必然的に常用するには不便ですが、抽象的であり小回りが利くというか、移植性に優れるみたいなところがあるみたいです。


そいで、さっきの関数を見てみましょう。



λx.x + 2

「x + 2」とか書いてあるわけですけど、xに2を足すってのはどういうことなんでしょう?そもそも2ってどういう数なんでしょう?


プログラマの視点から考えてみると、コンピュータというのは数のことなんて理解しているわけじゃないので、まずはその辺を定義し直さないといけない気がします。


小学生のとき、先生に数字のことを習ったとき、「1の次に2がきて、その次に3で...」とか言ってたと思います。それをもう少しラムダ計算的な言葉で定義してみましょう!


※念のため: 「x := expr」はxをexprと定義するといった感じの意味です



0 := λfx.x
1 := λfx.fx
2 := λfx.f(fx)
3 := λfx.f(f(fx)

たぶん直感的にわかると思いますが、要するにxに対してfを適用した回数がその数となるわけです。IIIまでのローマ数字や漢数字と同じですし、性質としては小学校で習うものとも同じです。


このように、ラムダ式で数を定義する方法は上記のもののほかにもいくつかあり、その中で上記の手法を「チャーチ数」と呼びます(教会とは関係なく、チャーチさんが考えたからチャーチ数なんだと思います。ちなみにラムダ計算を考えたのもチャーチさんです)。


ただし、チャーチ数はいわゆる「自然数」、つまり数の部分集合である実数の部分集合である整数の部分集合であるそれとは本質的に異なるものです。いわゆる自然数の性質はすべてチャーチ数には当てはまりません。たとえば自然数であれば「0での除算は未定義」というのが良く知られていますが、チャーチ数に関してはこれは今のところ特にないです。まず除算を定義して、その上で0除算できないことを確認しないといけないです。実際どうなるのかは僕は知りません...


こうすることで何がうれしいかというと、ラムダ計算での数少ない規則のひとつである「関数の適用」だけで数が表現できる、ということで、逆に言えば「関数の適用さえできるようにすれば数が使える」ということになるわけです。


これに関して僕は最初若干懐疑的な気持ちがあって、というのはこれって数学という道具を使って身振り手振りをしてるのと何ら変わらんのじゃないかなーとか思ったのですが、極論ですが、人類が滅びたあとに唯一残った文献を宇宙人が発見したとして、それがラムダ計算の文献であるのと、小学生の算数の教科書であるのとだと、後者のほうが数の理解自体はあっさり済むとは思うものの、人類の育てた数学の全体像は伝えられない。ラムダ計算であれば最小の規則のセットでそれを表現することができる(宇宙人がそれに気づくかどうかは別として)、というようなメリットがある。


といったところで、


ラムダ計算・チャーチ数についてはこんなところでまとめを終えておく。PCFの概要と、PCFでの操作的意味論を明日まとめるかー。


参考



  1. ラムダ計算 - Wikipedia

  2. 計算可能関数 - Wikipedia

  3. λ版 チャーチ数(再)|Chandler@Berlin

  4. λ計算とは - はてなキーワード

  5. プログラミング言語の意味論(PDF)

  6. 表示的意味論(PDF)



そもそも最後にPICを触ったのが2年前だし、それを含めてもCでPICファームを書くのは初めてなので、とりあえず簡単なことからはじめてみる。



#include<p18f2550.h>
#pragma config WDT = OFF

void wait( int _i, int _j, int _k);

void main(void){

TRISA = 0;
TRISB = 0;
PORTB = 0;
PORTA = 0;

while(1){
PORTB = 0xFF;
wait( 200, 50, 20);
PORTB = 0;
wait( 200, 50, 20);
}
}

void wait(int i_, int j_, int k_){
int i, j, k;
int dam = 0;

for( k = k_; k > 0; k--){
for( j = j_; j > 0; j--){
for( i = i_; i > 0; i--){
/*
_asm
nop
_endasm
*/
dam++;
}
}
}
}

これをコンパイル→書き込み→実行してみる。うまくいけばLEDの点滅が見られるはずなんだけど、そうはいかない。点滅の周波数が高すぎるのかと思って調節してみても変化なし。


困ってマニュアルのようなものをみてみると、どうやらC18はデフォルトで最適化を行っている様子。具体的には:



  • 同一文字列の併合

  • 分岐

  • バンキング

  • WREG コンテンツトラッキング

  • コード整理

  • 最後部コードの併合

  • 執行されないコード削除

  • コピー伝播

  • 冗長保存削除

  • 不要コードの削除

  • 過程抽象


というようなことを行っているらしい。


「不要コードの削除」か「執行されないコード削除」によって、wait関数内部のループが殺されてしまったのかなあと推測。MPLAB IDEの設定画面から、C18の実行時のオプションに-Oを加えてすべての最適化を抑制する。実効速度がクリティカルな影響を及ぼすようなデバイスではないので、問題ないと思うけど少し残念な気持ちが残る。


で、コンパイル→書き込み→実行すると、今度はうまくいく。若干点滅の挙動が不自然なのが気になる。


そういえば昨日のエントリで書いたように、C18 Student Editionは60日経過すると一部の最適化ができなくなるらしいのだけど、普段から-Oして開発してたら気にする必要ないのかな?ってそういう問題じゃないかなー。


とりあえず今日はちゃんと動かせることがわかったので満足。




OpenCVがらみのエントリに対してのトラックバックがあったことに今しがた気づいて、それを読むとMISTというライブラリがあるそうな。今のところOpenCVしか使ったことがないので、視野を広げるためにこいつも使ってみようと思う。


MIST 1.3.6のソースコードの入手


とりあえず本家TracMISTのダウンロードと環境設定を参考に、MISTのソースコードを入手。安定版の最新らしいMIST-1.3.6を使う。



% cd ~/
% mkdir -p src/mist-1.3.6
% cd src
% svn checkout http://mist.suenaga.m.is.nagoya-u.ac.jp/svn/mist/tags/version1.3.6 mist-1.3.6

mist-1.3.6の中を見てみるとbuildというディレクトリがあったので、この中でmakeしてみる。するとしばらくコンパイルが進んだあとに、以下のような文句を言われてコケる。



/usr/bin/ld: cannot find -llapack
collect2: ld はステータス 1 で終了しました
make[1]: *** [region_growing_test] エラー 1

llapackってなんだ?と思ってググるとこのようなページが。内部で行列演算を高速に行うために利用しているライブラリらしい。なので、こいつをビルドしてやる。


CLAPACKの入手・コンパイル


先ほどのページに従って作業を進める。進めようとする。まずはソースコードの入手と解凍。



% cd ~/src
% wget http://www.netlib.org/clapack/clapack.tgz
% tar xvfx clapack.tgz
% cd CLAPACK-3.1.1.1


次に,UNIX系の場合は clapack.tgz を解凍して,INSTALLフォルダの中にある各OSにあった make.inc.[OS名] (CYGWINの場合は make.inc.LINUX を利用する)を解凍後の一番上のフォルダにコピーします.あとはビルドすればOK.



とのことだが、INSTALLにはmake.inc.LINUXなんてない。だめもとでmake.inc.exampleをリネームして使ってみるけど当然だめ。


調べてみると、このバージョン(3.1.1.1)にはmake.inc.LINUXが欠けているらしい。



3.0のmake.inc.LINUXを参考に3.1.1のmake.inc.sampleを次のように書き換え、それをmake.incとします。


1.「SHELL = /bin/sh」を追加


2.「CC = gcc -ffloat-store」に変更。-ffloat-storeオプションをつけないとclapackのテストに通りません。


(詳細 ttp://icl.cs.utk.edu/lapack-forum/archives/lapack/msg00429.html)


3.「CFLAGS = -funroll-all-loops -O3」に変更


4.「LOADOPTS = $(CFLAGS)」に変更


5.「NOOPT = 」に変更(NOOPTは空にする)


cygwinのgccのデフォルトの出力ファイル名はa.exeになっているため、


F2CLIBS\libf2c\Makefileの186行目と187行目のa.outをa.exeに変更します。


以上でコンパイル、テストともに通るはずです。本当にありがとうございました。


BLAS 質問・相談スレッド1

多謝。このとおり変更する。F2CLIBS/libf2c/Makefileも書き換えようとしかけたけど、これはCygwinの場合の話だということにぎりぎりで気がつく。


これがおわればmakeするだけ。f2clibとblaslibはすぐに終わるが、lapacklibについては10分近く時間がかかるので注意。別に注意したところで短くなるわけではないのだけど...



% make f2clib
% make blaslib
% make lapacklib

で、できあがったファイルを見るとlibF77.a、libI77.aがない。調べていると先ほどのMIST Tracに HowToUseLapack 窶? MIST Project としてバイナリが配布されていたので、とりあえずこれを使うことに。/usr/local/libにコピー。


MISTのビルド


さあこれで問題なかろう、と再度make。


すると今度はやたら多量のエラーが出た。内容はどうもTIFF関連っぽいので調べてみると、libtiffのインストール漏れ。aptでインストールして再開。


今度はfltk-configがないと言われてる様子。fltkについては公式tracでは一切言及されていないので戸惑う。とりあえずaptでlibfltk1.1とlibfltl1.1-devをインストール。エラーは減る。


再度makeすると、またなにやら関数がないとのこと。で、関数名を見てみると、



% make
=== making ../test ===
volumerender_test.obj: In function `volr_draw_area::draw()':
volumerender_test.cpp:(.text+0xd36): undefined reference to `gluOrtho2D'
volumerender_test.cpp:(.text+0xdb8): undefined reference to `gluLookAt'
collect2: ld はステータス 1 で終了しました
make[1]: *** [volumerender_test] エラー 1

なんでGLU...('・ω・`)


ご存知の人はご存知でしょうが、GLUはOpenGL UtilityでOpenGLを使用する際に便利な関数を提供してくれるライブラリ。さすがにOpenGLをインストールしなきゃMISTが動かないなら、それは公式Tracに書くはず。


まさか、と思ってmake clean && makeすると、案の定コンパイルされてるソースのファイル名はすべて*_test.cpp……


結局むちゃくちゃ躍起になっていたのはテストコード用のライブラリ集めにしかならなかったようです。うーん、ものすごい時間を無駄にしたなー。明日は実際にmist使って何か書いてみる




カメラでキャプチャした画像にテキストをオーバレイしようと思ったんだけど、OpenCVのテキスト描画機能が貧弱(どのくらい貧弱かは『opencv.jp - OpenCV: テキスト(Text)サンプルコード -』のページ最下部のサンプルを参照)で、加えてASCIIな文字列しか扱えないそうだったので別の方法を考えることにした。


TrueTypeとかOpenTypeなフォントを扱うライブラリとしてはFreeTypeが有名。ベクタデータで表現されているフォントデータを、扱いやすいビットマップデータにしてくれる。


ビットマップにさえ持ち込んでしまえばあとはこちらで勝手に合成すればいいので、そこまでをいい感じに自動化してくれるラッパーを作りたい。そのためにとりあえず何らかのテキストを適当にビットマップに書き出してみた。


開発環境


高専プロコンに向けての調整の合間に作ったので、学校で使用している環境での作業となった。開発環境は以下のとおり。



  • Windows XP Pro

  • Visual C++ Express Edition

  • Core 2 Quad(2.66GHz)

  • 2GB


ちなみにid:Tnzkの自宅のPCはCeleron D, 516MB\(^0^)/


FreeTypeのインストール


Win用のバイナリはGnuWin32 projectにより提供されている。ダウンロードページのDownload節にある表の、Binariesの項目がWin用のバイナリ。"Zip"のリンクから落とせる。現時点での最新版は2.3.5らしい。


落としたzipは展開して適当な場所へ。ぼくはC:\freetypeに配置しました。


VC++プロジェクト作成


VCにて、空のプロジェクトを新規作成する。コンソールアプリケーション。


作成したらプロジェクトのプロパティを開き、構成プロパティを開き、以下の設定をする。



  • C/C++→全般→追加のインクルードディレクトリに、C:\freetype\include\freetype2;C:\freetype\includeのふたつを追加。

  • リンカ→追加のライブラリディレクトリにC:\freetype\libを追加。

  • リンカ→入力→追加の依存ファイルにfreetype.libを追加。


サンプルのビルド


FreeType Projectのページにはチュートリアルがある(FreeType 2 Tutorial)ので、まずはこれをビルドしてみる。コードを少しずつ書きながら完成に近づいていく形式なのでコピペしづらい。というかマークアップが特殊なのかコピペすると改行が残らない。エディタにコピペしてs/\;/\n\;/gすると幾分か楽になります。


7. Simple Text Rendering(見出しにくらいアンカーしてくれてもいいのに...)という項目があるが、これはレンダリングまで記述してあるわけではなく、my_draw_bitmapとかいう関数を呼んでお茶を濁してある。まあとりあえずは表示せずに、ビットマップにまで持ち込むことができれば良いので、my_draw_bitmapのくだりを省いてビルド→実行。動いたもののとてもむなしい。


ビットマップファイルに保存


ちゃんとラスタライズされてるかわかんないので、ビットマップに保存することに。かといってWin32APIにはなるべく関わりたくない(多くの開発者にはこの気持ちが理解していただけると思う)ので、なるべく出来合いのもので済ませようとしたところ、SaveBitmapFile( HDC, HBITMAP, LPCSTR)な使いやすそうな関数を公開している人がいたのでこれを利用することにした。「かなり使えるんで使いたいかたはどうぞ」とのこと。本当にありがとうございました。


SaveBitmapFileはデバイスコンテキストとビットマップのハンドルを準備する必要があるようなので、この辺りは仕方ないので自分で準備することにした。


一文字ごとに保存

チュートリアルを見ると各文字ごとにビットマップを生成しているようなので、まずは各文字ごとにそれぞれファイルを作成することにした。



#include <string.h>
#include <windows.h>
#include <ft2build.h>
#include FT_FREETYPE_H

#include "SaveBitmap.h"

int _main(){
FT_Library library;
FT_Face face;

FT_GlyphSlot slot;
FT_UInt glyph_index;

BYTE* m_pbits;
HBITMAP hBitmap;
BITMAPINFO *info;
info = (BITMAPINFO*)malloc( sizeof(BITMAPINFOHEADER));
ZeroMemory( info, sizeof(BITMAPINFOHEADER));
info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);

char text[] = "Hello, world!";
TCHAR filename[] = L"testA.bmp";
int pen_x, pen_y, n;
int num_chars = (int)strlen( text);

// FreeTypeの初期化とTrueTypeフォントの読み込み
FT_Init_FreeType( &library );
FT_New_Face( library, "C:\\WINDOWS\\Fonts\\trebuc.TTF", 0, &face );
slot = face->glyph;

FT_Set_Char_Size( face, 0, 16 * 64, 300, 300);

pen_x = 300;
pen_y = 200;
for ( n = 0;n < num_chars; n++ ){
int i;
DWORD writeSize;
FT_Bitmap bitmap;
HDC memDC;
HDC hBuffer;
HANDLE fh;
BITMAPFILEHEADER head={0};
RECT rect;

// n文字目の文字をビットマップ化
FT_Load_Char( face, text[n], FT_LOAD_RENDER);
bitmap = slot->bitmap;

// ビットマップヘッダの設定
head.bfType = 'MB';
head.bfSize = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + bitmap.rows * bitmap.width * 4;
head.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);
info->bmiHeader.biWidth = bitmap.width;
info->bmiHeader.biHeight = bitmap.rows;
info->bmiHeader.biPlanes = 1;
info->bmiHeader.biBitCount = 32;
info->bmiHeader.biCompression = BI_RGB;
info->bmiHeader.biSizeImage = bitmap.rows * bitmap.width * 4;
info->bmiHeader.biXPelsPerMeter = 0;
info->bmiHeader.biYPelsPerMeter = 0;

// DIBを作成
hBitmap = CreateDIBSection( NULL, info, DIB_RGB_COLORS, (VOID**)&m_pbits, NULL, 0);

// デバイスコンテキストを作成
memDC = CreateCompatibleDC( NULL);
hBuffer = CreateCompatibleDC( memDC);
SelectObject( memDC, hBitmap);
rect.left = 0;
rect.top = 0;
rect.bottom = bitmap.rows;
rect.right = bitmap.width;

for( i = 0; i < bitmap.rows * bitmap.width; i++){
SetPixel( memDC, i % bitmap.width, i / bitmap.width, RGB( bitmap.buffer[i], bitmap.buffer[i] >> 1, bitmap.buffer[i] >> 2));
}
// ビットマップを保存
SaveBitmapFile( memDC, hBitmap, filename);
filename[4]++;
pen_x += slot->advance.x >> 6;

}

return 0;

}

実行すると、文字列ごとにtestA.bmp, testB.bmpといったように連番で保存する。以下のような感じ。


f:id:Tnzk:20081005205452p:image


チュートリアルどおりに適当に動かしたところ8bitのグレースケール画像が作成されたので、SetPixelでオレンジ色になるように描画してみた。あくまでFreeTypeはフォントの形状を扱うだけなのでグレースケールを主に使うっぽい。まあそれさえあればグラデーションでも何でもできるので問題ないはず。


文字列を1枚の画像として保存


各文字ごとに保存できれば簡単な話だろなー、と思って次のように書いた。



#include <string.h>
#include <windows.h>
#include <ft2build.h>
#include FT_FREETYPE_H

#include "SaveBitmap.h"

int main(){
FT_Library library;
FT_Face face;
FT_GlyphSlot slot;
FT_UInt glyph_index;
FT_Error error;

char text[] = "Hello, world!";
TCHAR filename[] = L"test.bmp";
int pen_x, pen_y, n;

FT_Init_FreeType( &library );
FT_New_Face( library, "C:\\WINDOWS\\Fonts\\trebuc.TTF", 0, &face );
slot = face->glyph;
FT_Set_Char_Size( face, 0, 16 * 64, 300, 300);

BYTE* m_pbits;
HBITMAP hBitmap;
BITMAPINFO *info;
BITMAPFILEHEADER head={0};
HDC memDC;
HDC hBuffer;

head.bfType = 'MB';
head.bfSize = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + 640 * 480 * 4;
head.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);

info = (BITMAPINFO*)malloc( sizeof(BITMAPINFOHEADER));
ZeroMemory( info, sizeof(BITMAPINFOHEADER));
info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
int num_chars = (int)strlen( text);

info->bmiHeader.biWidth = 640;
info->bmiHeader.biHeight = 480;
info->bmiHeader.biPlanes = 1;
info->bmiHeader.biBitCount = 32;
info->bmiHeader.biCompression = BI_RGB;
info->bmiHeader.biSizeImage = 640 * 480 * 4;
info->bmiHeader.biXPelsPerMeter = 0;
info->bmiHeader.biYPelsPerMeter = 0;

hBitmap = CreateDIBSection( NULL, info, DIB_RGB_COLORS, (VOID**)&m_pbits, NULL, 0);

memDC = CreateCompatibleDC( NULL);
hBuffer = CreateCompatibleDC( memDC);

SelectObject( memDC, hBitmap);

int cpos_x = 50;

pen_x = 300;
pen_y = 200;
for ( n = 0;n < num_chars; n++ ){
int i;
DWORD writeSize;
FT_Bitmap bitmap;
RECT rect;

FT_Load_Char( face, text[n], FT_LOAD_RENDER);
bitmap = slot->bitmap;

rect.left = 0;
rect.top = 0;
rect.right = 640;
rect.bottom = 480;
for( i = 0; i < bitmap.rows * bitmap.width; i++){
SetPixel( memDC, ( i % bitmap.width) + cpos_x, ( i / bitmap.width), RGB( bitmap.buffer[i], bitmap.buffer[i] >> 1, bitmap.buffer[i] >> 2));
}
cpos_x += bitmap.width + 5;
pen_x += slot->advance.x >> 6;
}

SaveBitmapFile( memDC, hBitmap, filename);
}

書いたところ、次のようなことになった。繰り返しになるがFreeTypeはフォントの形状だけを扱い、レイアウトやカラーリングなどには関与しない。


f:id:Tnzk:20081005205453p:image


というわけで、まず描画領域(左上の座標と幅、高さ)を設定し、描画を開始するy座標と高さの和から文字の高さを引いたものをオフセットとし、このオフセットを加えて描画するようにした。次のようなコード。



#include <string.h>
#include <windows.h>
#include <ft2build.h>
#include FT_FREETYPE_H

#include "SaveBitmap.h"

int main(){
FT_Library library;
FT_Face face;
FT_GlyphSlot slot;
FT_UInt glyph_index;
FT_Error error;

char text[] = "Hello, world!";
TCHAR filename[] = L"test.bmp";
int pen_x, pen_y, n;

FT_Init_FreeType( &library );
FT_New_Face( library, "C:\\WINDOWS\\Fonts\\trebuc.TTF", 0, &face );
slot = face->glyph;
FT_Set_Char_Size( face, 0, 16 * 64, 300, 300);

BYTE* m_pbits;
HBITMAP hBitmap;
BITMAPINFO *info;
BITMAPFILEHEADER head={0};
HDC memDC;
HDC hBuffer;

head.bfType = 'MB';
head.bfSize = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER) + 640 * 480 * 4;
head.bfOffBits = sizeof(BITMAPINFOHEADER) + sizeof(BITMAPFILEHEADER);

info = (BITMAPINFO*)malloc( sizeof(BITMAPINFOHEADER));
ZeroMemory( info, sizeof(BITMAPINFOHEADER));
info->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
int num_chars = (int)strlen( text);

info->bmiHeader.biWidth = 640;
info->bmiHeader.biHeight = 480;
info->bmiHeader.biPlanes = 1;
info->bmiHeader.biBitCount = 32;
info->bmiHeader.biCompression = BI_RGB;
info->bmiHeader.biSizeImage = 640 * 480 * 4;
info->bmiHeader.biXPelsPerMeter = 0;
info->bmiHeader.biYPelsPerMeter = 0;

hBitmap = CreateDIBSection( NULL, info, DIB_RGB_COLORS, (VOID**)&m_pbits, NULL, 0);

memDC = CreateCompatibleDC( NULL);
hBuffer = CreateCompatibleDC( memDC);

SelectObject( memDC, hBitmap);

int cpos_x = 50;
int cpos_y = 60;
int cpos_w = 640;
int cpos_h = 50;

pen_x = 300;
pen_y = 200;
for ( n = 0;n < num_chars; n++ ){
int i;
DWORD writeSize;
FT_Bitmap bitmap;
RECT rect;

FT_Load_Char( face, text[n], FT_LOAD_RENDER);
bitmap = slot->bitmap;

rect.left = 0;
rect.top = 0;
rect.right = 640;
rect.bottom = 480;
int offset_y = cpos_h - bitmap.rows + cpos_y;
for( i = 0; i < bitmap.rows * bitmap.width; i++){
SetPixel( memDC, ( i % bitmap.width) + cpos_x, offset_y + ( i / bitmap.width), RGB( bitmap.buffer[i], bitmap.buffer[i] >> 1, bitmap.buffer[i] >> 2));
}
cpos_x += bitmap.width + 5;
pen_x += slot->advance.x >> 6;
}

SaveBitmapFile( memDC, hBitmap, filename);
}

結果は次のとおり。いい感じになった気がする。


f:id:Tnzk:20081005205454p:image


課題点


神経質な人にはわかると思うのだけど、コンマの位置がやけに高い(ぼくは言われるまでまったく気がつかなかった)。これはビットマップデータの高さを基準に座標を決定しているから起きる問題で、文字にはそれとは別に「ベースライン」という考え方があるらしい。これもやはりFreeTypeによって取得できるようなので、次はベースラインでの配置を行おうと思う。




セキュリティ&プログラミングキャンプにRejectされた人の集まりに参加してきました!22-24日の三日間、千葉県、ハチロクの方々の行きつけであるところの土善旅館にて。千葉県は涼しいなーと思ってたら、帰ったら同じような感じだったのでタイミングがよかっただけのようです。


Rejectの細かい流れはid:syou6162さんのまとめを参照。写真とかはsyouさんのまとめのほかに、id:sonodamさんのまとめ感想にたくさん上がってます。僕はほとんど撮れなかった、というより撮るの忘れてた。写真があるだけでまとめがかなりにぎやかに見える(いや実際にぎやかだったのですが)ので、今度からは撮るようにしたいなー。


参加者は、



の9人。上5人は機械学習チーム、id:isocchiさんから3人はWebプログラミングチームということで、本家同様ふたつのコースに。僕はWebプログラミングチームです。機械学習チームは似たテーマでひとりひとり別の研究を、Webプログラミングチームは3人でTwitterAPIを利用したサービスの開発を行っていました。


id:syou6162さんも書かれていますが、本家と違い講義的なものはないながら、最も重要な点であるほかの開発者との交流という点では本家にも劣らないものだったと思います。今まで自分に足りてなかったものをたくさん得ることができました。その傍らで思いっきり足を引っ張ってしまって申し訳ない気持ちもありますがorz


ちなみにWebプログラミングチームが開発していたものはまだ完成していないです。キャンプ終了後も継続して開発しようぜー的な流れになったので、それはそれで楽しみにしています。この辺のソフトさもRejectならではですね!


Rejectセキュリティ&プログラミングキャンプの割には、PCとか財布とか携帯とかを鍵のかかってない部屋に放置したままみんな爆睡しちゃったりとセキュリティ的にかなり危険な(笑)合宿でしたが、とても楽しかったです。


来年Rejectされた人は、まずtwitterでRejectされたことを晒しましょう。僕もそうだったのですが、正直「通るだろこれ」とか根拠のない自信を持っているとものすごい落ち込んでしまいます。


でもあなたが落ちたのと同じように、他にも何十人も落ちた人がいる。そしてその内の何人かはきっとついったらーだ。キャンプ応募者に占めるついったらーの割合は、たぶん今年より来年のほうが増えてる。そこにはしょぼんぬしてる人が何人もいるはず。


そのときあなたの中にまだ折れてない情熱があったら、声を上げてほしい。


「@ALL キャンプ落ちた人はりぷらいよこせ!」


「Rejectされた人で集まろうよ!」


誰かがきっかけさえ作ってしまえばあとは皆で話し合って計画を練るだけ……というわけにはいかないけど、でもそういう部分での困難も楽しいもんだよね。


僕がRejectに誘われたときはこんな感じだった。



@tnzk Rejectセキュリティ&プログラミングキャンプやるんだけど、来ない?


03:19 PM July 14, 2008 from web in reply to tnzk


Twitter

シンプル。とてもシンプル。千葉県遠いなーとは思ったけど、でもこの時点で行かないなんて選択肢はなかった。来年あなたが言い出したRejectキャンプ2009も、同じように人が集まるはず。


そんなわけで、僕を含めて来年のRejectな人たちは声を上げるといいと思います!そのときは僕のエントリより、上で示したid:syou6162さんのエントリのほうが参考になります。引用。



来年Rejectセキュリティ&プログラミングキャンプを企画する人へ


* 幹事みたいな仕事あんまりやったことないんだけど?


* 講義とかそんなのどうするの?


* 宿の予約とかどうしよう?→土善があります


* 大体人とか集まるか分からないよ?→Twitterで告知すれば、どうにかなります


Rejectセキュリティ&プログラミングキャンプをやってきたよ!!! - Seeking for my unique color.

この辺のことに限らず、id:syou6162さんのブログにはRejectの計画から実行までのプロセスが詳細にまとめられています。



他にも、エントリ一覧を眺めるとRubyKaigiを参考にしたことなどがまとめられています。来年のReject幹事は参考にすると元気が出ると思います。


最後になりますが、首謀者のid:syou6162さんをはじめ、参加者のみなさんは本当にありがとうございました&お疲れさまでした!初日から遅刻したりとご迷惑をおかけしましたが、(かなり手垢のついた表現になりますが)誇張抜きであっという間の3日間でびっくりでした。


それと、今回の件で機械学習と統計に興味を持ってしまったので、来年はその辺のネタやりたいなあと思っています。キャンプにRejectされなくても(`・ω・´)




Rejectセキュリティ&プログラミングキャンプの中身をどーすんのよ、っていうお話し - Seeking for my unique color.



id:athlonzに「こういうのは継続的にあるといいよね」と脅しをかけられたアドバイスをもらいました。来年もRejectセキュリティ&プログラミングキャンプをやる人が必ずやいるだろうということで、どういう感じでやっていったかを見える形で残しておきたいと思います。



来年も開催されることを祈りつつ、今回やりたいことについてまとめますね!


普段どういうことをやっていて、どういう知識なら持っているのか



  1. Webアプリケーション

    • Ruby。Railsは使えない

    • 車輪の再発明ばかりしている



  2. 画像処理

    • 404 Not Foundの真似事とか

    • プレゼンテーションのサポートツールとか



  3. ActionScript/Flexでゲーム(最近はじめた


どれも勉強中で未熟者です(´・ω・`)


自分がどういうことをやりたいか


最初は画像処理にフォーカスして、AR的なことをやろうかなと考えていたのですが、三日間でできることとなると限られてくるので難しいかなと思ってます。


WebCam + Flashで何か面白いことできる感じのWebサービスなんかを作りたいなーとも考えてます。


というわけで



  • AR的な面白いもの

  • WebCam + FlashなWebサービス


辺りがやってみたいかなと思ってます。


誰と一緒に開発やりたいか(もしくは一人でもくもくとやりたいか)


思いっきり足引っ張っちゃいそうな感じがするのですが



のかたがたと組めると勉強になりそうだなーと思っています。




なんだかんだいって最終的に自分で書くことになると思うので、大先輩であるOpenCVのcvMatchTemplate関数を読んでおべんきょー。コードは次のURLから入手できます。



テンプレートマッチングについてもまとめたのでどうぞ。


ちゃちゃっと読んでいくよ!



323 CV_IMPL void
324 cvMatchTemplate( const CvArr* _img, const CvArr* _templ, CvArr* _result, int method )

引数がたくさん!



  • const CvArr* _img

  • const CvArr* _templ

  • CvArr* _result

  • int method


CvArrはOpenCVで用いられる配列用の型。_imgはソース画像、_templはテンプレート画像。_resultはマッチングの結果を返すための変数。各座標ごとにマッチングの結果を配置して返すので、これもグレースケール画像のような出で立ちのデータになる。


methodはマッチングアルゴリズムの指定で、以下の定数が使える。



  • CV_TM_SQDIFF

  • CV_TM_CCORR

  • CV_TM_CCOEFF

  • CV_TM_SQDIFF_NORMED

  • CV_TM_CCORR_NORMED

  • CV_TM_CCOEFF_NORMED


これらの意味するところはOpenCVのリファレンスで確認することができるのですが、とりあえずコードを読んでいきたいと思います。


以下、しばらく変数の初期化や作業用バッファの確保などが続きます。



347 int num_type = method == CV_TM_CCORR || method == CV_TM_CCORR_NORMED ? 0 :
348 method == CV_TM_CCOEFF || method == CV_TM_CCOEFF_NORMED ? 1 : 2;
349 int is_normed = method == CV_TM_CCORR_NORMED ||
350 method == CV_TM_SQDIFF_NORMED ||
351 method == CV_TM_CCOEFF_NORMED;

若い世代のC使いは三項演算子が苦手です(ホントか?


まあそれはそれとして、まずはnum_typeの確定から。コードを見れば一目瞭然ですが、num_typeは_NORMEDの有無に関係なくSQDIFFか、CCORRか、CCOEFFかを表すようになります。CCORRなら0、CCORFFなら1、そうでなければ(つまりSQDIFFなら)2。


逆にis_normedは末尾に_NORMEDがついた定数かどうかをチェックします。_NORMEDのつく定数の場合はis_normedが真になる(Cでは真は0以外だけどC++でもそうなのかな?)。


続く3行。



353 CV_CALL( img = cvGetMat( img, &stub, &coi1 ));
354 CV_CALL( templ = cvGetMat( templ, &tstub, &coi2 ));
355 CV_CALL( result = cvGetMat( result, &rstub ));

CV_CALL?


たぶんプリプロセッサマクロっぽいけど、見慣れない記法なのでちょっと不安。


ググってみたら先達の足跡が。



CV_CALLってなによ?[後で調べる]

わかんねーきっとプリプロセッサで処理されるのだろうが・・・


MEMO - OpenGL/Cudaツホクウオュ

あとで調べたけど追記するの忘れたパターンですね!!(ごめんなさいじょうだんです


もうちょいググってみるとこんなのが。OpenCV開発者向けのコーディング規約みたいなものかなー。



Instead of above functions one can use macros, which are more convenient to use:



  • CV_ERROR and OPENCV_ERROR instead of cvError.

  • CV_CALL and OPENCV_CALL instead of calling function and checking the status.



いくつかの関数は、それ自体をコールする代わりにマクロを利用すると便利で、推奨されているみたい。


CV_CALLの場合は、関数呼び出しとその値のチェックの代わりに利用するとエラー処理とかをライブラリにおまかせできてイイカンジっぽい。


とりあえず突っ込んだことは必要ないので、とりあえず今は普通にコールしていると考えることにします。


エラー処理


357行目からしばらくエラー処理が続く。



357 if( CV_MAT_DEPTH( img->type ) != CV_8U &&
358 CV_MAT_DEPTH( img->type ) != CV_32F )
359 CV_ERROR( CV_StsUnsupportedFormat,
360 "The function supports only 8u and 32f data types" );

入力画像のデータ単位が8bit符号なし整数か32bit浮動小数点のときのみ許容され、それ以外の場合はエラー。



362 if( !CV_ARE_TYPES_EQ( img, templ ))
363 CV_ERROR( CV_StsUnmatchedSizes, "image and template should have the same type" );

入力画像とテンプレート画像のデータ単位が同じでなければエラー。



365 if( CV_MAT_TYPE( result->type ) != CV_32FC1 )
366 CV_ERROR( CV_StsUnsupportedFormat, "output image should have 32f type" );

結果を受け取るためのデータ単位は32bit浮動小数点でなければエラー。



368 if( img->rows < templ->rows || img->cols < templ->cols )
369 {
370 CvMat* t;
371 CV_SWAP( img, templ, t );
372 }

条件は入力画像よりテンプレートのほうが行・列どちらかでも大きい場合。


CV_SWAPに違和感があるなーと思ったのは、普通Swapと名のつく関数は二引数のものが多いからかなー。もうひとつはなんだろ。ワーキングバッファかな。


ググると先ほどの先達のページが。



CV_SWAP( a, b, t )

aとbを入れ替える関数.


どうやらOpenCVで対応しているCvPointやIplImageなどでも可能なようだ.


tは入れ替えの際に必要になる一時データ保存用変数.当然aとbと同じ型.


関数について - OpenCV Memo

お、当たり。


ってことは入力画像よりテンプレート画像のほうが大きいときは入力とテンプレートを入れ替えちゃうのかな。cvMatchTemplateには「入力>テンプレートでなければならない」って制約があったような気がするんだけど、これで対応されてるってことは大丈夫なのかな?(もっともテンプレートのほうが大きい状況もなかなかないと思うけど)



374 if( result->rows != img->rows - templ->rows + 1 ||
375 result->cols != img->cols - templ->cols + 1 )
376 CV_ERROR( CV_StsUnmatchedSizes, "output image should be (W - w + 1)x(H - h + 1)" );

結果格納用のバッファは幅・高さともに(入力 - テンプレート + 1)でなければならない。これはテンプレートマッチングの性質上自明な制約だったはずなんだけど、その理由をド忘れ中。書いてたら思い出すかなー。



378 if( method < CV_TM_SQDIFF || method > CV_TM_CCOEFF_NORMED )
379 CV_ERROR( CV_StsBadArg, "unknown comparison method" );

アルゴリズムの指定が定数として準備されているものかどうかの確認。ここでこういう記述をするなら347行目あたりのフラグの処理ももっと高速かつ簡潔に記述できるのに!わかりやすさを優先してるならそれで統一したらいいじゃない!



381 depth = CV_MAT_DEPTH(img->type);
382 cn = CV_MAT_CN(img->type);

depthは入力画像の色深度。cnは何かよくわからないけどとりあえずスルー。



384 if( is_normed && cn == 1 && templ->rows > 8 && templ->cols > 8 &&
385 img->rows > templ->cols && img->cols > templ->cols )
386 {

条件。is_normedが立ってて、cnが1で、テンプレートが8x8より大きく、入力画像がテンプレートより大きくなければならない。該当する場合、以下の処理。



387 CvTemplMatchIPPFunc ipp_func =
388 depth == CV_8U ?
389 (method == CV_TM_SQDIFF_NORMED ? (CvTemplMatchIPPFunc)icvSqrDistanceValid_Norm_8u32f_C1R_p :
390 method == CV_TM_CCORR_NORMED ? (CvTemplMatchIPPFunc)icvCrossCorrValid_Norm_8u32f_C1R_p :
391 (CvTemplMatchIPPFunc)icvCrossCorrValid_NormLevel_8u32f_C1R_p) :
392 (method == CV_TM_SQDIFF_NORMED ? (CvTemplMatchIPPFunc)icvSqrDistanceValid_Norm_32f_C1R_p :
393 method == CV_TM_CCORR_NORMED ? (CvTemplMatchIPPFunc)icvCrossCorrValid_Norm_32f_C1R_p :
394 (CvTemplMatchIPPFunc)icvCrossCorrValid_NormLevel_32f_C1R_p);

これはとんでもなく長い三項演算子の連続ですね!アクロバティックだけどifを書き連ねるよりはきれいかなー。


この長い三項演算子をスルーしつつ今日はおひらき。続きはまた明日。




f:id:Tnzk:20080718143756p:image:right:small


GeekぺーじなどでDirectShowを用いたプログラミングの際に入門例として取り上げられるGraphEditというアプリケーションが行方不明になっていた事件で、現在GraphEditはPlatformSDK/Bin/graphedt.exeにあることがわかりました。


居場所を通報したのはfenrirという検索型ランチャーで、同ランチャーによる先頭一致検索によって居場所が判明したとのことです。


警視庁は「fenrirがあればたいていのアプリケーションがキーストローク5つ以内で起動できるので、Windowsユーザの人はfenrirをインストールしてデスクトップをまっさらにしてほしい」と発表しています。ただしid:rosylillyは同様のカスタマイズを施したPCを他人に触らせた際「使いにくっ」と罵声を浴びせられているので注意が必要とのことです。


なお、GraphEditが見つからず停滞していたid:Tnzkの開発は今回の件により再開されるはずですが、現在は積み本(主にエロ漫画)の消化中だということです。




学校でPythonに関する小さな小さな勉強会みたいなものをすることになった。参加者10名未満という本当に小規模な発表会なんだけど、こういう集まりを主導するのは初めてなので、何を準備すべきかちょっと戸惑っている。でもg91とかでもやんなきゃだし、こういった経験は積めば積むほど


力になるから、できることを調べてやらないとなー。


あくまで規模は小さいもので、放課後のだいたい1時間くらいで終わる予定。内容は各参加者による5~10分ほどのライトニングトーク。Pythonに関わることなら何でもOKな感じ。


で、そんな矢先(というかまあ2日前なのだけど)、


1000人スピーカカンファレンス第6回について - 西尾泰和のはてなダイアリー


こんな記事。西尾さんには1000speakersのときにとんでもなくお世話になったので、こんなときさえ何の力にもなれない自分が悔しいし、つらい。でも今無理しても仕方ないので、できることをできるようにやろうと思う。


今ぼくがしたいこと。それは昔からの想いでもある、「たくさんの中高生に創作の楽しさを知らしめたい」というもの。


もっと独りよがりに言うなら「たくさんの中高生に、アクティブに動くという快感を味あわせたい」になる。


誰も彼も能動的に動きたいわけじゃないのは当然だけど、どっちつかずの姿勢をとっている人に自分の都合で働きをかけるのは悪いことじゃないと思う。本当に嫌なら拒絶するだろうし、そこまで強烈に抵抗しないということは別に嫌いじゃないだろう。


そんなわけでクラスの人たちがどのような動きを見せるか知りたいということもあって、id:yaakaitoが突如提唱したPython勉強会案を少し改造して、プチ勉強会に仕立て上げた。いきなり40分とかの長大な発表は負担が大きすぎると思ってのこと。


発表会はUstするものの、時間が時間(6/2 15:00頃)でしかも不確定なので、見るのは難しいかも。RecしてニコニコかYouTubeでの公開もする予定。話者の許可が出たもののみだけど。


うまくいくといーな、ということで色々調べてたら、上記の記事を見つけた。


そこでは1000speakersフランチャイズが提案されていて、1000speakers wikiにまとめられたそのノウハウへのリンクが掲載されていた。


1000speakers:tasks - どう書くwiki


当たり前だけどとても参考になる。本当にid:nishiohirokazuさんにはお世話になりっぱなしです。




ひょんなことからモーションキャプチャシステムみたいなものを作ることになって、その実験として小規模なものを開発しているところ。キャプチャシステム自体は未完成です。


準備するもの


以下のページに従って開発環境を整える。


インストール - OpenCV@Chihara-Lab.


具体的にインストールしたものは次のとおり。



  • Visual C++ 2005 Express Edition

    • SP1 for Windows Vista

    • Platform SDK



  • OpenCV

  • IPL


経験のある人は知ってると思うけど、Platform SDKのインストールには異常に時間がかかるので、本の一冊や二冊は準備しておくといいと思う。


開発環境


使用したマシンは工人舎のSH8WP12A。USBカメラが手元にないと思っていたところ、こいつが内臓していたのでこれを使うことにした。


ところが内臓しているため、PlatformSDKのインストール中にカメラを別のマシンで使うことができず、もう一台のマシン(以前にPlatform SDKインストール済み)で先んじてテストしておくことができなかった。


あとOpenCVのインストーラをメディアにコピーしておくのを忘れたために、これをインストールすることもできなかったので、ManyCamsを使ってRubyからキャプチャして時間を潰してた。これは本当に時間を無駄にしたと思うので、事前に準備しておくといいと思う。


サンプルをチェキ!


OpenCVをインストール後、パスを通せばサンプルが動くということなのでチェックしてみた。


OpenCV/samples/を開くと、ソースファイルやプロジェクトファイルなどにまぎれて実行ファイルがあったので、ビルドなどをする前にこっちを見てみた。エッジの抽出やフラクタルの描画みたいなことをやっていて面白かったので見てみるといいと思う。


で、肝心のビルドはというと、これは失敗した。読み取り専用がどうとか、見慣れないエラーが出てたので、そろそろ時間も遅いし帰るかーという流れに。


部室を出て電車に乗った頃に気づいたんだけど、サンプルが動くというのは「サンプルプロジェクトがビルドできる」という意味ではなく「サンプルの実行ファイルが動作する」という意味だったのかも、と思った。


ここまでが昨日の話。


テスト用プロジェクトの作成


そういう流れで、もう面倒くさいのでプロジェクト作成して依存ファイルの追加設定なども済ませてやろうと思った(今冷静に考えると、これはプロジェクト単位での設定なのでサンプルとは関係ないのよね)。


そのままここに則ってカメラキャプチャをしようとしたのだけど、動作はするしGUIも生成される一方でカメラの映像が取得できない。


色々調べてみると、映像の取得ではなくカメラの取得(デバイスハンドルの取得みたいな感じ)でコケてることが判明。cvCaptureFromCAMはその引数で取得するカメラの種類を指定するのだけど、サンプルではこれが -1 となっていた。引数に -1 を与えると、cvCaptureFromCAMはイイカンジにはからって適当にカメラを取得してくれる。


これが不審だったので、試しに定義されている CV_CAP_ANY, CV_CAP_MIL, CV_CAP_VFW, CV_CAP_IEEE1394をそれぞれ与えて実行してみたところ、CV_CAP_VFW以外は取得に失敗。自動選択されているのはCV_CAP_CFWらしい。


VFWはVideo For Windowsの略で、正攻法ではこいつとDirectShowを組み合わせて実装するらしい。今回はGW中に完成させる必要があるので、手っ取り早く使えるものが欲しいので、別の方法を考えた。


まず、サンプルでカメラが取得できるかどうか調べてみた。camcapture.c(みたいな感じの(うろ覚え))をビルドしてみると、先ほどテスト用プロジェクトで発生したのと同じ状態になった。


ところが、OpenCV/otherlibs/cvcams/samples/にあるサンプルを実行してみると、普通にキャプチャできた。


CvCamを使うことに


どうやらcvcamは廃れる運命にある(具体的にはOpenCVのchangeLogにおいてその撤廃が告知されている)ようなのだけど、別にそんな末永く使うつもりじゃないし、それまでにはDirectShow+VFWに移行できると思うので、今回はcvcamを使うことにした。


cvcamを使うと、普通にキャプチャできていい感じですね。


赤い部分の抽出


カメラベースでマーカーを利用したキャプチャを行おうと考えているので、特定の色を抽出する処理が必要。


(R,G,B) = (255,0,0)な画素だけ残して、あとは(0,0,0)で埋めてしまえばいいんじゃね!? とか思うほどバカではないですが、程度問題で結局のところバカでした。


(R > 100,G,B)な画素ryということで実装してみたところ、当然ながら白も黄色も拾ってしまって話にならず、まずはさらに安直に考えを進めて「G, Bが小さければいいんじゃね?」という発想に至る。


というわけで(R > 100, G < 70, B < 70)の画素を残すようにしてみる。そこそこきれいにはなったものの、影になっている部分(暗い赤)などを拾えず、形状が欠損してしまう。


というわけで今度は比にして考えてみた。100:70ということで( R/G > 1.42 && R/B > 1.42)の画素のみを残してみると、意外とイイカンジになった。比率を調整して、今日は最終的に以下のような感じに。


f:id:Tnzk:20080504221429j:image:w200


赤い領域の抽出(1)


f:id:Tnzk:20080504221428j:image:w200


赤い領域の抽出(2)


画面左下のウィンドウが原画像、左上のウィンドウが抽出した画像を表示している。


あとはこいつらの重心を求めたりして、なんとかして座標値にする。もちろんペンとかだとどこの座標にするかという話にもなるので、ちゃんとマーカーを作ってやる。この辺は明日。


で、問題点は同様に実装できるのがR,G,Bを抽出する場合のみということ。紫とかそういう色が欲しいときにどうすればいいかというのも問題なので、そこも明日の課題。比率からもう少し考えを進めれば実現できると思うけど、どうだろう。


今回のことですごく思ったんだけど、画像処理(というほどのことはやっていないけど)は楽しいよ!3Dレンダリングが楽しいのと同じで、処理結果が目に見えるのはうれしい。


このアーカイブについて

このページには、過去に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のカテゴリはバージョン管理です。

次のカテゴリは失敗です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

ウェブページ

Powered by Movable Type 4.32-ja