Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

VINS-Fusion: State Updates and Window Management in Visual-Inertial Navigation

Tech 1
After initializing and optimizing poses, the next step is to update these poses to the previous frame. Let's examine the updateLatestStates() source code:
void Navigator::updateLatestStates()
{
    // Lock mPropagate to ensure thread-safe updates to the latest state
    mPropagate.lock();
    // Update the latest timestamp with current frame timestamp plus time delay td
    current_time = FrameTimestamps[frame_index] + td;
    // Update the latest position information
    current_position = Positions[frame_index];
    // Update the latest orientation information (quaternion)
    current_orientation = Orientations[frame_index];
    // Update the latest velocity information
    current_velocity = Velocities[frame_index];
    // Update the latest accelerometer bias
    current_acc_bias = AccBiases[frame_index];
    // Update the latest gyroscope bias
    current_gyro_bias = GyroBiases[frame_index];
    // Update the latest accelerometer sensor data
    current_acc_0 = acc_0;
    // Update the latest gyroscope sensor data
    current_gyro_0 = gyro_0;
    // Lock mBuf to ensure thread-safe access to IMU data queue
    mBuf.lock();
    // Create a temporary accelerometer data queue
    queue> tmp_acc_queue = acc_queue;
    // Create a temporary gyroscope data queue
    queue> tmp_gyro_queue = gyro_queue;
    // Unlock mBuf
    mBuf.unlock();
    // Iterate through accelerometer and gyroscope data queues for fast prediction
    while(!tmp_acc_queue.empty())
    {
        // Extract timestamp from the queue
        double timestamp = tmp_acc_queue.front().first;
        // Extract accelerometer data from the queue
        Eigen::Vector3d accel_data = tmp_acc_queue.front().second;
        // Extract gyroscope data from the queue
        Eigen::Vector3d gyro_data = tmp_gyro_queue.front().second;
        // Call fastPredictIMU function for fast IMU prediction
        fastPredictIMU(timestamp, accel_data, gyro_data);
        // Remove processed data from the queue
        tmp_acc_queue.pop();
        tmp_gyro_queue.pop();
    }
    // Unlock mPropagate
    mPropagate.unlock();
}
This function primarily updates the estimator's latest state. Through locking mechanisms, it ensures thread safety, extracts the latest pose, velocity, biases, and sensor data, and utilizes IMU data queue for fast prediction. Let's examine the fast prediction function fastPredictIMU():
// Use inertial sensor data to quickly predict and update system state (pose, position, velocity)
// Ensure timely state prediction with each new sensor data arrival
void Navigator::fastPredictIMU(double t, Eigen::Vector3d linear_acceleration, Eigen::Vector3d angular_velocity)
{
    // Calculate time difference between current time and latest time
    double time_delta = t - current_time;
    // Update latest time to current time
    current_time = t;
    // Calculate initial gravity-free acceleration
    //current_orientation is the latest orientation quaternion, current_acc_0 is the latest accelerometer reading,
    //current_acc_bias is the accelerometer bias, g is gravity vector. The calculated gravity_free_accel_initial 
    //is the linear acceleration under current orientation minus gravity.
    Eigen::Vector3d gravity_free_accel_initial = current_orientation * (current_acc_0 - current_acc_bias) - g;
    // Calculate uncompensated gyroscope reading
    //current_gyro_0 is the latest gyroscope reading, angular_velocity is the current gyroscope reading,
    //current_gyro_bias is the gyroscope bias. The calculated un_gyro is the uncompensated gyroscope reading.
    Eigen::Vector3d un_gyro = 0.5 * (current_gyro_0 + angular_velocity) - current_gyro_bias;
    // Update orientation quaternion
    //Utility::deltaQ is a helper function to calculate rotation quaternion increment.
    //The updated orientation quaternion current_orientation is calculated based on current uncompensated 
    //gyroscope reading and time_delta.
    current_orientation = current_orientation * Utility::deltaQ(un_gyro * time_delta);
    // Calculate current gravity-free acceleration
    //linear_acceleration is the current accelerometer reading, current_acc_bias is the accelerometer bias.
    //The calculated gravity_free_accel_current is the linear acceleration under updated orientation minus gravity.
    Eigen::Vector3d gravity_free_accel_current = current_orientation * (linear_acceleration - current_acc_bias) - g;
    // Calculate average acceleration
    //Calculate the average of gravity_free_accel_initial and gravity_free_accel_current to get 
    //the average gravity-free acceleration.
    Eigen::Vector3d avg_gravity_free_accel = 0.5 * (gravity_free_accel_initial + gravity_free_accel_current);
    // Update position through double integration (displacement, velocity, and acceleration)
    //current_position is the current position, current_velocity is the current velocity, 
    //time_delta is the time difference, avg_gravity_free_accel is the average gravity-free acceleration.
    //Update position using quadratic integration formula.
    current_position = current_position + time_delta * current_velocity + 0.5 * time_delta * time_delta * avg_gravity_free_accel;
    // Update velocity through single integration
    //Update velocity using linear integration formula.
    current_velocity = current_velocity + time_delta * avg_gravity_free_accel;
    // Update latest accelerometer sensor data
    current_acc_0 = linear_acceleration;
    // Update latest gyroscope sensor data
    current_gyro_0 = angular_velocity;
}
This prediction process is based on existing IMU data, utilizing accelerometer and gyroscope sensor data to predict the state (including position, orientation, velocity, etc.). This process helps to quickly update and predict the system's current state before new IMU data arrives, thereby improving the system's real-time performance and continuity. Now, let's examine the sliding window function slideWindow():
void Navigator::slideWindow()
{
    // Create a timing object to record the marginalization operation time
    TicToc margin_timer;
    // If marginalization flag is MARGIN_OLD, remove the oldest frame from the sliding window
    if (marginalization_flag == MARGIN_OLD)
    {
        // Record the timestamp and orientation of the oldest frame
        double oldest_time = FrameTimestamps[0];
        back_R0 = Orientations[0];
        back_P0 = Positions[0];
        // If current frame count equals window size, the sliding window is full and needs to remove the oldest frame
        if (frame_index == WINDOW_SIZE)
        {
            // Shift each frame's data forward by one position
            for (int i = 0; i < WINDOW_SIZE; i++)
            {
                FrameTimestamps[i] = FrameTimestamps[i + 1];
                Orientations[i].swap(Orientations[i + 1]);
                Positions[i].swap(Positions[i + 1]);
                if (USE_IMU)
                {
                    std::swap(pre_integrations[i], pre_integrations[i + 1]);
                    time_delta_buf[i].swap(time_delta_buf[i + 1]);
                    linear_acceleration_buf[i].swap(linear_acceleration_buf[i + 1]);
                    angular_velocity_buf[i].swap(angular_velocity_buf[i + 1]);
                    Velocities[i].swap(Velocities[i + 1]);
                    AccBiases[i].swap(AccBiases[i + 1]);
                    GyroBiases[i].swap(GyroBiases[i + 1]);
                }
            }
            // Copy the last frame's data to the second-to-last position
            FrameTimestamps[WINDOW_SIZE] = FrameTimestamps[WINDOW_SIZE - 1];
            Positions[WINDOW_SIZE] = Positions[WINDOW_SIZE - 1];
            Orientations[WINDOW_SIZE] = Orientations[WINDOW_SIZE - 1];
            
            if (USE_IMU)
            {
                Velocities[WINDOW_SIZE] = Velocities[WINDOW_SIZE - 1];
                AccBiases[WINDOW_SIZE] = AccBiases[WINDOW_SIZE - 1];
                GyroBiases[WINDOW_SIZE] = GyroBiases[WINDOW_SIZE - 1];
                // Delete the last frame's pre-integration info and create a new pre-integration object
                delete pre_integrations[WINDOW_SIZE];
                pre_integrations[WINDOW_SIZE] = new IntegrationBase{acc_0, gyro_0, AccBiases[WINDOW_SIZE], GyroBiases[WINDOW_SIZE]};
                // Clear the last frame's time delta, linear acceleration, and angular velocity buffers
                time_delta_buf[WINDOW_SIZE].clear();
                linear_acceleration_buf[WINDOW_SIZE].clear();
                angular_velocity_buf[WINDOW_SIZE].clear();
            }
            
            // If in initial phase or solver_flag is INITIAL
            if (true || solver_flag == INITIAL)
            {
                // Delete the oldest frame's pre-integration info and remove it from all image frame records
                map::iterator frame_it;
                // Find the frame with timestamp oldest_time in all_image_frame and set iterator frame_it to it
                frame_it = all_image_frame.find(oldest_time);
                // Delete pre-integration info
                delete frame_it->second.pre_integration;
                // Delete image frame record
                all_image_frame.erase(all_image_frame.begin(), frame_it);
            }
            // Call slideWindowOld to handle marginalization of the oldest frame in the sliding window
            slideWindowOld();
        }
    }
    // If marginalization flag is not MARGIN_OLD, remove the newest frame from the sliding window
    else
    {
        // If current frame count equals window size, the sliding window is full and needs to remove the newest frame
        if (frame_index == WINDOW_SIZE)
        {
            // Copy the newest frame's data to the second-to-last position
            FrameTimestamps[frame_index - 1] = FrameTimestamps[frame_index];
            Positions[frame_index - 1] = Positions[frame_index];
            Orientations[frame_index - 1] = Orientations[frame_index];
            
            if (USE_IMU)
            {
                // Add the newest frame's IMU data to the second-to-last frame's pre-integration info
                for (unsigned int i = 0; i < time_delta_buf[frame_index].size(); i++)
                {
                    double tmp_dt = time_delta_buf[frame_index][i];
                    Vector3d tmp_linear_acceleration = linear_acceleration_buf[frame_index][i];
                    Vector3d tmp_angular_velocity = angular_velocity_buf[frame_index][i];
                    
                    pre_integrations[frame_index - 1]->push_back(tmp_dt, tmp_linear_acceleration, tmp_angular_velocity);
                    
                    time_delta_buf[frame_index - 1].push_back(tmp_dt);
                    linear_acceleration_buf[frame_index - 1].push_back(tmp_linear_acceleration);
                    angular_velocity_buf[frame_index - 1].push_back(tmp_angular_velocity);
                }
                
                // Copy the newest frame's velocity, accelerometer bias, and gyroscope bias to the second-to-last frame
                Velocities[frame_index - 1] = Velocities[frame_index];
                AccBiases[frame_index - 1] = AccBiases[frame_index];
                GyroBiases[frame_index - 1] = GyroBiases[frame_index];
                
                // Delete the last frame's pre-integration info and create a new pre-integration object
                delete pre_integrations[WINDOW_SIZE];
                pre_integrations[WINDOW_SIZE] = new IntegrationBase{acc_0, gyro_0, AccBiases[WINDOW_SIZE], GyroBiases[WINDOW_SIZE]};
                
                // Clear the last frame's time delta, linear acceleration, and angular velocity buffers
                time_delta_buf[WINDOW_SIZE].clear();
                linear_acceleration_buf[WINDOW_SIZE].clear();
                angular_velocity_buf[WINDOW_SIZE].clear();
            }
            // Call slideWindowNew to handle marginalization of the newest frame in the sliding window
            slideWindowNew();
        }
    }
}
This function primarily manages frames in the sliding window. When the sliding window size reaches the preset maximum, it is responsible for removing the oldest or newest frame to make space for new frames. Based on the marginalization flag, it determines whether to remove the oldest or newest frame in the window: Marginalizing the oldest frame: When the sliding window is full and needs to marginalize the oldest frame, it shifts each frame's data forward by one position, processes IMU pre-integration information, and then calls slideWindowOld() for further processing. Marginalizing the newest frame: When the sliding window is full and needs to marginalize the newest frame, it copies the newest frame's data to the second-to-last position, processes IMU pre-integration information, and then calls slideWindowNew() for further processing. This implements frame management in the sliding window, ensuring the system can process new image and IMU data while maintaining a constant number of frames in the window. The slideWindowOld() source code:
void Navigator::slideWindowOld()
{
    // Increment oldest frame counter
    oldest_frame_count++;
    
    // Determine if depth adjustment is needed
    // If the solver state is NON_LINEAR, shift_depth is true, otherwise false
    bool shift_depth = solver_flag == NON_LINEAR ? true : false;
    if (shift_depth)
    {
        // Define variables R0 and R1 for rotation matrices, P0 and P1 for position vectors
        Matrix3d R0, R1;
        Vector3d P0, P1;
        // Calculate R0 and R1, the rotation matrices for the oldest and current oldest frames in the sliding window,
        // multiplied by the camera-to-IMU extrinsic parameters
        R0 = back_R0 * camera_imu_rotation[0];
        R1 = Orientations[0] * camera_imu_rotation[0];
        // Calculate P0 and P1, the position vectors for the oldest and current oldest frames,
        // performing translation transformation
        P0 = back_P0 + back_R0 * camera_imu_translation[0];
        P1 = Positions[0] + Orientations[0] * camera_imu_translation[0];
        // Call the feature manager's removeBackShiftDepth method to remove features from the oldest frame
        // and perform depth adjustment
        feature_manager.removeBackShiftDepth(R0, P0, R1, P1);
    }
    else
        // If no depth adjustment is needed, directly remove features from the oldest frame
        feature_manager.removeBack();
}
The slideWindowNew() source code:
void Navigator::slideWindowNew()
{
    // Increment newest frame counter
    newest_frame_count++;
    // Remove features corresponding to the current frame count from the feature manager
    feature_manager.removeFront(frame_index);
}
The slideWindow() function shares similarities with the marginalization processing in the optimization function, with the main difference being that slideWindow() is responsible for actually removing features and frames, calling specific functions to remove frames and manage features (removeBackShiftDepth, removeBack, and removeFront), while the optimization function focuses more on the marginalization operation during the optimization process. For the removeBackShiftDepth() function:
// Manage feature point starting frame indices: For feature points not starting from the oldest frame,
// decrement their starting frame index by 1.
// Remove feature points from the oldest frame: For feature points starting from the oldest frame,
// remove their record in the oldest frame and check their remaining observation frames.
// If observation frames are less than 2, remove the feature point.
// Depth adjustment: For remaining feature points, calculate their estimated depth in the current frame
// and update their depth values.
void FeatureManager::removeBackShiftDepth(Eigen::Matrix3d marginalization_R, Eigen::Vector3d marginalization_P, 
                                          Eigen::Matrix3d new_frame_R, Eigen::Vector3d new_frame_P)
{
    // Iterate through all feature points
    for (auto it = features.begin(), it_next = features.begin();
         it != features.end(); it = it_next)
    {
        // Store the next iterator position in advance
        it_next++;
        
        // If the feature point's starting frame is not 0, it means this feature point does not appear in the oldest frame
        if (it->start_frame != 0)
            // Decrement the starting frame index by 1 because the oldest frame has been removed
            // and all frame indices need to move forward
            it->start_frame--;
        else
        {
            // If the feature point's starting frame is 0, it means this feature point appears in the oldest frame
            // Get the normalized image plane coordinates of this feature point in the oldest frame
            Eigen::Vector3d uv_i = it->feature_per_frame[0].point;  
            // Remove the record of this feature point in the oldest frame
            it->feature_per_frame.erase(it->feature_per_frame.begin());
            // If the number of observation frames for this feature point is less than 2 after removing the oldest frame,
            // it means this feature point doesn't have enough observations, so remove the feature point
            if (it->feature_per_frame.size() < 2)
            {
                // Remove this feature point from the feature list
                features.erase(it);
                continue;
            }
            else
            {
                // Calculate the 3D coordinates of this feature point in the oldest frame
                Eigen::Vector3d pts_i = uv_i * it->estimated_depth;
                // Transform the feature point's 3D coordinates from the oldest frame's camera coordinate system
                // to the world coordinate system
                Eigen::Vector3d world_pts_i = marginalization_R * pts_i + marginalization_P;
                // Transform the feature point's 3D coordinates from the world coordinate system
                // to the current frame's camera coordinate system
                Eigen::Vector3d pts_j = new_frame_R.transpose() * (world_pts_i - new_frame_P);
                // Get the depth value of the feature point in the current frame
                double depth_j = pts_j(2);
                // If the depth value is greater than 0, update the feature point's estimated depth
                if (depth_j > 0)
                    it->estimated_depth = depth_j;
                else
                    // If the depth value is less than or equal to 0, set the feature point's estimated depth
                    // to the initial depth
                    it->estimated_depth = INITIAL_DEPTH;
            }
        }
        // Remove tracking-lost feature after marginalization
        /*
        if (it->endFrame() < WINDOW_SIZE - 1)
        {
            features.erase(it);
        }
        */
    }
}
This function removes features from the oldest frame in the sliding window and handles feature management with depth adjustment when necessary. Specifically, it processes feature points based on the oldest and current frame pose information, performing depth adjustment on the features. For removeBack():
void FeatureManager::removeBack()
{
    // Iterate through all feature points
    for (auto it = features.begin(), it_next = features.begin();
         it != features.end(); it = it_next)
    {
        // Store the next iterator position in advance
        it_next++;
        
        // If the feature point's starting frame is not 0, it means this feature point does not appear in the oldest frame
        if (it->start_frame != 0)
            // Decrement the starting frame index by 1 because the oldest frame has been removed
            // and all frame indices need to move forward
            it->start_frame--;
        else
        {
            // If the feature point's starting frame is 0, it means this feature point appears in the oldest frame
            // Remove the record of this feature point in the oldest frame
            it->feature_per_frame.erase(it->feature_per_frame.begin());
            // If the number of observation frames for this feature point is 0 after removing the oldest frame,
            // it means this feature point has no observation frames left, so remove the feature point
            if (it->feature_per_frame.size() == 0)
                // Remove this feature point from the feature list
                features.erase(it);
        }
    }
}
This function is straightforward - it's the removeBackShiftDepth() function without depth adjustment updates. The removeFront() function deletes the newest frame's features, similar to the previous function:
void FeatureManager::removeFront(int frame_index)
{
    // Iterate through all feature points
    for (auto it = features.begin(), it_next = features.begin(); it != features.end(); it = it_next)
    {
        // Store the next iterator position in advance
        it_next++;
        
        // If the feature point's starting frame is the current frame index, it means this feature point appears in the newest frame
        if (it->start_frame == frame_index)
        {
            // Decrement the starting frame index by 1 because the newest frame has been removed
            // and all frame indices need to move forward
            it->start_frame--;
        }
        else
        {
            // Calculate the position of this feature point in the window
            int j = WINDOW_SIZE - 1 - it->start_frame;
            // If the feature point's end frame is less than the frame before the current newest frame,
            // it means this feature point doesn't need processing, continue to the next iteration
            if (it->endFrame() < frame_index - 1)
                continue;
            // Remove the record of this feature point in the current newest frame
            it->feature_per_frame.erase(it->feature_per_frame.begin() + j);
            // If the number of observation frames for this feature point is 0 after removing the newest frame,
            // it means this feature point has no observation frames left, so remove the feature point
            if (it->feature_per_frame.size() == 0)
                // Remove this feature point from the feature list
                features.erase(it);
        }
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.