Help with Ceres & sampling OpenCV Mat in cost function

443 views
Skip to first unread message

Andrew Hogue

unread,
Sep 15, 2021, 11:23:54 AM9/15/21
to Ceres Solver
I'm new to Ceres and trying to determine the appropriate way to use it to solve my minimization problem.  I was hoping someone could provide suggestions as I'm stuck at the moment.

Problem: 
- I have a 3D model of a subject, a number of cameras with estimates on extrinsics, and intrinsics.
- I also have an Signed Distance Function image that is ZERO at the boundary of the subject, negative inside and positive outside
- My current estimate on Extrinsics is correct up to a small offset in translation and potentially in rotation 
- My minimization problem is thus to estimate the translation offset from the current extrinsics given all of the above that minimizes the projected distances.

Currently, trying to use the AutoDifferentiation with a templated cost function but to compute the residuals, I need to first:
- project internal mesh using known intrinsics and estimated extrinsics 
- then sample the SDF image (OpenCV mat) at the projected locations to sum up the error residuals over the mesh.

Questions:
1. Is this an appropriate method or am I modeling it wrong?  Any suggestions would be helpful.  Should I have a residual per vertex or for the entire model?
2. How do I properly use an OpenCV mat in this instance?  The templated parameter is obviously a Jet, and all calculations internal to the cost function will be thus using this templated Jet.  How does one convert the projected point (u,v) into an index I can then use to grab the value from my OpenCV mat?  

There isn't much info or suggestions on using OpenCV Mat's internally in this context, one suggestion was to use the cubic-interpolator and Grid2D interface.  Does anyone have any examples on this for OpenCV ?

Thanks in Advance!

-- Andrew

Sameer Agarwal

unread,
Sep 15, 2021, 11:31:25 AM9/15/21
to ceres-...@googlegroups.com
Hi Andrew,
Let me understand your problem better first.

The signed distance image is just a representation for the contour of the object right?
And when you say you have a 3d model, you are saying you have 3d mesh of the object placed in space ? or do you just have the mesh and you have to also figure out the translation and rotation of the mesh in space for it to line up with the images?

and you want to refine the camera parameters so that the contours of the mesh when projected into the image line up?

Before we talk about automatic differentiation etc, what is the objective function that you are trying to minimize? can you give its mathematical form?

Sameer



--
You received this message because you are subscribed to the Google Groups "Ceres Solver" group.
To unsubscribe from this group and stop receiving emails from it, send an email to ceres-solver...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/ceres-solver/0cf8d67f-1391-4b2a-87e8-bcdbd8e20271n%40googlegroups.com.

Andrew Hogue

unread,
Sep 15, 2021, 12:01:26 PM9/15/21
to Ceres Solver
Hi Sameer, sorry I just accidentally sent the message privately.  Here is a recap for the group:
Here is what I have:
- A number of Kinect Azures (6-8) with pre-determined intrinsics/extrinsics but they are not necessarily perfectly accurate, I'd like to improve the extrinsics here
- a 3D mesh (triangular mesh) 
-RGB images and Depth images from the cameras as well as a B&W matte image for identifying the subject
- an SDF image denoting the contour (zero on the boundary of the matte image)


Essentially, when I was thinking of using ceres to refine the extrinsics of the cameras using the SDF in image space to help since the gradient will aid in the direction towards the contours

The cost function is thus:
- p = K*[R|t] * v
- [u,v] = p/p_z
- sdf = SDF(u,v)
SumSDF += sdf^2;

then minimize the SumSDF cost over the mesh vertices....

Dmitriy Korchemkin

unread,
Sep 15, 2021, 1:08:22 PM9/15/21
to ceres-...@googlegroups.com
Is 3d-mesh a "ground-truth" triangular mesh of the object? In this case it might make sense optimizing distance from the depth data to the mesh.



--
Dmitriy A Korchemkin

Andrew Hogue

