## C#.Net - Perspective Image Transform

Hello,

Today, I'm going to share one interesting thing that everyone may need - perspective image transformation.

Its 2D transformation that makes image look like in perspective view like 3D but without any 3D:

So, the main thing there is math. We take an image, specify four points and math will do the trick.

First step is to create and calculate matrices from the new four points:

//original points

Point p1 = new Point(0, 0);

Point p2 = new Point(width, 0);

Point p3 = new Point(width, height);

Point p4 = new Point(0, height);

//create matrix

A = Matrix3x3.Homogenous(p1, p2, p3, p4);

//new points

Point n1 = points[0];

Point n2 = points[1];

Point n3 = points[2];

Point n4 = points[3];

B = Matrix3x3.Homogenous(n1, n2, n3, n4);

A.Inverse();

C = B.MultMat(A);

C.Inverse();

Second step is to go through each pixel on your image and calculate new pixel:

Point ptDest = new Point(0, 0);

PointF ptOriginF = new Point(0, 0);

int iOrigX = 0;

Color pix = new Color();

for (int x = 0; x < width; ++x)

{

for (int y = 0; y < height; ++y)

{

ptDest.X = x;

ptDest.Y = y;

ptOriginF = C.Update(ptDest);

if (ptOriginF.X >= -5 && ptOriginF.X < width && ptOriginF.Y >= -5 && ptOriginF.Y < height)

{

iOrigX = (int)ptOriginF.X; // round to lowest integer

int iOrigY = (int)ptOriginF.Y; // round to lowest integer

double dx = ptOriginF.X - iOrigX;

double dy = ptOriginF.Y - iOrigY;

Point ptOrigin = new Point(iOrigX, iOrigY);

if (dx != 0.0f || dy != 0.0)

{

Color pix1 = Color.FromArgb(0, 255, 255, 255);

Color pix2 = Color.FromArgb(0, 255, 255, 255);

Color pix3 = Color.FromArgb(0, 255, 255, 255);

Color pix4 = Color.FromArgb(0, 255, 255, 255);

//

// Correct square's direction

//

int idx = (dx >= 0.0) ? 1 : -1;

int idy = (dy >= 0.0) ? 1 : -1;

dx = Math.Abs(dx);

dy = Math.Abs(dy);

//

// Get pixels of square

//

if (ptOrigin.X >= 0 && ptOrigin.X < width && ptOrigin.Y >= 0 && ptOrigin.Y < height)

pix1 = src.GetPixel(ptOrigin.X, ptOrigin.Y);

if (ptOrigin.X + idx >= 0 && ptOrigin.X + idx < width && ptOrigin.Y >= 0 && ptOrigin.Y < height)

pix2 = src.GetPixel(ptOrigin.X + idx, ptOrigin.Y);

if (ptOrigin.X >= 0 && ptOrigin.X < width && ptOrigin.Y + idy >= 0 && ptOrigin.Y + idy < height)

pix3 = src.GetPixel(ptOrigin.X, ptOrigin.Y + idy);

if (ptOrigin.X + idx >= 0 && ptOrigin.X + idx < width && ptOrigin.Y + idy >= 0 && ptOrigin.Y + idy < height)

pix4 = src.GetPixel(ptOrigin.X + idx, ptOrigin.Y + idy);

//

// Use bilinear interpolation

//

double r = pix1.R + (pix2.R - pix1.R) * dx + (pix3.R - pix1.R) * dy + (pix1.R - pix2.R - pix3.R + pix4.R) * dx * dy;

double g = pix1.G + (pix2.G - pix1.G) * dx + (pix3.G - pix1.G) * dy + (pix1.G - pix2.G - pix3.G + pix4.G) * dx * dy;

double b = pix1.B + (pix2.B - pix1.B) * dx + (pix3.B - pix1.B) * dy + (pix1.B - pix2.B - pix3.B + pix4.B) * dx * dy;

double a = pix1.A + (pix2.A - pix1.A) * dx + (pix3.A - pix1.A) * dy + (pix1.A - pix2.A - pix3.A + pix4.A) * dx * dy;

pix = Color.FromArgb((byte)a, (byte)r, (byte)g, (byte)b);

}

else

{

pix = src.GetPixel(ptOrigin.X, ptOrigin.Y);

}

dst.SetPixel(ptDest.X, ptDest.Y, pix);

}

}

}

