とにかく最悪の夢を見た。悪意のある人間しかでてこない、終始みじめな思いをしながらどこかに向かうようなやつ。どうしようもない。

鏡を使うと外から瞳孔を見ることはできるけど、内側は解剖しないと普通は見れない。しかしピンホールを使うと直接自分の瞳孔を観察できた。

T=0.05mm のステンレス板に φ0.1mm の穴をあけてみたところ (ピンホール)、これに目をできるだけ近づけてそこそこ強い光源を見ると、自分の瞳孔を見ることができることに気がついた。

簡単に再現する方法

ある程度小さい穴なら以下のような現象が再現できるので、手でピンホールを作って室内蛍光灯とかを見るだけでよかった。

こんな感じにして穴を片目にあてる。単に穴を通して向こう側が見えるだけに思えるが、もう片方の眼にあたる光を手で遮ったりすると瞳孔の動きがわかる。(瞳孔は左右の眼で同時に変化するので、観察している眼と逆の眼の光量を変えてやる)

歪な形の穴を通した場合、光量によって歪な形のまま大きさが変化する。いまいちなんで瞳孔の動きが観察できるのかわからない。

まだら模様の円内と、ぎざぎざした円縁

一定の光源のはずの円内はまだら模様にみえる。

その周辺にはぎざぎざした縁の円があって、急激に暗くなる。

なぜこれが瞳孔だと確認できるのか

片方の眼を遮光した状態で、もう片方の眼にピンホールをあてると大きな円が見える。この状態のまま片目の遮光を外すと急激に円が収縮する。もう一度遮光しなおすとゆるやかに円が拡大する。

よくみると静止していても円は拡大したり収縮したりする。

光源と網膜の間でこのような動きをするのは瞳孔だけなので、これは瞳孔だと思う。

瞳孔内のまだら模様は何なのか

まだら模様は眼球を回転させても移動しない。瞳孔の大きさも関係ない。常に一定の模様が見える。

角膜・水晶体・硝子体のうちのどれかが関係してそうだけどわからない。ちなみに角膜前についた埃も見えるのだけど、まだら模様よりすこし遅れて動くことから、たぶん角膜表面よりは瞳孔に近そう。

なぜ眼の外のピンホールで瞳孔が見えるのか

よくわからない。ピンホールを近づけすぎると、ピンホールが作る像が瞳孔でケラれてしまうからかな? だとすると瞳孔が見えているというよりは虹彩の影が見えているといえる。

  1. トップ
  2. tech
  3. 自分の瞳孔を内側から見る方法を発見してしまった
  1. トップ
  2. photo
  3. 自分の瞳孔を内側から見る方法を発見してしまった

MeArm のパクりっぽいやつ(設計はオープンだからパクりとはいわない気はする)を AliExpress で買ってみました。https://shop.mime.co.uk/

ロボットアームは複数の関節の回転運動を同時に行って特定座標への移動を行います。よって、実座標系からサーボモータの制御回転角への変換処理が必要になります。

FK IK

  • Forward Kinematics
  • Inverse Kinematics

FK は関節の根本から回転させていって先端の座標を求める処理で、IK は先端座標から逆算して関節の回転角を求める処理です。ロボットアームの場合は IK ができれば良いことになります。

MeArm の構造と関節角度

MeArm は根本にサーボモータが設置されておりリンク機構を介して関節を動かしているので、サーボモータの角度と関節の角度の対応を理解する必要があります。

  • X/Y/Z 論理座標系
  • IK 回転角
  • 物理サーボモータ回転角

の3つの要素があり、順に変換していく必要があります。

ベース

ベースは簡単です。90°で正面を向くようにして、0〜180°で可動します

ショルダー (後ろからみて右)

ショルダーも簡単です。90°でアームが真上を向くようにして、30〜150° (後〜前) 程度で可動します。

エルボー (後ろからみて左)

これは若干ややこしいです。MeArm の構造の場合、ショルダーの動きによって肘関節も連動してしまうためです。

