2014、ねこと電球、もう一度、今度はtumblrで始めます

あけてしまいました、2014年。

年越し3日前くらいに立ち上げました、ねこと電球。

3年くらい前に、ブログやってみたいな、と思って始めたこともあったような。

猫と電球

今年は少しづつでも積み上げられる年にしたい。

ProcessingでTerminalオンリーのWindow Systemもどきを作ってみた

ふとWindowSystemっぽいものを作ってみたくなってProcessingでゴリゴリと作ってみた。ソースコードはもう自分でも読みたくない。

image

機能としては、

  • 複数のウィンドウを描画
  • クリック、ドラッグでアクティベート
  • ドラッグで移動
  • 文字の入力、表示
  • 消去ボタン
  • ターゲットのポインティング、追跡

と、ウィンドウなら出来て当たり前のことばかりだけど、ゴリゴリ製作した身としては「機能なんだー!」と言ってしまいたい。

最後の「ターゲットのポインティング・追跡」機能はせっかくProcessingなんだから、ということで追加してみた。Processingで何か作った時にオブジェクトを追跡しながらステータス表示とか、そんな感じの賑やかし程度には使える気がする。

誰かもう作ってるだろう、と言うよりもっと便利なライブラリがあるような気がしてきたけど、それを目の当たりにすると心が折れそうなので今はまだ調べずにおく。

使い方はこんな感じで

WindowSystemと言いつつ、クラスの命名はTerminalだったりするのがまず分かりづらい。

グローバルにターミナルを管理するクラスTerminalManagerを作って、あとは適当な場所でTerminalを作ってTerminalManagerにガシガシ突っ込んでいく。

keyPressedイベントをTerminalManagerに渡してやれば、打ったキーはアクティブなTerminalに表示されるし、Terminalから直接print()メソッドを呼んで文字列を入力してもいいので、適当なオブジェクト内でTerminalを作れば上にも書いた通り簡易インスペクタとしても使える。

// ターミナルを管理するクラス
TerminalManager terminalManager;

// ターゲットとなる PVector
PVector target;

void setup() {
  
  size(480, 320);
  
  terminalManager  = new TerminalManager();

  target = new PVector(width/2, height/2);  
  
  // Terminal1
  Terminal terminal = new Terminal("Terminal1");
  terminal.setWindow(10, 10, 200, 200); // 場所とサイズ
  terminalManager.add(terminal); // マネージャに追加
  
  // Terminal2
  Terminal terminal2 = new Terminal("Terminal2");
  terminal2.setWindow(100, 100, 300, 300);
  terminal2.setLineNum(5); // heightを行数で上書き
  terminalManager.add(terminal2);
  
  // Terminal3: ターゲットを追跡する
  Terminal terminal3 = new Terminal("Terminal3");
  terminal3.setWindow(200, 50, 300, 200);
  terminal3.setTarget(target); // ターゲットをセット
  terminal3.setPositionRelatively(true); // ターゲットを追跡
  terminalManager.add(terminal3);
  
  // Terminal4: ターゲットをポイント
  Terminal terminal4 = new Terminal("Terminal4");
  terminal4.setWindow(200, 200, 200, 60);
  terminal4.setLineNum(10);
  terminal4.setTarget(target);
  terminalManager.add(terminal4);
}

void draw() {
  background(128);
  
  // ターゲットの移動
  target.x = width/2 + 100 * cos(0.01 * frameCount);
  target.y = height/2 + 100 * sin(0.01 * frameCount);

  // ターゲットの描画
  ellipse(target, 10);
  text("target", target.x + 5, target.y);
  
  // ウィンドウの更新・描画
  terminalManager.update();
  terminalManager.draw();
}

// 各種イベントを渡してやる
void keyPressed() { terminalManager.keyPressed(); }
void keyReleased() {}
void mouseMoved() { terminalManager.mouseMoved(); }
void mousePressed() { terminalManager.mousePressed(); }
void mouseReleased() { terminalManager.mouseReleased(); }
void mouseDragged() { terminalManager.mouseDragged(); }

そもそもGUIプログラミングしたこと無いのに一から作り始めるのはどうなんだろう。作っている最中は「だいたい思ったとおりの実装でいけるな」とか意気揚々としていたけど、使いやすさはどうせ既存の一般的なライブラリの方がいいんだろうな。

ソースコード:https://github.com/LightbulbCat/PTerm

  • Mac OS X 10.9 Marverics
  • Processing 2.0.3

Pythonはじめてひと月、標準ライブラリの充実ってやっぱりいいなとか

Python歴は今の時点でだいたい1ヶ月くらい。

本当は1年くらい前に、PythonでCSV読み込んでタイムスタンプのフォーマットを変えて、みたいな簡単なスクリプトを組んでたんだけど、それはもうそれっきりで、文法とかももうすっかり忘れて、1ヶ月前くらいから、また一からリファレンス読みながらのスクリプティングをしてきた。

これまでにやってきた言語といえばC、Java,C++。スクリプト言語だとweb系でPHP、Perlくらいなので、Pythonはコマンドラインから動かすことを考えたら始めてのスクリプト言語。

あんまり大きな声じゃ言えないけど、try-catchとかのエラー処理とか、よくわからないレベルの超ライトユーザなので、突っ込んだ話はできないけど、ここまでPythonに触れてきて「いいな、Python」と思ったことをちらほらと書いていく。だいたいがC++に比べて、って感じだけど…

見返してみると、「いいなPython」って言うより「いいなスクリプト言語」って感じ

