javascript - Three.js:如何计算相机图像中物体的最小外接矩形?

标签 javascript three.js

我想计算并绘制相机图像(2D 投影)中对象的最小边界矩形 (MBR)。 下图显示了下面提供的示例的 cube 对象的 MBR。 我截取了相机图像并用眼睛手动绘制了 MBR(红色矩形)。 如何以编程方式计算 MBR(相机图像中的大小和位置)并将其绘制在相机图像的 Canvas 中(实时)?

bounding rectangular

编辑: 我根据这个相关的 solution 添加了一个 MBR 到我的示例中,但我在 2D 覆盖 Canvas 上绘制了 MBR。在您移动相机(使用鼠标)之前,它工作正常。然后 MBR 不正确(移位且大小错误)。计算是否不适合我的示例,或者我的实现中是否存在错误?

注意:我不仅要绘制 MBR,还要找出相机图像上的 2D 坐标和大小。

function computeScreenSpaceBoundingBox(mesh, camera) {
  var vertices = mesh.geometry.vertices;
  var vertex = new THREE.Vector3();
  var min = new THREE.Vector3(1, 1, 1);
  var max = new THREE.Vector3(-1, -1, -1);

  for (var i = 0; i < vertices.length; i++) {
    var vertexWorldCoord = vertex.copy(vertices[i]).applyMatrix4(mesh.matrixWorld);
    var vertexScreenSpace = vertexWorldCoord.project(camera);
    min.min(vertexScreenSpace);
    max.max(vertexScreenSpace);
  }

  return new THREE.Box2(min, max);
}

function normalizedToPixels(coord, renderWidthPixels, renderHeightPixels) {
  var halfScreen = new THREE.Vector2(renderWidthPixels/2, renderHeightPixels/2)
  return coord.clone().multiply(halfScreen);
}

const scene = new THREE.Scene();
const light = new THREE.DirectionalLight( 0xffffff, 1 );
light.position.set( 1, 1, 0.5 ).normalize();
scene.add( light );
scene.add(new THREE.AmbientLight(0x505050));

const geometry = new THREE.BoxGeometry( 1, 1, 1 );
const material = new THREE.MeshLambertMaterial( { color: 0x00ff00 } );
const cube = new THREE.Mesh( geometry, material );
scene.add( cube );

const renderWidth = window.innerWidth;
const renderHeight = window.innerHeight;
const camera = new THREE.PerspectiveCamera( 75, renderWidth / renderHeight, 0.1, 1000 );
camera.position.z = 10;

const controls = new THREE.OrbitControls( camera );
controls.update();

const renderer = new THREE.WebGLRenderer();
renderer.setSize( window.innerWidth, window.innerHeight );
document.body.appendChild( renderer.domElement );

const clock = new THREE.Clock();

const overlayCanvas = document.getElementById('overlay');
overlayCanvas.width = renderWidth;
overlayCanvas.height = renderHeight;
const overlayCtx = overlayCanvas.getContext('2d');
overlayCtx.lineWidth = 4;
overlayCtx.strokeStyle = 'red';


const animate = function () {
  const time = clock.getElapsedTime() * 0.5;
  
  requestAnimationFrame( animate );

  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  cube.position.x = Math.sin(time) * 5

  controls.update();
  renderer.render( scene, camera );
  
  const boundingBox2D = computeScreenSpaceBoundingBox(cube, camera);
  // Convert normalized screen coordinates [-1, 1] to pixel coordinates:
  const {x: w, y: h} = normalizedToPixels(boundingBox2D.getSize(), renderWidth, renderHeight); 
  const {x, y} =
    normalizedToPixels(boundingBox2D.min, renderWidth, renderHeight)
      .add(new THREE.Vector2(renderWidth / 2, renderHeight / 2)); 
  
  overlayCtx.clearRect(0, 0, renderWidth, renderHeight);
  overlayCtx.strokeRect(x, y, w, h);
};

animate();
body {
  margin: 0px;
  background-color: #000000;
  overflow: hidden;
}

#overlay {
  position: absolute;
}
<body>
    <script src="https://unpkg.com/three@0.104.0/build/three.min.js"></script>
    <script src="https://unpkg.com/three@0.104.0/examples/js/Detector.js"></script>
    <script src="https://unpkg.com/three@0.104.0/examples/js/controls/OrbitControls.js"></script>
    <canvas id="overlay"></canvas>
</body>

注意:如果我的问题不清楚,请发表评论,以便我知道更详细地描述什么。

最佳答案

