Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Transforming a rectangle image into a quadrilateral using a CATransform3D

I have an image and a set of four points (describing a quadrilateral Q). I want to transform this image so that it is fits the quadrilateral Q. Photoshop calls this transformation "Distort." But according to the source of this quadrilateral (the perspective of the image moving in space), it is in fact the combination of a scale, a rotation and a perspective matrix.

I am wondering if this is possible using a CATransform3D 4x4 matrix. Do you have any hints on how to do that? I've tried to take the four points and build 16 equations (out of A' = A x u) but it did not work: I'm not sure of what I should use as z, z', w and w' coefficients…

The following picture shows what I want to do:

Transforming a rectangle image into a quadrilateral using a CATransform3D

Here are some examples of points:

276.523, 236.438,   517.656, 208.945,   275.984, 331.285,   502.23,  292.344 261.441, 235.059,   515.09,  211.5,     263.555, 327.066,   500.734, 295 229.031, 161.277,   427.125, 192.562,   229.16, 226,        416.48,  256 
like image 878
MonsieurDart Avatar asked Feb 27 '12 18:02

MonsieurDart


2 Answers

I've created a kit for doing this on iOS: https://github.com/hfossli/AGGeometryKit/


Make sure your anchor point is top left (CGPointZero).

+ (CATransform3D)rectToQuad:(CGRect)rect                      quadTL:(CGPoint)topLeft                      quadTR:(CGPoint)topRight                      quadBL:(CGPoint)bottomLeft                      quadBR:(CGPoint)bottomRight {     return [self rectToQuad:rect quadTLX:topLeft.x quadTLY:topLeft.y quadTRX:topRight.x quadTRY:topRight.y quadBLX:bottomLeft.x quadBLY:bottomLeft.y quadBRX:bottomRight.x quadBRY:bottomRight.y]; }  + (CATransform3D)rectToQuad:(CGRect)rect                     quadTLX:(CGFloat)x1a                     quadTLY:(CGFloat)y1a                     quadTRX:(CGFloat)x2a                     quadTRY:(CGFloat)y2a                     quadBLX:(CGFloat)x3a                     quadBLY:(CGFloat)y3a                     quadBRX:(CGFloat)x4a                     quadBRY:(CGFloat)y4a {     CGFloat X = rect.origin.x;     CGFloat Y = rect.origin.y;     CGFloat W = rect.size.width;     CGFloat H = rect.size.height;      CGFloat y21 = y2a - y1a;     CGFloat y32 = y3a - y2a;     CGFloat y43 = y4a - y3a;     CGFloat y14 = y1a - y4a;     CGFloat y31 = y3a - y1a;     CGFloat y42 = y4a - y2a;      CGFloat a = -H*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42);     CGFloat b = W*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);     CGFloat c = H*X*(x2a*x3a*y14 + x2a*x4a*y31 - x1a*x4a*y32 + x1a*x3a*y42) - H*W*x1a*(x4a*y32 - x3a*y42 + x2a*y43) - W*Y*(x2a*x3a*y14 + x3a*x4a*y21 + x1a*x4a*y32 + x1a*x2a*y43);      CGFloat d = H*(-x4a*y21*y3a + x2a*y1a*y43 - x1a*y2a*y43 - x3a*y1a*y4a + x3a*y2a*y4a);     CGFloat e = W*(x4a*y2a*y31 - x3a*y1a*y42 - x2a*y31*y4a + x1a*y3a*y42);     CGFloat f = -(W*(x4a*(Y*y2a*y31 + H*y1a*y32) - x3a*(H + Y)*y1a*y42 + H*x2a*y1a*y43 + x2a*Y*(y1a - y3a)*y4a + x1a*Y*y3a*(-y2a + y4a)) - H*X*(x4a*y21*y3a - x2a*y1a*y43 + x3a*(y1a - y2a)*y4a + x1a*y2a*(-y3a + y4a)));      CGFloat g = H*(x3a*y21 - x4a*y21 + (-x1a + x2a)*y43);     CGFloat h = W*(-x2a*y31 + x4a*y31 + (x1a - x3a)*y42);     CGFloat i = W*Y*(x2a*y31 - x4a*y31 - x1a*y42 + x3a*y42) + H*(X*(-(x3a*y21) + x4a*y21 + x1a*y43 - x2a*y43) + W*(-(x3a*y2a) + x4a*y2a + x2a*y3a - x4a*y3a - x2a*y4a + x3a*y4a));      const double kEpsilon = 0.0001;      if(fabs(i) < kEpsilon)     {         i = kEpsilon* (i > 0 ? 1.0 : -1.0);     }      CATransform3D transform = {a/i, d/i, 0, g/i, b/i, e/i, 0, h/i, 0, 0, 1, 0, c/i, f/i, 0, 1.0};      return transform; } 

I take no credit for this code. All I did was scouring the internet and put together various incomplete answers.

like image 197
hfossli Avatar answered Sep 26 '22 05:09

hfossli


We finally got this to work. We've tried several different methods, but most were failing. And some were even retrieving a non identity matrix when giving the same points as input and outputs (for example, the one from KennyTM… we must have been missing something there).

Using OpenCV as following, we get a CATransform3D ready to be used on a CAAnimation layer:

+ (CATransform3D)transformQuadrilateral:(Quadrilateral)origin toQuadrilateral:(Quadrilateral)destination {      CvPoint2D32f *cvsrc = [self openCVMatrixWithQuadrilateral:origin];      CvMat *src_mat = cvCreateMat( 4, 2, CV_32FC1 );     cvSetData(src_mat, cvsrc, sizeof(CvPoint2D32f));      CvPoint2D32f *cvdst = [self openCVMatrixWithQuadrilateral:destination];      CvMat *dst_mat = cvCreateMat( 4, 2, CV_32FC1 );     cvSetData(dst_mat, cvdst, sizeof(CvPoint2D32f));      CvMat *H = cvCreateMat(3,3,CV_32FC1);     cvFindHomography(src_mat, dst_mat, H);     cvReleaseMat(&src_mat);      cvReleaseMat(&dst_mat);       CATransform3D transform = [self transform3DWithCMatrix:H->data.fl];      cvReleaseMat(&H);       return transform;  }  + (CvPoint2D32f *)openCVMatrixWithQuadrilateral:(Quadrilateral)origin {      CvPoint2D32f *cvsrc = (CvPoint2D32f *)malloc(4*sizeof(CvPoint2D32f));      cvsrc[0].x = origin.upperLeft.x;     cvsrc[0].y = origin.upperLeft.y;     cvsrc[1].x = origin.upperRight.x;     cvsrc[1].y = origin.upperRight.y;     cvsrc[2].x = origin.lowerRight.x;     cvsrc[2].y = origin.lowerRight.y;     cvsrc[3].x = origin.lowerLeft.x;     cvsrc[3].y = origin.lowerLeft.y;      return cvsrc;  }  + (CATransform3D)transform3DWithCMatrix:(float *)matrix {     CATransform3D transform = CATransform3DIdentity;       transform.m11 = matrix[0];     transform.m21 = matrix[1];     transform.m41 = matrix[2];      transform.m12 = matrix[3];     transform.m22 = matrix[4];     transform.m42 = matrix[5];      transform.m14 = matrix[6];     transform.m24 = matrix[7];     transform.m44 = matrix[8];      return transform;  } 
like image 42
MonsieurDart Avatar answered Sep 22 '22 05:09

MonsieurDart