简体   繁体   English

相机投影

[英]Camera projection

I'm building a toy 3d renderer and I have a not yet identified problem. 我正在构建玩具3d渲染器,但尚未发现问题。 I have a camera which points to a certain point in space. 我有一台指向太空中某个特定点的相机。 The camera has a frame and a given focal length. 相机具有镜框和给定的焦距。 I want to project an arbitrary point on the camera's frame. 我想在相机的框架上投射一个任意点。 The X and Y coordinates are treated separately, as usual. 照常将X和Y坐标分别处理。 The image shows how I calculate the X. I use the cosine theorem for triangles: given the three triangle lengths I first find the angle, and then I get the X using the camera's focal length. 该图显示了如何计算X。我对三角形使用余弦定理:给定三个三角形的长度,我首先找到角度,然后使用相机的焦距得到X。

Image: 图片: 在此处输入图片说明

The same applies to the Y coordinate. Y坐标也一样。 To me it looks nice and clean, however the results are not as expected: I've set 8 points in space as a cube vertices, and I've set the camera to rotate around the origin. 对我来说,它看起来很干净整洁,但是结果却不如预期:我在空间中设置了8个点作为立方体顶点,并且将摄影机设置为绕原点旋转。 The cube deforms badly as the camera moves. 摄像机移动时,立方体变形严重。

The critical method: 关键方法:

private void project(double[][] points3D, int[][] points2D) {

    double x;
    double y;
    double angle;
    double camToPoint2;
    double camToCenter2;
    double centerToPoint2;
    double[] camToCenter;
    double[] centerToPoint;

    for(int i = 0; i < points3D.length; i++) {

        // x's projection

        camToCenter = new double[] {center[0]-camera.position[0], center[2]-camera.position[2]};
        centerToPoint = new double[] {points3D[i][0]-center[0], points3D[i][2]-center[2]};

        camToCenter2 = camToCenter[0]*camToCenter[0] + camToCenter[1]*camToCenter[1];
        centerToPoint2 = centerToPoint[0]*centerToPoint[0] + centerToPoint[1]*centerToPoint[1];
        camToPoint2 = (points3D[i][0]-camera.position[0])*(points3D[i][0]-camera.position[0]) +
                        (points3D[i][2]-camera.position[2])*(points3D[i][2]-camera.position[2]);

        angle = Math.acos((camToCenter2 + camToPoint2 - centerToPoint2) /
                (2 * Math.sqrt(camToCenter2) * Math.sqrt(camToPoint2)));

        x = camera.focalLength * Math.tan(angle);
        // check if x lies to the left or right of the frame's center
        x = camToCenter[0]*centerToPoint[1] + camToCenter[1]*centerToPoint[0] < 0 ? -x : x;
        // reescale
        points2D[i][0] = (int) (screenW * (0.5 * camera.frame[0] + x) / camera.frame[0]);

        // y's projection

        camToCenter = new double[] {center[1]-camera.position[1], center[2]-camera.position[2]};
        centerToPoint = new double[] {points3D[i][1]-center[1], points3D[i][2]-center[2]};

        camToCenter2 = camToCenter[0]*camToCenter[0] + camToCenter[1]*camToCenter[1];
        centerToPoint2 = centerToPoint[0]*centerToPoint[0] + centerToPoint[1]*centerToPoint[1];
        camToPoint2 = (points3D[i][1]-camera.position[1])*(points3D[i][1]-camera.position[1]) +
                        (points3D[i][2]-camera.position[2])*(points3D[i][2]-camera.position[2]);

        angle = Math.acos((camToCenter2 + camToPoint2 - centerToPoint2) /
                (2 * Math.sqrt(camToCenter2) * Math.sqrt(camToPoint2)));

        y = camera.focalLength * Math.tan(angle);
        // check if y lies to the left or right of the frame's center
        y = camToCenter[0]*centerToPoint[1] + camToCenter[1]*centerToPoint[0] < 0 ? -y : y;
        // reescale
        points2D[i][1] = (int) (screenH * (0.5 * camera.frame[1] + y) / camera.frame[1]);
    }
}

The code is an exact translation of the explained above. 该代码是上述内容的精确翻译。 The only additional operation is commented: a dot product is used to check whether the point to be projected lies on the left or the right side of the camera's frame center. 注释了唯一的附加操作:点积用于检查要投影的点是在相机镜框中心的左侧还是右侧。 This is discussed here Determining if one 2D vector is to the right or left of another . 这里讨论确定一个2D向量是在另一个向量的右边还是左边 Any clues on where the mistake may be? 关于错误可能在哪里的任何线索? Here I paste what is needed to test the code. 在这里,我粘贴了测试代码所需的内容。

Main.java Main.java

import javax.swing.JFrame;

public class Main {

