从 4 个角创建透视网格
Create a grid in perspective from 4 corners
我正在尝试从它的 4 个角生成点网格。由于这个角可以自由放置,所以看起来网格是有透视的。
我在 Processing 中编写了以下代码,其中角按顺时针顺序排列(从左上角开始)
PVector[][] setGrid(PVector[] corners, int cols, int rows) {
PVector[][] grid = new PVector[rows][cols];
for(int y = 0; y < rows; y++) {
float fY = (float)y / (rows - 1);
PVector p1 = PVector.lerp(corners[0], corners[3], fY);
PVector p2 = PVector.lerp(corners[1], corners[2], fY);
for(int x = 0; x < cols; x++) {
grid[y][x] = PVector.lerp(p1, p2, (float)x / (cols-1));
}
}
return grid;
}
这会生成一个带有插值点的网格,但它不对应于透视网格。所有直线上的点都是等距的,而在透视中最近的点应该比最远的点更分开。
如果可能的话,我希望能在 Java/Processing
编辑
澄清我的答案。我定义了 4 个随机角点,我想得到所有创建 perspective deformed grid 的点。请注意,由于透视 dX1 != dX2 以及 dY1 != dY2 。我写的代码没有这个效果(我知道这个,但我不知道如何做我需要的)因为点被插值导致 dX1 = dX2 = ... = dXi 和 dY1 = dY2 = ... = dYi
我读过透视变换,但我不需要变换图像,我只需要获取 grid points coordinates。
在您的示例图像中,透视效果是通过保持沿不同长度的边缘的线数不变来实现的。这就是您的实现所做的,所以老实说我没有看到问题。
这是调用 setGrid() 的草图:
PVector[] corners;
void setup(){
size(150,100);
corners = new PVector[4];
corners[0] = new PVector(35,20);
corners[1] = new PVector(15,height-30);
corners[2] = new PVector(width-10,height-10);
corners[3] = new PVector(width-30,10);
noLoop();
}
void draw(){
background(255);
PVector[][] results = setGrid(corners, 9, 9);
for(PVector[] pvs : results){
for(PVector pv : pvs){
ellipse(pv.x,pv.y,5,5);
}
}
}
PVector[][] setGrid(PVector[] corners, int cols, int rows) {
PVector[][] grid = new PVector[rows][cols];
for(int y = 0; y < rows; y++) {
float fY = (float)y / (rows - 1);
PVector p1 = PVector.lerp(corners[0], corners[3], fY);
PVector p2 = PVector.lerp(corners[1], corners[2], fY);
for(int x = 0; x < cols; x++) {
grid[y][x] = PVector.lerp(p1, p2, (float)x / (cols-1));
}
}
return grid;
}
...结果看起来几乎与您的目标图像完全一样。如果您看到不同的东西,也许您正在创建边长非常相似的网格?
如果您想将透视投影到规则的梯形上——就像人行道向远方后退——那么请考虑以下方法:
我已经采用几何方法解决了这个问题:从角落识别网格消失点,并从翻译的 horizon 线进行插值。我为此 GridPerspective 创建了一个 class。
只有两个要求:
转角必须按顺时针顺序。
网格边不能平行(灭点到无穷大).
处理代码:
GridPerspective grid;
void setup() {
size(600, 600, P2D);
grid = new GridPerspective(10, 10);
}
void draw() {
background(0);
grid.draw();
}
void mouseClicked() {
grid.addCorner(new PVector(mouseX, mouseY));
}
public class GridPerspective {
int cols, rows;
PVector[] corners = new PVector[4];
int selC;
PVector[][] points;
public GridPerspective(int cols, int rows) {
this.cols = cols;
this.rows = rows;
}
public void addCorner(PVector corner) {
if(selC < 4) {
corners[selC++] = corner;
if(selC == 4) update();
}
}
public void update() {
if(corners[0] == null || corners[1] == null || corners[2] == null || corners[3] == null) return;
PVector[] vanishing = new PVector[] {
linesIntersection(corners[0], corners[3], corners[1], corners[2]),
linesIntersection(corners[0], corners[1], corners[3], corners[2])
};
PVector topHorizon = PVector.sub(vanishing[1], vanishing[0]);
PVector bottomHorizon = PVector.add(corners[3], topHorizon);
PVector[] bottomLimits = new PVector[] {
linesIntersection(corners[3], bottomHorizon, vanishing[0], corners[1]),
linesIntersection(corners[3], bottomHorizon, vanishing[1], corners[1])
};
points = new PVector[rows][cols];
for(int r = 0; r < rows; r++) {
PVector bpr = PVector.lerp(corners[3], bottomLimits[0], (float)r / (rows-1));
for(int c = 0; c < cols; c++) {
PVector bpc = PVector.lerp(corners[3], bottomLimits[1], (float)c / (cols-1));
points[r][c] = linesIntersection(bpr, vanishing[0], bpc, vanishing[1]);
}
}
}
public void draw() {
if(points != null) {
fill(255);
for(int r = 0; r < rows; r++) {
for(int c = 0; c < cols; c++) {
ellipse(points[r][c].x, points[r][c].y, 4, 4);
}
}
}
}
private PVector linesIntersection(PVector p1, PVector p2, PVector p3, PVector p4) {
float d = (p2.x-p1.x) * (p4.y - p3.y) - (p2.y-p1.y) * (p4.x - p3.x);
if(d == 0) return null;
return new PVector(p1.x+(((p3.x - p1.x) * (p4.y - p3.y) - (p3.y - p1.y) * (p4.x - p3.x)) / d)*(p2.x-p1.x), p1.y+(((p3.x - p1.x) * (p4.y - p3.y) - (p3.y - p1.y) * (p4.x - p3.x)) / d)*(p2.y-p1.y));
}
}
我正在尝试从它的 4 个角生成点网格。由于这个角可以自由放置,所以看起来网格是有透视的。
我在 Processing 中编写了以下代码,其中角按顺时针顺序排列(从左上角开始)
PVector[][] setGrid(PVector[] corners, int cols, int rows) {
PVector[][] grid = new PVector[rows][cols];
for(int y = 0; y < rows; y++) {
float fY = (float)y / (rows - 1);
PVector p1 = PVector.lerp(corners[0], corners[3], fY);
PVector p2 = PVector.lerp(corners[1], corners[2], fY);
for(int x = 0; x < cols; x++) {
grid[y][x] = PVector.lerp(p1, p2, (float)x / (cols-1));
}
}
return grid;
}
这会生成一个带有插值点的网格,但它不对应于透视网格。所有直线上的点都是等距的,而在透视中最近的点应该比最远的点更分开。
如果可能的话,我希望能在 Java/Processing
编辑
澄清我的答案。我定义了 4 个随机角点,我想得到所有创建 perspective deformed grid 的点。请注意,由于透视 dX1 != dX2 以及 dY1 != dY2 。我写的代码没有这个效果(我知道这个,但我不知道如何做我需要的)因为点被插值导致 dX1 = dX2 = ... = dXi 和 dY1 = dY2 = ... = dYi
我读过透视变换,但我不需要变换图像,我只需要获取 grid points coordinates。
在您的示例图像中,透视效果是通过保持沿不同长度的边缘的线数不变来实现的。这就是您的实现所做的,所以老实说我没有看到问题。
这是调用 setGrid() 的草图:
PVector[] corners;
void setup(){
size(150,100);
corners = new PVector[4];
corners[0] = new PVector(35,20);
corners[1] = new PVector(15,height-30);
corners[2] = new PVector(width-10,height-10);
corners[3] = new PVector(width-30,10);
noLoop();
}
void draw(){
background(255);
PVector[][] results = setGrid(corners, 9, 9);
for(PVector[] pvs : results){
for(PVector pv : pvs){
ellipse(pv.x,pv.y,5,5);
}
}
}
PVector[][] setGrid(PVector[] corners, int cols, int rows) {
PVector[][] grid = new PVector[rows][cols];
for(int y = 0; y < rows; y++) {
float fY = (float)y / (rows - 1);
PVector p1 = PVector.lerp(corners[0], corners[3], fY);
PVector p2 = PVector.lerp(corners[1], corners[2], fY);
for(int x = 0; x < cols; x++) {
grid[y][x] = PVector.lerp(p1, p2, (float)x / (cols-1));
}
}
return grid;
}
...结果看起来几乎与您的目标图像完全一样。如果您看到不同的东西,也许您正在创建边长非常相似的网格?
如果您想将透视投影到规则的梯形上——就像人行道向远方后退——那么请考虑以下方法:
我已经采用几何方法解决了这个问题:从角落识别网格消失点,并从翻译的 horizon 线进行插值。我为此 GridPerspective 创建了一个 class。
只有两个要求:
转角必须按顺时针顺序。
网格边不能平行(灭点到无穷大).
处理代码:
GridPerspective grid;
void setup() {
size(600, 600, P2D);
grid = new GridPerspective(10, 10);
}
void draw() {
background(0);
grid.draw();
}
void mouseClicked() {
grid.addCorner(new PVector(mouseX, mouseY));
}
public class GridPerspective {
int cols, rows;
PVector[] corners = new PVector[4];
int selC;
PVector[][] points;
public GridPerspective(int cols, int rows) {
this.cols = cols;
this.rows = rows;
}
public void addCorner(PVector corner) {
if(selC < 4) {
corners[selC++] = corner;
if(selC == 4) update();
}
}
public void update() {
if(corners[0] == null || corners[1] == null || corners[2] == null || corners[3] == null) return;
PVector[] vanishing = new PVector[] {
linesIntersection(corners[0], corners[3], corners[1], corners[2]),
linesIntersection(corners[0], corners[1], corners[3], corners[2])
};
PVector topHorizon = PVector.sub(vanishing[1], vanishing[0]);
PVector bottomHorizon = PVector.add(corners[3], topHorizon);
PVector[] bottomLimits = new PVector[] {
linesIntersection(corners[3], bottomHorizon, vanishing[0], corners[1]),
linesIntersection(corners[3], bottomHorizon, vanishing[1], corners[1])
};
points = new PVector[rows][cols];
for(int r = 0; r < rows; r++) {
PVector bpr = PVector.lerp(corners[3], bottomLimits[0], (float)r / (rows-1));
for(int c = 0; c < cols; c++) {
PVector bpc = PVector.lerp(corners[3], bottomLimits[1], (float)c / (cols-1));
points[r][c] = linesIntersection(bpr, vanishing[0], bpc, vanishing[1]);
}
}
}
public void draw() {
if(points != null) {
fill(255);
for(int r = 0; r < rows; r++) {
for(int c = 0; c < cols; c++) {
ellipse(points[r][c].x, points[r][c].y, 4, 4);
}
}
}
}
private PVector linesIntersection(PVector p1, PVector p2, PVector p3, PVector p4) {
float d = (p2.x-p1.x) * (p4.y - p3.y) - (p2.y-p1.y) * (p4.x - p3.x);
if(d == 0) return null;
return new PVector(p1.x+(((p3.x - p1.x) * (p4.y - p3.y) - (p3.y - p1.y) * (p4.x - p3.x)) / d)*(p2.x-p1.x), p1.y+(((p3.x - p1.x) * (p4.y - p3.y) - (p3.y - p1.y) * (p4.x - p3.x)) / d)*(p2.y-p1.y));
}
}