エルボー用サーボは90°でアームが後ろに向くようにして、0°でアームが真上、180°でアームが真下に向くようにしてあります。

このセットアップでショルダー用サーボが90°、エルボー用サーボが90°のとき、肘関節も90°になります。ショルダー用サーボの角度が変化すると、肘関節もその角度分だけ動くため、エルボー用サーボに与える角度はショルダー用サーボに与える角度をひいて求める必要があります。

実装

mbed 用の実装Arduino用のインターフェイスを混ぜたような実装を書いて実験しました。

正確に動かすためには正確に制御方法を理解する方法があるので、結局ある程度自分でコードを試行錯誤して理解する必要がある気がします。

キャリブレーション方法が特に難しいのですが、以下のような方法で行いました。ロジックがあっていることが大前提ですが「動かしてみてあってるか確認する」のが難しいので、コードの検証はしっかりやらないとダメです。

  • 電源を切った状態で複数の座標にアームを動かしてみて、そのときの各サーボモータの角度を記録する
  • 実際に座標を計算して、サーボモータへ入力すべき角度との差分や係数を求める

#include <Arduino.h>
#include <Servo.h>

#include "math.h"
#include <Servo.h>

#define DEBUG 0

constexpr float DEG2RADIAN(float deg) {
	return deg * M_PI / 180;
}
constexpr float RADIAN2DEG(float rad) {
	return rad * 180 / M_PI;
}

class MeArm {
private:
	Servo base, shoulder, elbow, claw;
	float baseServoMin, baseServoMax, baseCoefficient, baseOffset;
	float shoulderServoMin, shoulderServoMax, shoulderCoefficient, shoulderOffset;
	float elbowServoMin, elbowServoMax, elbowCoefficient, elbowOffset;
	float clawServoMin, clawServoMax, clawCoefficient, clawOffset;

	float currentX, currentY, currentZ;
public:
	static const int16_t LENGTH_SEGMENT0 = 18; // base to shoulder
	static const int16_t LENGTH_SEGMENT1 = 80; // shoulder to elbow
	static const int16_t LENGTH_SEGMENT2 = 80; // elbow to wrist
	static const int16_t LENGTH_SEGMENT3 = 55; // wrist to grip point
	static const int16_t LENGTH_BASE_HEIGHT = 60;
	static const int16_t MAX_REACH = 220;

	static constexpr float lawOfCosines(float adj1, float adj2, float opp) {
		return acos((adj1*adj1 + adj2*adj2 - opp*opp) / (2*adj1*adj2));  
	}


	MeArm() {
		setServoCoefficient(
			0, 180, 1,  0,
			0, 180, -1, 0,
			0, 180, -1, 0,
			0, 180, 1, 0
		);
	}

	void begin(int pinBase, int pinShoulder, int pinElbow, int pinClaw) {
		base.attach(pinBase);
		shoulder.attach(pinShoulder);
		elbow.attach(pinElbow);
		claw.attach(pinClaw);
		base.write(90);
		shoulder.write(90);
		elbow.write(90);
		claw.write(90);
	}

	void setServoCoefficient(
		float _baseServoMin, float _baseServoMax, float _baseCoefficient, float _baseOffset,
		float _shoulderServoMin, float _shoulderServoMax, float _shoulderCoefficient, float _shoulderOffset,
		float _elbowServoMin, float _elbowServoMax, float _elbowCoefficient, float _elbowOffset,
		float _clawServoMin, float _clawServoMax, float _clawCoefficient, float _clawOffset
	) {
		baseServoMin = _baseServoMin;
		baseServoMax = _baseServoMax;
		baseCoefficient = _baseCoefficient;
		baseOffset = _baseOffset;
		shoulderServoMin = _shoulderServoMin;
		shoulderServoMax = _shoulderServoMax;
		shoulderCoefficient = _shoulderCoefficient;
		shoulderOffset = _shoulderOffset;
		elbowServoMin = _elbowServoMin;
		elbowServoMax = _elbowServoMax;
		elbowCoefficient = _elbowCoefficient;
		elbowOffset = _elbowOffset;
		clawServoMin = _clawServoMin;
		clawServoMax = _clawServoMax;
		clawCoefficient = _clawCoefficient;
		clawOffset = _clawOffset;
	}

