algorithm - 图像失真算法的错误行为

标签 algorithm graphics 3d projection

我正在尝试创建一个程序,该程序从特定角度和位置接收表面照片,并生成平面等距投影的图像。例如,给定一张棋盘的照片

enter image description here

和相机的定位和属性信息,它可以重建未失真图案的一部分

enter image description here

我的方法分为两部分。第一部分是创建四条光线,来自相机,沿着视野的四个角。我计算这些光线与平面相交的位置,以形成相机可以看到的平面区域的四边形,如下所示:

enter image description here

第二部分是用带纹理的四边形渲染平面的同构投影。我将四边形分成两个三角形,然后对于渲染上的每个像素,我将笛卡尔坐标转换为相对于每个三角形的重坐标,然后将其转换回相对于占据照片一半的相应三角形的笛卡尔坐标,以便我可以尝试一种颜色。

(我知道这可以用 OpenGL 更有效地完成,但出于后勤原因我不想使用它。我也知道质量会因缺少插值而受到影响,这与此无关任务。)

我正在用一些数据测试程序,但渲染没有按预期进行。这是照片:

enter image description here

程序输出如下:

enter image description here

我认为问题出在四边形渲染中,因为我绘制了投影顶点的图形,它们看起来是正确的:

enter image description here

我绝不是计算机图形学专家,所以如果有人知道是什么导致了这个问题,我将不胜感激。相关代码如下:

public class ImageProjector {

    private static final EquationSystem ground = new EquationSystem(0, 1, 0, 0);

    private double fov;
    private double aspectRatio;
    private vec3d position;
    private double xAngle;
    private double yAngle;
    private double zAngle;

    public ImageProjector(double fov, double aspectRatio, vec3d position, double xAngle, double yAngle, double zAngle) {
        this.fov = fov;
        this.aspectRatio = aspectRatio;
        this.position = position;
        this.xAngle = xAngle;
        this.yAngle = yAngle;
        this.zAngle = zAngle;
    }

    public vec3d[] computeVertices() {
        return new vec3d[] {
                computeVertex(1, 1),
                computeVertex(1, -1),
                computeVertex(-1, -1),
                computeVertex(-1, 1)
        };
    }

    private vec3d computeVertex(int horizCoef, int vertCoef) {
        vec3d p2 = new vec3d(tan(fov / 2) * horizCoef, tan((fov / 2) / aspectRatio) * vertCoef, 1);
        p2 = p2.rotateXAxis(xAngle);
        p2 = p2.rotateYAxis(yAngle);
        p2 = p2.rotateZAxis(zAngle);
        if (p2.y > 0) {
            throw new RuntimeException("sky is visible to camera: " + p2);
        }
        p2 = p2.plus(position);
        //System.out.println("passing through " + p2);
        EquationSystem line = new LineBuilder(position, p2).build();
        return new vec3d(line.add(ground).solveVariables());
    }


}


public class barypoint {

    public barypoint(double u, double v, double w) {
        this.u = u;
        this.v = v;
        this.w = w;
    }

    public final double u;
    public final double v;
    public final double w;

    public barypoint(vec2d p, vec2d a, vec2d b, vec2d c) {
        vec2d v0 = b.minus(a);
        vec2d v1 = c.minus(a);
        vec2d v2 = p.minus(a);
        double d00 = v0.dotProduct(v0);
        double d01 = v0.dotProduct(v1);
        double d11 = v1.dotProduct(v1);
        double d20 = v2.dotProduct(v0);
        double d21 = v2.dotProduct(v1);
        double denom = d00 * d11 - d01 * d01;
        v = (d11 * d20 - d01 * d21) / denom;
        w = (d00 * d21 - d01 * d20) / denom;
        u = 1.0 - v - w;
    }

    public barypoint(vec2d p, Triangle triangle) {
        this(p, triangle.a, triangle.b, triangle.c);
    }

    public vec2d toCartesian(vec2d a, vec2d b, vec2d c) {
        return new vec2d(
                u * a.x + v * b.x + w * c.x,
                u * a.y + v * b.y + w * c.y
        );
    }

    public vec2d toCartesian(Triangle triangle) {
        return toCartesian(triangle.a, triangle.b, triangle.c);
    }

}


public class ImageTransposer {

    private BufferedImage source;
    private BufferedImage receiver;

    public ImageTransposer(BufferedImage source, BufferedImage receiver) {
        this.source = source;
        this.receiver = receiver;
    }

    public void transpose(Triangle sourceCoords, Triangle receiverCoords) {
        int xMin = (int) Double.min(Double.min(receiverCoords.a.x, receiverCoords.b.x), receiverCoords.c.x);
        int xMax = (int) Double.max(Double.max(receiverCoords.a.x, receiverCoords.b.x), receiverCoords.c.x);
        int yMin = (int) Double.min(Double.min(receiverCoords.a.y, receiverCoords.b.y), receiverCoords.c.y);
        int yMax = (int) Double.max(Double.max(receiverCoords.a.y, receiverCoords.b.y), receiverCoords.c.y);
        for (int x = xMin; x <= xMax; x++) {
            for (int y = yMin; y <= yMax; y++) {
                vec2d p = new vec2d(x, y);
                if (receiverCoords.contains(p) && p.x >= 0 && p.y >= 0 && p.x < receiver.getWidth() && y < receiver.getHeight()) {
                    barypoint bary = new barypoint(p, receiverCoords);
                    vec2d sp = bary.toCartesian(sourceCoords);
                    if (sp.x >= 0 && sp.y >= 0 && sp.x < source.getWidth() && sp.y < source.getHeight()) {
                        receiver.setRGB(x, y, source.getRGB((int) sp.x, (int) sp.y));
                    }
                }
            }
        }
    }

}

public class ProjectionRenderer {

    private String imagePath;
    private BufferedImage mat;
    private vec3d[] vertices;
    private vec2d pos;
    private double scale;
    private int width;
    private int height;

    public boolean error = false;

    public ProjectionRenderer(String image, BufferedImage mat, vec3d[] vertices, vec3d pos, double scale, int width, int height) {
        this.imagePath = image;
        this.mat = mat;
        this.vertices = vertices;
        this.pos = new vec2d(pos.x, pos.z);
        this.scale = scale;
        this.width = width;
        this.height = height;
    }

    public void run() {
        try {
            BufferedImage image = ImageIO.read(new File(imagePath));

            vec2d[] transVerts = Arrays.stream(vertices)
                    .map(v -> new vec2d(v.x, v.z))
                    .map(v -> v.minus(pos))
                    .map(v -> v.multiply(scale))
                    .map(v -> v.plus(new vec2d(mat.getWidth() / 2, mat.getHeight() / 2)))
                    // this fixes the image being upside down
                    .map(v -> new vec2d(v.x, mat.getHeight() / 2 + (mat.getHeight() / 2 - v.y)))
                    .toArray(vec2d[]::new);
            System.out.println(Arrays.toString(transVerts));

            Triangle sourceTri1 = new Triangle(
                    new vec2d(0, 0),
                    new vec2d(image.getWidth(), 0),
                    new vec2d(0, image.getHeight())
            );
            Triangle sourceTri2 = new Triangle(
                    new vec2d(image.getWidth(), image.getHeight()),
                    new vec2d(0, image.getHeight()),
                    new vec2d(image.getWidth(), 0)
            );
            Triangle destTri1 = new Triangle(
                    transVerts[3],
                    transVerts[0],
                    transVerts[2]
            );
            Triangle destTri2 = new Triangle(
                    transVerts[1],
                    transVerts[2],
                    transVerts[0]
            );

            ImageTransposer transposer = new ImageTransposer(image, mat);
            System.out.println("transposing " + sourceTri1 + " -> " + destTri1);
            transposer.transpose(sourceTri1, destTri1);
            System.out.println("transposing " + sourceTri2 + " -> " + destTri2);
            transposer.transpose(sourceTri2, destTri2);
        } catch (IOException e) {
            e.printStackTrace();
            error = true;
        }
    }

}

最佳答案

它不起作用的原因是因为您的 transpose 函数完全适用于 2D 坐标,因此它无法补偿 3D 透视图导致的图像失真。你已经有效地实现了一个 2D affine transformation .平行线保持平行,它们在 3D 透视变换下不平行。如果在三角形上的两点之间画一条直线,则可以通过线性插值重心坐标在它们之间进行线性插值,反之亦然。

要将 Z 考虑在内,您可以保留重心坐标方法,但在 sourceCoords 中为每个点提供一个 Z 坐标。诀窍是在 1/Z 值之间进行插值(可以在透视图像中进行线性插值)而不是对 Z 本身进行插值。因此,不是为每个点插值有效的纹理坐标,而是插值除以 Z 的纹理坐标,以及 Z 的倒数,然后使用重心系统对所有这些坐标进行插值。然后在进行纹理查找之前除以 Z 的倒数以获取纹理坐标。

你可以这样做(假设 a b c 包含一个额外的 z 坐标给出与相机的距离):

public vec3d toCartesianInvZ(vec3d a, vec3d b, vec3d c) {
    // put some asserts in to check for z = 0 to avoid div by zero
    return new vec3d(
            u * a.x/a.z + v * b.x/b.z + w * c.x/c.z,
            u * a.y/a.z + v * b.y/b.z + w * c.y/c.z,
            u * 1/a.z + v * 1/b.z + w * 1/c.z
    );
}

(您显然可以通过预先计算所有这些划分并存储在 sourceCoords 中,并在 3D 中进行常规重心插值来加速/简化此过程)

然后在 transpose 中调用它之后,除以 inv Z 得到纹理坐标:

vec3d spInvZ = bary.toCartesianInvZ(sourceCoords);
vec2d sp = new vec2d(spInvZ.x / spInvZ.z, spInvZ.y / spInvZ.z);

等您需要的 Z 坐标是 3D 空间中的点与相机位置在相机指向的方向上的距离。如果您没有通过其他方式获得它,您可以使用点积来计算它:

float z = point.subtract(camera_pos).dot(camera_direction);

等等

关于algorithm - 图像失真算法的错误行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42462146/

相关文章:

algorithm - 信心和支持的 Mahout 算法

algorithm - DAG 中的最小路径覆盖

c++ - OpenGL - 对象轮廓

delphi - 如何检查Shader Model 3.0支持?

python - 如何使用 Python 创建 3D 网格?

java - 查找数组中的对数,其中 A[i] * A[j] > A[i] + A[j]

java - 正则表达式太慢了吗?现实生活中简单的非正则表达式替代方案更好的例子

opengl - 关于 OpenGL 布局 std140 统一 block 中的数组

Java,根据自己的功能快速图像变形

algorithm - 需要 3D 旋转算法