前言

随着视频监控联网系统的不断普及和发展, 网络摄像机更多的应用于监控系统中,尤其是高清时代的来临,更加快了网络摄像机的发展和应用。

在监控摄像机数量的不断庞大的同时,在监控系统中面临着严峻的现状问题:海量视频分散、孤立、视角不完整、位置不明确等问题,始终围绕着使用者。因此,如何更直观、更明确的管理摄像机和掌控视频动态,已成为提升视频应用价值的重要话题。所以当前项目正是从解决此现状问题的角度,应运而生。围绕如何提高、管理和有效利用前端设备采集的海量信息为公共安全服务,特别是在技术融合大趋势下,如何结合当前先进的视频融合,虚实融合、三维动态等技术,实现三维场景实时动态可视化监控,更有效的识别、分析、挖掘海量数据的有效信息服务公共应用,已成为视频监控平台可视化发展的趋势和方向。目前,在监控行业中,海康、大华等做监控行业领导者可基于这样的方式规划公共场所园区等的摄像头规划安放布局,可以通过海康、大华等摄像头品牌的摄像头参数,调整系统中摄像头模型的可视范围,监控方向等,更方便的让人们直观的了解摄像头的监控区域,监控角度等。

以下是项目地址:基于 HTML5 的 WebGL 自定义 3D 摄像头监控模型

效果预览

整体场景-摄像头效果图

局部场景-摄像头效果图

代码生成
摄像头模型及场景

项目中使用的摄像头模型是通过 3dMax 建模生成的,该建模工具可以导出 obj 与 mtl 文件,在 HT 中可以通过解析 obj 与 mtl 文件来生成 3d 场景中的摄像头模型。

项目中场景通过 HT 的 3d 编辑器进行搭建,场景中的模型有些是通过 HT 建模,有些通过 3dMax 建模,之后导入 HT 中,场景中的地面白色的灯光,是通过 HT 的 3d 编辑器进行地面贴图呈现出来的效果。

锥体建模

3D 模型是由最基础的三角形面拼接合成,例如 1 个矩形可以由 2 个三角形构成,1 个立方体由 6 个面即 12 个三角形构成, 以此类推更复杂的模型可以由许多的小三角形组合合成。因此 3D 模型定义即为对构造模型的所有三角形的描述, 而每个三角形由三个顶点 vertex 构成, 每个顶点 vertex 由 x, y, z 三维空间坐标决定,HT 采用右手螺旋定则来确定三个顶点构造三角形面的正面。

HT 中通过 ht.Default.setShape3dModel(name, model) 函数,可注册自定义 3D 模型,摄像头前方生成的锥体便是通过该方法生成。可以将该锥体看成由 5 个顶点,6 个三角形组成,具体图如下:

ht.Default.setShape3dModel(name, model)

1. name 为模型名称,如果名称与预定义的一样,则会替换预定义的模型
2. model 为JSON类型对象,其中 vs 表示顶点坐标数组,is 表示索引数组,uv 表示贴图坐标数组,如果想要单独定义某个面,可以通过 bottom_vs,bottom_is,bottom_uv,top_vs,top_is, top_uv 等来定义,之后便可以通过 shape3d.top.*, shape3d.bottom.*  等单独控制某个面

以下是我定义模型的代码:

// camera 是当前的摄像头图元
// fovy 为摄像头的张角的一半的 tan 值
var setRangeModel = function(camera, fovy) {
var fovyVal = 0.5 * fovy;
var pointArr = [0, 0, 0, -fovyVal, fovyVal, 0.5, fovyVal, fovyVal, 0.5, fovyVal, -fovyVal, 0.5, -fovyVal, -fovyVal, 0.5];
ht.Default.setShape3dModel(camera.getTag(), [{
vs: pointArr,
is: [2, 1, 0, 4, 1, 0, 4, 3, 0, 3, 2, 0],
from_vs: pointArr.slice(3, 15),
from_is: [3, 1, 0, 3, 2, 1],
from_uv: [0, 0, 1, 0, 1, 1, 0, 1]
}]);
}