感谢WestLangley为我指出相关的 solutionHolger L感谢manthrax修复了该解决方案中的一个错误,该错误使我错过了自己的错误。在我的示例中,我只是将 y 坐标从 WebGL 2D 屏幕坐标(原点位于 Canvas 中心且 y 轴指向上方)错误转换为常规 2D Canvas 坐标(原点位于 Canvas 的左上角)和 y 轴指向下方)。我没有考虑到 y 轴是反转的。下面是该示例的固定版本:

const computeScreenSpaceBoundingBox = (function () {
  const vertex = new THREE.Vector3();
  const min = new THREE.Vector3(1, 1, 1);
  const max = new THREE.Vector3(-1, -1, -1);
  return function computeScreenSpaceBoundingBox(box, mesh, camera) {
    box.set(min, max);
    const vertices = mesh.geometry.vertices;
    const length = vertices.length;
    for (let i = 0; i < length; i++) {
      const vertexWorldCoord =
        vertex.copy(vertices[i]).applyMatrix4(mesh.matrixWorld);
      const vertexScreenSpace = vertexWorldCoord.project(camera);
      box.min.min(vertexScreenSpace);
      box.max.max(vertexScreenSpace);
    }
  }
})();

const renderWidth = window.innerWidth;
const renderHeight = window.innerHeight;
const renderWidthHalf = renderWidth / 2;
const renderHeightHalf = renderHeight / 2;

const scene = new THREE.Scene();
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(1, 1, 0.5).normalize();
scene.add(light);
scene.add(new THREE.AmbientLight(0x505050));

const geometry = new THREE.BoxGeometry(1, 1, 1);
const material = new THREE.MeshLambertMaterial({color: 0x00ff00});
const cube = new THREE.Mesh(geometry, material);
scene.add(cube);

const camera = new THREE.PerspectiveCamera(75, renderWidth / renderHeight, 0.1, 1000);
camera.position.z = 10;

const controls = new THREE.OrbitControls(camera);
controls.update();

const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

const clock = new THREE.Clock();

const overlayCanvas = document.getElementById('overlay');
overlayCanvas.width = renderWidth;
overlayCanvas.height = renderHeight;
const overlayCtx = overlayCanvas.getContext('2d');
overlayCtx.lineWidth = 4;
overlayCtx.strokeStyle = 'red';

const boundingBox2D = new THREE.Box2();

const animate = function () {
  const time = clock.getElapsedTime() * 0.5;

  requestAnimationFrame(animate);

  cube.rotation.x += 0.01;
  cube.rotation.y += 0.01;
  cube.position.x = Math.sin(time) * 5;

  controls.update();
  renderer.render(scene, camera);

  computeScreenSpaceBoundingBox(boundingBox2D, cube, camera);
  // Convert normalized screen coordinates [-1, 1] to pixel coordinates:
  const x = (boundingBox2D.min.x + 1) * renderWidthHalf;
  const y = (1 - boundingBox2D.max.y) * renderHeightHalf;
  const w = (boundingBox2D.max.x - boundingBox2D.min.x) * renderWidthHalf;
  const h = (boundingBox2D.max.y - boundingBox2D.min.y) * renderHeightHalf;

  overlayCtx.clearRect(0, 0, renderWidth, renderHeight);
  overlayCtx.strokeRect(x, y, w, h);
};

animate();
body {
  margin: 0px;
  background-color: #000000;
  overflow: hidden;
}

#overlay {
  position: absolute;
}
<body>
    <script src="https://unpkg.com/three@0.104.0/build/three.min.js"></script>
    <script src="https://unpkg.com/three@0.104.0/examples/js/Detector.js"></script>
    <script src="https://unpkg.com/three@0.104.0/examples/js/controls/OrbitControls.js"></script>
    <canvas id="overlay"></canvas>
</body>

关于javascript - Three.js:如何计算相机图像中物体的最小外接矩形?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56210156/

相关文章:

php - 在附加的 HTML 上使用 jQuery 效果

javascript - Three.js 中的超链接 CSS3D 对象

javascript - Three.js - 从点缩放圆柱体

javascript - Node开发的站点请求突发怎么办?

javascript - 触发 Tinymce 上的按键事件

javascript - ThreeJS 禁用相机后面的面部渲染

javascript - 我不会制作线框框

javascript - Three.js - 在 3D 球体周围应用纹理(2D png 图像)显示为黑色

javascript - 如何制作带有时间数据的进度条?

javascript - 在 Javascript 中获取元素的 X,Y 位置