首页 > 代码库 > C# 透视效果

C# 透视效果

        最近在写一款软件,其中一个功能就是透视,在网上搜索了很久,翻阅了很多资料。第一份资料呈现给我的就是【猴健居士】写的一篇关于【C# 图片自由变换 任意扭曲】,我很想去实现那种效果,只是才疏学浅,无法明白其思路,即使有贴出来了代码,我也没有做到,很惭愧。

        而网上其他的方式多半是与【OpenCV】结合,这种很简单,却让我头大,这对于我来说无意是接触一种新的语言,奈何时间不会那么充裕。

        最终我还是决定,以C#自身的情况,来实现这种效果,我自己是无法办到的,就想到了第一次看到的那个文章,的确是用C#语言写的。只是我无法还原,于是找到了这个朋友,希望他可以帮忙。

        那朋友知道的来意后,帮了我,点出了我的问题,并告诉了这段代码的存在什么问题。我在得知后,很高兴,也很感谢,先能实现再说,具体有什么问题,在实现后,应该会逐步克服。

现在贴出我在得到代码后的变动。

1、之前代码会有黑色背景,我改动过避免了这种情况,毕竟有些图片带着透明效果的

2、在循环中增加了continue,可以缩短图片的转换时间,对于有透明效果的图片很明显。

3、代码了用了重载和函数,增加了使用型

4、去掉了一部分代码,我不知道那些代码会达到什么效果,只是在代码中意义不大,暂时放弃了。

图片效果如下

 技术分享

 

技术分享

我用一个类Perspective 来实现完成透视的效果,代码如下:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;

namespace ImageFile
{
    /// <summary>
    /// 透视
    /// </summary>
    public class Perspective
    {
        private int roundup(double a) { if (AafAbs(a - round(a)) < 1e-9) return round(a); else if ((int)a > a) return (int)a; else return (int)a + 1; }
        private int rounddown(double a) { if (AafAbs(a - round(a)) < 1e-9) return round(a); else if ((int)a < a) return (int)a; else return (int)a - 1; }
        private int round(double a) { return (int)(a + 0.5); }
        private byte byterange(double a) { int b = round(a); if (b <= 0) return 0; else if (b >= 255) return 255; else return (byte)b; }
        private double AafAbs(double a) { return (((a) < 0) ? (-(a)) : (a)); }
        private double aaf_min(double a, double b) { if (a < b) return a; else return b; }
        private double aaf_max(double a, double b) { if (a > b) return a; else return b; }

        private AafPnt[] pixelgrid;
        private AafPnt[] polyoverlap;
        private AafPnt[] polysorted;
        private AafPnt[] corners;

        private int outstartx;
        private int outstarty;
        private int outwidth;
        private int outheight;

        private Bitmap prepImg;
        private List<double> xDouList;
        private List<double> yDouList;


        int polyoverlapsize;
        int polysortedsize;

        int[] ja = new int[] { 1, 2, 3, 0 };

        public Perspective() {
       
        }

        public Perspective(double[] xcorner, double[] ycorner)
        {
            xDouList = xcorner.ToList();
            yDouList = ycorner.ToList();
        }
        public Perspective(Bitmap src,double[] xcorner, double[] ycorner)
        {
            prepImg = src;
            xDouList = xcorner.ToList();
            yDouList = ycorner.ToList();
        }
        public Bitmap CreateTransform()
        {
            return CreateTransform(prepImg);
        }

        public Bitmap CreateTransform(Bitmap src)
        {
            return CreateTransform(src, xDouList, yDouList);
        }

        public Bitmap CreateTransform(Bitmap src, List<double> xcorner, List<double> ycorner)
        {
            int right = 0, bottom = 0;

            //主要是根据新图片的坐标,计算出图片的宽和高,构造目标图片的Bitmap的
            double offx = xcorner[0];
            double offy = ycorner[0];
            for (int i = 1; i < 4; i++)
            {
                if (xcorner[i] < offx) offx = xcorner[i];
                if (ycorner[i] < offy) offy = ycorner[i];
            }

            for (int i = 0; i < 4; i++)
            {
                xcorner[i] -= offx;
                ycorner[i] -= offy;
                if (roundup(xcorner[i]) > right) right = roundup(xcorner[i]);
                if (roundup(ycorner[i]) > bottom) bottom = roundup(ycorner[i]);
            }
            Bitmap dst = new Bitmap(right, bottom);
            Transform(src, dst, xcorner, ycorner);
            return dst;
        }

