cvl-robot's diary

研究ノート メモメモ https://github.com/dotchang/

openFrameworksのshaderでGLSL4.2のatomic counter bufferを使って画素の数をカウントしてみる

GLSLは、OpenGLのシェーダを使うための記述言語です。並列計算はとっても速いので使えると格好良いです。が、GLSLはバージョン毎にすぐ記述方法やデータの受け渡し方が変わったりするので、混乱しがちです。
openFrameworksでは新しいものは面倒みないと割り切っているのか、#version 120もしくは#version 150までの対応が普通のようです。

ここでは、どうしても超高速に画素の数を数えたくなる用事ができたので、最近(#version 420)で追加されたらしいatomic counter bufferと言うのを、動くかどうか分からないまま恐る恐るテストしてみたいと思います。

main.cpp

#include "ofMain.h"
#include "ofApp.h"

//========================================================================
int main( ){
	//ofGLFWWindowSettings settings;
	ofGLWindowSettings settings;
	settings.setGLVersion(4, 5); //version of opengl corresponding to your GLSL version
	settings.width = 1280;
	settings.height = 720;
	ofCreateWindow(settings);

	// this kicks off the running of my app
	// can be OF_WINDOW or OF_FULLSCREEN
	// pass in width and height too:
	ofRunApp(new ofApp());
}

ofApp.h

#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{

	public:
		void setup();
		void update();
		void draw();

		void keyPressed(int key);
		void keyReleased(int key);
		void mouseMoved(int x, int y );
		void mouseDragged(int x, int y, int button);
		void mousePressed(int x, int y, int button);
		void mouseReleased(int x, int y, int button);
		void mouseEntered(int x, int y);
		void mouseExited(int x, int y);
		void windowResized(int w, int h);
		void dragEvent(ofDragInfo dragInfo);
		void gotMessage(ofMessage msg);
		
		ofShader shader;
		ofPixels pix;
		ofTexture tex;
		ofFbo fbo;

                GLuint atomicsBuffer;
};

ofApp.cpp

#include "ofApp.h"
// #include "ofxTimeMeasurements.h"

// from here: http://www.lighthouse3d.com/tutorials/opengl-atomic-counters/

void create_a_buffer_for_atomic_counters(GLuint& acb)
{
	glGenBuffers(1, &acb);
	// bind the buffer and define its initial storage capacity
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acb);
	glBufferData(GL_ATOMIC_COUNTER_BUFFER, sizeof(GLuint) * 3, NULL, GL_DYNAMIC_DRAW);
	// unbind the buffer 
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);
}

void reset_the_atomic_counter_buffers(GLuint& acb) {
	GLuint *userCounters;
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acb);
	// map the buffer, userCounters will point to the buffers data
	userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
		0,
		sizeof(GLuint) * 3,
		GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT | GL_MAP_UNSYNCHRONIZED_BIT
	);
	// set the memory to zeros, resetting the values in the buffer
	memset(userCounters, 0, sizeof(GLuint) * 3);
	// unmap the buffer
	glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);

	glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, acb); // added important
}

void simpler_reset_the_atomic_counter_buffers(GLuint& acb) {
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acb);

	GLuint a[3] = { 0,0,0 };
	glBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint) * 3, a);
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);

	glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, acb); // added important
}

void read_back_the_values_from_the_buffer(GLuint& acb, int& redPixels, int& greenPixels, int& bluePixels)
{
	GLuint *userCounters;
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acb);
	// again we map the buffer to userCounters, but this time for read-only access
	userCounters = (GLuint*)glMapBufferRange(GL_ATOMIC_COUNTER_BUFFER,
		0,
		sizeof(GLuint) * 3,
		GL_MAP_READ_BIT
	);

	// copy the values to other variables because...
	redPixels = (int)userCounters[0];
	greenPixels = (int)userCounters[1];
	bluePixels = (int)userCounters[2];
	// ... as soon as we unmap the buffer
	// the pointer userCounters becomes invalid.
	glUnmapBuffer(GL_ATOMIC_COUNTER_BUFFER);
}

