/**************************************************************************** * * Copyright (c) 2019 ECL Development Team. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * 3. Neither the name PX4 nor the names of its contributors may be * used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. * ****************************************************************************/ /** * Test the external vision functionality * @author Kamil Ritz */ #include #include "EKF/ekf.h" #include "sensor_simulator/sensor_simulator.h" #include "sensor_simulator/ekf_wrapper.h" #include "test_helper/reset_logging_checker.h" class EkfExternalVisionTest : public ::testing::Test { public: EkfExternalVisionTest(): ::testing::Test(), _ekf{std::make_shared()}, _sensor_simulator(_ekf), _ekf_wrapper(_ekf) {}; std::shared_ptr _ekf; SensorSimulator _sensor_simulator; EkfWrapper _ekf_wrapper; static constexpr float _tilt_align_time = 7.f; // Setup the Ekf with synthetic measurements void SetUp() override { _ekf->init(0); } // Use this method to clean up any memory, network etc. after each test void TearDown() override { } }; TEST_F(EkfExternalVisionTest, checkVisionFusionLogic) { _sensor_simulator.runSeconds(_tilt_align_time); // Let the tilt align _ekf_wrapper.enableExternalVisionPositionFusion(); _sensor_simulator.startExternalVision(); _sensor_simulator.runSeconds(2); EXPECT_TRUE(_ekf_wrapper.isIntendingExternalVisionPositionFusion()); EXPECT_FALSE(_ekf_wrapper.isIntendingExternalVisionVelocityFusion()); EXPECT_FALSE(_ekf_wrapper.isIntendingExternalVisionHeadingFusion()); EXPECT_TRUE(_ekf->local_position_is_valid()); EXPECT_FALSE(_ekf->global_position_is_valid()); _ekf_wrapper.enableExternalVisionVelocityFusion(); _sensor_simulator.runSeconds(2); EXPECT_TRUE(_ekf_wrapper.isIntendingExternalVisionPositionFusion()); EXPECT_TRUE(_ekf_wrapper.isIntendingExternalVisionVelocityFusion()); EXPECT_FALSE(_ekf_wrapper.isIntendingExternalVisionHeadingFusion()); EXPECT_TRUE(_ekf->local_position_is_valid()); EXPECT_FALSE(_ekf->global_position_is_valid()); _ekf_wrapper.enableExternalVisionHeadingFusion(); _sensor_simulator.runSeconds(2); EXPECT_TRUE(_ekf_wrapper.isIntendingExternalVisionPositionFusion()); EXPECT_TRUE(_ekf_wrapper.isIntendingExternalVisionVelocityFusion()); EXPECT_TRUE(_ekf_wrapper.isIntendingExternalVisionHeadingFusion()); EXPECT_TRUE(_ekf->local_position_is_valid()); EXPECT_FALSE(_ekf->global_position_is_valid()); } TEST_F(EkfExternalVisionTest, visionVelocityReset) { _sensor_simulator.runSeconds(_tilt_align_time); ResetLoggingChecker reset_logging_checker(_ekf); reset_logging_checker.capturePreResetState(); const Vector3f simulated_velocity(0.3f, -1.0f, 0.4f); _sensor_simulator._vio.setVelocity(simulated_velocity); _ekf_wrapper.enableExternalVisionVelocityFusion(); _sensor_simulator.startExternalVision(); // Note: test duration needs to allow time for tilt alignment to complete _sensor_simulator.runMicroseconds(2e5); // THEN: a reset to Vision velocity should be done // Note: velocity will drift after reset due to INAV errors so the tolerance needs to allow for this const Vector3f estimated_velocity = _ekf->getVelocity(); EXPECT_TRUE(isEqual(estimated_velocity, simulated_velocity, 0.01f)); // AND: the reset in velocity should be saved correctly reset_logging_checker.capturePostResetState(); EXPECT_TRUE(reset_logging_checker.isHorizontalVelocityResetCounterIncreasedBy(1)); EXPECT_TRUE(reset_logging_checker.isVerticalVelocityResetCounterIncreasedBy(1)); EXPECT_TRUE(reset_logging_checker.isVelocityDeltaLoggedCorrectly(0.01f)); } TEST_F(EkfExternalVisionTest, visionVelocityResetWithAlignment) { _sensor_simulator.runSeconds(_tilt_align_time); ResetLoggingChecker reset_logging_checker(_ekf); reset_logging_checker.capturePreResetState(); // GIVEN: Drone is pointing north, and we use mag (ROTATE_EV) // Heading of drone in EKF frame is 0° // WHEN: Vision frame is rotate +90°. The reported heading is -90° Quatf vision_to_ekf(Eulerf(0.0f,0.0f,math::radians(-90.0f))); _sensor_simulator._vio.setOrientation(vision_to_ekf.inversed()); _ekf_wrapper.enableExternalVisionAlignment(); const Vector3f simulated_velocity_in_vision_frame(0.3f, -1.0f, 0.4f); const Vector3f simulated_velocity_in_ekf_frame = Dcmf(vision_to_ekf) * simulated_velocity_in_vision_frame; _sensor_simulator._vio.setVelocity(simulated_velocity_in_vision_frame); _ekf_wrapper.enableExternalVisionVelocityFusion(); _sensor_simulator.startExternalVision(); _sensor_simulator.runMicroseconds(2e5); // THEN: a reset to Vision velocity should be done const Vector3f estimated_velocity_in_ekf_frame = _ekf->getVelocity(); EXPECT_TRUE(isEqual(estimated_velocity_in_ekf_frame, simulated_velocity_in_ekf_frame, 0.01f)); // And: the frame offset should be estimated correctly Quatf estimatedExternalVisionFrameOffset = _ekf->getVisionAlignmentQuaternion(); EXPECT_TRUE(matrix::isEqual(vision_to_ekf.canonical(), estimatedExternalVisionFrameOffset.canonical())); // AND: the reset in velocity should be saved correctly reset_logging_checker.capturePostResetState(); EXPECT_TRUE(reset_logging_checker.isHorizontalVelocityResetCounterIncreasedBy(1)); EXPECT_TRUE(reset_logging_checker.isVerticalVelocityResetCounterIncreasedBy(1)); EXPECT_TRUE(reset_logging_checker.isVelocityDeltaLoggedCorrectly(0.01f)); } TEST_F(EkfExternalVisionTest, visionHorizontalPositionReset) { _sensor_simulator.runSeconds(_tilt_align_time); const Vector3f simulated_position(8.3f, -1.0f, 0.0f); _sensor_simulator._vio.setPosition(simulated_position); _ekf_wrapper.enableExternalVisionPositionFusion(); _sensor_simulator.startExternalVision(); _sensor_simulator.runMicroseconds(2e5); // THEN: a reset to Vision velocity should be done const Vector3f estimated_position = _ekf->getPosition(); EXPECT_TRUE(isEqual(estimated_position, simulated_position, 1e-5f)); } TEST_F(EkfExternalVisionTest, visionHorizontalPositionResetWithAlignment) { _sensor_simulator.runSeconds(_tilt_align_time); // GIVEN: Drone is pointing north, and we use mag (ROTATE_EV) // Heading of drone in EKF frame is 0° // WHEN: Vision frame is rotate +90°. The reported heading is -90° Quatf vision_to_ekf(Eulerf(0.0f,0.0f,math::radians(-90.0f))); _sensor_simulator._vio.setOrientation(vision_to_ekf.inversed()); _ekf_wrapper.enableExternalVisionAlignment(); const Vector3f simulated_position_in_vision_frame(8.3f, -1.0f, 0.0f); const Vector3f simulated_position_in_ekf_frame = Dcmf(vision_to_ekf) * simulated_position_in_vision_frame; _sensor_simulator._vio.setPosition(simulated_position_in_vision_frame); _ekf_wrapper.enableExternalVisionPositionFusion(); _sensor_simulator.startExternalVision(); _sensor_simulator.runMicroseconds(2e5); // THEN: a reset to Vision velocity should be done const Vector3f estimated_position_in_ekf_frame = _ekf->getPosition(); EXPECT_TRUE(isEqual(estimated_position_in_ekf_frame, simulated_position_in_ekf_frame, 1e-2f)); } TEST_F(EkfExternalVisionTest, visionVarianceCheck) { _sensor_simulator.runSeconds(_tilt_align_time); const Vector3f velVar_init = _ekf->getVelocityVariance(); EXPECT_NEAR(velVar_init(0), velVar_init(1), 0.0001); _sensor_simulator._vio.setVelocityVariance(Vector3f{2.0f,0.01f,0.01f}); _ekf_wrapper.enableExternalVisionVelocityFusion(); _sensor_simulator.startExternalVision(); _sensor_simulator.runSeconds(4); const Vector3f velVar_new = _ekf->getVelocityVariance(); EXPECT_TRUE(velVar_new(0) > velVar_new(1)); } TEST_F(EkfExternalVisionTest, visionAlignment) { _sensor_simulator.runSeconds(_tilt_align_time); // GIVEN: Drone is pointing north, and we use mag (ROTATE_EV) // Heading of drone in EKF frame is 0° // WHEN: Vision frame is rotate +90°. The reported heading is -90° Quatf externalVisionFrameOffset(Eulerf(0.0f,0.0f,math::radians(90.0f))); _sensor_simulator._vio.setOrientation(externalVisionFrameOffset.inversed()); _ekf_wrapper.enableExternalVisionAlignment(); // Simulate high uncertainty on vision x axis which is in this case // the y EKF frame axis _sensor_simulator._vio.setVelocityVariance(Vector3f{2.0f,0.01f,0.01f}); _ekf_wrapper.enableExternalVisionVelocityFusion(); _sensor_simulator.startExternalVision(); const Vector3f velVar_init = _ekf->getVelocityVariance(); EXPECT_NEAR(velVar_init(0), velVar_init(1), 0.0001); _sensor_simulator.runSeconds(4); // THEN: velocity uncertainty in y should be bigger const Vector3f velVar_new = _ekf->getVelocityVariance(); EXPECT_TRUE(velVar_new(1) > velVar_new(0)); // THEN: the frame offset should be estimated correctly Quatf estimatedExternalVisionFrameOffset = _ekf->getVisionAlignmentQuaternion(); EXPECT_TRUE(matrix::isEqual(externalVisionFrameOffset.canonical(), estimatedExternalVisionFrameOffset.canonical())); } TEST_F(EkfExternalVisionTest, velocityFrameBody) { // GIVEN: Drone is turned 90 degrees const Quatf quat_sim(Eulerf(0.0f, 0.0f, math::radians(90.0f))); _sensor_simulator.simulateOrientation(quat_sim); _sensor_simulator.runSeconds(_tilt_align_time); // Without any measurement x and y velocity variance are close const Vector3f velVar_init = _ekf->getVelocityVariance(); EXPECT_NEAR(velVar_init(0), velVar_init(1), 0.0001); // WHEN: measurement is given in BODY-FRAME and // x variance is bigger than y variance _sensor_simulator._vio.setVelocityFrameToBody(); float vel_cov_data [9] = {2.0f, 0.0f, 0.0f, 0.0f, 0.01f, 0.0f, 0.0f, 0.0f, 0.01f}; const Matrix3f vel_cov_body(vel_cov_data); const Vector3f vel_body(1.0f, 0.0f, 0.0f); _sensor_simulator._vio.setVelocityCovariance(vel_cov_body); _sensor_simulator._vio.setVelocity(vel_body); _ekf_wrapper.enableExternalVisionVelocityFusion(); _sensor_simulator.startExternalVision(); _sensor_simulator.runSeconds(4); // THEN: As the drone is turned 90 degrees, velocity variance // along local y axis is expected to be bigger const Vector3f velVar_new = _ekf->getVelocityVariance(); EXPECT_NEAR(velVar_new(1) / velVar_new(0), 80.f, 10.f); const Vector3f vel_earth_est = _ekf->getVelocity(); EXPECT_NEAR(vel_earth_est(0), 0.0f, 0.1f); EXPECT_NEAR(vel_earth_est(1), 1.0f, 0.1f); } TEST_F(EkfExternalVisionTest, velocityFrameLocal) { // GIVEN: Drone is turned 90 degrees const Quatf quat_sim(Eulerf(0.0f, 0.0f, math::radians(90.0f))); _sensor_simulator.simulateOrientation(quat_sim); _sensor_simulator.runSeconds(_tilt_align_time); // Without any measurement x and y velocity variance are close const Vector3f velVar_init = _ekf->getVelocityVariance(); EXPECT_NEAR(velVar_init(0), velVar_init(1), 0.0001); // WHEN: measurement is given in LOCAL-FRAME and // x variance is bigger than y variance _sensor_simulator._vio.setVelocityFrameToLocal(); float vel_cov_data [9] = {2.0f, 0.0f, 0.0f, 0.0f, 0.01f, 0.0f, 0.0f, 0.0f, 0.01f}; const Matrix3f vel_cov_earth(vel_cov_data); const Vector3f vel_earth(1.0f, 0.0f, 0.0f); _sensor_simulator._vio.setVelocityCovariance(vel_cov_earth); _sensor_simulator._vio.setVelocity(vel_earth); _ekf_wrapper.enableExternalVisionVelocityFusion(); _sensor_simulator.startExternalVision(); _sensor_simulator.runSeconds(4); // THEN: Independently on drones heading, velocity variance // along local x axis is expected to be bigger const Vector3f velVar_new = _ekf->getVelocityVariance(); EXPECT_NEAR(velVar_new(0) / velVar_new(1), 80.f, 10.f); const Vector3f vel_earth_est = _ekf->getVelocity(); EXPECT_NEAR(vel_earth_est(0), 1.0f, 0.1f); EXPECT_NEAR(vel_earth_est(1), 0.0f, 0.1f); }