Windows7にruby-opencv環境のセッティング
色々調べたりトライしたりした結果、Windows7では32bit/64bitによらず下記の環境が一番良さそうなことがわかった。
cygwin ruby でビルドする方法もあるが、パッチを当てる必要があるし、時間がかかるし、割と面倒。
mingw ruby は環境によってはうまくopencvを見つけることができず、ビルドに失敗することがある。
mswin版rubyだが、Windows7が64bitであっても64bit版rubyだと、ruby用バイナリをうまくビルドできない。(ビルドしようとして、ポインタのサイズが違うとかでエラーが出た。適当に修正してどんな問題が出るか予想できなかったので俺はあきらめた)
また、2015年現在公開されている ActiveScriptRuby は VS10 でビルドされている為、最新版 VisualStudio 2013 でなく、VS10(2010)を使用する必要がある。
1. http://sourceforge.net/projects/opencvlibrary/ から opencv-2.4.11.exe をDL。
2. 上記exeを c:/opencv/ に解凍。exeサイズは350MBくらいだが、HD空きは8GBくらい必要。
3. システム環境変数 PATH に c:\opencv\build\x86\vc10\bin を追加。
4. インクルード/ライブラリ検索パスとして、c:/opencv/build/include等を追加。(※1)
5. http://www.microsoft.com/ja-jp/download/details.aspx?id=23691 とかから VisualStudio Express 10 をインストール
6. http://www.artonx.org/data/asr/ から Ruby-2.2 (32bit版)の msi をダウンロードして、c:/Ruby にインストール、 PATH にc:\Ruby\bin を追加。
7. "c:\Program Files (x86)\Microsoft Visual Studio 10.0\Common7\Tools\vsvars32.bat" を実行し、VisualStudio 2010 用の環境変数を設定。
8. gemで ruby-opencv をインストールする: gem install ruby-opencv --platform ruby -- --with-opencv-include=C:/opencv/build/include/ --with-opencv-lib=C:/opencv/build/x86/vc10/lib/
9. 下記の様な簡単なスクリプトを書いて、引数指定したJpegが表示されるのを確認。
require 'opencv' include OpenCV GUI::Window.new("Hello").show CvMat.load(ARGV.shift) GUI::wait_key
(※1) OpenCV関連のビルド時にINCLUDE / LIBの検索パス設定は必要だが、ruby-opencv しか使わないなら、コマンドラインで一時的に設定すればおっけー。
set LIB=c:\opencv\build\x86\vc10\lib set INCLUDE=c:\opencv\build\include
シーケンス図をテキストで出力する
PlantUMLをインストールしてみたのだけど、シーケンス図をテキスト形式で出力時に、日本語が含まれると表示がずれる。
まあ大抵の場合png出力で確認するとはいえ、なんか微妙にはがゆいので、日本語専用でPlantUMLのシーケンス図を出力するスクリプトを書いてみた。
(多分車輪の再発明)
日本 -> ソ連 : 日ソ中立条約 日本 -> アメリカ : 真珠湾攻撃 アメリカ --> 日本 : 原爆投下 ソ連 --> 日本 : 対日参戦 日本 -> 日本 : 無条件降伏
例えば、上記↑のテキストを下記↓の様に出力する。
┏━━┓ ┏━━┓ ┏━━━━┓ ┃日本┃ ┃ソ連┃ ┃アメリカ┃ ┗┳━┛ ┗┳━┛ ┗━┳━━┛ ┃日ソ中立条約 ┃ ┠────→┃ ┃ ┃ ┃ ┃ ┃真珠湾攻撃┃ ┃ ┠───────────→┃ ┃ ┃ ┃ ┃原爆投下 ┃ ┃ ┃← - - - - - - - - - - -┨ ┃ ┃ ┃ ┃対日参戦 ┃ ┃ ┃← - - - -┨ ┃ ┃ ┃ ┃ ┃無条件降伏┃ ┃ ┠←─ ┃ ┃ ┃ ┃ ┃ ┏┻━┓ ┏┻━┓ ┏━┻━━┓ ┃日本┃ ┃ソ連┃ ┃アメリカ┃ ┗━━┛ ┗━━┛ ┗━━━━┛
class String def jlen # SJISにおけるバイト数を返す # len = self.size nonAsciiLen = self.gsub(/[\x20-\x7e]/,'').size len + nonAsciiLen end end class Message attr_reader :name,:from,:to,:spec def initialize(name,from,to,spec=:normal) @name,@from,@to,@spec = name,from,to,spec end end class Participants # メッセージの送り手/受け手のクラスの集合 # @@DefOff = 2 def initialize @n = [] # Participantの名前の配列 # @p = [] # Participantそのものの配列 # @m = [] # Messageの配列 # end def add(*a) i = nil a.each do |name| i = @n.index(name) if !i i = @n.size loff = 0 loff = @@DefOff if i>0 @n << name @p << Participant.new(name,loff) end end i end def add_msg(name,from,to,spec=:normal) from = self.add(from) to = self.add(to) @m << Message.new(name,from,to,spec) end def draw_top @p.each{|p| print ' '*p.loff, p.gen_boxline(:top) };puts''#┏━┓ @p.each{|p| print ' '*p.loff, '┃'+(p.name)+'┃' };puts''#┃P ┃ @p.each{|p| print ' '*p.loff, p.gen_boxline(:bottom,'┳') };puts''#┗┳┛ end def draw_bottom @p.each{|p| print ' '*p.loff, p.gen_boxline(:top,'┻') };puts''#┏┻┓ @p.each{|p| print ' '*p.loff, '┃'+(p.name)+'┃' };puts''#┃P ┃ @p.each{|p| print ' '*p.loff, p.gen_boxline(:bottom) };puts''#┗━┛ end def draw_msgs @m.each do |m| start,stop = m.from,m.to if m.from > m.to # 送信元が 右側に居る場合 # start,stop = m.to,m.from end # Message#name の描画 # (0 ... start).each{|i| print ' '*(@p[i].loff+@p[i].coff), '┃', ' '*@p[i].roff} name,len = m.name,m.name.jlen if len%2 == 1 len += 1 name += " " end len /= 2 print (' '*(@p[start].loff+@p[start].coff)) + '┃' + name (start ... (@p.size-1)).each do |i| cnt = @p[i].roff + @p[i+1].loff + @p[i+1].coff if len > 0 cnt,len = cnt-len,len-cnt end print(' '*cnt) if cnt > 0 print (len>0) ? ' ' : '┃' end puts '' # 矢印部の描画 # head,body,tail = ' ',' ┃ ',' ' @p.size.times do |i| if i == start tail = (m.spec==:normal) ? '─' : ' -' body = (start==m.from) ? (' ┠'+tail) : ' ┃←' body = ' ┠←' if start == stop elsif i == stop tail = ' ' body = (stop==m.to) ? '→┃ ' : (head+'┨ ') end print head*(@p[i].loff+@p[i].coff-1), body, tail*(@p[i].roff-1) if i == stop head,body,tail = ' ',' ┃ ',' ' elsif i == start head = (m.spec==:normal) ? '─' : ' -' body = head*3 end end puts '' draw_lifelines # 行を詰めて表示したい場合はこの行をコメントアウトすべし # end end def draw_lifelines @p.each{|p| print ' '*(p.loff+p.coff), '┃', ' '*p.roff }; puts '' end def draw draw_top draw_msgs draw_bottom end end class Participant # メッセージの送り手/受け手のクラス # attr_reader :jlen,:name,:loff,:coff,:roff def initialize(name, loff=0) # loff : 箱部分の左端の、直前の箱からのオフセット距離 @name = name @jlen = name.jlen if @jlen%2 == 1 @jlen += 1 @name += " " end @jlen /= 2 @loff = loff @coff = (@jlen-1)/2+1 @roff = (@jlen%2==1) ? @coff : @coff+1 end def gen_boxline(top_or_bottom=:top,tail=nil,len=@jlen) #"┗┳┛" とか "┏━┓"を生成する # boxline = '' if tail boxline = '━'*(@coff-1) + tail + '━'*(@roff-1) else boxline = '━'*len end top_or_bottom==:top ? ('┏'+boxline+'┓') : ('┗'+boxline+'┛') end end ################################################################ if $0 == __FILE__ and ARGV.size > 0 p = Participants.new ARGF.each do |l| next if /^@/ =~ l or /^\s*$/ =~ l if /\s+([\-<>]+)\s+/ =~ l p1,arrow,p2,mes = $`,$1,$','' p2.chomp!;p2.chomp!;p2.sub!(/\s+$/,'') if /\s+:\s+/ =~ p2 p2,mes = $`,$' end p.add(p1,p2) if /^</=~arrow p1,p2=p2,p1 end arrow = (/\-\-/=~arrow) ? :dotted : :normal p.add_msg(mes,p1,p2,arrow) end end p.draw end __END__ #単独で使用する時は以下の様な感じ。 p = Participants.new #p.add("hogehogeho","foo","日本") p.add_msg('fooから「日本」へのメッセージ',"foo","日本",:dotted) p.add_msg('msg2(fooからhogehogeへ)',"foo","hogehogeho",:dotted) p.add_msg('msg3',"hogehogeho","日本") p.add_msg('msg4',"part1","part2") p.add_msg('msg5',"part2","foo",:dotted) p.draw
cygwin ruby1.9.3 と win32-clipboard-0.5.2
cygwin Ruby1.9.3 環境に win32-clipboard を gem install してみたんだが
日本語の出し入れができなくなってる。
で、とりあえず動作する様にしてみる。
/usr/lib/ruby/gems/1.9.1/gems/win32-clipboard-0.5.2/lib/win32/clipboard.rb
--- clipboard.rb.org 2012-12-21 19:59:31.288117400 +0900 +++ clipboard.rb 2012-12-21 23:46:27.373912900 +0900 @@ -70,7 +70,7 @@ # NULL terminate text case format when TEXT, OEMTEXT, UNICODETEXT - clip_data << "\0" + clip_data.force_encoding('ASCII-8BIT') << "\0" end # Global Allocate a movable piece of memory. @@ -120,7 +120,11 @@ when TEXT, OEMTEXT, UNICODETEXT clip_data = 0.chr * GlobalSize(handle) memcpy(clip_data, handle, clip_data.size) - clip_data = clip_data[ /^[^\0]*/ ] + if format == UNICODETEXT + clip_data.force_encoding('UTF-16LE') + else + clip_data.force_encoding('Windows-31J').sub!(/\0.*/,'') + end when HDROP clip_data = get_file_list(handle) when ENHMETAFILE
自分の日本語環境でさえ動きゃいいや、という超適当な修正。
アラビア文字とハングルで試してみたけど、一応コピペできてる。
#!/usr/bin/ruby -Ku # coding: utf-8 require 'win32/clipboard' include Win32 h = {'korean'=>"(ハングル)".encode('UTF-16LE'), 'arabian'=>'(アラビア文字)'.encode('UTF-16LE')} Clipboard.set_data(h[ARGV.shift], Clipboard::UNICODETEXT) puts Clipboard.data(Clipboard::UNICODETEXT)
Windowsの言語環境情報から Ruby のエンコーディングラベル('Windows-31J'とか)を簡単に生成できないのかな。
1.7.5のLANGまわりの挙動が変
久し振りに cygwin のバージョンを上げたら、挙動がおかしくなった。
どうも、内部的に扱う文字列を勝手に UTF-8 に変換かけてるっぽい。
LANGを設定してなかったら勝手に LANG=C.UTF-8 に設定して、Windows内で標準で設定されているエンコーディングの文字列(CP932)を勝手に UTF-8 に変換してるっぽい…
/etc/profile.d/lang.sh:test -z "${LC_ALL:-${LC_CTYPE:-$LANG}}" && export LANG=C.UTF-8
LANG=C.UTF-8 な状態で「ruby -Ks -e 'Dir.mkdir "日本語"'」とかすると、
ruby内部ではちゃんと「日本語」という SJISエンコーディングなディレクトリを作ろうとするのだが、
cygwin内部で mkdir が呼ばれる時に、UTF-8エンコーディングに変換されて UTF-8 エンコーディングのディレクトリ名でディレクトリを作ろうとしてる…
とりあえず、コンパネのシステムの環境変数で LANG=ja_JP.SJIS を設定しておく…と、まあ何となくまともに動く様になったっぽい。
~/trash/tmp$ uname -a CYGWIN_NT-5.1 xxx 1.7.5(0.225/5/3) 2010-04-12 19:07 i686 Cygwin ~/trash/tmp$ echo $LANG ja_JP.SJIS ~/trash/tmp$ ruby -v ruby 1.8.7 (2008-08-11 patchlevel 72) [i386-cygwin] ~/trash/tmp$ ruby -Ks -e 'Dir.mkdir "日本語"' ~/trash/tmp$ LANG=C.UTF-8 ruby -Ks -e 'Dir.mkdir "日本語"' ~/trash/tmp$ ll drwxr-xr-x 1 ???????? ???????? 0 2010-06-02 20:49 日本語/ drwxr-xr-x 1 ???????? ???????? 0 2010-06-02 20:50 日本語/ ~/trash/tmp$ echo * | hex -is -os 0x00000000: 93 fa 96 7b 8c ea 20 93 - fa 96 7b 8c ea 0a 日本語 日本語
…おい。ちょっと待て。
この変な仕様(バグ?)は、ちょっと勘弁して欲しいなあ。
(因みに Explorer とかから見ると、後から作成した UTF-8 なディレクトリの方はちゃんと(?)文字化けして見える)
iecache
IEのキャッシュ情報をテキスト形式で取得して抽出/定型処理したいなーと思いコマンドラインツールを作ってみる。
調べたら、WindowsAPIで、以下の様に使用できるらしい。
1. まずFindFirstUrlCacheEntryA()をコール。
2. FindNextUrlCacheEntryA()を呼び続けることで次々にキュッシュ情報をゲット。
3. 最後にFindCloseUrlCache()をコール。
ゲットできるキャッシュ情報は以下の様な構造体。
c:/cygwin/usr/include/w32api/wininet.h:648:
typedef struct _INTERNET_CACHE_ENTRY_INFOA { DWORD dwStructSize; LPSTR lpszSourceUrlName; LPSTR lpszLocalFileName; DWORD CacheEntryType; DWORD dwUseCount; DWORD dwHitRate; DWORD dwSizeLow; DWORD dwSizeHigh; FILETIME LastModifiedTime; FILETIME ExpireTime; FILETIME LastAccessTime; FILETIME LastSyncTime; PBYTE lpHeaderInfo; DWORD dwHeaderInfoSize; LPSTR lpszFileExtension; DWORD dwReserved; } INTERNET_CACHE_ENTRY_INFOA,*LPINTERNET_CACHE_ENTRY_INFOA;
↑この情報をテキストでゲットして、5/26 0:00 〜 5/27 0:00 のアクセス履歴をgrepできる様にしたい。以下の様な感じで。
~/$ iecache -f 05260000 -t 05270000 | grep twitter
以下の様にしてクッキー情報を抽出するとか。
~/$ iecache -f 05260000 -t 05270000 | grep ^Cookie
…
で、大体以下の様な感じで、とりあえず動作するのを確認。
~/s/c/iecache/iecache.c:0:
/* $ gcc -mwindows iecache.c -lwininet -o iecache */ #include "wininet.h" #include <stdio.h> #include <unistd.h> /* getopt用 */ /* winerror.h:190:#define ERROR_NO_MORE_ITEMS 259L */ UINT64 ftime2u64(FILETIME * pFt) { UINT64 u64 = pFt->dwHighDateTime; return (u64<<32) + pFt->dwLowDateTime; } UINT64 str2u64(char * s) {/* hhmmss で渡された文字列を元に FILETIME と同等の uint64 を生成する */ SYSTEMTIME st; int len = strlen(s); int hhmm = atoi(s); UINT64 u64; FILETIME ft; GetLocalTime(&st); if (hhmm < 0) { SystemTimeToFileTime(&st, &ft); u64 = ftime2u64(&ft); /* FileTime は100ナノ秒単位の64bit値 */ /* "-100" と指定された時、100分前と解釈すべきなのか 1:00 前(一時間前)と解釈すべきなのか 悩ましいところだが、とりあえずは 100分前と解釈するものとする。 */ u64 = u64 / 600000000UL - (-hhmm); u64 *= 600000000UL; } else { st.wSecond = st.wMilliseconds = 0; /* 大小比較時にややこしくなるのでゼロに初期化しておく */ /* 例えば現在が 12:34 で 1.23 と指定された場合は 12:01.23 と解釈 */ if (len > 3 && s[len-3] == '.') { st.wSecond = atoi(s+len-2); len -= 3; /* 末尾の ".XX" の分をマイナスする */ } if (len > 8) { /* MMDDhhmm で8桁。20億を越えると 32bit桁溢れするので2回に分けて処理する */ if (len == 10 || len == 12) { /* yyYYMMDDhhmm */ char yy[5]; hhmm = atoi(s + len - 8); /* MMDDhhmm */ strncpy(yy, s, len - 8); /* yyYY */ st.wYear = atoi(yy); if (len == 10) st.wYear += 2000; } else { fprintf(stderr, "Strange date : %s\n", s); exit(0); } } st.wMinute = hhmm%100; hhmm /= 100; if (hhmm < 100) { if (hhmm != 0) st.wHour = hhmm;/* hourの桁がゼロの時は、現在時刻の wHourをそのまま使用(ってどうなんだろ。わかりにくい?? */ } else { /* 上位桁に DD が付いてる */ st.wHour = hhmm%100; hhmm /= 100; if (hhmm < 100) { st.wDay = hhmm; } else { /* 上位桁に MM(月) が付いてる */ st.wDay = hhmm%100; hhmm /= 100; if (hhmm < 100) { st.wMonth = hhmm; } else { /* 上位桁に YY が付いてる */ st.wMonth = hhmm%100; hhmm /= 100; if (hhmm < 100) hhmm += 2000; st.wYear = hhmm; } } } SystemTimeToFileTime(&st, &ft); u64 = ftime2u64(&ft); } return u64; } int main(int ac, char ** av) { HANDLE hFind; DWORD dwLen; DWORD dwLen_pre = MAX_CACHE_ENTRY_INFO_SIZE; INTERNET_CACHE_ENTRY_INFO* psInfo; SYSTEMTIME st; FILETIME ft; long er = 0; int ch; UINT64 u64; UINT64 from = 0; UINT64 to = 0; BOOL ret; char def_wildcard[] = "*.*"; char * wildcard = def_wildcard; extern char *optarg; extern int optind, opterr; while ((ch = getopt(ac, av, "f:t:")) != -1){ switch (ch){ /* FromTime から ToTime までの間のIEキャッシュのみ取り出す。 引数の指定方式としては touch と大体同じ形式を採用。 つまり、 [[CC]YY]MMDDhhmm[.ss] という感じ。 ただし、ちょっと拡張して、hhmmのみ/mmのみの指定も許す。 [[[[CC]YY]MMDD]hh]mm[.ss] という感じ。 あと、負数指定も許し、「-5」で「5分前」と指定できる様にしてみる。 */ case 'f': // FromTime from = str2u64(optarg); break; case 't': // ToTime to = str2u64(optarg); break; default: ; } } ac -= optind; av += optind; /* これ以降は,av[0]〜av[ac-1]にオプションを除いた引数が入る */ if (ac == 1) wildcard = av[0]; dwLen = dwLen_pre; psInfo = (INTERNET_CACHE_ENTRY_INFO*) malloc(dwLen_pre); /* hFind = FindFirstUrlCacheEntryA("visited:", psInfo, &dwLen); //例えば "cookie:" とか "visited:" とかが指定可能 */ hFind = FindFirstUrlCacheEntryA(wildcard, psInfo, &dwLen); if (dwLen == 0 || hFind == 0) { if (hFind) FindCloseUrlCache(hFind); return 0; } if (dwLen > dwLen_pre) { psInfo = realloc(psInfo, dwLen); if (psInfo == NULL) { FindCloseUrlCache(hFind); return 0; } dwLen_pre = dwLen; hFind = FindFirstUrlCacheEntryA(wildcard, psInfo, &dwLen); } FileTimeToLocalFileTime(&psInfo->LastAccessTime, &ft); FileTimeToSystemTime(&ft, &st); while (1) { ret = 1; u64 = ftime2u64(&ft); if (from && u64 < from) ret = 0; if (to && u64 > to) ret = 0; if (ret == 1) { char * s = psInfo->lpszLocalFileName; printf("%s\r%04d/%02d/%02d %02d:%02d:%02d\r%s\n", psInfo->lpszSourceUrlName, st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond, s?s:"" ); } ret = FindNextUrlCacheEntryA(hFind, psInfo, &dwLen); if (dwLen == 0) break; if (dwLen > dwLen_pre) { psInfo = realloc(psInfo, dwLen); if (psInfo == NULL) { FindCloseUrlCache(hFind); return 0; } dwLen_pre = dwLen; ret = FindNextUrlCacheEntryA(hFind, psInfo, &dwLen); } if (ret == 0) { er = GetLastError(); if (er == ERROR_NO_MORE_ITEMS) { break; } } FileTimeToLocalFileTime(&psInfo->LastAccessTime, &ft); FileTimeToSystemTime(&ft, &st); } FindCloseUrlCache(hFind); return 0; }
skkimeでskk-todayをカスタマイズする方法
WindowsXPの場合。
1. コンパネ『地域と言語のオプション』開く。
2. 言語タブの『詳細』を開く。
3. 『テキストサービスと入力言語』で設定タブの『インストールされているサービス』で『SKKIME ver.1.5』を選択し、『プロパティ(P)』を開く。
4. 『C:\Documents and Settings\(俺様)\Application Data\skk\skki1_5』にぐぐってげっとしたinit.elを入れておく。
5. 『SKKIME1.5(…)のプロパティ』で辞書設定タブを選択し、ユーザー辞書とファイル辞書のパスを「C:\Documents and Settings\(俺様)\Application Data\skk\skki1_5』に設定し直す。
6. キー設定タブのキーマップで『変更』を押して『@』に対して『skk-today』を設定する。
7. コンパネを閉じ、skkimeを起動し、「@」[変換]で現在の日付に変換されることを確認。
以上で、基本的な部分は動作することが確認できた。
で、個人的には、
2010-05-23(日)13:00
というフォーマットにしたい。
(日時だけ、「秒」無し、曜日部分は漢字)
init.elとラージ辞書に定義されたlispコード読んでみたけど、
どうやら、下記を辞書登録すれば良さげ。
(skk-current-date (lambda (date-information format gengo and-time) (skk-default-current-date date-information "%s-%s-%s(%s)%s:%s" 0 nil 0 0 0 0)))
ということで、てきとーに打ち易いキーに対して、上記を辞書登録。
(Ff[SPACE]で現在日時が入力できる様にしておいた)