        private void Transform(Bitmap src, Bitmap dst, List<double> xcorner, List<double> ycorner)
        {
            //Make sure the coordinates are valid
            if (xcorner.Count != 4 || ycorner.Count != 4) {
                return;
            }
            //Load the src bitmaps information
            //create the intial arrays
            //根据原图片生成一个比原图宽高多一个单位的图片,
            //这个矩阵就是用来记录转换后各个像素点左上角的坐标
            pixelgrid = new AafPnt[(src.Width + 1) * (src.Height + 1)];
            polyoverlap = new AafPnt[16];
            polysorted = new AafPnt[16];
            corners = new AafPnt[4];

            //Load the corners array
            double[] dx = { 0.0, 1.0, 1.0, 0.0 };
            double[] dy = { 0.0, 0.0, 1.0, 1.0 };
            for (int i = 0; i < 4; i++)
            {
                corners[i].x = dx[i];
                corners[i].y = dy[i];
            }

            //Find the rectangle of dst to draw to
            outstartx = rounddown(xcorner[0]);
            outstarty = rounddown(ycorner[0]);
            outwidth = 0;
            outheight = 0;
            //这里计算出变换后起始点的坐标
            for (int i = 1; i < 4; i++)
            {
                if (rounddown(xcorner[i]) < outstartx) outstartx = rounddown(xcorner[i]);
                if (rounddown(ycorner[i]) < outstarty) outstarty = rounddown(ycorner[i]);
            }
            for (int i = 0; i < 4; i++)
            {
                if (roundup(xcorner[i] - outstartx) > outwidth) outwidth = roundup(xcorner[i] - outstartx);
                if (roundup(ycorner[i] - outstarty) > outheight) outheight = roundup(ycorner[i] - outstarty);
            }


            //fill out pixelgrid array
            //计算出理想目标图片中各个“像素格”中的左上角的坐标
            if (CreateGrid(src, xcorner, ycorner))
            {
                //Do the transformation
                //进行转换
                DoTransform(src, dst);
            }
        }

        private bool CreateGrid(Bitmap src, List<double> xcorner, List<double> ycorner)
        {
            //mmm geometry
            double[] sideradius = new double[4];
            double[] sidecos = new double[4];
            double[] sidesin = new double[4];

            //First we find the radius, cos, and sin of each side of the polygon created by xcorner and ycorner
            int j;
            for (int i = 0; i < 4; i++)
            {
                j = ja[i];
                sideradius[i] = Math.Sqrt((xcorner[i] - xcorner[j]) * (xcorner[i] - xcorner[j]) + (ycorner[i] - ycorner[j]) * (ycorner[i] - ycorner[j]));
                sidecos[i] = (xcorner[j] - xcorner[i]) / sideradius[i];
                sidesin[i] = (ycorner[j] - ycorner[i]) / sideradius[i];
            }

            //Next we create two lines in Ax + By = C form
            for (int x = 0; x < src.Width + 1; x++)
            {
                double topdist = ((double)x / (src.Width)) * sideradius[0];//每个像素点变换后的坐标点
                double ptxtop = xcorner[0] + topdist * sidecos[0];
                double ptytop = ycorner[0] + topdist * sidesin[0];

                double botdist = (1.0 - (double)x / (src.Width)) * sideradius[2];
                double ptxbot = xcorner[2] + botdist * sidecos[2];
                double ptybot = ycorner[2] + botdist * sidesin[2];

                double Ah = ptybot - ptytop;
                double Bh = ptxtop - ptxbot;
                double Ch = Ah * ptxtop + Bh * ptytop;//叉乘

                for (int y = 0; y < src.Height + 1; y++)
                {
                    double leftdist = (1.0 - (double)y / (src.Height)) * sideradius[3];
                    double ptxleft = xcorner[3] + leftdist * sidecos[3];
                    double ptyleft = ycorner[3] + leftdist * sidesin[3];

                    double rightdist = ((double)y / (src.Height)) * sideradius[1];
                    double ptxright = xcorner[1] + rightdist * sidecos[1];
                    double ptyright = ycorner[1] + rightdist * sidesin[1];

                    double Av = ptyright - ptyleft;
                    double Bv = ptxleft - ptxright;
                    double Cv = Av * ptxleft + Bv * ptyleft;

                    //Find where the lines intersect and store that point in the pixelgrid array
                    double det = Ah * Bv - Av * Bh;
                    if (AafAbs(det) < 1e-9)
                    {
                        return false;
                    }
                    else
                    {
                        int ind = x + y * (src.Width + 1);
                        pixelgrid[ind].x = (Bv * Ch - Bh * Cv) / det;
                        pixelgrid[ind].y = (Ah * Cv - Av * Ch) / det;
                    }
                }
            }
            //Yayy we didn‘t fail
            return true;
        }