コード量が少ない

同じ働きをするコードを書くと、C++と比べてPythonは30%くらいの行数になるんじゃないか、ってくらいのイメージ。

コード量が少ないと読むのにかかる時間がだいぶ違ってくる。プログラムを読む時って「この変数がここで使われてて…」とコード内を何度も往復して読んでるから、読むのにかかる時間はきっとコード量の2乗くらいで効いてくる。コード量が2倍なら理解に掛かる時間は4倍。根拠はないけど。

これなら標準ライブラリの実装とかも眺められるんじゃないかと思ってスレッド周りのライブラリをチラ見してみたけど案の定のカオスっぷり&英語コメント読めないで諦めた。

僕のスキルなんてまあ、こんなもの。

標準ライブラリの充実さ

CSV読むのも標準ライブラリで一発だし、スレッドも簡単に使えるし。Cとかだとboostである程度標準化されてる気がするけど、でもこういうプログラムで一般的に使われる要素がPythonだと「標準」として組み込まれていて、この辺が強いんじゃないかなと。

C++11だとboostがsdtに組み込まれてsdt::threadとかで使えるんだっけ

「標準」の安心感って初心者にはとても心強い。リファレンスも多そうだし、なによりPythonの仕様とか実装を決めてるコミュニティの人たちが煮詰めて煮詰めて作ったんでしょ、みたいな根拠は薄いけどとりあえずいい感じのパフォーマンスを発揮してくれるんだろう、みたいな安心感がある。

もっと書こうと思ったんだけど

もっと書こうと思ったけど書きづらい。Python、すごく使いやすいけど他の言語と比べて具体的にどうこう、とかは自分のレベルじゃあんまり具体的に思いつかない。

どういう要素が働いているか解らないけれど、なぜか作りたいものが作りたいときに「ぱっと」作れるようになってるんだな、ってことぐらいしか、今はまだ書けない。

よくわからないけど、Python、よくできてる気がする。

PythonでOpenCVのwaitKey()はメインスレッドじゃないと動かない?

マルチスレッドとOpenCVの勉強を同時進行でやろうと、PythonでOpenCVをマルチスレッドで動かそうとした時にちょっとハマったのでメモ。

初めのコード

やりたかったのは、描画するウィンドウ関係の処理は全部それ用のスレッドに投げてしまって、メインスレッドはスレッドを起動するだけ、みたいなこと。どうせスレッドたくさん立てる事態になったらこういうことしたくなるんだろうな、と。

import threading
import cv2

# 描画スレッド
class ImageProcessing(threading.Thread):

	def __init__(self):

		threading.Thread.__init__(self)

		self.windowname = "ImageProcessing"
		cv2.namedWindow(self.windowname)
		self.capture = cv2.VideoCapture(-1)

	def run(self):

		while True:
			ret, frame = self.capture.read()
			cv2.imshow(self.windowname, frame)

			if cv2.waitKey(10) == 27: # ESC
				break

# メインスレッド
if __name__ == "__main__":

	ip = ImageProcessing()
	ip.start()

	ip.join()

で、動きませんでした。初めの画像が映るだけで画像が更新されない静止画状態。ついでにwaitKey() も反応しない。ウィンドウが完全にフリーズしたような状態。

そして当のスレッドは走りっぱなしなのでターミナルを強制終了したり立ち上げたりが本当に面倒だった。

何が悪いのか

取得した frame を適当にターミナルに出力してみると値自体は更新されているようなので、問題はウィンドウの更新らへんにありそう。でもその辺りのことはあまり詳しくない。 試行錯誤の途中、スレッドを走らせる際の start() を run() に書き換えると画像も更新されてwaitKey()も反応するようになった。

if __name__ == "__main__":
    ip = ImageProcessing()
    ip.run()
    ip.join()

ただこれだと、スレッディングではなくメイン文の中で描画ループを回しているだけ。

で、検索してみた結果、C++だけど似たような報告があったので、どうやらwaitKey()はメインスレッドじゃないと動かないってことでいいのかな。不便だけど、もしそうなら他の人はどうしてるんだろう。

imshow() and waitkey() from within a thread –> not updating the display

結局処理をメインスレッドに

というわけで描画ウィンドウの生成と waitKey() はメインスレッドに移した。ついでに終了処理を渡したりするのが面倒なのでデーモンスレッドに

import threading
import cv2

class ImageProcessing(threading.Thread):

	# 描画ウィンドウ名を受け取るように
	def __init__(self, windowname):

		threading.Thread.__init__(self)

		# デーモンスレッドに
		threading.Thread.daemon = True

		self.windowname = windowname
		self.capture = cv2.VideoCapture(-1)

	def run(self):

		while True:
			ret, frame = self.capture.read()
			cv2.imshow(self.windowname, frame)

if __name__ == "__main__":

	# 描画ウィンドウ生成をメインスレッド内に
	windowname = "ImageProcessing"
	cv2.namedWindow(windowname)

	ip = ImageProcessing(windowname)
	ip.start()

	# waitKey() をメインスレッド内に
	while True:
		if cv2.waitKey(10) == 27: # ESC
			break

これで画像も更新されて waitKey() も動作するようになった。imshow() はメインスレッドじゃなくても動きそう。 でもこれだと、結局何がしたかったんだか…ってことに。メイン文が太るのはあんまり好きじゃない。何かいい方法はないものか。

  • Mac OS X 10.9
  • Python 2.7.4
  • OpenCV 2.4.4