Match3D library function bug : MatchDownsampledPointClouds()

When using the function MatchDownsampledPointClouds() from the Match 3D library to align two different point clouds, the output data is not complete. The error being shown is that the output Transform Matrix is invalid. Why is this?

Image 1 of the error discussed in the questions.

Image 2 of the error discussed in the questions.

1 Like

Hi @Z.Fatih,

I have just tested this with the two files that ship with :cvb: 13.1 (Key1.ply and Key2.ply) and it generally works, but apparently in failure situations no proper Cvb.Core3D.Transformation object is created, which might give rise to what you’re seeing.

Could you verify the value of success after the MatchDownsampledPointClouds call? If the match could be made, the return value should be zero or positive. If something went wrong, the negative return value indicates the problem - you’ll need to process and cast it with (Cvb.Error.CVC_ERROR_CODES)Cvb.Error.CVC_ERROR_FROM_HRES(success), then you can compare it to what the documentation lists:

But the uninitialized Transformation structure should (and will) be fixed.

2 Likes

Hi @illusive,

I have tested this as I was quite curious, and the return ot the MatchDownsampledPointCloud is 0.
Still the returned struct has a onedimensional array for the matrix, where a twodimensional one would be expected.
I wonder, how this wrong initialization happens, I would understand if there was no initialization at all.

Cheers
Chris

1 Like

Interesting. What exactly did you use as input - we have not seen anything like this during testing?

My code is exactly the same as @Z.Fatih has posted.
I have also used Key1.ply and Key2.ply as pointCloudModel.

Code I got from @Z.Fatih:

Cvb.Core.COMPOSITE pointCloudModel;
Cvb.Core.COMPOSITE pointCloud1;
Cvb.Core.COMPOSITE downsampledCloudModel;
Cvb.Core.COMPOSITE downsampledCloud1;
Cvb.Core.COMPOSITE transformedPointCloud1;


private void loadFile()
{
   int success = Cvb.Core3D.LoadFile(@"C:\Program Files\STEMMER IMAGING\Common Vision Blox\Tutorial\Match3D\Images\Key1.ply", out pointCloudModel);
   int success2 = Cvb.Core3D.LoadFile(@"C:\Program Files\STEMMER IMAGING\Common Vision Blox\Tutorial\Match3D\Images\Key2.ply", out pointCloud1);

   _cvbDisplayModel.AddPointCloudVariable(pointCloudModel, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0);
   _cvbDisplayModel.ResetCamera();
   _cvbDisplayPart.AddPointCloudVariable(pointCloud1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0);
   _cvbDisplayPart.ResetCamera();
   cloudPointAllignment()
}

private void cloudPointAllignment()
{
   //Checks the layout of the two pointclouds are valid
   Cvb.Core3D.PointCloudLayout pointCloudModelLayout;
   Cvb.Core3D.PointCloudAnalyzeLayout(pointCloudModel, out pointCloudModelLayout); 
   Cvb.Core3D.PointCloudLayout pointCloud1Layout;
   Cvb.Core3D.PointCloudAnalyzeLayout(pointCloud1, out pointCloud1Layout);

    //Downsamples both the model point cloud and the part point cloud
    int success = Cvb.Core3D.CreateDownsampledPointCloud(pointCloudModel, Cvb.Core3D.DownsampleMode.CVC3DDownsampleByFactor, 10, out downsampledCloudModel);
    success = Cvb.Core3D.CreateDownsampledPointCloud(pointCloud1, Cvb.Core3D.DownsampleMode.CVC3DDownsampleByFactor, 10, out downsampledCloud1);

    //Sets the match paramenters to the defualt values
    Cvb.Match3D.MatchParameters testParameter= Cvb.Match3D.MatchParameters.Default;
    testParameter.MaxIterations = 20;
    testParameter.DeltaRmsThreshold = 0.1;
    testParameter.MaxStdDevDistance = 1e-006;

     double finalRMS = 0.0;
     int numIterationsNeeded = -1;
     Cvb.Core3D.Transformation testTransform = new Cvb.Core3D.Transformation();         

     //Gets the number of points in the downsampled point cloud and then uses the match function to align the part and model
     long maxDownsampledCloudModelPoints;
     long maxDownsampledCloud1Points;
     success = Cvb.Core3D.PointCloudGetNumPoints(downsampledCloudModel, out maxDownsampledCloudModelPoints);
     success = Cvb.Core3D.PointCloudGetNumPoints(downsampledCloud1, out maxDownsampledCloud1Points);                                                                                                                   /*ERROR*/
     success = Cvb.Match3D.MatchDownsampledPointClouds(downsampledCloudModel, maxDownsampledCloudModelPoints, downsampledCloud1, maxDownsampledCloud1Points,
                                                              testParameter, out finalRMS, out numIterationsNeeded, out testTransform);                                          
      //This is where the error occurs as the value of the testTransform.Matrix is returned as invalid 
      var val = testTransform.Matrix[0, 0];
      success = Cvb.Core3D.CreateTransformedPointCloud(downsampledCloud1, testTransform, out transformedPointCloud1);

      _cvbDisplayModelandPart.AddPointCloudVariable(transformedPointCloud1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 0);
      _cvbDisplayModelandPart.ResetCamera();               
}

