WebRTCのgetUserMediaで取得した映像をいじってみた その②【魚眼レンズ風に加工する】
前回の記事では、getUserMediaで取得した映像の画素値を変更して、映像を加工していた。前回は、加工前の画素の位置と加工後の画素の位置は同じだったが、今回は加工前の画素を射影して、魚眼レンズ風の映像を作る。
射影の方法
魚眼レンズ風にするにはどうすればよいかを以下の図を用いて説明する。
図の説明
上図は、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);