图像失真算法

时间:2017-02-25 22:07:50

标签: 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;
        }
    }

}

1 个答案:

答案 0 :(得分:0)

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

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

你可以这样做(假设一个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);