首页 > 代码库 > 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# 透视效果