c#实现透视投影

news/2024/11/15 4:15:23/

之前在研究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文库


http://www.ppmy.cn/news/193990.html

相关文章

java 梯形校正_高清投影神器 联想YOGA平板2 Pro评测

联想YOGA平板2 Pro集成了投影功能&#xff0c;这让其成为名副其实的高清神器&#xff0c;那么在实际使用中它的表现如何&#xff1f;且看评测揭晓。 高清投影神器 联想YOGA平板2 Pro评测 视频作为现在一种最普遍的娱乐方式&#xff0c;已然成为我们日常生活中不可缺少的一部分&…

投影仪上能安装摄像头上互动网课吗?保姆级投影仪上网课教程分享

如今越来越多的学生朋友加入了网课大军的队伍&#xff0c;网课设备也越来越齐全。从手机、电视慢慢增加&#xff0c;变成了手机、电视、平板、投影仪。投影仪作为新晋网课神器&#xff0c;可能大家还没有完完全全的了解它的宝藏&#xff0c;今天就让我们一起来看看怎么用手机电…

android 手机 平板同屏,酷乐视Q6投影仪Android手机/平板同屏方法汇总

酷乐视Q6投影仪Android手机/平板同屏方法汇总&#xff0c;详情如下&#xff1a; Q&#xff1a;华为手机如何同屏&#xff1f; 1)手机与投影机同时打开WiFi开关(是否连接网络均可)&#xff1b; 2)投影机点击主界面无线同屏图标 3)手机端操作如下所示&#xff1a; x6q4.png (157.…

3D全息投影制作教程

3D全息投影制作教程 1.材料和工具&#xff08;带*为非必要&#xff09; ①透明塑料片&#xff08;最小为20*20cm的正方形&#xff0c;材质要比较硬&#xff0c;最好用平板电脑的贴膜&#xff0c;外面买差不多就10块钱一张&#xff0c;稍厚一点的那种&#xff0c;高透明的效果…

android usb投影win10,分享win10投影到安卓平板上的方法

内容来源&#xff1a;系统家园 今天来聊聊一篇关于分享win10投影到安卓平板上的方法的文章,现在就为大家来简单介绍下分享win10投影到安卓平板上的方法,希望对各位小伙伴们有所帮助。 一、首先&#xff0c;准备给电脑和平板分别下载安装程序&#xff0c;分别是&#xff1a; 1.s…

android全息投影,超低成本手机全息3D投影制作教程

最近女神初音未来的演唱会搅动了很多屌丝宅男的心,而网上流传的初音3D全息投影又勾起了极客屌丝们动手的欲望,所以初音未来全息3D投影就降临到了民间,论坛上看到各路兄弟做的各种大小材质形状的初音未来全息3D投影设备模型,为了满足哥的一颗好奇心,哥也做了一个金字塔型的…

android win10 投屏,安卓Android手机或平板如何投屏到WIN10电脑? - 常见问题 - 服务支持 - 冠艺Guanyee,科技造就冠军品质...

安卓Android手机或平板如何投屏到WIN10电脑&#xff1f; 在我们日常办公使用的时候&#xff0c;有时需于将笔记本电脑屏幕投屏到大屏幕上&#xff0c;有时也需要将手机屏幕投屏到笔记本电脑上 这个时候很多人会不知道怎么做&#xff0c;其实可以直接通过Windows自带的功能来实现…

android usb投影win10,win10如何投影到安卓平板上

win10如何投影到安卓平板上?win10系统下的投影仪是我们经常会使用的东西&#xff0c;使用的时候有时用户需要进行投影设置&#xff0c;今天给大家带来了win10投影仪设置投影到安卓平板的方法&#xff0c;具体的一起来看看吧。 win10如何投影到安卓平板上 一、首先&#xff0c;…