    public static void main(String[] args) {

        Universe universe = new Universe();

        JFrame frame = new JFrame("3D Projection");

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(universe);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);

        universe.loop();
    }
}

Camera.java Camera.java

public class Camera {

    // both measures in meters
    public final double focalLength = 50e-3;
    public final double[] frame = {36e-3, 24e-3};

    public double[] position;

    public Camera(double x, double y, double z) {

        position = new double[] {x, y, z};
    }
}

Universe.java Universe.java

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JPanel;

public class Universe extends JPanel {

private int screenW;
private int screenH;
private int[][] points2D;

private double[] center;
private double[][] points3D;

private Camera camera;  

public Universe() {

    screenW = 864;
    screenH = 576;

    setPreferredSize(new Dimension(screenW, screenH));

    points2D = new int[8][2];

    center = new double[] {0, 0, 0};

    camera = new Camera(0, 0, 10);

    points3D = new double[][] {{1, 1, 1},
                                {1, 1, -1},
                                {1, -1, 1},
                                {1, -1, -1},
                                {-1, 1, 1},
                                {-1, 1, -1},
                                {-1, -1, 1},
                                {-1, -1, -1}};
}

public void paint(Graphics g) {

    g.setColor(new Color(0, 0, 0));
    g.fillRect(0, 0, screenW, screenH);

    g.setColor(new Color(255, 255, 255));
    g.drawLine(points2D[0][0], points2D[0][1], points2D[1][0], points2D[1][1]);
    g.drawLine(points2D[2][0], points2D[2][1], points2D[3][0], points2D[3][1]);
    g.drawLine(points2D[4][0], points2D[4][1], points2D[5][0], points2D[5][1]);
    g.drawLine(points2D[6][0], points2D[6][1], points2D[7][0], points2D[7][1]);
    g.drawLine(points2D[1][0], points2D[1][1], points2D[5][0], points2D[5][1]);
    g.drawLine(points2D[0][0], points2D[0][1], points2D[4][0], points2D[4][1]);
    g.drawLine(points2D[3][0], points2D[3][1], points2D[7][0], points2D[7][1]);
    g.drawLine(points2D[2][0], points2D[2][1], points2D[6][0], points2D[6][1]);
    g.drawLine(points2D[0][0], points2D[0][1], points2D[2][0], points2D[2][1]);
    g.drawLine(points2D[1][0], points2D[1][1], points2D[3][0], points2D[3][1]);
    g.drawLine(points2D[5][0], points2D[5][1], points2D[7][0], points2D[7][1]);
    g.drawLine(points2D[4][0], points2D[4][1], points2D[6][0], points2D[6][1]);
}

public void loop() {

    double t = 0;
    double dt = 0.02;

    while(true) {

        try {
            Thread.sleep(50);
        } catch(InterruptedException ex) {
            Thread.currentThread().interrupt();
        }

        camera.position[0] = 10 * Math.sin(t % (2 * Math.PI));
        camera.position[2] = 10 * Math.cos(t % (2 * Math.PI));

        project(points3D, points2D);

        repaint();
        t += dt;
    }
}

private void project(double[][] points3D, int[][] points2D) {

    double x;
    double y;
    double angle;
    double camToPoint2;
    double camToCenter2;
    double centerToPoint2;
    double[] camToCenter;
    double[] centerToPoint;

    for(int i = 0; i < points3D.length; i++) {

        // x's projection

        camToCenter = new double[] {center[0]-camera.position[0], center[2]-camera.position[2]};
        centerToPoint = new double[] {points3D[i][0]-center[0], points3D[i][2]-center[2]};

        camToCenter2 = camToCenter[0]*camToCenter[0] + camToCenter[1]*camToCenter[1];
        centerToPoint2 = centerToPoint[0]*centerToPoint[0] + centerToPoint[1]*centerToPoint[1];
        camToPoint2 = (points3D[i][0]-camera.position[0])*(points3D[i][0]-camera.position[0]) +
                        (points3D[i][2]-camera.position[2])*(points3D[i][2]-camera.position[2]);

        angle = Math.acos((camToCenter2 + camToPoint2 - centerToPoint2) /
                (2 * Math.sqrt(camToCenter2) * Math.sqrt(camToPoint2)));

        System.out.print(angle * (360/(2*Math.PI)) + " ");

        x = camera.focalLength * Math.tan(angle);
        x = camToCenter[0]*centerToPoint[1] + camToCenter[1]*centerToPoint[0] < 0 ? -x : x;

        points2D[i][0] = (int) (screenW * (0.5 * camera.frame[0] + x) / camera.frame[0]);

        // y's projection

        camToCenter = new double[] {center[1]-camera.position[1], center[2]-camera.position[2]};
        centerToPoint = new double[] {points3D[i][1]-center[1], points3D[i][2]-center[2]};

        camToCenter2 = camToCenter[0]*camToCenter[0] + camToCenter[1]*camToCenter[1];
        centerToPoint2 = centerToPoint[0]*centerToPoint[0] + centerToPoint[1]*centerToPoint[1];
        camToPoint2 = (points3D[i][1]-camera.position[1])*(points3D[i][1]-camera.position[1]) +
                        (points3D[i][2]-camera.position[2])*(points3D[i][2]-camera.position[2]);

        angle = Math.acos((camToCenter2 + camToPoint2 - centerToPoint2) /
                (2 * Math.sqrt(camToCenter2) * Math.sqrt(camToPoint2)));

        System.out.println(angle * (360/(2*Math.PI)));

        y = camera.focalLength * Math.tan(angle);
        y = camToCenter[0]*centerToPoint[1] + camToCenter[1]*centerToPoint[0] < 0 ? -y : y;

        points2D[i][1] = (int) (screenH * (0.5 * camera.frame[1] + y) / camera.frame[1]);
    }

    System.out.println();
}
}