        private void DoTransform(Bitmap src, Bitmap dst)
        {

            //Get source bitmap‘s information
            if (src =http://www.mamicode.com/= null) return;

            //Create the source dib array and the dstdib array
            aaf_dblrgbquad[] dbldstdib = new aaf_dblrgbquad[outwidth * outheight];
            for (int i = 0; i < dbldstdib.Length; i++)
                dbldstdib[i] = new aaf_dblrgbquad();

            //Create polygon arrays
            AafPnt[] p = new AafPnt[4];
            AafPnt[] poffset = new AafPnt[4];
            //Loop through the source‘s pixels
            //遍历原图(实质上是pixelgrid)各个点
            for (int x = 0; x < src.Width; x++)
            {
                for (int y = 0; y < src.Height; y++)
                {
                    //取当前点 下一点 右一点 斜右下角点 四点才组成一个四边形
                    //这个四边形是原图像的一个像素点
                    //Construct the source pixel‘s rotated polygon from pixelgrid
                    p[0] = pixelgrid[x + y * (src.Width + 1)];
                    p[1] = pixelgrid[(x + 1) + y * (src.Width + 1)];
                    p[2] = pixelgrid[(x + 1) + (y + 1) * (src.Width + 1)];
                    p[3] = pixelgrid[x + (y + 1) * (src.Width + 1)];

                    //Find the scan area on the destination‘s pixels
                    int mindx = int.MaxValue;
                    int mindy = int.MaxValue;
                    int maxdx = int.MinValue;
                    int maxdy = int.MinValue;
                    for (int i = 0; i < 4; i++)
                    {
                        if (rounddown(p[i].x) < mindx) mindx = rounddown(p[i].x);
                        if (roundup(p[i].x) > maxdx) maxdx = roundup(p[i].x);
                        if (rounddown(p[i].y) < mindy) mindy = rounddown(p[i].y);
                        if (roundup(p[i].y) > maxdy) maxdy = roundup(p[i].y);
                    }

                    int SrcIndex = x + y * src.Width;
                    //遍历四边形包含了目标图几个像素点
                    //按照相交面积占整个像素的的百分比,把颜色按照该比例存放一个目标像素点颜色的数组中
                    //这里计算出来的颜色只是初步颜色,还没到最终结果
                    //loop through the scan area to find where source(x, y) overlaps with the destination pixels
                    for (int xx = mindx - 1; xx <= maxdx; xx++)
                    {
                        if (xx < 0 || xx >= dst.Width)
                            continue;
                        for (int yy = mindy - 1; yy <= maxdy; yy++)
                        {
                            if (yy < 0 || yy >= dst.Height)
                                continue;

                            //offset p and by (xx,yy) and put that into poffset
                            for (int i = 0; i < 4; i++)
                            {
                                poffset[i].x = p[i].x - xx;
                                poffset[i].y = p[i].y - yy;
                            }

                            //FIND THE OVERLAP *a whole lot of code pays off here*
                            //这里则是计算出覆盖了面积占当前像素的百分比
                            double dbloverlap = PixOverlap(poffset);
                            //按照百分比来为目标像素点累加颜色
                            //因为一个目标像素点有可能有几个原来像素的覆盖了
                            if (dbloverlap > 0)
                            {
                                int dstindex = xx + yy * outwidth;
                                int srcWidth = src.Width;
                                Color srcColor = new Color();
                                if (SrcIndex == 0)
                                {
                                    srcColor = src.GetPixel(0, 0);
                                }
                                else
                                {
                                    srcColor = src.GetPixel(SrcIndex % src.Width, SrcIndex / src.Width);
                                }
                                if (srcColor.Name != "0")
                                {
                                    //Add the rgb and alpha values in proportion to the overlap area
                                    dbldstdib[dstindex].Red += (double)((srcColor.R) * dbloverlap);
                                    dbldstdib[dstindex].Blue += (double)(srcColor.B) * dbloverlap;
                                    dbldstdib[dstindex].Green += (double)(srcColor.G) * dbloverlap;
                                    dbldstdib[dstindex].Alpha += dbloverlap;
                                }
                                else
                                {
                                    continue;
                                }
                            }
                            else {
                                continue;
                            }
                        }
                    }
                }
            }
            //Free memory no longer needed
            //Create final destination bits
            Color c = new Color();
            //这里是实际上真正像素点的颜色,并且填到了目标图片中去
            //Write to dstdib with the information stored in dbldstdib
            for (int x = 0; x < outwidth; x++)
            {
                if (x + outstartx >= dst.Width)
                    continue;
                for (int y = 0; y < outheight; y++)
                {
                    if (y + outstarty >= dst.Height) {
                        continue;
                    }
                       
                    int offindex = x + y * outwidth;
                    int dstindex = x + outstartx + (y + outstarty) * dst.Width;

                    int dstIndexX = dstindex / dst.Width;
                    int dstIndexY = dstindex % dst.Width;
                    if (dbldstdib[offindex].Alpha==0)
                    {
                        continue;
                    }
                    else{
                       
                        if (dbldstdib[offindex].Alpha > 1)
                        {
                            //handles wrap around for non-convex transformations
                            c = Color.FromArgb(byterange(dbldstdib[offindex].Red / dbldstdib[offindex].Alpha), byterange(dbldstdib[offindex].Green / dbldstdib[offindex].Alpha), byterange(dbldstdib[offindex].Blue / dbldstdib[offindex].Alpha));
                        }
                        else
                        {
                            //Color dstColor = dst.GetPixel(dstIndexX, dstIndexY);
                            c = Color.FromArgb(byterange(dbldstdib[offindex].Red + (1 - dbldstdib[offindex].Alpha) * c.R), byterange(dbldstdib[offindex].Green + (1 - dbldstdib[offindex].Alpha) * c.G), byterange(dbldstdib[offindex].Blue + (1 - dbldstdib[offindex].Alpha) * c.B));
                        }
                    }
                   
                    dst.SetPixel(dstIndexY, dstIndexX, c);
                }
            }
        }

        double PixOverlap(AafPnt[] p)
        {
            polyoverlapsize = 0;
            polysortedsize = 0;

            double minx, maxx, miny, maxy;
            int j;

            double z;

            for (int i = 0; i < 4; i++)
            {
                //Search for source points within the destination quadrolateral
                if (p[i].x >= 0 && p[i].x <= 1 && p[i].y >= 0 && p[i].y <= 1)
                    polyoverlap[polyoverlapsize++] = p[i];

                //Search for destination points within the source quadrolateral
                if (PtinConvexPolygon(p, corners[i]))
                    polyoverlap[polyoverlapsize++] = corners[i];

                //Search for line intersections
                j = ja[i];
                minx = aaf_min(p[i].x, p[j].x);
                miny = aaf_min(p[i].y, p[j].y);
                maxx = aaf_max(p[i].x, p[j].x);
                maxy = aaf_max(p[i].y, p[j].y);

                if (minx < 0.0 && 0.0 < maxx)
                {//Cross left
                    z = p[i].y - p[i].x * (p[i].y - p[j].y) / (p[i].x - p[j].x);
                    if (z >= 0.0 && z <= 1.0)
                    {
                        polyoverlap[polyoverlapsize].x = 0.0;
                        polyoverlap[polyoverlapsize++].y = z;
                    }
                }
                if (minx < 1.0 && 1.0 < maxx)
                {//Cross right
                    z = p[i].y + (1 - p[i].x) * (p[i].y - p[j].y) / (p[i].x - p[j].x);
                    if (z >= 0.0 && z <= 1.0)
                    {
                        polyoverlap[polyoverlapsize].x = 1.0;
                        polyoverlap[polyoverlapsize++].y = z;
                    }
                }
                if (miny < 0.0 && 0.0 < maxy)
                {//Cross bottom
                    z = p[i].x - p[i].y * (p[i].x - p[j].x) / (p[i].y - p[j].y);
                    if (z >= 0.0 && z <= 1.0)
                    {
                        polyoverlap[polyoverlapsize].x = z;
                        polyoverlap[polyoverlapsize++].y = 0.0;
                    }
                }
                if (miny < 1.0 && 1.0 < maxy)
                {//Cross top
                    z = p[i].x + (1 - p[i].y) * (p[i].x - p[j].x) / (p[i].y - p[j].y);
                    if (z >= 0.0 && z <= 1.0)
                    {
                        polyoverlap[polyoverlapsize].x = z;
                        polyoverlap[polyoverlapsize++].y = 1.0;
                    }
                }
            }

            //Sort the points and return the area
            SortPoints();
            return Area();
        }

        private double Area()
        {
            double ret = 0.0;
            //Loop through each triangle with respect to (0, 0) and add the cross multiplication
            for (int i = 0; i + 1 < polysortedsize; i++)
                ret += polysorted[i].x * polysorted[i + 1].y - polysorted[i + 1].x * polysorted[i].y;
            //Take the absolute value over 2
            return AafAbs(ret) / 2.0;
        }

        private void SortPoints()
        {
            //Why even bother?
            if (polyoverlapsize < 3)
                return;

            //polyoverlap is a triangle, points cannot be out of order
            if (polyoverlapsize == 3)
            {
                polysortedsize = polyoverlapsize - 1;
                polysorted[0].x = polyoverlap[1].x - polyoverlap[0].x;
                polysorted[0].y = polyoverlap[1].y - polyoverlap[0].y;
                polysorted[1].x = polyoverlap[2].x - polyoverlap[0].x;
                polysorted[1].y = polyoverlap[2].y - polyoverlap[0].y;
                return;
            }


            aaf_indll root = new aaf_indll();
            root.next = null;

            //begin sorting the points.  Note that the first element is left out and all other elements are offset by it‘s values
            for (int i = 1; i < polyoverlapsize; i++)
            {
                polyoverlap[i].x = polyoverlap[i].x - polyoverlap[0].x;
                polyoverlap[i].y = polyoverlap[i].y - polyoverlap[0].y;

                aaf_indll node = root;
                //Loop until the point polyoverlap[i] is can be sorted (counter) clockwiswe (I‘m not sure which way it‘s sorted)
                while (true)
                {
                    if (node.next != null)
                    {
                        if (polyoverlap[i].x * polyoverlap[node.next.ind].y - polyoverlap[node.next.ind].x * polyoverlap[i].y < 0)
                        {
                            //Insert point before this element
                            aaf_indll temp = node.next;
                            node.next = new aaf_indll();
                            node.next.ind = i;
                            node.next.next = temp;
                            break;
                        }
                    }
                    else
                    {
                        //Add point to the end of list
                        node.next = new aaf_indll();
                        node.next.ind = i;
                        node.next.next = null;
                        break;
                    }
                    node = node.next;
                }
            }

            //We can leave out the first point because it‘s offset position is going to be (0, 0)
            polysortedsize = 0;

            aaf_indll node2 = root;
            aaf_indll temp2;

            //Add the sorted points to polysorted and clean up memory
            while (node2 != null)
            {
                temp2 = node2;
                node2 = node2.next;
                if (node2 != null)
                    polysorted[polysortedsize++] = polyoverlap[node2.ind];

            }
        }

        private bool PtinConvexPolygon(AafPnt[] p, AafPnt pt)
        {
            int dir = 0;
            int j;

            //Basically what we are doing is seeing if pt is on the same side of each face of the polygon through cross multiplication
            for (int i = 0; i < 4; i++)
            {
                j = ja[i];
                double cross = (p[i].x - pt.x) * (p[j].y - pt.y) - (p[j].x - pt.x) * (p[i].y - pt.y);

                if (cross == 0)
                    continue;

                if (cross > 0)
                {
                    if (dir == -1)
                        return false;

                    dir = 1;
                }
                else
                {
                    if (dir == 1)
                        return false;

                    dir = -1;
                }
            }
            return true;
        }
    }

