あんまり見ないでください

プログラミング・技術関連,アイディア,気づいたことなどを低レベルで垂れ流す場所.

WebRTCのgetUserMediaで取得した映像をいじってみた その②【魚眼レンズ風に加工する】

前回の記事では、getUserMediaで取得した映像の画素値を変更して、映像を加工していた。前回は、加工前の画素の位置と加工後の画素の位置は同じだったが、今回は加工前の画素を射影して、魚眼レンズ風の映像を作る。

射影の方法

魚眼レンズ風にするにはどうすればよいかを以下の図を用いて説明する。

f:id:artak:20140812172932p:plain

図の説明

上図は、D だけ離れた平面の被写体を魚眼レンズで映している状況を表したものである。被写体の中心(魚眼レンズから被写体に垂線を引いたときの交点)から、L だけ離れた点から魚眼レンズに入射する光の入射角を θ とする。

射影の式の導出

魚眼レンズの射影方式はいくつかあるが、今回は(wikipedia さんによると)ほとんどの魚眼レンズで採用されているという、等距離射影方式を採用した。等距離射影方式とは、光の入射角と射影先の中心からの距離が比例するように投影する方法である。
つまり、魚眼レンズの投影先の中心からの距離を I 、比例定数を r とすると、

I = rθ

の関係が成り立っている。ここで被写体の中心を原点として、そこから L だけ離れた点の座標を (x, y) とすると、投影先の座標 (X, Y) は、

X = x * I / L = r*x*θ / L
= r*x*arctan(L / D) / L
= r*x*arctan(sqrt(x^2 + y^2) / D) / sqrt(x^2 + y^2)

Y = y * I / L
= r*y*arctan(sqrt(x^2 + y^2) / D) / sqrt(x^2 + y^2)

と表すことができる。

ソースコード

テンプレートは前回と一緒。

gyogan.html

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="gyogan.js"></script>
<title>Gyogan</title>
</head>
<body>
<video id="video"></video>
<canvas id="display_canvas"></canvas>
</body>
</html>

gyogan.js

navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia ;
window.URL = window.URL || window.webkitURL ;

function initialize() {
	navigator.getUserMedia(
		{audio: true, video: true},
		function(stream) {
			var video = document.getElementById('video');
			video.src = URL.createObjectURL(stream);
			video.play();
			renderStart();
		},
		function(error) {
			console.error(error);
		}
	);
}

function renderStart() {
	var video = document.getElementById('video');
	var buffer = document.createElement('canvas');
	var display = document.getElementById('display_canvas');
	var bufferContext = buffer.getContext('2d');
	var displayContext = display.getContext('2d');
	var render = function() {
		requestAnimationFrame(render);
		var width = video.videoWidth;
		var height = video.videoHeight;
		if (width == 0 || height == 0) {return;}
		buffer.width = display.width = width;
		buffer.height = display.height = height;
		bufferContext.drawImage(video, 0, 0);
		
		var src = bufferContext.getImageData(0, 0, width, height);
		var dest = bufferContext.createImageData(buffer.width, buffer.height);
		
		var r = 80;
		var d = 80;
		
		for (var src_row = 0; src_row < height; src_row++) {
			for (var src_col = 0; src_col < width; src_col++) {
				// 原点を画像の中心にもっていく
				var src_x = src_col - width/2;
				var src_y = height/2 - src_row;
				
				// 等距離射影
				var l = Math.sqrt(Math.pow(src_x, 2) + Math.pow(src_y, 2));
				var dest_x = Math.floor(r * src_x * Math.atan(l/d) / l);
				var dest_y = Math.floor(r * src_y * Math.atan(l/d) / l);
				
				// 原点を左上に戻す
				var dest_col = width/2 + dest_x;
				var dest_row = height/2 - dest_y;
				
				var src_address = (src_row*width + src_col)*4;
				var dest_address = (dest_row*width + dest_col)*4;
				
				dest.data[dest_address + 0] = src.data[src_address + 0];
				dest.data[dest_address + 1] = src.data[src_address + 1];
				dest.data[dest_address + 2] = src.data[src_address + 2];
				dest.data[dest_address + 3] = 255;
			}
		}
		
		displayContext.putImageData(dest, 0, 0);
	};
	render();
}

window.addEventListener('load', initialize);

動作確認

上記のソースコードを動かしてみた。

加工前のカメラの映像
f:id:artak:20140812171153p:plain

加工後のカメラの映像
f:id:artak:20140812171234p:plain

魚眼っぽくなっているのが分かる。r と D を変更することで、射影のされ方が変わる。D を小さくすれば、加工後の映像がより丸みを帯びるようになるが、r よりも小さくなると以下のように画像に白い線が入ってしまう。

f:id:artak:20140812172337p:plain