4

I'd like to take the perspective transform matrix returned from OpenCV's findHomography function and convert it (either in C++ or Objective-C) to iOS' CATransform3D. I'd like them to be as close as possible in terms of accurately reproducing the "warp" effect on the Core Graphics side. Example code would really be appreciated!

From iOS' CATransform3D.h:

/* Homogeneous three-dimensional transforms. */

struct CATransform3D
{
    CGFloat m11, m12, m13, m14;
    CGFloat m21, m22, m23, m24;
    CGFloat m31, m32, m33, m34;
    CGFloat m41, m42, m43, m44;
};

Similar questions:

Apply homography matrix using Core Graphics

convert an opencv affine matrix to CGAffineTransform

Community
  • 1
  • 1
taber
  • 3,166
  • 4
  • 46
  • 72

3 Answers3

5

Disclaimer

I have never tried this so take it with a grain of salt.

CATRansform3D is a 4x4 matrix which operates on a 3 dimensional homogeneous vector (4x1) to produce another vector of the same type. I am assuming that when rendered, objects described by a 4x1 vector have each element divided by the 4th element and the 3rd element is used only to determine which objects appear on top of which. Assuming this is correct...

Reasoning

The 3x3 matrix returned by findHomography operates on a 2 dimensional homogeneous vector. That process can be thought of in 4 steps

  1. The first column of the homography is multiplied by x
  2. The second column of the homography is multiplied by y
  3. The third column of the homography is multiplied by 1
  4. the resulting 1st and 2nd vector elements are divided by the 3rd

You need this process to be replicated in a 4x4 vector in which I am assuming the 3rd element in the resulting vector is meaningless for your purposes.

Solution

Construct your matrix like this (H is your homography matrix)

[H(0,0), H(0,1), 0, H(0,2),
 H(1,0), H(1,1), 0, H(1,2),
      0,      0, 1,      0
 H(2,0), H(2,1), 0, H(2,2)]

This clearly satisfies 1,2 and 3. 4 is satisfied because the homogeneous element is always the last one. That is why the "homogeneous row" if you will had to get bumped down one line. The 1 on the 3rd row is to let the z component of the vector pass through unmolested.

All of the above is done in row major notation (like openCV) to try to keep things from being confusing. You can look at Tommy's answer to see how the conversion to column major looks (you basically just transpose it). Note however that at the moment Tommy and I disagree about how to construct the matrix.

Hammer
  • 10,109
  • 1
  • 36
  • 52
  • I actually think we no longer disagree, and I explicitly think you were right. I've updated my answer but, to be honest, if the code I've given is correct then I think you should get the tick. Having supplied some copy and paste stuff that makes the point about row/column major feels less helpful than having made the point that a 2d coordinate in computer graphics is usually implicit for (x, y, 1) and a 3d for (x, y, z, 1) with the implicit bit being the homogenous bit and always pushing out towards the right. – Tommy Jan 23 '13 at 02:23
  • @Tommy It still looks to me like the matrix that you have posted there is different than the one I am suggesting, notice how on mine the 3rd column and the 3rd row are both empty except for the 1. Take the bottom right element for instance, I say it is H(2,2) you say it is 1. – Hammer Jan 23 '13 at 06:48
  • Thank you guys so much for the answers. It helped having more than 1 viewpoint on the topic. Really is appreciated. As it turns out, Hammer's solution did the trick. Tommy, yours was close but I still noticed some small visual discrepancies. The row vs column stuff (and needing to transpose) is pretty freaking confusing. And thanks Hammer for the detailed description. – taber Jan 23 '13 at 06:58
  • @taber thanks for letting us know! And I agree the transposing drives me crazy when I have to deal with it – Hammer Jan 23 '13 at 06:59
  • I think the main reason it's confusing is that if there's no rotation or scaling, or things are pretty close to the identity transform then you can be using either column or row major and not even know you're in the wrong. – taber Jan 23 '13 at 07:01
  • Are you sure that this solution is correct? From the OpenCV doc and the Apple Doc it looks like that Tommy's solution is the correct one. See here https://developer.apple.com/library/ios/documentation/cocoa/conceptual/coreanimation_guide/CoreAnimationBasics/CoreAnimationBasics.html and here http://docs.opencv.org/modules/calib3d/doc/camera_calibration_and_3d_reconstruction.html?highlight=findhomography#findhomography – Antonio Sesto Jul 19 '14 at 22:01
  • @AntonioSesto I haven't tried it myself no. I don't see anything in those links though that makes me doubt my answer... Can you elaborate? – Hammer Jul 25 '14 at 18:58
  • For taber and others who have gotten this working, did you just apply the resulting CATransform3D to the transform property of a UIView's CA Layer? This just doesn't seem to work for me. The layer is transformed on the X axis far away and the image seems to have no .m34 rotation. My homography can be used to transform perspective property using OpenCV functions, but I need to apply this to a CALayer to effectively apply the effect in realtime. I posted a question to the OpenCV forums, but it was tagged as "off topic" for an unknown reason. – VTPete Aug 29 '20 at 21:14
4

From my reading of the documentation, m11 in CATransform3D is equivalent to a in CGAffineTransform, m12 is equivalent to b and so on.

As per your comment below, I understand the matrix OpenCV returns to be 3x3 (which, in retrospect, is the size you'd expect). So you'd fill in the other elements with those equivalent to the identity matrix. As per Hammer's answer, you want to preserve the portion that deals with the (usually implicit) homogenous coordinate in its place while padding everything else with the identity.

[aside: my original answer was wrong. I've edited it to be correct since I've posted code and Hammer hasn't. This post is marked as community wiki to reflect that it's in no sense solely my answer]

So I think you'd want:

CATransform3D MatToTransform(Mat cvTransform)
{
    CATransform3D transform;

    transform.m11 = cvTransform.at<float>(0, 0);
    transform.m12 = cvTransform.at<float>(1, 0);
    transform.m13 = 0.0f;
    transform.m14 = cvTransform.at<float>(2, 0);

    transform.m21 = cvTransform.at<float>(0, 1);
    transform.m22 = cvTransform.at<float>(1, 1);
    transform.m23 = 0.0f;
    transform.m24 = cvTransform.at<float>(2, 1);

    transform.m31 = 0.0f;
    transform.m32 = 0.0f;
    transform.m33 = 1.0f;
    transform.m34 = 0.0f;

    transform.m41 = cvTransform.at<float>(0, 2);
    transform.m42 = cvTransform.at<float>(1, 2);
    transform.m43 = 0.0f;
    transform.m44 = cvTransform.at<float>(2, 2);

    return transform;
}

Or use cvGetReal1D if you're keeping C++ out of it.

Tommy
  • 99,986
  • 12
  • 185
  • 204
  • Yes, but findHomography returns a 3x3 matrix not a 4x4 – Hammer Jan 22 '13 at 23:12
  • +1 for the row major to column major though. The m11 notation always confuses me for some reason. I seem to think best in row major – Hammer Jan 22 '13 at 23:32
  • In that case it should be safe to fill the other entries with the identity elements. Hang on, I'll update... – Tommy Jan 23 '13 at 00:10
  • Thanks guys, I'll give this a try after dinner. – taber Jan 23 '13 at 00:36
  • I tried this but it did not work. The resulting matrix always has rotation as a multiple of pi/2 and scales are enormous (scales and rotation competed as answered in http://stackoverflow.com/questions/15420693/how-to-get-rotation-translation-shear-from-a-3x3-homography-matrix-in-c-sharp) – Max Snijders Aug 21 '15 at 22:41
  • Has anyone got this function to work? I have two images that I've found the homography for. I can use opencv's warpPerspective to correctly transform one image properly, so I know my homography is correct. However, when I try to create a CATransform3D with the above code to apply to a calayer, it's just not even close to being correct. This post is the closest thing to an answer on the internet, so I was hoping to resurrect the topic and see if there's a proven solution. – VTPete Apr 18 '19 at 21:55
  • It works for me, just need to use double when casting from Mat. – Pavlo Razumovskyi Apr 14 '20 at 02:15
0

Tommy answer worked for me, but I needed to use double, instead of float. This is also shortened version of the code:

CATransform3D MatToCATransform3D(cv::Mat H) {
    return {
        H.at<double>(0, 0), H.at<double>(1, 0), 0.0, H.at<double>(2, 0),
        H.at<double>(0, 1), H.at<double>(1, 1), 0.0, H.at<double>(2, 1),
        0.0, 0.0, 1.0, 0.0,
        H.at<double>(0, 2), H.at<double>(1, 2), 0.0f, H.at<double>(2, 2)
    };
}