I believe that your problem is that you've neglected to model camera pose. 我相信您的问题是您忽略了建模相机姿势的方法。 Picture a cube floating in a room somewhere. 想象一个漂浮在房间某处的立方体。 Got it? 得到它了? Now in your mind, draw a Cartesian coordinate system, rigidly attached to the cube, with the x- and z-axes in the horizontal plane, and the y-axis pointing toward the ceiling. 现在,您就可以绘制一个直角坐标系,该坐标系牢固地附着在立方体上,x轴和z轴位于水平面,y轴指向天花板。 Since the camera is the thing which is moving around in your example code, you may equally well choose to visualize the cube's coordinate system as being rigidly attached to the room if you would prefer. 由于摄像机是示例代码中正在移动的物体,因此,如果愿意,您也可以选择可视化立方体的坐标系,使其牢固地附着在房间上。

Now, picture the camera moving around, looking at the cube from different angles. 现在,给相机拍照,从不同角度看立方体。 Imagine a separate Cartesian coordinate system rigidly attached to that as well. 想象一下一个单独的直角坐标系也牢固地附着于此。 It has an x-axis that points out the right side of the camera, a y-axis that points out the top, and a z-axis that points out the back (ie, toward your face as you are looking through the viewfinder). 它的x轴指向相机的右侧,y轴指向顶部,而z轴指向背面(即,通过取景器看时朝向您的脸) 。 As you walk around the cube holding the camera, it has to rotate about the y-axis in order to keep facing the cube. 当您拿着相机绕着立方体走动时,它必须 y轴旋转以保持面对立方体。 Assuming you hold the camera upright, the camera's y-axis will always remain parallel with the cube's y-axis, but the relationship between the two x- and z-axes will change over time. 假设您直立握持相机,则相机的y轴将始终与立方体的y轴保持平行,但是两个x轴和z轴之间的关系会随着时间而变化。

This relationship, between the three axes of two Cartesian coordinate frames, may be thought of as the camera's "orientation" or "pose", and you are not currently modeling it. 两个笛卡尔坐标系的三个轴之间的这种关系可能被认为是相机的“方向”或“姿势”,而您目前尚未对其建模。 In order to model the pose, you need a 3X3 matrix. 为了对姿势建模,您需要一个3X3矩阵。 If you don't know much about the mathematics of coordinate frame rotations, I'd recommend that you study up here and here and here . 如果您对坐标系旋转的数学知识不太了解,建议您在这里这里这里学习

I've added the pose model as a 3X3 rotation matrix in your Camera class, and updated the Universe class to be able to take advantage of it. 我在您的Camera类中将姿势模型添加为3X3旋转矩阵,并更新了Universe类以能够利用它。 The Main class remains unchanged. Main类保持不变。 The new Camera class is here: 新的Camera类在这里:

public class Camera {

    // both measures in meters
    public final double focalLength = 50e-3;
    public final double[] frame = {36e-3, 24e-3};

    public double[] position;
    // The rotation vector gives the unit vector directions, in the coordinate
    // frame of the object, of each of the axes of the camera's coordinate
    // frames
    public double[][] rotation;

    public Camera(double[] pos, double[][] rot) {

        position = pos;
        rotation = rot;
    }
}

and the new Universe class is here: 新的Universe类在这里:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;

import javax.swing.JPanel;

public class Universe extends JPanel {

private int screenW;
private int screenH;
private int[][] points2D;

private double[] center;
private double[][] points3D;

private Camera camera;  

public Universe() {

    screenW = 864;
    screenH = 576;

    setPreferredSize(new Dimension(screenW, screenH));

    points2D = new int[8][2];

    center = new double[] {0, 0, 0};

    // Initialize the camera object with "placeholder" values, just to
    // reserve space in memory for two suitably sized arrays
    double[] initpos = new double[] {0, 0, 10};
    double[][] initrot = new double[][] {{1, 0, 0}, {0, 1, 0}, {0, 0, 1}};
    camera = new Camera(initpos, initrot);

    points3D = new double[][] {{1, 1, 1},
                                {1, 1, -1},
                                {1, -1, 1},
                                {1, -1, -1},
                                {-1, 1, 1},
                                {-1, 1, -1},
                                {-1, -1, 1},
                                {-1, -1, -1}};
}

public void paint(Graphics g) {

    g.setColor(new Color(0, 0, 0));
    g.fillRect(0, 0, screenW, screenH);

    g.setColor(new Color(255, 255, 255));
    g.drawLine(points2D[0][0], points2D[0][1], points2D[1][0], points2D[1][1]);
    g.drawLine(points2D[2][0], points2D[2][1], points2D[3][0], points2D[3][1]);
    g.drawLine(points2D[4][0], points2D[4][1], points2D[5][0], points2D[5][1]);
    g.drawLine(points2D[6][0], points2D[6][1], points2D[7][0], points2D[7][1]);
    g.drawLine(points2D[1][0], points2D[1][1], points2D[5][0], points2D[5][1]);
    g.drawLine(points2D[0][0], points2D[0][1], points2D[4][0], points2D[4][1]);
    g.drawLine(points2D[3][0], points2D[3][1], points2D[7][0], points2D[7][1]);
    g.drawLine(points2D[2][0], points2D[2][1], points2D[6][0], points2D[6][1]);
    g.drawLine(points2D[0][0], points2D[0][1], points2D[2][0], points2D[2][1]);
    g.drawLine(points2D[1][0], points2D[1][1], points2D[3][0], points2D[3][1]);
    g.drawLine(points2D[5][0], points2D[5][1], points2D[7][0], points2D[7][1]);
    g.drawLine(points2D[4][0], points2D[4][1], points2D[6][0], points2D[6][1]);
}

public void loop() {

    double t = 0;
    double dt = 0.02;

    while(true) {

        try {
            Thread.sleep(50);
        } catch(InterruptedException ex) {
            Thread.currentThread().interrupt();
        }

        camera.position[0] =  10 * Math.sin(t % (2 * Math.PI));
        camera.position[1] =   0;
        camera.position[2] =  10 * Math.cos(t % (2 * Math.PI));

        // The x unit vector of the camera plane coordinate frame, expressed
        // in the cube's coordinate frame
        camera.rotation[0][0] = Math.cos(t % (2 * Math.PI));
        camera.rotation[0][1] = 0;
        camera.rotation[0][2] = -Math.sin(t % (2 * Math.PI));
        // The y unit vector of the camera plane coordinate frame, expressed
        // in the cube's coordinate frame
        camera.rotation[1][0] = 0; 
        camera.rotation[1][1] = 1;
        camera.rotation[1][2] = 0;
        // Ditto, z unit vector
        camera.rotation[2][0] = Math.sin(t % (2 * Math.PI));
        camera.rotation[2][1] = 0;
        camera.rotation[2][2] = Math.cos(t % (2 * Math.PI));

        project(points3D, points2D);

        repaint();
        t += dt;
    }
}

private void project(double[][] points3D, int[][] points2D) {;

    for(int i = 0; i < points3D.length; i++) {

        double[] camToPoint = new double[3];
        double[] rotPoint   = new double[3];

        // You may visualize this operation as "shifting" the vertices of the
        // cube to some new translational offset within an unrotated camera
        // coordinate frame.
        for(int j = 0; j < 3; j++) {
            camToPoint[j]  = points3D[i][j] - camera.position[j];
        }

        // Picture this operation as "rotating" the camera by the correct
        // amount so that it will always be facing the cube, no matter what
        // the current absolute position of the camera is within the cube's
        // coordinate frame.  If you don't do this, then the cube will pan
        // across your view and back around behind the camera much like the
        // sun rotating through the sky over the course of one complete day/
        // night cycle.
        rotPoint = new double[] {0, 0, 0};
        for(int j = 0; j< 3; j++) {
            for(int k = 0; k < 3; k++) {
                rotPoint[j] += camera.rotation[j][k] * camToPoint[k];
            }
        }

        // Project the cube onto the camera plane.
        points2D[i][0] = (int) (screenW * (0.5 * camera.frame[0] +
                            camera.focalLength * rotPoint[0] /
                            rotPoint[2]) / camera.frame[0]);
        points2D[i][1] = (int) (screenH * (0.5 * camera.frame[1] +
                            camera.focalLength * rotPoint[1] /
                            rotPoint[2]) / camera.frame[1]);
    }
}
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM