Why is a cv:: Mat object behaving differently between debug and release?

204 views Asked by At

I'm having a real head-scratcher of a time trying to figure out why the population of my OpenCV matrix works exactly as I expect it to in debug mode, yet when I try to release the project, it truncates all the values that are given to it, and populates the matrix incorrectly!

A short description of my setup:

  • I'm using both the OpenCV and ViSP libraries to work with a USB Flycapture camera.
  • I'm running everything on Ubuntu 16.04 in Qt-Creator, since I plan on having a GUI in this project.
  • The parameters populating the matrix are important for the calibration of the camera, so the truncation is an absolute no-go!

On to the code!

First, I load the already saved parameters from a file using ViSP's built-in classes. For debugging purposes (and to ensure I wasn't losing my mind,) I also saved the values to global variables for quick reference:

bool load_intrinsics()
{
  vpCameraParameters cam;
  vpXmlParserCaemra parser;
  std::string intrinsicFilePath = "...path to file...";
  std::string cameraName = "Camera";

  if (parser.parse(cam, intrinsicFilePath, camera_name, vpCameraParameters::perspectiveProjWithDistortion) != vpXmlParserCamera::SEQUENCE_OK)
  {
    std::cout << "ERROR WITH LOADING INTRINSICS" << std::endl;
    return false;
  }
  else
  { // variables here are <double> variables, defined globally
    global_px = cam.get_px();
    global_py = cam.get_py();
    global_u0 = cam.get_u0();
    global_v0 = cam.get_v0();
    global_kud = cam.get_kud();
    return true;
  }
}

`

This all works as expected. The function reads all the values correctly and stores those very values (not truncated!) to the global variables.

Next I try to pass those very variables to a cv::Mat object, and that's where things start to go down hill...

void createCvMat()
{
  //  create camera matrix
  cv::Mat tempCamMat = cv::Mat::zeros(3, 3, CV_64F);
  tempCamMat.at<double>(0, 0) = global_px;
  tempCamMat.at<double>(0, 2) = global_u0;
  tempCamMat.at<double>(1, 1) = global_py;
  tempCamMat.at<double>(1, 2) = global_v0;
  tempCamMat.at<double>(2, 2) = 1.0;
  // create distortion matrix
  cv::Mat tempDisMat = cv::Mat::zeros(5, 1, CV_64F);
  tempDisMat.at<double>(0, 0) = global_kud;
  // pass temporary matrices to empty global matrices
  tempCamMat.copyTo(cameraMatrix);
  tempDisMat.copyTo(distortionMatrix);
}

Now, as mentioned in the first code-snippit, I've already made sure the global variables have the correct values in them. Yet when I use these matrices to, for example, use cv::undistort(...), it becomes painfully apparent that the matrices did not load properly.

As a sanity-check, I tried to write out the matrices to the console, and make sure the values were being loaded correctly:

void sanityCheck(cv::Mat checkMatrix)
{
  std::cout << std::endl << "Matrix holds:" <<std::endl;
  for (int r = 0; r < checkMatrix.rows; r++)
  {
    for (int c = 0; c < checkMatrix.cols; c++)
    {
      std::cout << checkMatrix.at<double>(r, c) << " | ";
    }
    std::cout << std::endl;
  }
}

Calling the sanity check for both matrices in debug mode, I get the following output:

`

Camera
Matrix Includes:
606.204 | 0 | 607.664 |
0 | 612.354 | 521.775 |
0 | 0 | 1 |

Distortion
Matrix Includes:
-0.240543 |
0 |
0 |
0 |
0 |

`

Note that the std::cout console output does truncate the value written to six significant numbers, as the numbers read from the file have nearly twenty significant numbers. Using the iomanip library in conjunction with std::setprecision(...), I have confirmed that the full number is properly carried over and inserted into the matrix.

Calling the same function in release, I get the following output:

Camera
Matrix Includes:
606 | 0 | 607 |
0 | 612 | 521 |
0 | 0 | 1 |

Distortion
Matrix Includes:
-0 |
0 |
0 |
0 |
0 |

` Note, that even though double variables were passed to a matrix capable of holding double variables, only integer numbers are populating the matrix! For the life in me, I can't figure out why it does this in the release version of the project, but not the debug version!

As a current work-around, I have tried copy-pasta'ing the numbers directly from the "*.xml" file to the variables as constants, before passing them to the cv::Mat object:

// define each global variable (don't read from file)
global_px = 606.20444013030078;
...
global_kud = -0.24054321189834804;
// populate matrices
createCvMat();

. And this works just fine... I don't know why, but it does.

As any programmer would most likely tell you, this "hard-coding" of constants should be avoided. If I ever change the settings on my camera for whatever reason and have to re-calibrate, or if the end-user wishes to calibrate their own camera, it would be best to simply read these intrinsic values from an external file.

1

There are 1 answers

2
Robot Fox On BEST ANSWER

The Details:

The issue with loading a double variable to a cv::Mat<double> object has to do with a localization issue of the installed Ubuntu-16.04 OS. Due to the PC being configured in Germany, regardless the fact all parameters were set up to mimic the EN-standards on install, the PC still set its numeric, currency, time etc. interpretation to DE-standards.

This means, parsing a double variable from an external file will cause the OS to mistake the EN-standard decimal place, which is a period (.), for the DE-standard thousandths separator. This results in a truncation of the fraction portion of the double variable!

The Solution:

Using the Ubuntu-16.04 OS, this can be solved through changing the system locales from within the terminal. Opening the terminal, and entering the locale command will give the user a list of the lingual standards used for the system localization interpretations, as such:

LANG=en_US.UTF-8
LANGUAGE=en_US
LC_CTYPE="en_US.UTF-8"
LC_NUMERIC=en_US.UTF-8
LC_TIME=de_DE.UTF-8
LC_COLLATE="en_US.UTF-8"
LC_MONETARY=en_US.UTF-8
...

Changing the LC_CTYPE, LC_NUMERIC, and LC_MEASUREMENT tags to en_US.UTF-8 solved the issue on my side. This can either be done through the terminal, with the following commands:

sudo update-locale LC_CTYPE=en_US.UTF-8
sudo update-locale LC_NUMERIC=en_US.UTF-8
sudo update-locale LC_MEASUREMENT=en_US.UTF-8

Or in a text editor of your choice, editing the /etc/default/locale file with root privileges. Once the file has been updated, make sure to reboot to finalize the changes!

A great big thank-you to @Scheff for their insight on the matter. I doubt I would have solved this without their suggestion! Much thanks, such wow!