unread,
Sep 15, 2021, 1:20:51 PM9/15/21
to Ceres Solver
Sort of.  Either way I need to be able to sample the (u,v) from an OpenCV mat in the cost function, if the (u,v) comes from a calculation using the free templated parameters then how would I convert it to something I can use as an index, or I suppose convert the OpenCV mat into something I can sample from the cost function?

I'm hesitant to use just the depth data here anyways, I want the problem to incorporate an appearance model later on in the cost estimate as well but baby steps first :) 

Dmitriy Korchemkin

unread,
Sep 15, 2021, 1:28:12 PM9/15/21
to ceres-...@googlegroups.com
If interpolation of the data you store in cv::Mat makes sense from the modelling perspective - there is a ceres::BicubicInterpolator class  http://www.ceres-solver.org/nnls_modeling.html?highlight=evaluate#_CPPv4N5ceres19BiCubicInterpolatorE for obtaining a differentiable (and auto-differentiable) access to an underlying constant array of data.



--
Dmitriy A Korchemkin

Sameer Agarwal

unread,
Sep 15, 2021, 7:42:26 PM9/15/21
to ceres-...@googlegroups.com
As Dimitry pointed out, this is exactly what the bicubicinterpolator is for. 

Andrew Hogue

unread,
Sep 16, 2021, 12:23:23 PM9/16/21
to Ceres Solver
Hi again,

This doesn't solve my issue however.... I now have a class with a BiCubicInterpolator defined as a member,  I create this in the constructor and populate it with the image data.
In my templated operator() for the cost function, it now needs to use this cubic interpolator to sample the image values.
So, I do the appropriate steps and take compute the projection using the free parameter for extrinsics and my fixed intrinsics.
I project and get a (u,v) which is now using the templated parameter, T.

However, I can't use this to sample with the BiCubicInterpolator since it has originally been defined with double as the type in the main class:

error C2664: 'void ceres::BiCubicInterpolator<ceres::Grid2D<double,1,true,true>>::Evaluate(const double &,const double &,double *) const': cannot convert argument 1 from 'T' to 'double'

So, what is the process to use the BiCubicInterpolator in the cost function with auto-differentiation?  Do I have to create a new interpolator in the operator() cost function each time it is called (which would be inefficient) or am I missing something completely when I define the member in my class...?

If anyone has a substantial example that solves a similar issue I would love to see it.

Thanks for any insight,

Sameer Agarwal

unread,
Sep 16, 2021, 1:48:42 PM9/16/21
to ceres-...@googlegroups.com
Andrew,
The interpolator has two Evaluate methods.
One is as you point out defined over doubles, but there is another one which allows you to use Jets for input and output.


This is the one you should calling.

Sameer


Andrew Hogue

unread,
Sep 16, 2021, 2:04:33 PM9/16/21
to Ceres Solver
ah, cool.  Do you have (or know of) a good example of using that within a cost function? 

Just took a look at the evaluator examples and I can see how to wrap the R,C into 2 different Jets passed to the function.... still fighting with it though :) 

If you know of any example I'd love to see it

Thanks again



Sameer Agarwal

unread,
Sep 16, 2021, 2:22:25 PM9/16/21
to ceres-...@googlegroups.com

Dmitriy Korchemkin

unread,
Sep 16, 2021, 2:23:12 PM9/16/21
to Ceres Solver

Sameer Agarwal

unread,
Sep 16, 2021, 2:24:27 PM9/16/21
to ceres-...@googlegroups.com
Dmitriy,
Can you convince you to contribute this as an example to Ceres? 
Sameer


Andrew Hogue

unread,
Sep 16, 2021, 2:35:31 PM9/16/21
to Ceres Solver
thank you so much!  That helps immensely

Dmitriy Korchemkin

unread,
Sep 16, 2021, 3:27:48 PM9/16/21
to Ceres Solver
I'll submit change request to Gerrit on weekend.

Sameer Agarwal

unread,
Sep 16, 2021, 3:28:28 PM9/16/21
to ceres-...@googlegroups.com

Andrew Hogue

unread,
Sep 16, 2021, 4:45:16 PM9/16/21
to Ceres Solver
Also, something to note, please verify that in the interpolator it is Row/Col vs Col/Row...... 

For instance, I wanted to verify that the interpolator was going to work for me as you had above and populated it with my OpenCV data and then afterwards I pulled it out of the interpolator into a new image I could display it to verify correctness.

In order for me to obtain correct values per pixel, I had to have the grid be addressed using (Cols, Rows) in a snippet below.  This works but it is not obvious why by default it seems to be using Column Major even though the default should be RowMajor...... something to verify with your own implementation.

const int cols = imSDF.cols;
        const int rows = imSDF.rows;
        Eigen::ArrayXXd img_data = Eigen::ArrayXXd(rows,cols);

        for (int j = 0; j < rows; j++) {
            for (int i = 0; i < cols; i++)
            {
                float sdf = imSDF.at<float>(cv::Point(i,j));
                img_data(j, i) = (double)sdf;
            }
        }
Grid theGrid(img_data.data(), 0, img_data.cols(), 0, img_data.rows());
        theInterpolator = new Interpolator(theGrid);

//   pulling data per pixel out here to test:
   cv::Mat imTest = cv::Mat::zeros(cv::Size(cols,rows), CV_32F);
        for (int j = 0; j < rows; j++) {
            for (int i = 0; i < cols; i++)
            {
                ceres::Jet<double, 4> r_jet, c_jet,ff;
                r_jet.a = (double)j;
                r_jet.v[0] = 1.0;
                r_jet.v[1] = 1.1;
                r_jet.v[2] = 1.2;
                r_jet.v[3] = 1.3;
                c_jet.a = (double)i;
                c_jet.v[0] = 2.0;
                c_jet.v[1] = 2.1;
                c_jet.v[2] = 2.2;
                c_jet.v[3] = 2.3;
               
                theInterpolator->Evaluate(c_jet,r_jet, &ff);
                imTest.at<float>(cv::Point(i, j)) = (float)ff.a;
            }
        }

        cv::imshow("test", imTest);

Dmitriy Korchemkin

unread,
Sep 16, 2021, 4:54:52 PM9/16/21
to Ceres Solver
Are you sure the problem is not in Eigen::ArrayXXd being column-major by default (and other differences in notation between 3 libraries you're using together)?

Note also that BiCubicInterpolator::Evaluate expects coordinates supplied in order {row, col} (and opencv - in {col, row}, when used via cv::Mat::at(cv::Point) method).

Andrew Hogue

unread,
Sep 16, 2021, 5:12:08 PM9/16/21
to Ceres Solver
possibly... there's a lot going on in there that I have to sort out I'm sure.... something to verify and check if you are ever trying something similar..... 

I bet its possibly something I'm doing.... but at the moment it will just be documented and re-verified later...

Thanks for all your help!

Andrew Hogue

unread,
Sep 16, 2021, 5:23:46 PM9/16/21
to Ceres Solver
aha, you were right of course.

Switched everything back to row-major as default and then changed my Eigen::ArrayXXd as:
using EigArray = Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic, Eigen::RowMajor>;

just to force it to be rowmajor.... works now :) 

Thanks, I hope this discussion helps someone later!

Andrew Hogue

unread,
Sep 18, 2021, 1:25:13 PM9/18/21
to Ceres Solver
Just to finish up the discussion on this thread.  I was able to get it working with your help!  Thanks!

If you take a look at this image you can see the results of before and after the nonlinear minimization.

I parameterized the problem as follows:
- solving for a 3D translation vector offset for my camera extrinsics
- added a residual block/cost function for each vertex in the input mesh
- cost function performs a projection using the intrinsics and extrinsics + offset from the parameters
- residual is computed using a distance field lookup image computed from a B&W matte image 

Thanks for all your help!

Screen Shot 2021-09-18 at 12.35.14 PM.png

Sameer Agarwal

unread,
Sep 18, 2021, 1:28:51 PM9/18/21
to ceres-...@googlegroups.com
Reply all
Reply to author
Forward
0 new messages