c#实现透视投影

之前在研究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 vector
            basisb.x = camera.to.x - camera.from.x;
            basisb.y = camera.to.y - camera.from.y;
            basisb.z = camera.to.z - camera.from.z;
            //单位化3
            Normalise(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.01745329252
        public _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 vector
            basisb.x = camera.to.x - camera.from.x;
            basisb.y = camera.to.y - camera.from.y;
            basisb.z = camera.to.z - camera.from.z;
            //单位化3
            Normalise(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文库

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值