我将当前摄像头的 tag 标签值作为模型的名称,tag 标签在 HT 中用于唯一标识一个图元,用户可以自定义 tag 的值。通过 pointArr 记录当前五面体的五个顶点坐标信息,代码中通过 from_vs, from_is, from_uv 单独构建五面体底面,底面用于显示当前摄像头呈现的图像。

代码中设置了锥体 style 对象的 wf.geometry 属性,通过该属性可以为锥体添加模型的线框,增强模型的立体效果,并且通过 wf.color,wf.width 等参数调节线框的颜色,粗细等。

相关模型 style 属性的设置代码如下:

 1 rangeNode.s({
2 'shape3d': cameraName,
3 // 摄像头模型名称
4 'shape3d.color': 'rgba(52, 148, 252, 0.3)',
5 // 锥体模型颜色
6 'shape3d.reverse.flip': true,
7 // 锥体模型的反面是否显示正面的内容
8 'shape3d.light': false,
9 // 锥体模型是否受光线影响
10 'shape3d.transparent': true,
11 // 锥体模型是否透明
12 '3d.movable': false,
13 // 锥体模型是否可移动
14 'wf.geometry': true // 是否显示锥体模型线框
15 });

摄像头图像生成原理

透视投影

透视投影是为了获得接近真实三维物体的视觉效果而在二维的纸或者画布平面上绘图或者渲染的一种方法,它也称为透视图。 透视使得远的对象变小,近的对象变大,平行线会出现先交等更更接近人眼观察的视觉效果。

如上图所示,透视投影最终显示到屏幕上的内容只有截头锥体( View Frustum )部分的内容, 因此 Graph3dView 提供了 eye, center, up, far,near,fovy 和 aspect 参数来控制截头锥体的具体范围。具体的透视投影可以参考 HT for Web 的 3D 手册。

根据上图的描述,在本项目中可以在摄像头初始化之后,缓存当前 3d 场景 eyes 眼睛的位置,以及 center 中心的位置,之后将 3d 场景 eyes 眼睛和 center 中心设置成摄像头中心点的位置,然后在这个时刻获取当前 3d 场景的截图,该截图即为当前摄像头的监控图像,之后再将 3d 场景的 center 与 eyes 设置成开始时缓存的 eyes 与 center 位置,通过该方法即可实现 3d 场景中任意位置的快照,从而实现摄像头监控图像实时生成。

相关伪代码如下:

 1 function getFrontImg(camera, rangeNode) {
2 var oldEye = g3d.getEye();
3 var oldCenter = g3d.getCenter();
4 var oldFovy = g3d.getFovy();
5 g3d.setEye(摄像头位置);
6 g3d.setCenter(摄像头朝向);
7 g3d.setFovy(摄像头张角);
8 g3d.setAspect(摄像头宽高比);
9 g3d.validateImp();
10 g3d.toDataURL();
11 g3d.setEye(oldEye);;
12 g3d.setCenter(oldCenter);
13 g3d.setFovy(oldFovy);
14 g3d.setAspect(undefined);
15 g3d.validateImp();
16 }

经过测试之后,通过该方法进行图像的获取会导致页面有所卡顿,因为是获取当前 3d 场景的整体截图,由于当前3d场景是比较大的,所以 toDataURL 获取图像信息是非常慢的,因此我采取了离屏的方式来获取图像,具体方式如下:
   1. 创建一个新的 3d 场景,将当前场景的宽度与高度都设置为 200px 的大小,并且当前 3d 场景的内容与主屏的场景是一样的,HT中通过 new ht.graph3d.Graph3dView(dataModel) 来新建场景,其中的 dataModel 为当前场景的所有图元,所以主屏与离屏的 3d 场景都共用同一个 dataModel,保证了场景的一致。
   2. 将新创建的场景位置设置成屏幕看不到的地方,并且添加进 dom 中。
   3. 将之前对主屏获取图像的操作变成对离屏获取图像的操作,此时离屏图像的大小相对之前主屏获取图像的大小小很多,并且离屏获取不需要保存原来的眼睛 eyes 的位置以及 center 中心的位置,因为我们没有改变主屏的 eyes 与 center 的位置, 所以也减少的切换带来的开销,大大提高了摄像头获取图像的速度。