	// https://developer.mbed.org/users/eencae/code/MeArm/docs/tip/MeArm_8cpp_source.html
	// by eencae
	bool solveInverseKinematics(
			float x, float y, float z,
			float& thetaBase, float& thetaShoulder, float& thetaElbow
	) const {
		float target = sqrt(x * x + y * y);
		if (target > MAX_REACH) return false;

		float wrist  = target - LENGTH_SEGMENT3;
		float shoulder = LENGTH_SEGMENT0;

		// shoulder to wrist
		float s2w = sqrt( (wrist - shoulder) * (wrist - shoulder) + (z - LENGTH_BASE_HEIGHT) * (z - LENGTH_BASE_HEIGHT) );
		if (s2w == 0.0) {
			return false;
		}

		if (DEBUG) {
			Serial.print("x = "); Serial.print(x); Serial.print(" ");
			Serial.print("y = "); Serial.print(y); Serial.print(" ");
			Serial.print("z = "); Serial.print(z); Serial.print(" ");
			Serial.print("target = "); Serial.print(target); Serial.print(" ");
			Serial.print("wrist = "); Serial.print(wrist); Serial.print(" ");
			Serial.print("shoulder = "); Serial.print(shoulder); Serial.print(" ");
			Serial.print("s2w = "); Serial.print(s2w); Serial.print(" ");
		}

		float thetaSw = acos( (wrist - shoulder) / s2w );
		if (isnan(thetaSw)) {
			Serial.println("invalid thetaSw");
			return false;
		}

		if (z < LENGTH_BASE_HEIGHT) {
			thetaSw *= -1.0;
		}

		float thetaElbow0 = lawOfCosines(LENGTH_SEGMENT1, LENGTH_SEGMENT2, s2w);
		if (isnan(thetaElbow0)) {
			Serial.println("thetaElbow0 is NaN");
			return false;
		}
		float thetaShoulder0 = lawOfCosines(s2w, LENGTH_SEGMENT1, LENGTH_SEGMENT2);
		if (isnan(thetaShoulder0)) {
			Serial.println("thetaShoulder0 is NaN");
			return false;
		}
		thetaShoulder = thetaSw + thetaShoulder0;
		thetaElbow = thetaElbow0;
		thetaBase = atan2(y, x);
		return true;
	}

	bool convertIKAnglesToServoAngles(
			float thetaBase, float thetaShoulder, float thetaElbow,
			float& thetaBaseServo, float& thetaShoulderServo, float& thetaElbowServo
		) {
		thetaElbow = M_PI - thetaShoulder - thetaElbow;
		// thetaElbow = thetaElbow - thetaShoulder - (M_PI/2.0);
		if (DEBUG) {
			Serial.print(" beta="); Serial.print(RADIAN2DEG(thetaElbow)); Serial.print(" ");
		}

		// convert angles to real angles
		thetaBaseServo = RADIAN2DEG(thetaBase) * baseCoefficient + baseOffset;
		if (thetaBaseServo < baseServoMin) thetaBaseServo = baseServoMin;
		if (baseServoMax < thetaBaseServo) thetaBaseServo = baseServoMax;

		thetaShoulderServo = RADIAN2DEG(thetaShoulder) * shoulderCoefficient + shoulderOffset;
		if (thetaShoulderServo < shoulderServoMin) thetaShoulderServo = shoulderServoMin;
		if (shoulderServoMax < thetaShoulderServo) thetaShoulderServo = shoulderServoMax;

		thetaElbowServo = RADIAN2DEG(thetaElbow) * elbowCoefficient + elbowOffset;
		if (thetaElbowServo < elbowServoMin) thetaElbowServo = elbowServoMin;
		if (elbowServoMax < thetaElbowServo) thetaElbowServo = elbowServoMax;
		return true;
	}

