对象的准确边界框

Posted

技术标签:

【中文标题】对象的准确边界框【英文标题】:Accurate bounding box of an object 【发布时间】:2017-10-07 20:55:51 【问题描述】:

我正在尝试创建,但如果对象未与轴对齐(我认为),则该框似乎未与对象对齐。

例如:

粉红色和更接近橙色的顶点是这面墙的 Box3.min、Box3.max,但你看到红色、绿色和蓝色不在那面墙上。您可以忽略水色顶点。

这是创建边界框的代码(返回 Box3):

  static getWorldBoundingBox(model, dbId) 

    return new Promise(async(resolve, reject)=>

      try

        var fragIds = await ViewerToolkit.getFragIds(
          model, dbId);

        if(!fragIds.length)

          return reject('No geometry, invalid dbId?');
        

        var fragList = model.getFragmentList();

        var fragbBox = new THREE.Box3();
        var nodebBox = new THREE.Box3();

        fragIds.forEach(function(fragId) 

          fragList.getWorldBounds(fragId, fragbBox);
          nodebBox.union(fragbBox);
        );

        return resolve(nodebBox);
      
      catch(ex)

        return reject(ex);
      
    );
  

这就是我从最小值、最大值创建盒子的方式:

    let ddd = new THREE.Vector3(min.x, min.y, min.z);
    let ddu = new THREE.Vector3(min.x, min.y, max.z);
    let dud = new THREE.Vector3(min.x, max.y, min.z);
    let udd = new THREE.Vector3(max.x, min.y, min.z);

    let duu = new THREE.Vector3(min.x, max.y, max.z);
    let uud = new THREE.Vector3(max.x, max.y, min.z);
    let udu = new THREE.Vector3(max.x, min.y, max.z);
    let uuu = new THREE.Vector3(max.x, max.y, max.z);

    this.drawVertices([ddd,ddu,dud,udd,duu,uud,udu,uuu]);

    let facesPoints = [
        
            BL: ddd.clone(),
            UL: ddu.clone(),
            UR: udu.clone(),
            BR: udd.clone()
        ,
        
            BL: udd.clone(),
            UL: udu.clone(),
            UR: uuu.clone(),
            BR: uud.clone()
        ,
        
            BL: uud.clone(),
            UL: uuu.clone(),
            UR: duu.clone(),
            BR: dud.clone()
        ,
        
            BL: dud.clone(),
            UL: duu.clone(),
            UR: ddu.clone(),
            BR: ddd.clone()
        
    ];

我想避免对所有顶点对的所有距离进行排序并取前两个的蛮力方法。

是否有另一种数据结构会暴露一个立方体的 8 个点,而不是像上面的函数一样,我可以给它多边形来构建它的 2 个点?

【问题讨论】:

【参考方案1】:

边界框与世界轴对齐。如果您的形状在空间中旋转,只需将形状的世界矩阵应用到其边界框(的副本)。这应该会给你形状的世界边界框。

在下面的示例中,红色立方体的边界框是从局部空间计算出来的,我将红色立方体的矩阵应用于边界框。绿色立方体的边界框每帧都会重新计算,从而生成一个与世界轴对齐的框,该框会随着框的旋转而增长和缩小。

var renderer, scene, camera, controls, stats, rotationMatrix, tmpPos, cube1, cube2, cube1BBox, cube2BBox;

var WIDTH = window.innerWidth,
	HEIGHT = window.innerHeight,
	FOV = 35,
	NEAR = 1,
	FAR = 1000;
  
function populateExample()
  rotationMatrix = new THREE.Matrix4().makeRotationY(0.5 * (Math.PI / 180));
	var cubeGeo = new THREE.BoxBufferGeometry(10, 10, 10),
		cube1Mat = new THREE.MeshPhongMaterial( color: "red" ),
    cube2Mat = new THREE.MeshPhongMaterial( color: "green" );
  cube1Mat.polygonOffset = true;
  cube1Mat.polygonOffsetFactor = 1;
  cube1Mat.polygonOffsetUnits = 0.5;
  cube2Mat.polygonOffset = true;
  cube2Mat.polygonOffsetFactor = 1;
  cube2Mat.polygonOffsetUnits = 0.5;
  
	cube1 = new THREE.Mesh(cubeGeo, cube1Mat);
	scene.add(cube1);
  
  cube2 = new THREE.Mesh(cubeGeo, cube2Mat);
	scene.add(cube2);
  
  cube1BBox = new THREE.BoxHelper(cube1, 0xffffff);
  scene.add(cube1BBox);
  
  cube2BBox = new THREE.BoxHelper(cube2, 0xffffff);
  scene.add(cube2BBox);
  
  cube1.position.set(-10, 0, 0);
  cube2.position.set(10, 0, 0);
  
  cube1BBox.position.set(-10, 0, 0);