以下是该方法实现的代码:

 1 function getFrontImg(camera, rangeNode) {
2 // 截取当前图像时将该摄像头所属的五面体隐藏
3 rangeNode.s('shape3d.from.visible', false);
4 rangeNode.s('shape3d.visible', false);
5 rangeNode.s('wf.geometry', false);
6 var cameraP3 = camera.p3();
7 var cameraR3 = camera.r3();
8 var cameraS3 = camera.s3();
9 var updateScreen = function() {
10 demoUtil.Canvas2dRender(camera, outScreenG3d.getCanvas());
11 rangeNode.s({
12 'shape3d.from.image': camera.a('canvas')
13 });
14 rangeNode.s('shape3d.from.visible', true);
15 rangeNode.s('shape3d.visible', true);
16 rangeNode.s('wf.geometry', true);
17 };
18
19 // 当前锥体起始位置
20 var realP3 = [cameraP3[0], cameraP3[1] + cameraS3[1] / 2, cameraP3[2] + cameraS3[2] / 2];
21 // 将当前眼睛位置绕着摄像头起始位置旋转得到正确眼睛位置
22 var realEye = demoUtil.getCenter(cameraP3, realP3, cameraR3);
23
24 outScreenG3d.setEye(realEye);
25 outScreenG3d.setCenter(demoUtil.getCenter(realEye, [realEye[0], realEye[1], realEye[2] + 5], cameraR3));
26 outScreenG3d.setFovy(camera.a('fovy'));
27 outScreenG3d.validate();
28 updateScreen();
29 }

上面代码中有一个 getCenter 方法是用于获取 3d 场景中点 A 绕着点 B 旋转 angle 角度之后得到的点 A 在 3d 场景中的位置,方法中采用了 HT 封装的 ht.Math 下面的方法,以下为代码:

 1 // pointA 为 pointB 围绕的旋转点
2 // pointB 为需要旋转的点
3 // r3 为旋转的角度数组 [xAngle, yAngle, zAngle] 为绕着 x, y, z 轴分别旋转的角度
4 var getCenter = function(pointA, pointB, r3) {
5 var mtrx = new ht.Math.Matrix4();
6 var euler = new ht.Math.Euler();
7 var v1 = new ht.Math.Vector3();
8 var v2 = new ht.Math.Vector3();
9
10 mtrx.makeRotationFromEuler(euler.set(r3[0], r3[1], r3[2]));
11
12 v1.fromArray(pointB).sub(v2.fromArray(pointA));
13 v2.copy(v1).applyMatrix4(mtrx);
14 v2.sub(v1);
15
16 return [pointB[0] + v2.x, pointB[1] + v2.y, pointB[2] + v2.z];
17 };

这里应用到向量的部分知识,具体如下:

OA + OB = OC

方法分为以下几个步骤求解:

1.  var mtrx = new ht.Math.Matrix4() 创建一个转换矩阵,通过 mtrx.makeRotationFromEuler(euler.set(r3[0], r3[1], r3[2])) 获取绕着 r3[0],r3[1],r3[2] 即 x 轴,y 轴,z 轴旋转的旋转矩阵。
   2. 通过 new ht.Math.Vector3() 创建 v1,v2 两个向量。
   3. v1.fromArray(pointB) 为建立一个从原点到 pointB 的一个向量。
   4. v2.fromArray(pointA) 为建立一个从原点到 pointA 的一个向量。
   5. v1.fromArray(pointB).sub(v2.fromArray(pointA)) 即向量 OB - OA 此时得到的向量为 AB,此时 v1 变为向量 AB。
   6. v2.copy(v1) v2 向量拷贝 v1 向量,之后通过 v2.copy(v1).applyMatrix4(mtrx) 对 v2 向量应用旋转矩阵,变换之后即为 v1向量绕着 pointA 旋转之后的的向量 v2。
   7. 此时通过 v2.sub(v1) 就获取了起始点为 pointB,终点为 pointB 旋转之后点构成的向量,该向量此时即为 v2。
   8. 通过向量公式得到旋转之后的点为 [pointB[0] + v2.x, pointB[1] + v2.y, pointB[2] + v2.z]。