	bool goDirectlyTo(float x, float y, float z) {
		float thetaBase, thetaShoulder, thetaElbow;
		bool ok = solveInverseKinematics(x, y, z, thetaBase, thetaShoulder, thetaElbow);
		if (DEBUG) {
			Serial.print("goDirectlyTo "); Serial.print(ok); Serial.print(" ");
			Serial.print(RADIAN2DEG(thetaBase)); Serial.print(" ");
			Serial.print(RADIAN2DEG(thetaShoulder)); Serial.print(" ");
			Serial.print(RADIAN2DEG(thetaElbow)); Serial.print(" ");
		}
		if (ok) {
			convertIKAnglesToServoAngles(
				thetaBase, thetaShoulder, thetaElbow,
				thetaBase, thetaShoulder, thetaElbow
			);
			if (DEBUG) {
				Serial.print("converted: ");
				Serial.print(thetaBase); Serial.print(" ");
				Serial.print(thetaShoulder); Serial.print(" ");
				Serial.print(thetaElbow); Serial.print(" ");
			}
			base.write(thetaBase);
			shoulder.write(thetaShoulder);
			elbow.write(thetaElbow);
			currentX = x;
			currentY = y;
			currentZ = z;
			if (DEBUG) Serial.println("");
			return true;
		} else {
			if (DEBUG) Serial.println("");
			return false;
		}
	}

	bool gotoPoint(float x, float y, float z, float step=5, float wait=20) {
		float x0 = currentX; 
		float y0 = currentY; 
		float z0 = currentZ;
		float distance = sqrt((x0-x)*(x0-x)+(y0-y)*(y0-y)+(z0-z)*(z0-z));
		for (float i = 0; i < distance; i+= step) {
			bool ok = goDirectlyTo(x0 + (x-x0)*i/distance, y0 + (y-y0) * i/distance, z0 + (z-z0) * i/distance);
			if (!ok) return false;
			delay(wait);
		}
		bool ok = goDirectlyTo(x, y, z);
		if (!ok) return false;
		delay(wait);
		return true;
	}

	void openGripper() {
		moveGripper(M_PI/2);
	}

	void closeGripper() {
		moveGripper(0);
	}

	void moveGripper(float thetaClaw) {
		float thetaClawServo = RADIAN2DEG(thetaClaw) * clawCoefficient + clawOffset;
		if (thetaClawServo < clawServoMin) thetaClawServo = clawServoMin;
		if (clawServoMax < thetaClawServo) thetaClawServo = clawServoMax;
		claw.write(thetaClawServo);
	}
};

MeArm arm;

void setup() {
	Serial.begin(9600);
	Serial.println("start");

	if (0) {
		Serial.println("debug");
		Servo middle, left, right, claw ;  // creates 4 "servo objects"
		middle.attach(2);
		left.attach(3);
		right.attach(4);
		claw.attach(5);
		middle.write(90);
		left.write(90);
		right.write(90);
		claw.write(120);
		for (;;);

		uint16_t prev = 0;
		for (;;) {
			uint16_t read = static_cast<uint32_t>(analogRead(0)) * 180 / 512;
			if (read != prev) {
				Serial.print("read=");
				Serial.println(read);
				left.write(read);
				prev = read;
				delay(250);
			}
		}
		for (;;) {
			Serial.println("0");
			left.write(0);
			right.write(0);
			delay(2000);
			Serial.println("90");
			left.write(90);
			right.write(90);
			delay(2000);
			Serial.println("180");
			left.write(180);
			right.write(180);
			delay(2000);
		}
	}

	arm.begin(2, 4, 3, 5);
	arm.setServoCoefficient(
		0, 180, 1,  0,
		20, 170, -1, 170,
		20, 130, -1, 106,
		75, 115, -0.5, 115
	);
	arm.goDirectlyTo(0, 100, 50);
	// arm.openGripper();
}