void alternative_read_back_the_values_from_the_buffer(GLuint& acb, int& redPixels, int& greenPixels, int& bluePixels) {
	GLuint userCounters[3];
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, acb);
	glGetBufferSubData(GL_ATOMIC_COUNTER_BUFFER, 0, sizeof(GLuint) * 3, userCounters);
	glBindBuffer(GL_ATOMIC_COUNTER_BUFFER, 0);
	redPixels = userCounters[0];
	greenPixels = userCounters[1];
	bluePixels = userCounters[2];
}


// from here: http://tokyo.supersoftware.co.jp/code/720

#define STRINGIFY(A) #A

//--------------------------------------------------------------
void ofApp::setup() {
	string fragmentShaderProgram = STRINGIFY(
		#version 450 compatibility\n
		#extension GL_ARB_shader_atomic_counters : enable\n
		#extension GL_EXT_gpu_shader4 : enable\n

		layout(binding = 0, offset = 0) uniform atomic_uint atRed;
		layout(binding = 0, offset = 4) uniform atomic_uint atGreen;
		layout(binding = 0, offset = 8) uniform atomic_uint atBlue;

		uniform sampler2DRect tex0;

		in vec4 gl_FragCoord;
		out vec4 colorOut;

		void main() {
			vec2 pos = vec2(gl_FragCoord.x, gl_FragCoord.y); // テクスチャ上の座標を取得する
	
			float r = texture2DRect(tex0, pos).r;
			float g = texture2DRect(tex0, pos).g;
			float b = texture2DRect(tex0, pos).b;
			float a = texture2DRect(tex0, pos).a;

			if (r > 0.5) atomicCounterIncrement(atRed);
			if (g > 0.5) atomicCounterIncrement(atGreen);
			if (b > 0.5) atomicCounterIncrement(atBlue);

			colorOut = vec4(r, g, b, a);
		}
	);
	shader.setupShaderFromSource(GL_FRAGMENT_SHADER, fragmentShaderProgram);
	shader.linkProgram();

	// 画像の読み込み
	ofLoadImage(pix, "Koala.jpg"); // 汎用性のためにofPixelsで読み込み
	tex.allocate(pix, true);       // trueが要るのかどうか不明
	tex.setTextureWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER);
	fbo.allocate((int)tex.getWidth(), (int)tex.getHeight());

	// メインウィンドウの大きさ調整
	ofSetWindowShape((int)tex.getWidth(), (int)tex.getHeight());

        // アトミックカウンタバッファの初期化
	create_a_buffer_for_atomic_counters(atomicsBuffer);
	//reset_the_atomic_counter_buffers(atomicsBuffer);
	simpler_reset_the_atomic_counter_buffers(atomicsBuffer);
}

//--------------------------------------------------------------
void ofApp::update() {

}

//--------------------------------------------------------------
void ofApp::draw() {
	int r, g, b;
	ofBackground(0);

	//TS_START("measurement1");
	fbo.begin();
	shader.begin();
	shader.setUniformTexture("tex0", tex, 0); // テクスチャを渡す
	ofDrawRectangle(-tex.getWidth() / 2, -tex.getHeight() / 2, tex.getWidth(), tex.getHeight()); // 原点を調整してfboにレンダリング
	shader.end();
	fbo.end();

        // アトミックカウンタバッファから結果の受け取りとリセット
	//read_back_the_values_from_the_buffer(atomicsBuffer, r, g, b);
	alternative_read_back_the_values_from_the_buffer(atomicsBuffer, r, g, b);
	simpler_reset_the_atomic_counter_buffers(atomicsBuffer);
	//TS_STOP("measurement1");

	fbo.draw(0, 0);

	cout << "rgb count=" << r << ", " << g << ", " << b << std::endl;
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key) {

}

//--------------------------------------------------------------
void ofApp::keyReleased(int key) {

}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button) {

}

