前言

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

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

以下是项目地址:基于 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.*  等单独控制某个面

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

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

我将当前摄像头的 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 Web3D 手册。

根据上图的描述,在本项目中可以在摄像头初始化之后,缓存当前 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 的代码:

  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 添加进面板的代码如下:

formPane.addRow([{ element: camera.a('canvas') }], 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 技术实现的操作:

基于 HTML5 的 WebGL 自定义 3D 摄像头监控模型的更多相关文章

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

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

  2. 基于HTML5的电信网管3D机房监控应用

    先上段视频,不是在玩游戏哦,是规规矩矩的电信网管企业应用,嗯,全键盘的漫游3D机房: 随着PC端支持HTML5浏览器的普及,加上主流移动终端Android和iOS都已支持HTML5技术,新一代的电信网 ...

  3. 基于HTML5和WebGL的3D网络拓扑结构图

    现在,3D模型已经用于各种不同的领域.在医疗行业使用它们制作器官的精确模型:电影行业将它们用于活动的人物.物体以及现实电影:视频游戏产业将它们作为计算机与视频游戏中的资源:在科学领域将它们作为化合物的 ...

  4. 基于HTML5的WebGL经典3D虚拟机房漫游动画

    第一人称在 3D 中的用法要参考第一人称在射击游戏中的使用,第一人称射击游戏(FPS)是以第一人称视角为中心围绕枪和其他武器为基础的视频游戏类型 ; 也就是说,玩家通过主角的眼睛来体验动作.自从流派开 ...

  5. 基于 HTML5 的 WebGL 和 VR 技术的 3D 机房数据中心可视化

    前言 在 3D 机房数据中心可视化应用中,随着视频监控联网系统的不断普及和发展, 网络摄像机更多的应用于监控系统中,尤其是高清时代的来临,更加快了网络摄像机的发展和应用. 在监控摄像机数量的不断庞大的 ...

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

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

  7. 基于HTML5技术的电力3D监控应用(一)

    最近参与了国网计量中心的四线一库自动化检定系统的项目开发,团队封闭开发了大半年终于快到尾声了,整个项目过程实在非常累,我的mentor杨杨老师是这样描述的:累的不想说话了.我估计是我太渴望新知识,整天 ...

  8. 基于HTML5的WebGL电信网管3D机房监控应用

    先上段视频,不是在玩游戏哦,是规规矩矩的电信网管企业应用,嗯,全键盘的漫游3D机房: http://www.hightopo.com/guide/guide/core/3d/examples/exam ...

  9. 基于 HTML5 的 WebGL 3D 智能楼宇监控系统

    前言 智能监控的领域已经涉及到了各大领域,工控.电信.电力.轨道交通.航天航空等等,为了减少人员的消耗,监控系统必不可少.之前我写过一篇 2D 的智能地铁监控系统广受好评,突然觉得,既然 2D 的这么 ...

随机推荐

  1. js内存深入学习(一)

    一. 内存空间储存 某些情况下,调用堆栈中函数调用的数量超出了调用堆栈的实际大小,浏览器会抛出一个错误终止运行.这个就涉及到内存问题了. 1. 数据结构类型 栈: 后进先出(LIFO)的数据结构  堆 ...

  2. iptables 指南

    iptables 是 Linux 内核集成的防火墙系统, 几乎所有 Linux 发行版都会内置 iptables. iptables 对进出的 IP 数据报进行处理和过滤, 过滤规则(rule)存储在 ...

  3. CAN总线学习记录之二:系统结构与帧结构

    CAN总线系统结构 CAN 控制器  接收控制单元中微处理器发出的数据,处理数据并传给 CAN 收发器 CAN 收发器 将数据传到总线 or 从总线接收数据给 CAN 控制器 CAN 数据传递终端 避 ...

  4. [JavaScript] audio在浏览器中自动播放

    audio 在浏览器中自动播放 autoplay 属性 autoplay 属性规定一旦音频就绪马上开始播放. 如果设置了该属性,音频将自动播放. 使用 autoplay 属性进行播放 //使用auto ...

  5. Scala(二) —— 函数

    try 表达式 var result = try{ Integer.parseInt("dog") }catch{ case _ => 0 }finally{ println ...

  6. .Net语言 APP开发平台——Smobiler学习日志:基于Access数据库的Demo

    说明:该demo是基于Access数据库进行客户信息的新增.查看.编辑 新增客户信息和客户列表 Demo下载:https://github.com/comsmobiler/demo-videos  中 ...

  7. 面向对象的一小步:添加ActiveRecord的Scope功能

    问题场景 我们用Yii2的ActiveRecord功能非常的方便,假如我们有个Model叫Student,那么ActiveQuery可以通过这种方式轻便地获得: $query = Student::f ...

  8. 微信小程序异步请求问题

    微信小程序为了提高用户体验,提供的api大部分都是异步操作,除了数据缓存操作里面有一些同步操作.是提高了用户体验,但是在开发的时候, 就有点坑了,例如我要写一个公共方法,发起网络请求,去后台去一些数据 ...

  9. android 仿微信表情雨下落!

    文章链接:https://mp.weixin.qq.com/s/yQXn-YjEFSW1X7A7CcuaVg 众所周知,微信聊天中我们输入一些关键词会有表情雨下落,比如输入「生日快乐」「么么哒」会有相 ...

  10. Android网络图片转换成bitmap保存到本地指定文件夹

    下列代码,请求网络图片转换为bitmap,然后保存到指定文件夹,微信,QQ分享,要求缩略图不大于32kb 压缩图片代码,使用了Glide来进行图片压缩处理 Glide.get(ShopDetailsA ...