2016.03.28
- 人×技術
WebGL 動体検知 tiger5
TIGER-TANIOKAです。
WebGLで動体検知やります。
今回はWEBカメラを使用します。
WebRTC(WebRealTimeCommunication)というものを使用します。
WebRTCとは、WebRealTimeCommunicationの略です。
WebRTCを使用することにより、カメラ、マイクから動画、音声を取り込むことが可能になります。
ではやってきます〜
1.HTML側にVIDEOタグとCANVASタグ配置します。
video(style='float:left;display:none;' , id='webcam' , width='900' , height='500' , autoplay)
canvas(style='float:left;',id='cvs',width='900' , height='500')
2.Navigator.getUserMediaを使用する。
getUserMedia(API)とはPCに接続されたウェブカムやマイクなどの情報を返してくれるAPIです。
navigator.getUserMedia = (
navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia
);
if(navigator.getUserMedia){
navigator.getUserMedia({
'video' : true, //動画を利用
'audio' : false //今回音はなし
},
successHandler, //LocalMediaStream オブジェクトが取得できた場合、呼び出されるコールバック関数。
errorHandler //呼び出しが失敗した際に実行されるコールバック関数
);
}else{
window.alert('error'); //getUserMediaがサポートされていない場合
}
コールバック///////////////
function successHandler(_stream){//LocalMediaStreamを取得
var _url = window.URL || window.webkitURL;//_url
video.src = _url.createObjectURL(_stream);
}
function errorHandler(){
win.alert('error')
}
これでvideoタグにwebcamでとってる動画が映りました。
3.シェーダーで四角形を描画
cpu(js側)で四角形の座標、uniform変数等用意してシェーダー側で描画するプログラムかきます。
※多少はしょります。
js側
var CreateProgram = function(_vs , _fs , _gl){
var _prg = _gl.createProgram();
// プログラムオブジェクトにシェーダを追加
_gl.attachShader(_prg , _vs);
_gl.attachShader(_prg , _fs);
// プログラムオブジェクトをリンク
_gl.linkProgram(_prg);
// シェーダーのリンクが上手くいったかチェック
if(_gl.getProgramParameter(_prg, _gl.LINK_STATUS)){
_gl.useProgram(_prg);
return _prg;
}else{
alert('programError' + _gl.getProgramInfoLog(_prg));
}
}
var SetAttribute = function(_vbo , _attL , _attS , _gl){
_gl.bindBuffer(_gl.ARRAY_BUFFER , _vbo);
// attribute属性を有効にする
_gl.enableVertexAttribArray(_attL);
// attribute属性を登録
_gl.vertexAttribPointer(_attL , _attS , _gl.FLOAT , false, 0 , 0);
}
var CreateVbo = function(_data , _gl){
var _vbo = _gl.createBuffer();
_gl.bindBuffer(_gl.ARRAY_BUFFER , _vbo);
_gl.bufferData(_gl.ARRAY_BUFFER , new Float32Array(_data) , _gl.DYNAMIC_DRAW);
_gl.bindBuffer(_gl.ARRAY_BUFFER , null);
return _vbo;
}
var vertex = [
-1.0, 1.0, 0.0,
1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, 1.0, 0.0,
1.0, -1.0, 0.0,
-1.0, -1.0, 0.0
];
function draw(){
var tmpMtx = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(uniLocations['matrix'] , false , tmpMtx);
gl.drawArrays(gl.TRIANGLES, 0 , 6);
gl.flush();
}
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
cvs = doc.getElementById('cvs');
gl = cvs.getContext('webgl') || cvs.getContext('experimental-webgl');
video = doc.getElementById('webcam');
vtxShader = CreateShader('vs' , gl);
frgShader = CreateShader('fs' , gl);
prg = CreateProgram(vtxShader , frgShader, gl);
uniLocations['matrix'] = gl.getUniformLocation(prg , 'mvpMatrix');
attLocations['position'] = gl.getAttribLocation(prg , 'position');
strids['position'] = 3;
vbos['position'] = CreateVbo(vertex , gl);
SetAttribute(vbos['position'] , attLocations['position'] , strids['position'] , gl);
setInerval(draw , 40 / 1000);
{% endhighlight %}
####シェーダー側
{% highlight ruby %}
script(id='vs' , type='x-shader/x-vertex').
attribute vec3 position;
uniform mat4 mvpMatrix;
void main(){
gl_Position = mvpMatrix * vec4(position.x ,position.y, 0.0, 1.0);
}
script(id='fs' , type='x-shader/x-fragment').
precision mediump float;
void main(){
gl_FragColor = vec4(0.0 , 0.0 , 0.0 , 1.0);
}
シェーダー側
script(id='vs' , type='x-shader/x-vertex').
attribute vec3 position;
uniform mat4 mvpMatrix;
void main(){
gl_Position = mvpMatrix * vec4(position.x ,position.y, 0.0, 1.0);
}
script(id='fs' , type='x-shader/x-fragment').
precision mediump float;
void main(){
gl_FragColor = vec4(0.0 , 0.0 , 0.0 , 1.0);
}
黒い四角形が描画ができたら
次はこのエリアに先ほどのウェブカメラの映像を描画します。
4.シェーダー側に動画をテクスチャとして渡す。
ウェブカメラでとってる動画をテクスチャ化
シェーダーに渡します。
js側
function canPlay(){
createTexture('video' , video);
setInterval(draw , FRAME_RATE / 1000);
}
function successHandler(_stream){//LocalMediaStreamを取得
var _url = window.URL || window.webkitURL;//_url
video.addEventListener('canplay' , canPlay);
video.src = _url.createObjectURL(_stream);
}
function createTexture(_name , _texSrc){
var _tex = gl.createTexture();//テクスチャ生成
gl.bindTexture(gl.TEXTURE_2D , _tex);//バインド
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);//反転
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, _texSrc);//テクスチャに画像データを紐付け
//テクスチャが縮小される際のパラメータ
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
//テクスチャが縮小される際のパラメータ
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null); //バインド解除
textures[_name] = _tex;
}
function draw(){
var tmpMtx = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(uniLocations['matrix'] , false , tmpMtx);
gl.uniform2f(uniLocations['screen'] , win.innerWidth , win.innerHeight);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, textures['video']);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, video);
gl.uniform1i(uniLocations['video'] , 0);
gl.drawArrays(gl.TRIANGLES, 0 , 6);
gl.flush();
}
〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜
cvs = doc.getElementById('cvs');
gl = cvs.getContext('webgl') || cvs.getContext('experimental-webgl');
video = doc.getElementById('webcam');
vtxShader = CreateShader('vs' , gl);
frgShader = CreateShader('fs' , gl);
prg = CreateProgram(vtxShader , frgShader, gl);
uniLocations['matrix'] = gl.getUniformLocation(prg , 'mvpMatrix');
attLocations['position'] = gl.getAttribLocation(prg , 'position');
strids['position'] = 3;
vbos['position'] = CreateVbo(vertex , gl);
SetAttribute(vbos['position'] , attLocations['position'] , strids['position'] , gl);
uniLocations['video'] = gl.getUniformLocation(prg , 'videoTex');
uniLocations['screen'] = gl.getUniformLocation(prg , 'screen');
シェーダー側
script(id='vs' , type='x-shader/x-vertex').
attribute vec3 position;
uniform mat4 mvpMatrix;
void main(){
gl_Position = mvpMatrix * vec4(position.x ,position.y, 0.0, 1.0);
}
script(id='fs' , type='x-shader/x-fragment').
precision mediump float;
uniform sampler2D videoTex;
uniform vec2 screen;
vec4 videoTexC;
void main(){
float texColorX = gl_FragCoord.x / screen.x;
float texColorY = gl_FragCoord.y / screen.y;
videoTexC = texture2D(videoTex, vec2(texColorX , texColorY));
gl_FragColor = videoTexC;
}
5.シェーダー側でエッジを検出
モノクロ画像にして前フレームとの差分を検出
js側
jsのほうで3フレーム前までのテクスチャをつくり、
シェーダーにわたします。
function canPlay(){
createTexture('video' , video);
createTexture('frame0');
createTexture('frame1');
createTexture('frame2');
setInterval(draw , 40 / 1000);
}
function createTexture(_name , _texSrc){
var _tex = gl.createTexture();//テクスチャ生成
gl.bindTexture(gl.TEXTURE_2D , _tex);//バインド
gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);//反転
if(!_texSrc){
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, showWidth, showHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
}else{
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, _texSrc);
}
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, _texSrc);//テクスチャに画像データを紐付け
//テクスチャが縮小される際のパラメータ
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
//テクスチャが縮小される際のパラメータ
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null); //バインド解除
textures[_name] = _tex;
}
function draw(){
var prevFrameCnt = count - 2;
if(prevFrameCnt < 0) prevFrameCnt = prevFrameCnt + 3;
var tmpMtx = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
gl.clearColor(1.0, 1.0, 1.0, 1.0);
gl.clearDepth(1.0);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.uniformMatrix4fv(uniLocations['matrix'] , false , tmpMtx);
gl.uniform2f(uniLocations['screen'] , width , height);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, textures['video']);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, video);
gl.uniform1i(uniLocations['video'] , 0);
if(textures['frame' + prevFrameCnt]){
gl.activeTexture(gl.TEXTURE1);
gl.bindTexture(gl.TEXTURE_2D, textures['frame' + prevFrameCnt]);
gl.uniform1i(uniLocations['prevFrameVideo'] , 1);
}
gl.drawArrays(gl.TRIANGLES, 0 , 6);
gl.flush();
count++;
if(count == 3) count = 0;
//前フレームのテクスチャ格納
gl.bindTexture(gl.TEXTURE_2D , textures['frame' + count]);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA,gl.UNSIGNED_BYTE, video);
}
シェーダー側
輪郭を検出します。
script(id='fs' , type='x-shader/x-fragment').
precision mediump float;
uniform sampler2D videoTex;
uniform vec2 screen;
uniform sampler2D prevFrameVideo;
uniform sampler2D afterImage;
vec4 videoTexC;
vec4 prevVideoTexC;
vec4 afterImageTexC;
//差分抽出
vec4 getEdge(vec4 _color1 ,vec4 _color2){
float _glay = (_color1[0] - _color2[0]);
vec4 _diff = vec4(_glay , _glay , _glay , 1.0);
return _diff;
}
//グレースケールに変換
vec4 toGlay(vec4 _color){
float _glay = (_color[0] * 0.3) + (_color[1] * 0.59) + (_color[2] * 0.11);
return vec4(vec3(_glay), 1.0);
}
void main(){
//座標正規化
float texColorX = gl_FragCoord.x / screen.x;
float texColorY = gl_FragCoord.y / screen.y;
videoTexC = toGlay(texture2D(videoTex, vec2(texColorX , texColorY)));
prevVideoTexC = toGlay(texture2D(prevFrameVideo, vec2(texColorX , texColorY)));
vec4 edgeC = getEdge(videoTexC , prevVideoTexC) * 4.0;
gl_FragColor = edgeC;
}
うまく動体検知できました。
これに色をつけちょっとゆらゆらエフェクトをつけてみると
まぁ素敵。
フラグメントシェーダーおもしろいですね。
今回はこのあたりで。