//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y) {

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h) {

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg) {

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo) {

}

atomic counterは、shaderの中でatomic_uint型という特殊な変数を使いますが、これを受け渡す方法はofShaderには(まだ)用意されていません。
[1]の記事を参考に、というかまんま借りてきて関数化して使うことにします。ところがそのままだと上手く配列を渡せていないようです。
[2]の記事を参考に、初期化の関数の最後に

glBindBufferBase(GL_ATOMIC_COUNTER_BUFFER, 0, acb);

の一行を加えてみますと、上手く動きました。

コアラの色をカウントしてみましょう。処理時間は1024×768pixelsで0.8msecでした。オーバーヘッドが大きいことを考えて、100回ループをまわしたところおよそ30msecでした。
f:id:cvl-robot:20170519213323p:plain

rgb count=376021, 307367, 232583

ちゃんと数字があっているのか確認のため100×100サイズに一点だけ赤くした画像
f:id:cvl-robot:20170519211800p:plain
で試してみると、結果は

rgb count=1, 0, 0

でした。良かった、合っていますね。

速度の比較のために、CPUでの画像操作を想定したダミーコードの実行時間を測定したところ、1回の走査で8.9msecで、100回で788.9msecした。20倍ぐらいShaderの方が速そうですね。

	unsigned char *image = new unsigned char[1024 * 768 * 4];
	TS_START("measurement2");
#ifdef _OPENMP
#pragma omp parallel for
#endif
	for (int i = 0; i < 1024; i++) {
		for (int j = 0; j < 768; j++) {
			image[(j * 1024 + i) * 4 + 0] = 255;
			image[(j * 1024 + i) * 4 + 1] = 255;
			image[(j * 1024 + i) * 4 + 2] = 255;
			image[(j * 1024 + i) * 4 + 3] = 255;
		}
	}
	TS_STOP("measurement2");
	delete[] image;

追記:公平を期すためにOpenMPによる簡単な並列化をしたところ、一回の走査で2.5msec、100回で80-100msecでした。
CPUは、DUAL XEON E5-2620の24論理スレッド構成です。

参考にした記事はこちらです。
[1] http://www.lighthouse3d.com/tutorials/opengl-atomic-counters/
[2] https://shikihuiku.wordpress.com/2012/08/15/atomicoperationinpixelshader/
[3] http://wlog.flatlib.jp/item/1635
[4] https://rauwendaal.net/category/glsl/
GLSLをStringで渡す方法はこちらを参考にしました。
[5] http://tokyo.supersoftware.co.jp/code/720

openFrameworksでOpenGLの処理を速くしたいときの確認項目

この方法で、(にわかに信じられませんが)場合によっては10倍ぐらい速くなることがあります。

普段、あまりいじることの無いmain.cppの最初で呼び出している

	ofSetupOpenGL(1024,768,OF_WINDOW);			// <-------- setup the GL context

を、

	ofGLFWWindowSettings settings;
	//ofGLWindowSettings settings;
	settings.setGLVersion(4, 5); //version of opengl corresponding to your GLSL version
	settings.setSize(1280, 720);
	//settings.width = 1280; // old version of OF
	//settings.height = 720;
	ofCreateWindow(settings);

に変更します。GLVersionやWindowサイズの数値は、使用している環境に合わせて調整してください。

ofShaderを使うときだけ変更する必要があるのかと思いきや、ofTextureやofFboを使うときにも効果があります。テスト環境はnVidia Quadro K4000, Windows7です。

# 常識なのかな、、、今まで気がつかなかった。

今日のNew Balance

靴はNewBalanceが良いよ、と時々聞きますが、NewBalanceにも型番が一杯あって何を選べばいいのか分かりません。次の3モデルの中から、適当に選べば良いそうです。

M1400

MRL996

ML574