项目中的 3D 场景例子其实是 Hightopo 最近贵州数博会,HT 上工业互联网展台的 VR 示例,大众对 VR/AR 的期待很高,但路还是得一步步走,即使融资了 23 亿美金的 Magic Leap 的第一款产品也只能是 Full of Shit,这话题以后再展开,这里就上段当时现场的视频照片:

2d 图像贴到 3d 模型

通过上一步的介绍我们可以获取当前摄像机位置的截屏图像,那么如何将当前图像贴到前面所构建的五面体底部呢?前面通过 from_vs, from_is 来构建底部的长方形,所以在 HT 中可以通过将五面体的 style 中 shape3d.from.image 属性设置成当前图像,其中 from_uv 数组用来定义贴图的位置,具体如下图:

以下为定义贴图位置 from_uv 的代码:

 1 from_uv: [0, 0, 1, 0, 1, 1, 0, 1] 

from_uv 就是定义贴图的位置数组,根据上图的解释,可以将 2d 图像贴到 3d 模型的 from 面。

控制面板

HT 中通过 new ht.widget.Panel() 来生成如下图的面板:

面板中每个摄像头都有一个模块来呈现当前监控图像,其实这个地方也是一个 canvas,该 canvas 与场景中锥体前面的监控图像是同一个 canvas,每一个摄像头都有一个自己的 canvas 用来保存当前摄像头的实时监控画面,这样就可以将该 canvas 贴到任何地方,将该 canvas 添加进面板的代码如下:

1 formPane.addRow([{ 2 element: camera.a('canvas') 3 }], 240, 240);

代码中将 canvas 节点存储在摄像头图元的 attr 属性下面,之后便可以通过 camera.a('canvas') 来获取当前摄像头的画面。

在面板中的每一个控制节点都是通过 formPane.addRow 来进行添加,具体可参考 HT for Web 的表单手册。之后通过 ht.widget.Panel 将表单面板 formPane 添加进 panel 面板中,具体可参考 HT for Web 的面板手册

部分控制代码如下:

 1 formPane.addRow(['rotateY', {
2 slider: {
3 min: -Math.PI,
4 max: Math.PI,
5 value: r3[1],
6 onValueChanged: function() {
7 var cameraR3 = camera.r3();
8 camera.r3([cameraR3[0], this.getValue(), cameraR3[2]]);
9 rangeNode.r3([cameraR3[0], this.getValue(), cameraR3[2]]);
10 getFrontImg(camera, rangeNode);
11 }
12 }
13 }], [0.1, 0.15]);

控制面板通过 addRow 来添加控制元素,以上代码为添加摄像头绕着 y 轴进行旋转的控制,onValueChanged 在 slider 的数值改变的时候调用,此时通过 camera.r3() 获取当前摄像头的旋转参数, 由于是绕着 y 轴旋转所以 x 轴与 z 轴的角度是不变的,变的是 y 轴的旋转角度,所以通过 camera.r3([cameraR3[0], this.getValue(), cameraR3[2]]) 来调整摄像头的旋转角度以及通过 rangeNode.r3([cameraR3[0], this.getValue(), cameraR3[2]]) 来设置摄像头前方锥体的旋转角度,然后调用之前封装好的 getFrontImg 函数来获取此时旋转角度下面的实时图像信息。

项目中通过 Panel 面板的配置参数 titleBackground: rgba(230, 230, 230, 0.4) 即可将标题背景设置为具有透明度的背景,其它类似的 titleColor, titleHeight 等标题参数都可以配置,通过 separatorColor,separatorWidth 等分割参数可以设置内部面板之间分割线的颜色,宽度等。最后面板通过 panel.setPositionRelativeTo('rightTop') 将面板的位置设置成右上角,并且通过 document.body.appendChild(panel.getView()) 将面板最外层的 div 添加进页面中, panel.getView() 用来获取面板的最外层 dom 节点。

具体初始化面板代码如下:

 1 function initPanel() {
2 var panel = new ht.widget.Panel();
3 var config = {
4 title: "摄像头控制面板",
5 titleBackground: 'rgba(230, 230, 230, 0.4)',
6 titleColor: 'rgb(0, 0, 0)',
7 titleHeight: 30,
8 separatorColor: 'rgb(67, 175, 241)',
9 separatorWidth: 1,
10 exclusive: true,
11 items: []
12 };
13 cameraArr.forEach(function(data, num) {
14 var camera = data['camera'];
15 var rangeNode = data['rangeNode'];
16 var formPane = new ht.widget.FormPane();
17 initFormPane(formPane, camera, rangeNode);
18 config.items.push({
19 title: "摄像头" + (num + 1),
20 titleBackground: 'rgba(230, 230, 230, 0.4)',
21 titleColor: 'rgb(0, 0, 0)',
22 titleHeight: 30,
23 separatorColor: 'rgb(67, 175, 241)',
24 separatorWidth: 1,
25 content: formPane,
26 flowLayout: true,
27 contentHeight: 400,
28 width: 250,
29 expanded: num === 0
30 });
31 });
32 panel.setConfig(config);
33 panel.setPositionRelativeTo('rightTop');
34 document.body.appendChild(panel.getView());
35 window.addEventListener("resize",
36 function() {
37 panel.invalidate();
38 });
39 }

在控制面板中可以调整摄像头的方向,摄像头监控的辐射范围,摄像头前方锥体的长度等等,并且摄像头的图像是实时生成,以下为运行截图:

以下是本项目采用的 3D 场景结合 VR 技术实现的操作:

