diff --git a/include/core/processor/processor_motion.h b/include/core/processor/processor_motion.h
index 1f62a82a39dfcf5ae2de6183d341051d5785b45c..6a0f15a90885f5438c18c2cf01ed714c488ba599 100644
--- a/include/core/processor/processor_motion.h
+++ b/include/core/processor/processor_motion.h
@@ -247,11 +247,11 @@ class ProcessorMotion : public ProcessorBase, public IsMotion
 
         double updateDt();
         void integrateOneStep();
-        void reintegrateBuffer(CaptureMotionPtr _capture_ptr);
+        void reintegrateBuffer(CaptureMotionPtr _capture_ptr) const;
         void splitBuffer(const wolf::CaptureMotionPtr& capture_source,
                          TimeStamp ts_split,
                          const FrameBasePtr& keyframe_target,
-                         const wolf::CaptureMotionPtr& capture_target);
+                         const wolf::CaptureMotionPtr& capture_target) const;
 
         /** Pre-process incoming Capture
          *
@@ -416,14 +416,26 @@ class ProcessorMotion : public ProcessorBase, public IsMotion
         virtual Eigen::VectorXd correctDelta(const Eigen::VectorXd& delta_preint,
                                              const Eigen::VectorXd& delta_step) const = 0;
 
+        /** \brief merge two consecutive capture motions into the second one
+         * Merge two consecutive capture motions into the second one.
+         * The first capture motion is not removed.
+         * If the second capture has feature and factor emplaced, they are replaced by a new ones.
+         * @param cap_prev : the first capture motion to be merged (input)
+         * @param cap_target : the second capture motion (modified)
+         */
+        void mergeCaptures(CaptureMotionConstPtr cap_prev,
+                           CaptureMotionPtr cap_target);
+
     protected:
         /** \brief emplace a CaptureMotion
-         * \param _ts time stamp
+         * \param _frame_own frame owning the Capture to create
          * \param _sensor Sensor that produced the Capture
+         * \param _ts time stamp
          * \param _data a vector of motion data
-         * \param _sata_cov covariances matrix of the motion data data
-         * \param _frame_own frame owning the Capture to create
-         * \param _frame_origin frame acting as the origin of motions for the MorionBuffer of the created MotionCapture
+         * \param _data_cov covariances matrix of the motion data data
+         * \param _calib calibration vector
+         * \param _calib_preint calibration vector used during pre-integration
+         * \param _capture_origin_ptr capture acting as the origin of motions for the MorionBuffer of the created MotionCapture
          */
         virtual CaptureMotionPtr emplaceCapture(const FrameBasePtr& _frame_own,
                                                 const SensorBasePtr& _sensor,
@@ -491,18 +503,19 @@ class ProcessorMotion : public ProcessorBase, public IsMotion
 
     protected:
         // helpers to avoid allocation
-        double dt_;                             ///< Time step
-        Eigen::VectorXd x_;                     ///< current state
-        Eigen::VectorXd delta_;                 ///< current delta
-        Eigen::MatrixXd delta_cov_;             ///< current delta covariance
-        Eigen::VectorXd delta_integrated_;      ///< integrated delta
-        Eigen::MatrixXd delta_integrated_cov_;  ///< integrated delta covariance
-        Eigen::VectorXd calib_preint_;          ///< calibration vector used during pre-integration
-        Eigen::MatrixXd jacobian_delta_preint_; ///< jacobian of delta composition w.r.t previous delta integrated
-        Eigen::MatrixXd jacobian_delta_;        ///< jacobian of delta composition w.r.t current delta
-        Eigen::MatrixXd jacobian_calib_;        ///< jacobian of delta preintegration wrt calibration params
-        Eigen::MatrixXd jacobian_delta_calib_;  ///< jacobian of delta wrt calib params
-        Eigen::MatrixXd unmeasured_perturbation_cov_; ///< Covariance of unmeasured DoF to avoid singularity
+        mutable double dt_;                             ///< Time step
+        mutable Eigen::VectorXd x_;                     ///< current state
+        mutable Eigen::VectorXd delta_;                 ///< current delta
+        mutable Eigen::MatrixXd delta_cov_;             ///< current delta covariance
+        mutable Eigen::VectorXd delta_integrated_;      ///< integrated delta
+        mutable Eigen::MatrixXd delta_integrated_cov_;  ///< integrated delta covariance
+        mutable Eigen::VectorXd calib_preint_;          ///< calibration vector used during pre-integration
+        mutable Eigen::MatrixXd jacobian_delta_preint_; ///< jacobian of delta composition w.r.t previous delta integrated
+        mutable Eigen::MatrixXd jacobian_delta_;        ///< jacobian of delta composition w.r.t current delta
+        mutable Eigen::MatrixXd jacobian_calib_;        ///< jacobian of delta preintegration wrt calibration params
+        mutable Eigen::MatrixXd jacobian_delta_calib_;  ///< jacobian of delta wrt calib params
+
+        Eigen::MatrixXd unmeasured_perturbation_cov_;   ///< Covariance of unmeasured DoF to avoid singularity
 };
 
 }
diff --git a/src/processor/processor_motion.cpp b/src/processor/processor_motion.cpp
index f56b8c80a344e864f16be3ca2f8f006a0996052f..3a770b6f10dac4cfd05494725674b32c1f783eea 100644
--- a/src/processor/processor_motion.cpp
+++ b/src/processor/processor_motion.cpp
@@ -54,10 +54,46 @@ ProcessorMotion::~ProcessorMotion()
 //    std::cout << "destructed     -p-Mot" << id() << std::endl;
 }
 
-void ProcessorMotion::splitBuffer(const wolf::CaptureMotionPtr& _capture_source,
+
+void ProcessorMotion::mergeCaptures(CaptureMotionConstPtr cap_prev,
+                                    CaptureMotionPtr cap_target)
+{
+    assert(cap_prev == cap_target->getOriginCapture() && "merging not consecutive capture motions");
+
+    // add prev buffer (discarding the first zero motion)
+    cap_target->getBuffer().pop_front();
+    cap_target->getBuffer().insert(cap_target->getBuffer().begin(),
+                                   cap_prev->getBuffer().begin(),
+                                   cap_prev->getBuffer().end());
+    cap_target->getBuffer().print();
+
+    // change origin
+    cap_target->setOriginCapture(cap_prev->getOriginCapture());
+
+    // change calibration params for preintegration from origin
+    cap_target->setCalibrationPreint(getCalibration(cap_prev->getOriginCapture()));
+
+    // reintegrate buffer
+    reintegrateBuffer(cap_target);
+
+    // replace existing feature and factor (if they exist)
+    if (!cap_target->getFeatureList().empty())
+    {
+        // remove feature and factor (automatically)
+        cap_target->getFeatureList().back()->remove();
+
+        assert(cap_target->getFeatureList().empty());// there should be one feature only
+
+        // emplace new feature and factor
+        auto new_feature = emplaceFeature(cap_target);
+        emplaceFactor(new_feature, cap_prev->getOriginCapture());
+    }
+}
+
+void ProcessorMotion::splitBuffer(const CaptureMotionPtr& _capture_source,
                                   TimeStamp _ts_split,
                                   const FrameBasePtr& _keyframe_target,
-                                  const wolf::CaptureMotionPtr& _capture_target)
+                                  const CaptureMotionPtr& _capture_target) const
 {
     /** we are doing this:
      *
@@ -732,7 +768,7 @@ void ProcessorMotion::integrateOneStep()
     statePlusDelta(odometry_, delta_, dt_, odometry_);
 }
 
-void ProcessorMotion::reintegrateBuffer(CaptureMotionPtr _capture_ptr)
+void ProcessorMotion::reintegrateBuffer(CaptureMotionPtr _capture_ptr) const
 {
     VectorXd calib              = _capture_ptr->getCalibrationPreint();
 
diff --git a/test/gtest_processor_motion.cpp b/test/gtest_processor_motion.cpp
index 743b0fcc56a4088ceb87073a6941200fd74ccd33..d905bcad555b88e4cd92afed94771c2e7f46546d 100644
--- a/test/gtest_processor_motion.cpp
+++ b/test/gtest_processor_motion.cpp
@@ -289,6 +289,83 @@ TEST_F(ProcessorMotion_test, IntegrateCircleFixPrior)
     ASSERT_MATRIX_APPROX(problem->getState().vector("PO"), (Vector3d()<<0,0,0).finished(), 1e-8);
 }
 
+TEST_F(ProcessorMotion_test, mergeCaptures)
+{
+    // Prior
+    Vector3d x0; x0 << 0, 0, 0;
+    Matrix3d P0; P0.setIdentity();
+
+    data << 1, 2*M_PI/10; // advance in circle
+    data_cov.setIdentity();
+    TimeStamp t(0.0);
+
+    for (int i = 0; i<10; i++) // one full turn exactly
+    {
+        t += dt;
+        capture->setTimeStamp(t);
+        capture->setData(data);
+        capture->setDataCovariance(data_cov);
+        processor->captureCallback(capture);
+        WOLF_DEBUG("t: ", t, "  x: ", problem->getState().vector("PO").transpose());
+    }
+
+    SensorBasePtr    S = processor->getSensor();
+
+    TimeStamp        t_target = 8.5;
+    FrameBasePtr     F_target = problem->emplaceKeyFrame(t_target);
+    CaptureMotionPtr C_source = std::dynamic_pointer_cast<CaptureMotion>(processor->getLast());
+    CaptureMotionPtr C_target = CaptureBase::emplace<CaptureMotion>(F_target,
+                                                                    "ODOM 2d",
+                                                                    t_target,
+                                                                    sensor,
+                                                                    data,
+                                                                    nullptr);
+
+    // copy initial buffer
+    MotionBuffer initial_buffer = C_source->getBuffer();
+
+    processor->splitBuffer(C_source,
+                           t_target,
+                           F_target,
+                           C_target);
+
+    C_target->getBuffer().print(1,1,1,0);
+    C_source->getBuffer().print(1,1,1,0);
+
+    // merge captures
+    processor->mergeCaptures(C_target, C_source);
+
+    C_target->getBuffer().print(1,1,1,0);
+    C_source->getBuffer().print(1,1,1,0);
+
+    // Check that merged buffer is the initial one (before splitting)
+    EXPECT_EQ(C_source->getBuffer().size(), initial_buffer.size());
+
+    auto init_it = initial_buffer.begin();
+    auto res_it = C_source->getBuffer().begin();
+    while (init_it != initial_buffer.end())
+    {
+        EXPECT_EQ(init_it->data_size_, init_it->data_size_);
+        EXPECT_EQ(init_it->delta_size_, init_it->delta_size_);
+        EXPECT_EQ(init_it->cov_size_, init_it->cov_size_);
+        EXPECT_EQ(init_it->calib_size_, init_it->calib_size_);
+        EXPECT_EQ(init_it->ts_, init_it->ts_);
+
+        EXPECT_MATRIX_APPROX(init_it->data_ ,init_it->data_, 1e-12);
+        EXPECT_MATRIX_APPROX(init_it->data_cov_,init_it->data_cov_, 1e-12);
+        EXPECT_MATRIX_APPROX(init_it->delta_, init_it->delta_, 1e-12);
+        EXPECT_MATRIX_APPROX(init_it->delta_cov_,  init_it->delta_cov_, 1e-12);
+        EXPECT_MATRIX_APPROX(init_it->delta_integr_, init_it->delta_integr_, 1e-12);
+        EXPECT_MATRIX_APPROX(init_it->delta_integr_cov_, init_it->delta_integr_cov_, 1e-12);
+        EXPECT_MATRIX_APPROX(init_it->jacobian_delta_, init_it->jacobian_delta_, 1e-12);
+        EXPECT_MATRIX_APPROX(init_it->jacobian_delta_integr_, init_it->jacobian_delta_integr_, 1e-12);
+        EXPECT_MATRIX_APPROX(init_it->jacobian_calib_, init_it->jacobian_calib_, 1e-12);
+
+        init_it++;
+        res_it++;
+    }
+}
+
 TEST_F(ProcessorMotion_test, splitBufferAutoPrior)
 {
     // Prior