M1400は最上位モデルです。一番良い靴がほしいときにはこれを選びます。ちなみに(ひどい偏見ですが)年をとって小銭を持ったオタクの人は、総じてニューバランスのM1400NVを履くそうです。
MRL996は街歩き向けの軽量でクッションが薄めのタイプです。
ML574はMRL996に比べるとクッションが厚めでやや重いらしいです、が、ほとんど変わりません。安いです。定番のオーソドックスな色にはタブにClassicと書かれてます。デザイン的に落ち着いているので、これを選ぶと無難です。

ofFboにofTextureから大きな画像の一部を斜めに切り出す方法

大きな画像から、任意の位置と角度で、適当な矩形を切り出す方法をメモしておきます。

注記:普通にやるならこちらです。
cvl-robot.hateblo.jp


f:id:cvl-robot:20170515212006p:plain
画像サイズ1024×768。描画なしで800fps程度。描画ありだと、verticalSyncに引っ張られて60fps。
普通にOpenCVで書くのとどっちが速いだろう???

カーソル操作は自動車(戦車?)のように移動します。
← 左に1度旋回
→ 右に1度旋回
↑ 前進
↓ 後退

マウスは、ドラッグした位置に切り抜き中心が移動します。スクロール上で左に旋回、下で右に旋回します。 

ofApp.cpp

#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
	// 描画の有無とフレームレートの設定
	flag_draw = true;
	ofSetVerticalSync(flag_draw);
	ofSetFrameRate(0);

	// 切り抜き画像サイズ
	crop_size.x = 100.f;
	crop_size.y = 100.f;

	// 画像の読み込み
	ofLoadImage(pix, "Koala.jpg"); // 汎用性のためにPixelsで読み込み
	tex.allocate(pix);
	tex.setTextureWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_BORDER);

	// 切り抜き中心位置の初期化
	pos.x = tex.getWidth() / 2.f;
	pos.y = tex.getHeight() / 2.f;

	// fboの準備
	fbo.allocate((int)crop_size.x, (int)crop_size.y);

	// メインウィンドウの大きさ調整
	ofSetWindowShape((int)ceil(tex.getWidth() + crop_size.x), (int)tex.getHeight());
	flag_updated = true; // 更新フラグ
}

//--------------------------------------------------------------
void ofApp::update(){
	// cout << "FrameRate: " << ofToString(ofGetFrameRate(), 0) << "\t\r";
}

//--------------------------------------------------------------
// Texture tの中心位置p,角度aからFbo fにfのサイズで画像を切り出す関数
void ofApp::rotationalCrop(ofFbo& f, ofTexture& t, ofVec2f p, float a)
{
	float w = f.getWidth();
	float h = f.getHeight();
	// float diag = sqrtf(powf(w, 2.f) + powf(h, 2.f));	// 対角線の長さ

	// 外接する長方形の長辺
	float rad = ofDegToRad(a);
	float i = fabs(w*cos(rad)) + fabs(h*sin(rad));
	float j = fabs(w*sin(rad)) + fabs(h*cos(rad));
	float r = ceil((i>j)?i:j);

	f.begin();
	ofClear(0.f);
	ofPushMatrix();
	// Fboの中心で回転
	ofTranslate(0.5f*w, 0.5f*h);
	ofRotate(a);
	ofTranslate(-0.5f*w, -0.5f*h);
	// 長辺長の正方形を位置pを中心にして描画
	t.drawSubsection(-0.5f*(r-w), -0.5f*(r-h), r, r, p.x - 0.5f*r, p.y - 0.5f*r, r, r);
	ofPopMatrix();
	f.end();
}

