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:
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
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.
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; }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With