So this is bilinear interpolation is the main thing to transform your image.

Thank you.

PS

will extend this and provide source code if you will request.

PPS

public class Matrix3x3 { double [,] m_val = new double [3,3]; public Matrix3x3() { } public Matrix3x3(Point p1, Point p2, Point p3) { m_val = new double[3, 3]; m_val[0, 0] = p1.X; m_val[0, 1] = p2.X; m_val[0, 2] = p3.X; m_val[1, 0] = p1.Y; m_val[1, 1] = p2.Y; m_val[1, 2] = p3.Y; m_val[2, 0] = 1.0; m_val[2, 1] = 1.0; m_val[2, 2] = 1.0; } public double Determinant() { double result = 0; result = m_val[0, 0] * (m_val[1, 1] * m_val[2, 2] - m_val[1, 2] * m_val[2, 1]); result-= m_val[0, 1] * (m_val[1, 0] * m_val[2, 2] - m_val[1, 2] * m_val[2, 0]); result+= m_val[0, 2] * (m_val[1, 0] * m_val[2, 1] - m_val[1, 1] * m_val[2, 0]); return result; } public double a(int i, int j) { return m_val[i-1, j-1]; } public void Inverse() { double a11 = a(2, 2) * a(3, 3) - a(2, 3) * a(3, 2); double a12 = a(2, 1) * a(3, 3) - a(2, 3) * a(3, 1); double a13 = a(2, 1) * a(3, 2) - a(2, 2) * a(3, 1); double a21 = a(1, 2) * a(3, 3) - a(1, 3) * a(3, 2); double a22 = a(1, 1) * a(3, 3) - a(1, 3) * a(3, 1); double a23 = a(1, 1) * a(3, 2) - a(1, 2) * a(3, 1); double a31 = a(1, 2) * a(2, 3) - a(1, 3) * a(2, 2); double a32 = a(1, 1) * a(2, 3) - a(1, 3) * a(2, 1); double a33 = a(1, 1) * a(2, 2) - a(1, 2) * a(2, 1); double od = 1.0/Determinant(); m_val[0, 0] = od * a11; m_val[0, 1] = -od * a21; m_val[0, 2] = od * a31; m_val[1, 0] = -od * a12; m_val[1, 1] = od * a22; m_val[1, 2] = -od * a32; m_val[2, 0] = od * a13; m_val[2, 1] = -od * a23; m_val[2, 2] = od * a33; } public void MultByVec(double [] vector) { for (int row = 0; row < 3; ++row) { m_val[row, 0] *= vector[0]; m_val[row, 1] *= vector[1]; m_val[row, 2] *= vector[2]; } } public PointD Update(PointD vector) { double x = a(1, 1) * vector.X + a(1, 2) * vector.Y + a(1, 3) * 1.0; double y = a(2, 1) * vector.X + a(2, 2) * vector.Y + a(2, 3) * 1.0; double z = a(3, 1) * vector.X + a(3, 2) * vector.Y + a(3, 3) * 1.0; // PointD result = new PointD(); result.X = (x / z); result.Y = (y / z); return result; } public Matrix3x3 MultMat(Matrix3x3 B) { Matrix3x3 result = new Matrix3x3(); result.m_val[0, 0] = a(1, 1) * B.a(1, 1) + a(1, 2) * B.a(2, 1) + a(1, 3) * B.a(3, 1); result.m_val[0, 1] = a(1, 1) * B.a(1, 2) + a(1, 2) * B.a(2, 2) + a(1, 3) * B.a(3, 2); result.m_val[0, 2] = a(1, 1) * B.a(1, 3) + a(1, 2) * B.a(2, 3) + a(1, 3) * B.a(3, 3); result.m_val[1, 0] = a(2, 1) * B.a(1, 1) + a(2, 2) * B.a(2, 1) + a(2, 3) * B.a(3, 1); result.m_val[1, 1] = a(2, 1) * B.a(1, 2) + a(2, 2) * B.a(2, 2) + a(2, 3) * B.a(3, 2); result.m_val[1, 2] = a(2, 1) * B.a(1, 3) + a(2, 2) * B.a(2, 3) + a(2, 3) * B.a(3, 3); result.m_val[2, 0] = a(3, 1) * B.a(1, 1) + a(3, 2) * B.a(2, 1) + a(3, 3) * B.a(3, 1); result.m_val[2, 1] = a(3, 1) * B.a(1, 2) + a(3, 2) * B.a(2, 2) + a(3, 3) * B.a(3, 2); result.m_val[2, 2] = a(3, 1) * B.a(1, 3) + a(3, 2) * B.a(2, 3) + a(3, 3) * B.a(3, 3); return result; } public static Matrix3x3 Homogenous(Point p1, Point p2, Point p3, Point p4) { Matrix3x3 Major = new Matrix3x3(p1, p2, p3); Matrix3x3 Minor1 = new Matrix3x3(p4, p2, p3); Matrix3x3 Minor2 = new Matrix3x3(p1, p4, p3); Matrix3x3 Minor3 = new Matrix3x3(p1, p2, p4); double MajorD = Major.Determinant(); double Minor1D = Minor1.Determinant(); double Minor2D = Minor2.Determinant(); double Minor3D = Minor3.Determinant(); double[] coeff = new double[3]; coeff[0] = Minor1D / MajorD; coeff[1] = Minor2D / MajorD; coeff[2] = Minor3D / MajorD; // Major.MultByVec(coeff); return Major; } }

## Daniel

9/27/2018 4:10:15 PM |Thanks for this. In the end I went with solution that didn't use matrices:

Vector ab = new Vector(Points[0], Points[1]);

Vector bc = new Vector(Points[1], Points[2]);

Vector cd = new Vector(Points[2], Points[3]);

Vector da = new Vector(Points[3], Points[0]);

ab /= ab.Magnitude;

bc /= bc.Magnitude;

cd /= cd.Magnitude;

da /= da.Magnitude;

double dab = Math.Abs((new Vector(Points[0], point).CrossProduct(ab)));

double dbc = Math.Abs((new Vector(Points[1], point).CrossProduct(bc)));

double dcd = Math.Abs((new Vector(Points[2], point).CrossProduct(cd)));

double dda = Math.Abs((new Vector(Points[3], point).CrossProduct(da)));

point.X = (int)(width * (dda / (dda + dbc)));

point.Y = (int)(height * (dab / (dab + dcd)));

## okarpov

9/27/2018 4:19:10 PM |Cool! Thank you! Hope it will help anybody.

## Christa Thresher

11/4/2018 2:29:18 PM |Thankyou for helping out, great info .

## okarpov

11/4/2018 2:32:53 PM |Thank you

## Shayne Gienger

11/4/2018 3:05:43 PM |You have brought up a very great details , appreciate it for the post.

## Talia Kole

11/4/2018 3:39:15 PM |Wow, fantastic blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your site is fantastic, as well as the content!

## Margarito Karstens

11/4/2018 4:17:26 PM |I do believe all the ideas you've introduced in your post. They're really convincing and can certainly work. Still, the posts are very short for newbies. Could you please lengthen them a bit from next time? Thanks for the post.

## Stephenie Orantes

11/4/2018 6:23:53 PM |I don’t even know how I ended up here, but I thought this post was good. I don't know who you are but definitely you're going to a famous blogger if you aren't already ;) Cheers!

## Brad Jeffcoat

11/26/2018 8:16:10 PM |I like the valuable information you provide in your articles. I’ll bookmark your weblog and check again here frequently. I'm quite certain I’ll learn plenty of new stuff right here! Best of luck for the next!

## Dusti Eblin

1/7/2019 6:26:36 PM |This is really interesting, You're a very skilled blogger. I've joined your rss feed and look forward to seeking more of your excellent post. Also, I have shared your website in my social networks!

## Anti aging

4/20/2019 12:47:39 AM |Enjoyed the post.

## skin care

4/20/2019 3:52:49 AM |I truly wanted to write a brief note so as to say thanks to you for the stunning secrets you are sharing here. My extensive internet investigation has at the end of the day been paid with pleasant suggestions to share with my friends and family. I would repeat that we readers actually are unequivocally blessed to live in a very good network with many brilliant individuals with very helpful techniques. I feel somewhat grateful to have encountered your entire webpages and look forward to some more pleasurable minutes reading here. Thank you once more for everything.