//--------------------------------------------------------------
void ofApp::draw(){
	if (tex.isAllocated() && fbo.isAllocated() && flag_updated) {
		rotationalCrop(fbo, tex, pos, angle);
		flag_updated = false;
	}

	if (!flag_draw) return;

	if (tex.isAllocated()) {
		// テクスチャの表示
		tex.draw(0, 0);

		// カーソルの表示
		ofPushStyle();
		ofNoFill();
		ofPushMatrix();
		ofTranslate(pos.x, pos.y);
		ofRotate(-angle);
		ofDrawRectangle(-crop_size.x/2.f, -crop_size.y/2.f, crop_size.x, crop_size.y);
		ofPopMatrix();
		ofPopStyle();
	}

	if (fbo.isAllocated()) {
		// 切り抜き結果の表示
		ofPushMatrix();
		ofTranslate(tex.getWidth(), 0);
		fbo.draw(0, 0);
		ofPopMatrix();
	}

	// フレームレートの表示
	// ofDrawBitmapString(ofToString(ofGetFrameRate(), 0), 20, 20); // bug
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){
	// 回転
	if (key == OF_KEY_LEFT) {
		angle += 1.f;
		flag_updated = true;
	}
	else if (key == OF_KEY_RIGHT) {
		angle -= 1.f;
		flag_updated = true;
	}

	// 前進・後退
	if (key == OF_KEY_UP) {
		float r = 2.f;
		float rad = ofDegToRad(angle + 90.f);
		ofVec2f d(r*cos(rad), -r*sin(rad));
		pos = ofVec2f(ofClamp(pos.x + d.x, 0.f, tex.getWidth()), ofClamp(pos.y + d.y, 0.f, tex.getHeight()));
		flag_updated = true;
	}
	else if (key == OF_KEY_DOWN) {
		float r = 2.f;
		float rad = ofDegToRad(angle + 90.f);
		ofVec2f d(-r*cos(rad), r*sin(rad));
		pos = ofVec2f(ofClamp(pos.x + d.x, 0.f, tex.getWidth()), ofClamp(pos.y + d.y, 0.f, tex.getHeight()));
		flag_updated = true;
	}

	// 切り出したFboをファイルに保存
	// from here: http://www.atnr.net/how-to-save-fbo-screenshot-on-of/
	if (key == 's') {
		ofImage img;
		ofPixels pixels;

		fbo.readToPixels(pixels);
		img.setFromPixels(pixels);
		char fileNameStr[255];
		string date = ofGetTimestampString(); // タイムスタンプをファイル名にする
		sprintf(fileNameStr, "%s.png", date.c_str());
		img.save(fileNameStr, OF_IMAGE_QUALITY_BEST);
	}
}

//--------------------------------------------------------------
void ofApp::keyReleased(int key){
	
}

//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y ){
	
}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){
	pos = ofVec2f(ofClamp(x, 0, tex.getWidth()), ofClamp(y, 0, tex.getHeight()));
	flag_updated = true;
}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y){

}

//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y){

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){ 

}

//--------------------------------------------------------------
void ofApp::mouseScrolled(int x, int y, float scrollX, float scrollY) {
	angle += scrollY;
	flag_updated = true;
}

ofApp.h

#pragma once

#include "ofMain.h"

#include "ofPixels.h"
#include "ofTexture.h"
#include "ofFbo.h"

class ofApp : public ofBaseApp {
public:
	void setup();
	void update();
	void draw();

	void keyPressed(int key);
	void keyReleased(int key);
	void mouseMoved(int x, int y);
	void mouseDragged(int x, int y, int button);
	void mousePressed(int x, int y, int button);
	void mouseReleased(int x, int y, int button);
	void mouseEntered(int x, int y);
	void mouseExited(int x, int y);
	void windowResized(int w, int h);
	void dragEvent(ofDragInfo dragInfo);
	void gotMessage(ofMessage msg);
	
	void mouseScrolled(int x, int y, float scrollX, float scrollY);
	void rotationalCrop(ofFbo& f, ofTexture& t, ofVec2f p, float a);

protected:
	ofPixels pix;
	ofTexture tex;
	ofFbo fbo;

	ofVec2f crop_size;
	ofVec2f pos;
	float angle;

private:
	bool flag_updated;
	bool flag_draw;
};

64ビットコンパイルすると、時々落ちるようになってしまったので、Framerateの表示をコメントアウトしました。
f:id:cvl-robot:20170518171139p:plain