    public delegate bool Aaf_callback(double paraDouble);

    struct AafPnt
    {
        public double x, y;
        public AafPnt(double x, double y)
        {
            this.x = x < 0 ? 0 : x;
            this.y = y < 0 ? 0 : y;
        }
    }

    class aaf_dblrgbquad
    {
        public double Red { get; set; }
        public double Green { get; set; }
        public double Blue { get; set; }
        public double Alpha { get; set; }
    }
    class aaf_indll
    {
        public aaf_indll next;
        public int ind;
    }
}

这里是调用代码,参数xcorner和ycorner  对应的是矩形4个角的坐标,按顺序是左上、右上、右下、左下。

       /// <summary>
        /// 透视
        /// 2016-12-15
        /// </summary>
        /// <param name="p_Bitmap"></param>
        /// <param name="xcorner"></param>
        /// <param name="ycorner"></param>
        /// <returns></returns>
        public static Bitmap PrepImage(Bitmap p_Bitmap, double[] xcorner, double[] ycorner)
        {
            Perspective pimg = new Perspective(p_Bitmap, xcorner, ycorner);
            return pimg.CreateTransform();
        }

如果在窗体上直接打印,记得加上 下面这句代码

g.Clear(this.BackColor);

目前代码优化到此,不排除我还会继续优化,之后会再贴出来与大家分享!也很感谢猴健居士。

C# 透视效果