void loop() {
	Serial.println("loop");
//	arm.gotoPoint(0,100,50);
//
//	arm.goDirectlyTo(0, 110, 60);  delay(1000);// b=90 s=90 e=30
//	arm.goDirectlyTo(100, 110, 60);  delay(1000);// b=90+45 s=100 e=40
//	arm.goDirectlyTo(100, 160, 60);  delay(1000);// b=90+45 s=120 e=45

	arm.openGripper();
	arm.gotoPoint(0,110,60); arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(100,110,60);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(100,160,60);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint( 0,160,60);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(-100,160,60);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(-100,110,60);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint( 0,110,60);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.closeGripper();
	delay(1000);

	arm.openGripper();
	arm.gotoPoint(0,110,20); arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(100,110,20);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(100,160,20);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint( 0,160,20);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(-100,160,20);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(-100,110,20);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint( 0,110,20);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.closeGripper();
	delay(1000);

	arm.openGripper();
	arm.gotoPoint(0,110,110); arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(100,110,110);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(100,160,110);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint( 0,160,110);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(-100,160,110);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint(-100,110,110);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.gotoPoint( 0,110,110);  arm.openGripper(); delay(250);arm.closeGripper();  delay(250);
	arm.closeGripper();
	delay(1000);
}

ref.

  1. トップ
  2. tech
  3. MeArm っぽいロボットアームの制御

 -

5.0 / 5.0

1000円の時計。3年前に買って去年に電池を交換したけど、バンドがかなりヘタって、もうダメという感じだったので交換した。

[ランドン] 【バネ棒+工具付】 時計ベルト 時計バンド 本革 カーフ 18mm 茶 - Randon(ランドン)

Randon(ランドン)

4.0 / 5.0

780円。交換は簡単。今のところは問題ないみたい。これにより 1780円+オレの技術料 の価値の時計となった。

たまにやってくるピンホールカメラ期 | photo - 氾濫原, Eマウント ピンホールカメラの広角化 | photo - 氾濫原 とやって、前回 T=0.05mm やもう少し小さい穴を試せなかったのでさらに追試です。画角は焦点距離で14mm相当です。

T=0.05mm のステンレスシムと、φ0.1 のエンドミルを手に入れたので φ0.175mmのピンホールをあけて撮影してみました。φ0.175mm というのは、ちゃんと加工できれいればって話なんですが、エンドミルが細すぎてどうしてもしなってしまうので、ちょっと小さめになっている気がします。

見ての通りで、厚さを減らしたにも関わらず周辺光量減光はあまり改善しませんでした。よくよく観察してみると、白色光源に向けたとき、画像周辺部分では青カブリするような挙動をするため、単純な周辺光量減光というよりは、光線角度が急すぎるせいでセンサー上のマイクロレンズで捉えられる範囲外になっているような気もします。

無理して14mmにするよりレンズキャップ全面位置 (25mm) ぐらいが丁度よさそうです。

他の作例



  1. トップ
  2. tech
  3. Eマウント ピンホールカメラの広角化 - 2
  1. トップ
  2. photo
  3. Eマウント ピンホールカメラの広角化 - 2

非合理的思考の一覧

  • 仕事のメールボックスを開くとき、何か嫌なニュースがある気がして開けない (実際はそんなことは滅多にない)
  • 不定期で自分はみんなからバカにされていると感じる (バカバカしいと思いつつ、真実であるとも感じる。特に根拠はない)
  • コミュニケーション全般。何かイヤな思いをするのではないか、うまく意志疎通できないのではないかという根拠のない不安。相手のことを考えうる限り最悪な性格である場合を想定してしまう
  • 仕事の手順のうち、文書化されてない (ないしは複数の文書にまたがる) かなり手数の多い手順を実行する際「抜けを起こさずやる」ことができない気がして無性にやりたくなくなる (実際は多少抜けてても取り返しがつくことが多い)