构建于 B/S 端的 3D 摄像头可视化监控方案的更多相关文章

  1. 基于 Web 端 3D 地铁站可视化系统

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...

  2. 英特尔实感3D摄像头

    RealSense 3D(实感3D)是英特尔提供的一套感知计算解决方案,包括了手势的识别.控制,人脸的识别.认证.控制,语音识别与控制,增强现实,3D扫描和重构等许多先进的技术.包括立体眼镜(暂未公开 ...

  3. 使用WebGL 自定义 3D 摄像头监控模型

    前言 随着视频监控联网系统的不断普及和发展, 网络摄像机更多的应用于监控系统中,尤其是高清时代的来临,更加快了网络摄像机的发展和应用. 在监控摄像机数量的不断庞大的同时,在监控系统中面临着严峻的现状问 ...

  4. 基于 HTML5 的 WebGL 自定义 3D 摄像头监控模型

    前言 随着视频监控联网系统的不断普及和发展, 网络摄像机更多的应用于监控系统中,尤其是高清时代的来临,更加快了网络摄像机的发展和应用. 在监控摄像机数量的不断庞大的同时,在监控系统中面临着严峻的现状问 ...

  5. h5端呼起摄像头扫描二维码并解析

    2016年6月29日补充: 最近做了一些与表单相关的项目,使用了h5的input控件,在使用过程中遇到了很多的坑.也包括与这篇文章相关的. 首先我们应该知道使用h5新提供的属性getUserMedia ...

  6. MQTT是IBM开发的一个即时通讯协议,构建于TCP/IP协议上,是物联网IoT的订阅协议,借助消息推送功能,可以更好地实现远程控制

    最近一直做物联网方面的开发,以下内容关于使用MQTT过程中遇到问题的记录以及需要掌握的机制原理,主要讲解理论. 背景 MQTT是IBM开发的一个即时通讯协议.MQTT构建于TCP/IP协议上,面向M2 ...

  7. 基于 B/S 端构建的 3D 楼宇自控可视化监控

    前言 智慧楼宇和人们的生活息息相关,楼宇智能化程度的提高,会极大程度的改善人们的生活品质,在当前工业互联网大背景下受到很大关注.目前智慧楼宇可视化监控的主要优点包括: 智慧化 -- 智慧楼宇是一个生态 ...

  8. “全隐藏式3D摄像头”亮相,FindX如何将设计与体验融为一体

    北京时间6月20日,OPPO在卢浮宫发布暌违四年之久的Find旗舰系列新手机--Find X.在Find X背后,我认为其设计值得深思.尤其是Find X为突破传统设计束缚,首创双轨潜望结构有着重要启 ...

  9. 基于 WebGL 的 HTML5 楼宇自控 3D 可视化监控

    前言 智慧楼宇和人们的生活息息相关,楼宇智能化程度的提高,会极大程度的改善人们的生活品质,在当前工业互联网大背景下受到很大关注.目前智慧楼宇可视化监控的主要优点包括: 智慧化 -- 智慧楼宇是一个生态 ...

随机推荐

  1. 模板列传值到子窗体中,子窗体中多选gridview中checkbox保存数据多项到数据库中

    <html xmlns="http://www.w3.org/1999/xhtml"> <head runat="server"> &l ...

  2. WTM重磅更新,LayuiAdmin and more

    从善如登,从恶如崩.对于一个开发人员来说,那就是做一个好的系统不容易,想搞砸一个系统很简单,删库跑路会还不会么. 对于我们开源框架的作者来说,做一个好的框架就像登山(也许是登天),我们一步一步往上走, ...

  3. [Revit]开始:编写一个简单外部命令

    1 创建项目 以Visual Stidio作为开发工具,测试平台为Revit 2017 打开VS,创建一个C# .NET Framwork类库项目,选择..net框架版本为.NET Framwork ...

  4. POJ-2018 Best Cow Fences 二分

    题意:找到一个连续区间,区间的长度至少大于f,现在要求这个区间的平均值最大. 题解: 二分找答案. 每次对于2分的mid值, 都把原来的区间减去mid, 然后找到一长度至少为f的区间, 他们的区间和& ...

  5. vim命令的三种模式

    对于vim这个命令来讲是有三种模式的,分别是:正常模式,编辑模式以及命令模式.接下来就写一个demo作为演示 前期准备,先在本地准备一个文档,我这里就写了一个大家耳熟能详的例子,如下: 然后用rz命令 ...

  6. 解决go get下载包失败问题

    由于某些不可抗力的原因,国内使用go get命令安装包时会经常会出现timeout的问题.本文介绍几个常用的解决办法. 从github克隆 golang在github上建立了一个镜像库,如https: ...

  7. Spring Boot2 系列教程(五)Spring Boot中的 yaml 配置

    搞 Spring Boot 的小伙伴都知道,Spring Boot 中的配置文件有两种格式,properties 或者 yaml,一般情况下,两者可以随意使用,选择自己顺手的就行了,那么这两者完全一样 ...

  8. Python集训营45天—Day03

    目录 1. 分支结构 1.1 初步介绍 1.2 使用案例 1.3 练习 2.循环结构 1.1 初步介绍 1.2 使用案例 1. 分支结构 1.1 初步介绍 至今,我们所写的Python代码都是顺序执行 ...

  9. 三句话告诉你break、return、continue!

    break:终止循环执行循环体下面的代码 return:终止循环并且退出循环所在的方法 continue:终止当前循环,进行下一次循环

  10. DirectX12 3D 游戏开发与实战第四章内容(下)

    Direct3D的初始化(下) 学习目标 了解Direct3D在3D编程中相对于硬件所扮演的角色 理解组件对象模型COM在Direct3D中的作用 掌握基础的图像学概念,例如2D图像的存储方式,页面翻 ...