Skip to content

[Evaluation] Possible camera-space/world-space mismatch in the scene-flow metrics of Table 1 #9

Description

@ZhouZhanbo

First of all, thank you for releasing MotionCrafter and its evaluation pipeline. I found the joint geometry-and-motion formulation very interesting, and the benchmark covering Kubric, Spring, Virtual KITTI 2, Dynamic Replica, and Point Odyssey is especially valuable.

I am developing another model that also predicts dense 3D scene flow. Among the existing benchmarks, I believe the MotionCrafter benchmark is the most suitable one for evaluating my model. Before reporting results for my own method, I first tried to reproduce the MotionCrafter results in Table 1 to verify that my preprocessing, inference, and evaluation pipeline were correct.

During this reproduction, I noticed a possible coordinate-frame mismatch between the scene-flow annotations produced by the released preprocessing scripts and the scene-flow evaluation.

Possible coordinate-frame mismatch

As far as I understand, the preprocessed scene_flow is stored as a camera-space endpoint offset (referred to as CSO below).

For a transition from frame (t) to frame (t+1), the preprocessing scripts effectively compute

[
F_t^{\mathrm{CSO}}

P_{t+1}^{C_{t+1}} - P_t^{C_t},
]

where:

  • (P_t^{C_t}) is the point map at frame (t), expressed in camera (t);
  • (P_{t+1}^{C_{t+1}}) is the corresponding endpoint, expressed in camera (t+1).

For example, datasets/preprocess/gen_spring_video.py contains:

scene_flow = cam_coords - point_map

Similarly, the Kubric preprocessing constructs the next-frame point map from the tracked coordinates and next-frame depth, followed by:

scene_flow = point_map_deform - point_map

Therefore,

point_map[t] + scene_flow[t]

recovers the corresponding endpoint in the coordinate system of camera (t+1), rather than a world-space endpoint.

However, in the released evaluation behavior, the predicted point map and predicted scene flow are aligned/transformed into the world coordinate system, while the GT scene flow is passed directly to sceneflow_metrics:

sflow = sceneflow_metrics(
    aligned_sflow[:-1],
    gt_sflow[:-1],
    gt_dmask[:-1],
)

This appears to compare a world-space predicted displacement with the original camera-space endpoint offset.

Please correct me if I have misunderstood the intended convention.

Reproduction setup

I evaluated three settings:

  1. Reproduce 1

    • INFER_FRAMES=8
    • released scene-flow evaluation behavior
    • predicted point map treated as a world map
  2. Reproduce 2

    • INFER_FRAMES=25
    • released scene-flow evaluation behavior
    • predicted point map treated as a world map
    • the inference window is consistent with the default MotionCrafter setting
  3. Reproduce 3

    • INFER_FRAMES=25
    • predicted point map treated as a world map
    • GT scene flow converted from CSO to the first-camera world coordinate system before evaluation

For convenience, I added a local Boolean flag:

--no-eval_scene_flow_in_world

to reproduce the released evaluator behavior, and

--eval_scene_flow_in_world

to evaluate the prediction and GT consistently in the world coordinate system.

The following values use the same presentation scale as Table 1: (\mathrm{Rel}_p) and EPE are multiplied by 100, while (\delta^p) and APD are percentages.

Geometry results

Method Kubric Rel↓ Kubric δ↑ Spring Rel↓ Spring δ↑ VKITTI2 Rel↓ VKITTI2 δ↑ Dynamic Replica Rel↓ Dynamic Replica δ↑ Point Odyssey Rel↓ Point Odyssey δ↑
MotionCrafter, Table 1 3.40 98.73 29.20 77.27 14.60 84.58 4.04 99.00 9.94 94.90
Reproduce 1, 8 frames 6.38 95.58 32.43 75.98 15.63 82.38 4.89 98.77 11.66 92.76
Reproduce 2, 25 frames 6.63 95.20 27.98 78.60 15.33 82.87 4.49 98.99 10.58 93.41
Reproduce 3, 25 frames, corrected flow coordinates 6.63 95.20 27.98 78.60 15.33 82.87 4.49 98.99 10.58 93.41