異常なクセ、強迫行為に準ずるもの

  • 弁当がない日に駅までついてから家に確認に帰る (滅多にない)
  • はんだごてのスイッチを切り忘れてないかある程度時間が経過してから再度確認する(実際はオフ時限タイマーを追加で付けているし、消す習慣もあるので消し忘れたことはない)
  • 指のさきにささくれをつくる

去年も今年も年間テーマが健康なのだがうまい成果があがってない。よって何かしら方針が間違えている。

不安を解消しようとすると破滅する

OCD(強迫性障害)の治療の本を読んだところ、不安をなくそうと考えると余計に囚われるので一旦思考停止して別のことに注意を向けろ (不安を受け流せ) ということが書いてあって、これを自分では「不安を直接解消しようとしてはならない」と受けとめた。今までは不安は根本的に解消すべきだと思ってたのでひとつの発見に思えた。

現状の不安の大部分は仕事に起因しており、この原因を直接的に解消するなら仕事をやめればいいのだが、なかなか難しいというか破滅する。実際、やめることで発生する不安のほうが大きいので解決にならない (逆にやめることで発生する不安のほうが少ないならやめられる)。

ということで、受け流す方法が必要になるが、結局これは能動的に不安に対する思考停止をおこなえってことだ。以下の要領で不安を処理するルーチンを定めこれを行うルールとし、できる限り実践することで健康を目指す。

  1. Getting Things Done の要領で不安の一覧を非合理的なことも含めて全て書き出して頭から追い出し思考停止させる
  2. 書き出した一覧を検討しなおし、合理的で検討に値するものだけ検討していく
  3. 合理的に検討に値するもののうち、解決可能なものを順次解決する

ハサミを使う本を買っていて、子どもが「はさみやりたい」と言ったタイミングでときどきやってるけど、なかなかうまくいかない。

といっても直線切りは結構うまくできている。連続して開いて進めて切るということもできるし、持ちかえてやりやすいようにするのもある程度はできる。

しかし本のほうがだんだん難しくなってきて、四角形を切り抜くために途中ではさみの方向転換をするとか、小さい円を切り抜くとかになるんだけど、方向転換がうまくできない。

方向転換をするためには

  • 方向転換する角まで切り口を見ながら正確に切りすすめる
  • ハサミを動かさずに紙を持ちかえて方向転換する

という作業が必要だけど、「角まで切り口を見ながら切る」ことができず、手前でやめてしまうことが多く、そのうえハサミを動かして無理な体勢で方向転換してしまう。


昨日は小さい丸を切り抜くやつだったが、これも難しい。大きい丸なら直線の連続でもそんなにおかしいことにはならないが、曲率が大きいと雑な直線の連続で近似するのが難しく

  • ハサミを固定し一定のスピードを閉じる
  • 紙を連続的に動かして切る方向をコントロールする

を同時にできないとうまくできない。つまり四角のときにばらばらでシーケンシャルでやっていたことを、丸の場合はパラレルにやる必要があって難易度が上がる。

「はさみを持つ手は動かさないで、こっちの手で動かすんだよ」と一緒にやってたら途中で「できない」といって泣き出してしまった。わるいことをした。ちょっとすすめて方向転換を繰替えすんだよと言ったほう (コンカレント版) が直線切りの延長でできるので良かったかもしれない。慣れてればはさみを閉じるのは無意識にできるので紙のコントロールに集中できるけど、子どもはそうじゃない。反省。

くもんのすくすくノート はじめてのきりえ - くもん出版(KUMON PUBLISHING)

くもん出版(KUMON PUBLISHING)

5.0 / 5.0

↑ これは終わった。最後の1ページだけ難しかった気がする。

くもんのすくすくノート やさしいきりえ - くもん出版(KUMON PUBLISHING)

くもん出版(KUMON PUBLISHING)

5.0 / 5.0

↑ 今やってるけど難しい。

くもんのすくすくノート はじめてのかみこうさく - くもん出版(KUMON PUBLISHING)