Lidarを使った2D SLAM実装のためのお勉強

Atsushi SakaiさんのブログMyEnigmaで紹介されているmatlabのコードをいくつかc++(openFrameworks環境)に移植してみました。
myenigma.hatenablog.com
とても勉強になり大変に有難いです。移植の目的は、matlabに慣れたかったのと、現実的な実行速度を確認したかった為です。matlabpythonよりc++寄りで移植しやすいですね。

1. ofxGridMapSample.h


f:id:cvl-robot:20170511220657p:plain

2. ofxParticleFilterLocalization.h


f:id:cvl-robot:20170511220819p:plain

3. ofxICPSample.h


f:id:cvl-robot:20170511220928p:plain

main()関数やsetup(), update(), draw()の呼び出しは、openFrameworksのテンプレートから適当に作ってください。
また追加のaddonとしてofMatrix2x2をプロジェクトに加えてください。
GitHub - naokiring/ofMatrix2x2: 2x2 matrix class for openFrameworks

Globally Optimalな点群位置合わせ手法の調査

2010年台ごろに入ってから、3次元点群位置合わせの手法として"Globally Optimal*1"な物の提案が増えてきました。理論はおいて置いて、どのくらい実用になるかをテストするために、実装が公開されているものを中心にリストにまとめておきたいと思います。

1. Go-ICP: A Globally Optimal Solution to 3D ICP Point-Set Registration

Jiaolong Yang, et. al. ICCV2013, PAMI
iitlab.bit.edu.cn
解説

www.slideshare.net

2. Super4PCS: Fast Global Pointcloud Registration via Smart Indexing

Nicolas Mellado, Dror Aiger, Niloy J. Mitra CGF2014
SGP | UCL
github.com
つかってみた、試してみた解説
「 Super4PCS 」という点群結合のライブラリを試してみました - Natural Software
JVR ― 自腹でバーチャルリアリティ ― Super4PCS 速度面で実用的ではなさそう。

3. Fast Global Registration

Qian-Yi Zhou, Jaesik Park, Vladlen Koltun, ECCV2016
Fast Global Registration - Vladlen Koltun
github.com
入力が3次元点群+FPFHなどの3次元特徴量ベクトルなので、3次元特徴量の計算にも時間コストがかかることに留意。
速度的には、まだAlignment.exe(Indexed Imageを使った方法)で位置合わせした方が速そう。
読むべき論文多すぎて、サーベイがおっつかないよ。。。

*1:Globally Optimalの正しい和訳は知りませんが、端的な意訳は"初期位置あわせの要らない"だと思います。

初任給で買うと良いものの研究2017

初任給を貰ったら、まず親に御礼代わりのプレゼントを買うべきです。

が、自分で稼いだお金で自分の欲しいものを買うのも良いことです。
とくに、新生活が始まったばかりの人にとっては、今後の生活の潤いのためになるものを揃えておくのが良いでしょう。
というわけで、今自分が新社会人だったら何が欲しかったかなぁ、と考えてリストにまとめます。(仕事が手につかずに遊んでいる、ともいうが。。。)
物入りな時期でしょうから、予算は最大5万円をめどとします。

1. 大事なデータを保存しておくNAS

デジカメで撮りためた写真や書類など、日々溜まっていく重要なデータはほっておくと、すぐにどこかに行ってしまいなくして後悔します。
早めにデータの置き場所を決めて固めておいておく癖をつけておくとよいです。とくに、転勤などで引っ越しがあったときにも慌てずに済むので、オススメです。
HDDは時間が経つと壊れてしまうので、おすすめはミラーリングで良いのでRAID構成にしておくことです。

いろいろ調べた結果、性能値段比が一番いいのはQNAP TS-228のようです。

本体にはHDDが付属していませんので、別途2台用意します。TOSHIBAの4TBモデルのAMAZON限定モデルは、2年保証が付いているのでお得感があります。(会社が心配ですが・・・)

