之前在研究c++ opencv,通过掉opencv接口,实现三维坐标转到二维坐标,但是一直没有时间去搞。刚好最近项目需要,用到c#来实现透视投影,然后就细致研究了下。总体而言还是离不开小孔成像模型:
关键就是三个步骤:
- 世界坐标转相机局部坐标
- 相机坐标转为归一化坐标
得到的模型是
- 归一化坐标转像素坐标
下边得图片是我对整个过程得总结:
待会会给出源码,现在来看看每个步骤得关键步骤和代码。
世界坐标转相机局部坐标
public void Trans_World2Eye(_3Dpoint w, _3Dpoint e){/* 将物体转换到相机下,得到相对坐标 */w.x -= camera.from.x;w.y -= camera.from.y;w.z -= camera.from.z;/* Convert to eye coordinates using basis vectors *///用三个在初始化过程计算出来的基向量来表达物体坐标,//这时候e 为物体在相机坐标系下得坐标e.x = w.x * basisa.x + w.y * basisa.y + w.z * basisa.z; e.y = w.x * basisb.x + w.y * basisb.y + w.z * basisb.z;e.z = w.x * basisc.x + w.y * basisc.y + w.z * basisc.z;}
代码中,basisa、basisb、basisc是相机坐标系得三个基向量,是这样求解得:
//view direction vectorbasisb.x = camera.to.x - camera.from.x;basisb.y = camera.to.y - camera.from.y;basisb.z = camera.to.z - camera.from.z;//单位化3Normalise(basisb);///向量叉乘,得到第三个垂直向量basisa = CrossProduct(camera.up, basisb);Normalise(basisa);/* Are the up vector and view direction colinear */if (EqualVertex(basisa, origin)){return (false);}basisc = CrossProduct(basisb, basisa);
得到三个基向量。
相机坐标转归一化坐标
public void Trans_Eye2Norm(_3Dpoint e, _3Dpoint n){double d;if (camera.projection == 0){d = camera.zoom / e.y;n.x = d * e.x / tanthetah;n.y = e.y;n.z = d * e.z / tanthetav;}else{n.x = camera.zoom * e.x / tanthetah;n.y = e.y;n.z = camera.zoom * e.z / tanthetav;}}
这里输入了一些相机参数,个人得理解是类似下图:
参数化对于计算得到三维向量得两个方向进行了归一化,这里略微有点不同,是因为后边转像素坐标时候,y轴忽略掉了,不参与计算。
归一化坐标转像素坐标
像素坐标坐标原点默认是左上角,因此转换了之后需要进行平移操作。
public void Trans_Norm2Screen(_3Dpoint norm, _2Dpoint projected){//MessageBox.Show("the value of are");projected.h = Convert.ToInt32(screen.center.h - screen.size.h * norm.x / 2);projected.v = Convert.ToInt32(screen.center.v - screen.size.v * norm.z / 2);}
核心代码
关键思路和核心代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;namespace Graphics3Dto2D
{class Projection{private _3Dpoint origin;private _3Dpoint e1, e2, n1, n2;private Camera camera;private Screen screen;private double tanthetah, tanthetav;private _3Dpoint basisa, basisb, basisc;private double EPSILON;private double DTOR;// 0.01745329252public _2Dpoint p1;public _2Dpoint p2;public Projection(){EPSILON = 0.001;DTOR = 0.01745329252;camera = new Camera();screen = new Screen();origin = new _3Dpoint();basisa = new _3Dpoint();basisb = new _3Dpoint();basisc = new _3Dpoint();p1 = new _2Dpoint();p2 = new _2Dpoint();e1 = new _3Dpoint();e2 = new _3Dpoint();n1 = new _3Dpoint();n2 = new _3Dpoint();if (Trans_Initialise() != true){MessageBox.Show("Error in initializing variable");}}public bool Trans_Initialise(){#region 确保up from to 都有意义/* Is the camera position and view vector coincident ? 摄像机位置和视图向量是否一致(判断to和from是否重叠,重叠就不给予接下去得计算,没有意义)*/if (EqualVertex(camera.to, camera.from)){return (false);}/* 是否设置了up向量 */if (EqualVertex(camera.up, origin)){return (false);}#endregion//view direction vectorbasisb.x = camera.to.x - camera.from.x;basisb.y = camera.to.y - camera.from.y;basisb.z = camera.to.z - camera.from.z;//单位化3Normalise(basisb);///向量叉乘,得到第三个垂直向量basisa = CrossProduct(camera.up, basisb);Normalise(basisa);/* Are the up vector and view direction colinear */if (EqualVertex(basisa, origin)){return (false);}basisc = CrossProduct(basisb, basisa);/*成像平面合法 ? */if (camera.angleh < EPSILON || camera.anglev < EPSILON){return (false);}/* 计算相机光圈静校正,注意:角度为度数 */tanthetah = Math.Tan(camera.angleh * DTOR / 2);tanthetav = Math.Tan(camera.anglev * DTOR / 2);/* 合法的镜头缩放 ? */if (camera.zoom < EPSILON){return (false);}/* 裁剪平面合法 ? */if (camera.front < 0 || camera.back < 0 || camera.back <= camera.front){return (false);}return true;}public void Trans_World2Eye(_3Dpoint w, _3Dpoint e){/* 将物体转换到相机下,得到相对坐标 */w.x -= camera.from.x;w.y -= camera.from.y;w.z -= camera.from.z;/* Convert to eye coordinates using basis vectors *///用三个在初始化过程计算出来的基向量来表达物体坐标,//这时候e 为物体在相机坐标系下得坐标e.x = w.x * basisa.x + w.y * basisa.y + w.z * basisa.z; e.y = w.x * basisb.x + w.y * basisb.y + w.z * basisb.z;e.z = w.x * basisc.x + w.y * basisc.y + w.z * basisc.z;}public bool Trans_ClipEye(_3Dpoint e1, _3Dpoint e2){double mu;//判断是否在切平面前后,从而判断是否能看观测到改物体/* Is the vector totally in front of the front cutting plane ? */if (e1.y <= camera.front && e2.y <= camera.front){return (false);}/* Is the vector totally behind the back cutting plane ? */if (e1.y >= camera.back && e2.y >= camera.back){return (false);}/* Is the vector partly in front of the front cutting plane ? */if ((e1.y < camera.front && e2.y > camera.front) ||(e1.y > camera.front && e2.y < camera.front)){mu = (camera.front - e1.y) / (e2.y - e1.y);if (e1.y < camera.front){e1.x = e1.x + mu * (e2.x - e1.x);e1.z = e1.z + mu * (e2.z - e1.z);e1.y = camera.front;}else{e2.x = e1.x + mu * (e2.x - e1.x);e2.z = e1.z + mu * (e2.z - e1.z);e2.y = camera.front;}}/* Is the vector partly behind the back cutting plane ? */if ((e1.y < camera.back && e2.y > camera.back) ||(e1.y > camera.back && e2.y < camera.back)){mu = (camera.back - e1.y) / (e2.y - e1.y);if (e1.y < camera.back){e2.x = e1.x + mu * (e2.x - e1.x);e2.z = e1.z + mu * (e2.z - e1.z);e2.y = camera.back;}else{e1.x = e1.x + mu * (e2.x - e1.x);e1.z = e1.z + mu * (e2.z - e1.z);e1.y = camera.back;}}return (true);}public void Trans_Eye2Norm(_3Dpoint e, _3Dpoint n){double d;if (camera.projection == 0){d = camera.zoom / e.y;n.x = d * e.x / tanthetah;n.y = e.y;n.z = d * e.z / tanthetav;}else{n.x = camera.zoom * e.x / tanthetah;n.y = e.y;n.z = camera.zoom * e.z / tanthetav;}}public bool Trans_ClipNorm(_3Dpoint n1, _3Dpoint n2){double mu;/* Is the line segment totally right of x = 1 ? */if (n1.x >= 1 && n2.x >= 1)return (false);/* Is the line segment totally left of x = -1 ? */if (n1.x <= -1 && n2.x <= -1)return (false);/* Does the vector cross x = 1 ? */if ((n1.x > 1 && n2.x < 1) || (n1.x < 1 && n2.x > 1)){mu = (1 - n1.x) / (n2.x - n1.x);if (n1.x < 1){n2.z = n1.z + mu * (n2.z - n1.z);n2.x = 1;}else{n1.z = n1.z + mu * (n2.z - n1.z);n1.x = 1;}}/* Does the vector cross x = -1 ? */if ((n1.x < -1 && n2.x > -1) || (n1.x > -1 && n2.x < -1)){mu = (-1 - n1.x) / (n2.x - n1.x);if (n1.x > -1){n2.z = n1.z + mu * (n2.z - n1.z);n2.x = -1;}else{n1.z = n1.z + mu * (n2.z - n1.z);n1.x = -1;}}/* Is the line segment totally above z = 1 ? */if (n1.z >= 1 && n2.z >= 1)return (false);/* Is the line segment totally below z = -1 ? */if (n1.z <= -1 && n2.z <= -1)return (false);/* Does the vector cross z = 1 ? */if ((n1.z > 1 && n2.z < 1) || (n1.z < 1 && n2.z > 1)){mu = (1 - n1.z) / (n2.z - n1.z);if (n1.z < 1){n2.x = n1.x + mu * (n2.x - n1.x);n2.z = 1;}else{n1.x = n1.x + mu * (n2.x - n1.x);n1.z = 1;}}/* Does the vector cross z = -1 ? */if ((n1.z < -1 && n2.z > -1) || (n1.z > -1 && n2.z < -1)){mu = (-1 - n1.z) / (n2.z - n1.z);if (n1.z > -1){n2.x = n1.x + mu * (n2.x - n1.x);n2.z = -1;}else{n1.x = n1.x + mu * (n2.x - n1.x);n1.z = -1;}}return (true);}public void Trans_Norm2Screen(_3Dpoint norm, _2Dpoint projected){//MessageBox.Show("the value of are");projected.h = Convert.ToInt32(screen.center.h - screen.size.h * norm.x / 2);projected.v = Convert.ToInt32(screen.center.v - screen.size.v * norm.z / 2);}/// <summary>/// 投影点/// </summary>/// <returns></returns>public bool Trans_Point(_3Dpoint w1){//转换到相机坐标系下Trans_World2Eye(w1, e1);//判断相机是否能够看到物体if (Trans_ClipEye(e1, e2)){//相机坐标系转归一化平面Trans_Eye2Norm(e1, n1);//归一化平面转屏幕坐标Trans_Norm2Screen(n1, p1);}return true;}/// <summary>/// 投影线/// </summary>/// <param name="w1"></param>/// <param name="w2"></param>/// <returns></returns>public bool Trans_Line(_3Dpoint w1, _3Dpoint w2){//转换到相机坐标系下Trans_World2Eye(w1, e1);Trans_World2Eye(w2, e2);//判断相机是否能够看到物体if (Trans_ClipEye(e1, e2)){//相机坐标系转归一化平面 Trans_Eye2Norm(e1, n1);Trans_Eye2Norm(e2, n2);if (Trans_ClipNorm(n1, n2)){//归一化平面转屏幕坐标Trans_Norm2Screen(n1, p1);Trans_Norm2Screen(n2, p2);return (true);}}return (true);}public void Normalise(_3Dpoint v){double length;length = Math.Sqrt(v.x * v.x + v.y * v.y + v.z * v.z);v.x /= length;v.y /= length;v.z /= length;}public _3Dpoint CrossProduct(_3Dpoint p1, _3Dpoint p2){_3Dpoint p3;p3 = new _3Dpoint(0, 0, 0);p3.x = p1.y * p2.z - p1.z * p2.y;p3.y = p1.z * p2.x - p1.x * p2.z;p3.z = p1.x * p2.y - p1.y * p2.x;return p3;}public bool EqualVertex(_3Dpoint p1, _3Dpoint p2){if (Math.Abs(p1.x - p2.x) > EPSILON)return (false);if (Math.Abs(p1.y - p2.y) > EPSILON)return (false);if (Math.Abs(p1.z - p2.z) > EPSILON)return (false);return (true);}}
}
总结
十分感谢国外作者如此优秀得实现,才能让我看到这整个过程,并且实现在自己得程序当中,目前还有问题,但是看懂了原理也能够分析存在问题得原因。因此再次分享给大家。后续会上传源程序,并且增加了中文得解释,大家相互学习。
代码下载:(56条消息) Graphics3Dto2D.rar-互联网文档类资源-CSDN文库