くもん出版(KUMON PUBLISHING)

5.0 / 5.0

↑ これは終わった。ほぼ直線だから簡単だった気がする。

実際に値をセットしてみて、そのキーの容量を求めることができるコマンドがある。

https://github.com/sripathikrishnan/redis-rdb-tools

  • redis-memory-for-key [key]

このコマンドは DUMP key コマンドを発行した結果を再度 Python でパースしながら消費容量を計算している。割と面倒くさいことをして正確に出そうとしてる。

参考:ダメな方法

DUMP してサイズを見る

DUMP key して出てくる文字列のサイズを単純に見ると、これはファイルに書き出すときの形式になっており、文字列が LZF で圧縮されていたりする。

ついでにいうとキーや期限などのオーバーヘッドの容量が含まれない。

DEBUG OBJECT key の serializedlength

DUMP された結果のサイズを表示しているようで、DUMP と同様に圧縮されたサイズがでるっぽい。

  1. トップ
  2. tech
  3. Redis のメモリ消費量を見積る

redeveloped タグをつけてる写真は過去の写真を演出を変えて再現像したものになってる。

なので、過去のエントリがあるならリンクを貼りたいなと思ったけど、日記のどこに貼ったかは全くわからないので難しい。しかも全て画像は Google Photos にある。

やるとしたら

  1. 一度日記内の画像を全てダウンロードして特徴量検出とインデックス化をする
  2. 日記保存時にも同様のことしつつ、類似画像を検索する

みたいになるのかなあ。「類似」といっても、現像パラメータの違いだけなので、ほとんど全く同じなんだよなあ。

redeveloped タグをつけてる写真は過去の写真を演出… | Thu, Jul 13. 2017 - 氾濫原 をやろうと思って、画像ハッシュ化方法である pHash (64bit) avgHash (64bit) を試してみたけど、ウーンって感じだった。まぁまぁうまくいっている気もするけど、全然似てない写真の距離が近いこともある。

理由として想像すると

  • pHash も avgHash も色情報を捨ててる
  • 64bit (8x8) だと足りない

なので、色を考慮したハッシュ化をしたい。チャンネルごとに pHash にするとかなのかなあ。8x8 のままで RGB 3chとると単純に192bitになる。

もうちょっとコントロール可能にするなら、YCbCr にして、それぞれハミング距離を求めつつ、チャンネルごとに係数をかける (どの程度色情報を考慮するか決定する) とか?

libpuzzle の Perl binding である Image::Libpuzzle を使って関連画像を実装してみた。pHash や avgHash も試してみたけど、どれが良いかというとなんともいえない。

現像パラメータ違いみたいな写真同士のものはちゃんとスコアが高く出ている気がするが、それ以外の場合はいまいち似ているとは言えないような感じで、いまいちよくわからない。基本的に同一画像判定用な気がするので、あまり大きな期待はできなそう。

SQLite スキーマ

CREATE TABLE images (
	`id` INTEGER PRIMARY KEY,
	`uri` TEXT NOT NULL,
	`entry_id` INTEGER NOT NULL,
	`sig` BLOB NOT NULL
);
CREATE UNIQUE INDEX index_images_uri ON images (`uri`, `entry_id`);

CREATE TABLE ngram (
	image_id INTEGER NOT NULL,
	word BLOB NOT NULL,
	PRIMARY KEY (`image_id`, `word`)
) WITHOUT ROWID;
CREATE INDEX index_word_image_id ON ngram (`word`, `image_id`);

こういう感じのスキーマ。複数のエントリに同じURLを貼ると無駄になるけど滅多にないので気にしない。

実装

Service::SimilarImage にだいたいまとめて実装した。SQL 自体は php - Libpuzzle Indexing millions of pictures? - Stack Overflow に書いてあるのとほぼ同じ。

  1. トップ
  2. tech
  3. 関連画像を表示

氷川女體神社 → 氷川神社と行ってみた。

氷川女體神社


東浦和から徒歩でいった。1時間ぐらい歩く。


だいたい見沼代用水西縁緑道を歩いていけば気持ちよく歩ける。元々見沼だったところは開発規制されているので、びっくりするほど田舎の風景が広がっている。

前に訪れたときは本殿改修中だったが今回は大丈夫だった。


祭祀遺跡というのがあってちょっと面白い。RPG に出てくるみたいな離れ小島みたいなものがある。

氷川神社



大宮駅から、先に本殿に御参りしてから参道を逆に歩き、さいたま新都心駅まで。

氷川神社、訪れたことがあったはずと思っていたけど、どうやら別の氷川神社だったみたいで、完全に初見だった。

大宮駅自体も下車するのは初めて (新幹線に乗るために行くことはある)。思ったより駅前が開発されてないんだなという印象。

思ったよりもかなり参拝客が多かった。大宮駅に近いことは近いが、微妙に歩く必要がある (明治神宮と原宿みたいな近さではない)


境内も広くて立派な神社だった。


氷川神社は参道が 2km と全国最長とのこと。ちゃんと参道を一の鳥居から歩くならさいたま新都心駅で降りないといけない。まぁ参道といっても車道が通っていたり住宅が隣接したりしていて、純粋に参道っぽい参道はかなり短い。

この参道入口はブラタモリの大宮編に出てきたので見たことある人は多そう。

ほか

氷川神社 (男体社) と氷川女體神社 (女体社) を直線で結んだ中間あたりに中山神社 (中氷川神社・氷王子社)というのがあって、これも行きたかったが今回は諦めた。

これら3つの神社は見沼というかつてさいたまにあった沼の辺に建っていたらしい。

もともと沼も含めて (今は分かれている) 3つの神社が本来1つの神社として広大な神域を持っていた説があるらしくて夢がある。

  1. トップ
  2. photo
  3. 氷川神社・氷川女體神社

仕事を中心にして考えてるのがそもそもの間違いだと思う。あたりまえだけど業務時間外は「自分の時間」であって、仕事のための勉強をする時間ではない。

前も書いたけど、趣味すなわち自分の時間で自分の意思によって主体的に行うことが一番重要なのであって、仕事はそれに付随しているものでしかない。

「趣味でやった結果がなんとなく仕事に役に立つ」は良いが「仕事のために趣味の時間を使う」のは間違えている。

僕が嫌だなと思うのは、趣味で好きでやってた結果仕事に役に立って金を稼いでいるケースなのにも関わらず、それを「努力」の結果だとしようとするところで、一見マトモそうな人でもこういうことを平然と言ったりするので気が萎える。あくまで、仕事に役に立つことに興味を持ってとりくんでいるのは「たまたま」なので、それでマウンティングかけるようなのはイライラしてしまう。

趣味と仕事が直結しているのは (たとえば自分もそうだけど)、基本的にはチートなので、あんまりおおっぴらに誇れることではなくて、たまたまそういう状態にあるというだけだと思う。まぁチート・ずると言うとトゲがあるけど、業務の視点からいえば残業して評価をあげるのとなんら変わらない。


コミュニケーションには非対称性があって、立場が違うと言っていいこととわるいことが変わる。趣味と仕事が結びついていなくても、ある行為を自分のためにすることかどうかというのが肝心だと思う。

経営者が「個人の時間を使わないと不利益があるぞ」というのは全くダメだし、もともと業務時間内だけでも十分成果が出せるような人はいるので、時間の使いかたに口を出すのは間違えてる。

とはいえ「個人の時間を使わないと不利益があるぞ」というのは正しい。このとき「今の仕事」だけで考えてはダメで、従業員的には1つの会社にバインドされるのを避けないといけない。このときにする「勉強」っていうのは業務すなわち所属会社のためのものではなくて、自分の生存のためのものでリスクヘッジのためにある。それが結果的に今の仕事に役に立つことはあるかもしれないけど、それは「たまたま」である。