2. 寝っ転がって本を読むタブレット端末

NASにデータを集める環境が作れたら、そのデータを簡単に閲覧する方法が欲しくなります。
PCがあればそれでも良いのですが、仕事で疲れているときには寝っ転がって写真を見たり本を読んだりしたくなります。
とくに仕事を始めると、眼の疲れを最小にすることの重要さにそのうち気が付きます。
タブレット端末は進化が早いので安いやつで良いのですが、ディスプレイの質の良いものがオススメです。
今選ぶなら、HuaweiのMedia T2 8.0 Proが2万未満で買えて、画面が奇麗でよいです。

3. 腰や肩を大事にするための敷布団マットレス

仕事中は自由に体を動かせず、毎日決まった姿勢をし続けなければならないような状況が起きてしまいます。
とくにPCを相手にする仕事の場合、腰に負担がかかります。
普通の布団では寝ていても腰に力が掛かってしまい、あまり疲れが取れないという状況が起きます。
良さげなマットレスが世の中にいっぱいありますが、お年寄向けのものも多く、ちょっと気が引けます。
その点、西川Airはオシャレです。価格の高いモデルもあり、更にかっこよいですが、エアー01で十分効果があります。
長年使っているうちに多少ヘタって来てしまいますので、標準体重ぐらいの男性はハード、女性はソフトが良いように思います。

シーツは洗濯の時に取り外ししやすい、純正のラップシーツが良いですよ。

4. 首を大事にするための良い枕

首は、腰と同じように負担がかかります。
自分の体に合った枕を選ぶのが一番良いですが、西川Airのマットレスを買ったら、同じシリーズの枕で揃えるのも良いですね。
中のマットを抜くと高さを調整することができます。

カバーは、マットレスの時と同じように、洗濯のために取り外ししやすいものが良いですよ。

5. 快適な睡眠のためのゴアテックス羽毛掛け布団

敷布団は、西川Airがとても良いのですが、合わせる掛け布団に悩みます。
温かくて、軽くて、水分調整ができて、虫がわきにくい、そんな都合が良い羽毛掛け布団が西川にあります。

ちょっと5万円をはみ出てしまいますが、普通の羽毛布団に比べてメンテナンスが楽な分、長い間使えるメリットがあります。
掛け布団カバーは、デザイン重視でお好きなものを。
西川 リビング 掛けふとん カバー 150×210cm ON15 シングル ベージュ 2138-15137

西川 リビング 掛けふとん カバー 150×210cm ON15 シングル ベージュ 2138-15137

毛布は肌触りの良いものを。


結局、ほとんど睡眠用品になってしまいましたが、社会人にはそれだけ睡眠の質が重要だということ。

Emlid Navio2で動かすardupilotにxwindowを入れる

要らないものもあるかもしれないけれど、とりあえず起動するのでOK。

sudo apt-get update
sudo apt-get install lxde
sudo apt-get install lightdm
sudo apt-get install xinit
sudo apt-get install xutils
sudo apt-get install xserver-xorg

startxでLXDEというデスクトップが立ち上がります。

lxdeは古いので、新しいPIXELを使いたい、という場合はこちら。

sudo apt-get install raspberrypi-ui-mods

sudo apt-get install firefox-esr

単にxwindowを入れた状態ではlibEGLの設定がおかしな状態になっていて、openFrameworksやQtが立ち上がらないという問題があります。
[1]のサイトを参考に、libEGLのライブラリへのリンクを張りなおすと立ち上がるようになります。
raspi-configでGL DriverをLegacyに設定するのを忘れずに。

# sudo ln -fs /opt/vc/lib/libEGL.so /usr/lib/arm-linux-gnueabihf/libEGL.so
# sudo ln -fs /opt/vc/lib/libGLESv2.so /usr/lib/arm-linux-gnueabihf/libGLESv2.so
# sudo ldconfig

[1] raspbian - Qt applications don't work due to libEGL - Raspberry Pi Stack Exchange