function exampleRenderAction()
  tmpPos.copy(cube1.position);
  
  cube1.position.sub(tmpPos);
  cube1.updateMatrix();
  cube1.applyMatrix(rotationMatrix);
  cube1.position.add(tmpPos);
  cube1.updateMatrix();
  
  cube1BBox.matrix.copy(cube1.matrix);
  
  tmpPos.copy(cube2.position);
  
  cube2.position.sub(tmpPos);
  cube2.updateMatrix();
  cube2.applyMatrix(rotationMatrix);
  cube2.position.add(tmpPos);
  cube2.updateMatrix();
  
  cube2BBox.update();


function init() 
  tmpPos = new THREE.Vector3();
  rotation = 0;
  rotationSpeed = 0.5;
	document.body.style.backgroundColor = "slateGray";

	renderer = new THREE.WebGLRenderer( antialias: true, alpha: true );

	document.body.appendChild(renderer.domElement);
	document.body.style.overflow = "hidden";
	document.body.style.margin = "0";
	document.body.style.padding = "0";

	scene = new THREE.Scene();

	camera = new THREE.PerspectiveCamera(FOV, WIDTH / HEIGHT, NEAR, FAR);
	camera.position.set(0, 40, 40);
	scene.add(camera);

	controls = new THREE.TrackballControls(camera, renderer.domElement);
	controls.dynamicDampingFactor = 0.5;
	controls.rotateSpeed = 3;

	var light = new THREE.PointLight(0xffffff, 1, Infinity);
	camera.add(light);

	stats = new Stats();
	stats.domElement.style.position = 'absolute';
	stats.domElement.style.top = '0';
	document.body.appendChild(stats.domElement);

	resize();
	window.onresize = resize;

	populateExample();

	animate();


function resize() 
	WIDTH = window.innerWidth;
	HEIGHT = window.innerHeight;
	if (renderer && camera && controls) 
		renderer.setSize(WIDTH, HEIGHT);
		camera.aspect = WIDTH / HEIGHT;
		camera.updateProjectionMatrix();
		controls.handleResize();
	


function render() 
	renderer.render(scene, camera);


function animate() 
	requestAnimationFrame(animate);
	render();
  exampleRenderAction();
	controls.update();
	stats.update();


function threeReady() 
	init();


(function () 
	function addScript(url, callback) 
		callback = callback || function ()  ;
		var script = document.createElement("script");
		script.addEventListener("load", callback);
		script.setAttribute("src", url);
		document.head.appendChild(script);
	

	addScript("https://threejs.org/build/three.js", function () 
		addScript("https://threejs.org/examples/js/controls/TrackballControls.js", function () 
			addScript("https://threejs.org/examples/js/libs/stats.min.js", function () 
				threeReady();
			)
		)
	)
)();

基于 cmets 中的说明的扩展答案:

边界框是THREE.Box3,其中包含minmax THREE.Vector3s。所以要像你已经做的那样获得边界框的 8 个角:

var corners = [
    new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.min.z),
    new THREE.Vector3(bbox.min.x, bbox.min.y, bbox.max.z),
    new THREE.Vector3(bbox.min.x, bbox.max.y, bbox.max.z),
    new THREE.Vector3(bbox.min.x, bbox.max.y, bbox.min.z),
    new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.max.z),
    new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.max.z),
    new THREE.Vector3(bbox.max.x, bbox.min.y, bbox.min.z),
    new THREE.Vector3(bbox.max.x, bbox.max.y, bbox.min.z)
];

您可以按照自己的喜好安排这些。要将这些转换为世界坐标,您需要再执行一步。 请注意,以下步骤是破坏性的,因此如果您需要保留原始角值,则需要保存它们的副本。

顶点当前是对象的局部,所以你需要用对象的矩阵来更新它们:

for(var i = 0, len = corners.length; i < len; ++0)
    // this will apply all transformations from all parents
    corners[i].applyMatrix4(myObj.matrixWorld);

或者,您可以使用localToWorld 将这些点转换为世界坐标。

for(var i = 0, len = corners.length; i < len; ++0)
    // this literally does the same thing as the code above
    myObj.localToWorld(corners[i]);

但不要两者都做,否则你会得到不正确的值。

【讨论】:

你从哪里得到立方体的角? (8 个 Vectors3s) 哪个立方体?几何图形还是边界框?你是指变换后的立方体顶点的世界坐标吗? 世界坐标,立方体在世界中的位置,说我想在这些点之间画线来制作立方体的线框。 获取顶点取决于您使用的是THREE.Geometry 还是THREE.BufferGeometry。前者有一个THREE.Vector3格式的顶点数组(myGeometry.vertices),而后者有一个属性,其中包含一个TypedArray中的顶点位置信息(myBufferGeometry.attributes.position)(每个顶点是每三个值的子集)。 也就是说,如果你想要一个线框,我建议使用基础立方体的顶点创建它(你不需要世界坐标),并将立方体的矩阵应用于线框网格的矩阵。 【参考方案2】:

我找到了一种方法,首先我使用 Autodesk 的查看器扩展(网格数据)中的此功能将所有顶点收集到 general set:

  static getMeshVertices(viewer, fragId) 

    var fragProxy = viewer.impl.getFragmentProxy(
      viewer.model,
      fragId);

    var renderProxy = viewer.impl.getRenderProxy(
      viewer.model,
      fragId);

    fragProxy.updateAnimTransform();

    var matrix = new THREE.Matrix4();
    fragProxy.getWorldMatrix(matrix);

    const verticesSet = new GeneralSet();
    const geometry = renderProxy.geometry;
    const attributes = geometry.attributes;

    if (attributes && attributes.index !== undefined) 

      const indices = attributes.index.array || geometry.ib;
      const positions = geometry.vb ? geometry.vb : attributes.position.array;
      const stride = geometry.vb ? geometry.vbstride : 3;
      let offsets = geometry.offsets;

      if (!offsets || offsets.length === 0) 

        offsets = [ start: 0, count: indices.length, index: 0 ];
      

      for (var oi = 0, ol = offsets.length; oi < ol; ++oi) 

        const start = offsets[oi].start;
        const count = offsets[oi].count;
        const index = offsets[oi].index;

        for (var i = start, il = start + count; i < il; i += 3) 

          const vA = new THREE.Vector3();
          const vB = new THREE.Vector3();
          const vC = new THREE.Vector3();
          const a = index + indices[i];
          const b = index + indices[i + 1];
          const c = index + indices[i + 2];

          vA.fromArray(positions, a * stride);
          vB.fromArray(positions, b * stride);
          vC.fromArray(positions, c * stride);

          vA.applyMatrix4(matrix);
          vB.applyMatrix4(matrix);
          vC.applyMatrix4(matrix);

          verticesSet.add(vA);
          verticesSet.add(vB);
          verticesSet.add(vC);
        
      

      return verticesSet;
    
    else 

      var positions = geometry.vb ? geometry.vb : attributes.position.array;
      var stride = geometry.vb ? geometry.vbstride : 3;

      for (var i = 0, j = 0, il = positions.length; i < il; i += 3, j += 9) 
        let vA = new THREE.Vector3();
        let vB = new THREE.Vector3();
        let vC = new THREE.Vector3();
        var a = i;
        var b = i + 1;
        var c = i + 2;

        vA.fromArray(positions, a * stride);
        vB.fromArray(positions, b * stride);
        vC.fromArray(positions, c * stride);

        vA.applyMatrix4(matrix);
        vB.applyMatrix4(matrix);
        vC.applyMatrix4(matrix);

        verticesSet.add(vA);
        verticesSet.add(vB);
        verticesSet.add(vC);
      

      return verticesSet;
    
  

然后我从这里使用了一个图直径算法:https://cs.stackexchange.com/a/213/43035 一次在顶点集(好像该集代表一个完整的图)上得到两个对角,我们称它们为 u,w。

然后我从顶点集合中删除 u, w 并再次运行图形直径,得到另外两个角。

现在有四个角,可以生成所有其余角,并对它们进行排序,有 3 个条件来检查 4 个角中的每一个(这将显示其他 4 个角),与相机的距离(更近或更远) , 高度(上角或下角)和对象中间的左或右(使用十字和点,像这样https://forum.unity3d.com/threads/left-right-test-function.31420/(他们有js代码))。

这将为您提供 8 个角,以便您知道哪个角在哪里,并且角在对象上,对象与轴的对齐方式无关紧要。

【讨论】:

以上是关于对象的准确边界框的主要内容,如果未能解决你的问题,请参考以下文章

如何在另一个对象边界框下面选择对象

缩放对象以适合某些边界框three.js

根据对象边界框裁剪旋转图像 - Matlab

转换引用对象的边界框?

TensorFlow对象检测API教程中获取边界框坐标

Tensorflow 对象检测 API 数据增强边界框