From d8fdd5ff024e773bd43f320b7317e84e8fdfdf08 Mon Sep 17 00:00:00 2001 From: BISTU-gzy <269895055+BISTU-gzy@users.noreply.github.com> Date: Sun, 12 Apr 2026 20:13:16 +0800 Subject: [PATCH] Perception: isolate location refinement state per object Rebuild the location refiner postprocessor options for each object so ground-boundary line segments do not leak across detections. Also skip refinement cleanly when the calibration service cannot provide a valid ground plane, and add regression tests for both behaviors. --- .../camera_location_refinement/BUILD | 13 ++ .../location_refiner_postprocessor.cc | 31 +++-- .../location_refiner_postprocessor.h | 11 ++ .../location_refiner_postprocessor_test.cc | 119 ++++++++++++++++++ 4 files changed, 162 insertions(+), 12 deletions(-) create mode 100644 modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor_test.cc diff --git a/modules/perception/camera_location_refinement/BUILD b/modules/perception/camera_location_refinement/BUILD index 51fa135a073..8bf6306f47d 100644 --- a/modules/perception/camera_location_refinement/BUILD +++ b/modules/perception/camera_location_refinement/BUILD @@ -38,6 +38,7 @@ apollo_cc_library( "//modules/perception/common/camera:apollo_perception_common_camera", "//modules/perception/common/lib:apollo_perception_common_lib", "//modules/perception/common/onboard:apollo_perception_common_onboard", + "@com_google_googletest//:gtest", ], ) @@ -64,6 +65,18 @@ apollo_component( ], ) +apollo_cc_test( + name = "location_refiner_postprocessor_test", + size = "small", + srcs = [ + "location_refiner/location_refiner_postprocessor_test.cc", + ], + deps = [ + ":apollo_perception_camera_location_refinement", + "@com_google_googletest//:gtest_main", + ], +) + apollo_package() cpplint() diff --git a/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor.cc b/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor.cc index e4e1d3deb32..d7c5dd4c692 100644 --- a/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor.cc +++ b/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor.cc @@ -30,6 +30,19 @@ LocationRefinerPostprocessor::LocationRefinerPostprocessor() { postprocessor_.reset(new ObjPostProcessor); } +ObjPostProcessorOptions LocationRefinerPostprocessor::BuildPostprocessorOptions( + const float bbox2d[4], const float dimension_hwl[3], float rotation_y, + const float query_plane[4]) { + ObjPostProcessorOptions options; + memcpy(options.bbox, bbox2d, sizeof(options.bbox)); + options.check_lowerbound = true; + options.line_segs.emplace_back(bbox2d[0], bbox2d[3], bbox2d[2], bbox2d[3]); + memcpy(options.hwl, dimension_hwl, sizeof(options.hwl)); + options.ry = rotation_y; + memcpy(options.plane, query_plane, sizeof(options.plane)); + return options; +} + bool LocationRefinerPostprocessor::Init( const PostprocessorInitOptions &options) { std::string config_file = @@ -56,6 +69,9 @@ bool LocationRefinerPostprocessor::Process(const PostprocessorOptions &options, Eigen::Vector4d plane; if (!calibration_service_->QueryGroundPlaneInCameraFrame(&plane)) { AINFO << "No valid ground plane in the service."; + // Refinement is defined on a calibrated ground plane, so skip cleanly when + // the service cannot provide one. + return true; } float query_plane[4] = { @@ -78,7 +94,6 @@ bool LocationRefinerPostprocessor::Process(const PostprocessorOptions &options, const int height_image = frame->data_provider->src_height(); postprocessor_->Init(k_mat, width_image, height_image); - ObjPostProcessorOptions obj_postprocessor_options; int nr_valid_obj = 0; for (auto &obj : frame->detected_objects) { @@ -124,17 +139,9 @@ bool LocationRefinerPostprocessor::Process(const PostprocessorOptions &options, rotation_y -= 2 * PI; } - // process - memcpy(obj_postprocessor_options.bbox, bbox2d, sizeof(float) * 4); - obj_postprocessor_options.check_lowerbound = true; - camera::LineSegment2D line_seg(bbox2d[0], bbox2d[3], bbox2d[2], - bbox2d[3]); - obj_postprocessor_options.line_segs.push_back(line_seg); - memcpy(obj_postprocessor_options.hwl, dimension_hwl, sizeof(float) * 3); - obj_postprocessor_options.ry = rotation_y; - // refine with calibration service, support ground plane model currently - // {0.0f, cos(tilt), -sin(tilt), -camera_ground_height} - memcpy(obj_postprocessor_options.plane, query_plane, sizeof(float) * 4); + ObjPostProcessorOptions obj_postprocessor_options = + BuildPostprocessorOptions(bbox2d, dimension_hwl, rotation_y, + query_plane); // changed to touching-ground center object_center[1] += dimension_hwl[0] / 2; diff --git a/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor.h b/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor.h index d94d68b7b28..0d2450fd75e 100644 --- a/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor.h +++ b/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor.h @@ -18,6 +18,8 @@ #include #include +#include "gtest/gtest_prod.h" + #include "modules/perception/camera_location_refinement/location_refiner/proto/location_refiner.pb.h" #include "modules/perception/camera_location_refinement/interface/base_postprocessor.h" @@ -72,7 +74,16 @@ class LocationRefinerPostprocessor : public BasePostprocessor { return x > left && x < right; } + static ObjPostProcessorOptions BuildPostprocessorOptions( + const float bbox2d[4], const float dimension_hwl[3], float rotation_y, + const float query_plane[4]); + private: + FRIEND_TEST(LocationRefinerPostprocessorTest, + build_postprocessor_options_test); + FRIEND_TEST(LocationRefinerPostprocessorTest, + process_skips_when_ground_plane_unavailable_test); + std::unique_ptr postprocessor_; std::shared_ptr calibration_service_; location_refiner::LocationRefinerParam location_refiner_param_; diff --git a/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor_test.cc b/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor_test.cc new file mode 100644 index 00000000000..9cc8dfa6444 --- /dev/null +++ b/modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor_test.cc @@ -0,0 +1,119 @@ +/****************************************************************************** + * Copyright 2026 The Apollo Authors. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an AS IS BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + *****************************************************************************/ + +#include "modules/perception/camera_location_refinement/location_refiner/location_refiner_postprocessor.h" + +#include "gtest/gtest.h" + +#include "modules/perception/common/base/object.h" + +namespace apollo { +namespace perception { +namespace camera { + +class FakeCalibrationService : public BaseCalibrationService { + public: + bool Init(const CalibrationServiceInitOptions &options = + CalibrationServiceInitOptions()) override { + return true; + } + + bool BuildIndex() override { return true; } + + bool QueryGroundPlaneInCameraFrame( + Eigen::Vector4d *plane_param) const override { + if (plane_param != nullptr) { + *plane_param = Eigen::Vector4d::Zero(); + } + return false; + } + + std::string Name() const override { return "FakeCalibrationService"; } +}; + +class LocationRefinerPostprocessorTest : public ::testing::Test {}; + +TEST_F(LocationRefinerPostprocessorTest, build_postprocessor_options_test) { + const float bbox2d[4] = {10.0f, 20.0f, 30.0f, 40.0f}; + const float dimension_hwl[3] = {1.5f, 2.5f, 3.5f}; + const float rotation_y = 0.25f; + const float query_plane[4] = {0.0f, 1.0f, 0.0f, -1.6f}; + + auto first = LocationRefinerPostprocessor::BuildPostprocessorOptions( + bbox2d, dimension_hwl, rotation_y, query_plane); + auto second = LocationRefinerPostprocessor::BuildPostprocessorOptions( + bbox2d, dimension_hwl, rotation_y, query_plane); + + ASSERT_EQ(first.line_segs.size(), 1u); + ASSERT_EQ(second.line_segs.size(), 1u); + + EXPECT_FLOAT_EQ(first.bbox[0], bbox2d[0]); + EXPECT_FLOAT_EQ(first.bbox[1], bbox2d[1]); + EXPECT_FLOAT_EQ(first.bbox[2], bbox2d[2]); + EXPECT_FLOAT_EQ(first.bbox[3], bbox2d[3]); + EXPECT_FLOAT_EQ(first.hwl[0], dimension_hwl[0]); + EXPECT_FLOAT_EQ(first.hwl[1], dimension_hwl[1]); + EXPECT_FLOAT_EQ(first.hwl[2], dimension_hwl[2]); + EXPECT_FLOAT_EQ(first.ry, rotation_y); + EXPECT_FLOAT_EQ(first.plane[0], query_plane[0]); + EXPECT_FLOAT_EQ(first.plane[1], query_plane[1]); + EXPECT_FLOAT_EQ(first.plane[2], query_plane[2]); + EXPECT_FLOAT_EQ(first.plane[3], query_plane[3]); + EXPECT_FLOAT_EQ(first.line_segs[0].pt_start[0], bbox2d[0]); + EXPECT_FLOAT_EQ(first.line_segs[0].pt_start[1], bbox2d[3]); + EXPECT_FLOAT_EQ(first.line_segs[0].pt_end[0], bbox2d[2]); + EXPECT_FLOAT_EQ(first.line_segs[0].pt_end[1], bbox2d[3]); + + first.line_segs.emplace_back(0.0f, 0.0f, 1.0f, 1.0f); + + EXPECT_EQ(first.line_segs.size(), 2u); + EXPECT_EQ(second.line_segs.size(), 1u); +} + +TEST_F(LocationRefinerPostprocessorTest, + process_skips_when_ground_plane_unavailable_test) { + LocationRefinerPostprocessor postprocessor; + postprocessor.calibration_service_ = + std::make_shared(); + postprocessor.location_refiner_param_.set_min_dist_to_camera(30.0f); + postprocessor.location_refiner_param_.set_roi_h2bottom_scale(0.5f); + + onboard::CameraFrame frame; + auto obj = std::make_shared(); + obj->camera_supplement.local_center = Eigen::Vector3f(3.0f, 0.0f, 5.0f); + obj->camera_supplement.box.xmin = 10.0f; + obj->camera_supplement.box.ymin = 20.0f; + obj->camera_supplement.box.xmax = 30.0f; + obj->camera_supplement.box.ymax = 40.0f; + obj->size = Eigen::Vector3f(4.0f, 2.0f, 1.5f); + frame.detected_objects.push_back(obj); + + PostprocessorOptions options; + options.do_refinement_with_calibration_service = true; + + EXPECT_TRUE(postprocessor.Process(options, &frame)); + EXPECT_EQ(frame.data_provider, nullptr); + EXPECT_FLOAT_EQ(frame.detected_objects[0]->camera_supplement.local_center(0), + 3.0f); + EXPECT_FLOAT_EQ(frame.detected_objects[0]->camera_supplement.local_center(1), + 0.0f); + EXPECT_FLOAT_EQ(frame.detected_objects[0]->camera_supplement.local_center(2), + 5.0f); +} + +} // namespace camera +} // namespace perception +} // namespace apollo