Ok. So the problem are not really the “?” in the screenshot from @Z.Fatih, but the fact that the array is 1D and should have been 2D. Got it. This will need to fixed in a patch release (watch out for version 13.01.003…) - in the meantime I suggest copying the (valid!) values from the 1D array to a 2D matrix

transformation = new Core3D.Transformation();
transformation.Translation = testTransformation.Translation;
transformation.Matrix = new double[3, 3];
if (retval >= 0)
{
  var m = (double[])(object)testTransformation.Matrix;
  for (int row = 0; row < 3; ++row)
    for (int col = 0; col < 3; ++col)
      transformation.Matrix[row, col] = m[row * 3 + col];
}
1 Like

After testing this with 3 different pairs of point clouds, I can confirm this fixes the problem. Thank you.

Hi @illusive,

thank you for your suggestion which is working fine. Out of curiosity, we tried to create a different work-around after @Z.Fatih posted the question by wrapping the C-function in C#:

[DllImport(@"CVMatch3D.dll")]
private static extern  Int32 CVM3DMatchDownsampledPointClouds(IntPtr PointCloudModel, Int64 MaxModelPoints, IntPtr PointCloudScene, Int64 MaxScenePoints, Cvb.Match3D.MatchParameters MatchParameters, out double FinalRmsDistance, out Int32 NumIterationsNeeded, ref Test2 Transformation);

[StructLayout(LayoutKind.Sequential, Size = 12)]
public struct Test2
{
    public double t1;
    public double t2;
    public double t3;
    public double m1;
    public double m2;
    public double m3;
    public double m4;
    public double m5;
    public double m6;
    public double m7;
    public double m8;
    public double m9;
};

Excuse the bad naming of the struct :crazy_face: as it was only for testing purposes. This worked. But as soon as we tried to use an array within the struct, it started to throw an access violation:

[StructLayout(LayoutKind.Sequential)]
public class Test
{
    /// char[15]
    [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 3)]
    public double[] Translation = new double[3];

    /// int[15]
    [MarshalAsAttribute(UnmanagedType.ByValArray, SizeConst = 9)]
    public double[] Matrix = new double[9];
}

Out of curiosity, what would be the correct way of mapping a struct/class with arrays to pass it to the underlying C/C++ function.

I understand that this is slightly of topic and not CVB related (but I never managed to work it out) and every time written horrible wrappers to deal with it.

Thanks.

As far as I know there is no automatic way to marshal a 2d by val array. You always need to do it manually.

In your first example the Size Parameter of the StructLayout seems to be ignored as it is smaller than the actual size (its unit is bytes). From the documentation setting a smaller value is not allowed. Thus we have undefined behavior.

The second example would work if it wouldn’t be a class. Marshaling only works on value types (structs). But still you would need to copy the data to a double[3,3] array.

The third way would be to use unsafe Code and pointers.

Thank you :slight_smile: Have a nice weekend.