As expected, changing only the GT scene-flow coordinate conversion does not affect the geometry metrics.

Scene-flow results

Method Kubric EPE↓ Kubric APD0.05↑ Spring EPE↓ Spring APD0.1↑ VKITTI2 EPE↓ VKITTI2 APD0.3↑ Dynamic Replica EPE↓ Dynamic Replica APD0.05↑ Point Odyssey EPE↓ Point Odyssey APD0.05↑
MotionCrafter, Table 1 4.60* 68.01* 5.61* 90.17* 71.75* 25.90* 0.51 99.72 3.49 80.66
Reproduce 1, 8 frames 7.26 58.98 5.72 86.62 72.53 25.13 0.49 98.77 3.45 80.95
Reproduce 2, 25 frames 7.03 60.74 5.87 86.96 72.59 24.47 0.49 99.82 3.39 81.44
Reproduce 3, 25 frames, corrected flow coordinates 1.78 91.95 2.70 94.39 9.30 94.49 0.35 99.82 1.50 94.17
Image

The 25-frame result using the released behavior is quite close to the Table 1 numbers, particularly on Spring, Virtual KITTI 2, Dynamic Replica, and Point Odyssey.

After converting the GT scene flow into world coordinates, all five datasets improve substantially. The largest change is on Virtual KITTI 2:

EPE:       72.59 -> 9.30
APD_0.3:   24.47 -> 94.49

This large difference seems consistent with a camera/world coordinate mismatch, since Virtual KITTI 2 contains significant camera motion.

My proposed conversion

Given the stored CSO convention, I convert the GT flow using both the current-frame and next-frame camera poses.

Let (T_t^{C\rightarrow W}) be the camera-to-world pose. The world-space scene flow should be

[
F_t^W =
T_{t+1}^{C\rightarrow W}
\left(P_t^{C_t} + F_t^{\mathrm{CSO}}\right)

T_t^{C\rightarrow W}P_t^{C_t}.
]

My implementation is:

def scene_flow_to_world(
    scene_flow,
    pose_c2w,
    point_map,
    device,
):
    # Current point in world coordinates.
    point_map_world = to_world(
        point_map[:-1],
        pose_c2w[:-1],
        device=device,
    )

    # point_map[t] + scene_flow[t] is the corresponding endpoint
    # represented in camera t+1.
    point_map_deformed = point_map[:-1] + scene_flow[:-1]

    # Transform the endpoint using the next-frame camera pose.
    point_map_deformed_world = to_world(
        point_map_deformed,
        pose_c2w[1:],
        device=device,
    )

    return point_map_deformed_world - point_map_world

I then changed the metric computation from:

sflow = sceneflow_metrics(
    aligned_sflow[:-1],
    gt_sflow[:-1],
    gt_dmask[:-1],
)

to:

gt_sflow_world = scene_flow_to_world(
    gt_sflow,
    gt_pose,
    gt_pmap,
    device=device,
)

sflow = sceneflow_metrics(
    aligned_sflow[:-1],
    gt_sflow_world,
    gt_dmask[:-1],
)

Here, gt_pose has first been normalized so that the first camera pose is the identity, consistent with the world-coordinate convention used by the evaluator.

Questions

Could the authors please help clarify the following?

  1. Is the preprocessed scene_flow intended to be the CSO quantity

    [
    P_{t+1}^{C_{t+1}} - P_t^{C_t}?
    ]

  2. In Table 1, was the world-space prediction compared directly against this untransformed CSO ground truth?

  3. Is the conversion above the intended way to evaluate both prediction and GT in a common world coordinate system?

  4. If so, would it be possible to clarify the distinction between the released/Table 1 protocol and a coordinate-consistent world-space protocol in the evaluation documentation?

I completely understand that benchmark implementations can contain historical conventions, and I am not suggesting that the published numbers should simply be replaced. My main goal is to ensure that future methods using this benchmark report scene-flow results under a clearly defined and coordinate-consistent protocol.

Thank you again for releasing the code, models, and benchmark. I would greatly appreciate any clarification.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions