前言

在 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.ge