@@ -441,6 +441,23 @@ Eigen::MatrixXf trackSequence(
441441 return outMotion;
442442}
443443
444+ // / Check if the global transform is zero by checking if any rigid parameters are non-zero.
445+ // /
446+ // / This is used to determine whether initialization is needed for pose tracking.
447+ // / If all rigid parameters are zero, we need to solve for an initial rigid transform.
448+ // /
449+ // / @param dof The parameter vector to check
450+ // / @param rigidParams Parameter set defining which parameters are rigid/global
451+ // / @return True if global transform is zero (needs initialization), false otherwise
452+ bool isGlobalTransformZero (const Eigen::VectorXf& dof, const ParameterSet& rigidParams) {
453+ for (Eigen::Index i = 0 ; i < dof.size (); ++i) {
454+ if (rigidParams.test (i) && dof[i] != 0 .0f ) {
455+ return false ;
456+ }
457+ }
458+ return true ;
459+ }
460+
444461// / Track poses independently per frame with fixed character identity.
445462// /
446463// / This is the main production tracking function used after character calibration.
@@ -462,167 +479,24 @@ Eigen::MatrixXf trackPosesPerframe(
462479 const size_t frameStride) {
463480 const size_t numFrames = markerData.size ();
464481 MT_CHECK (numFrames > 0 , " Input data is empty." );
465- MT_CHECK (
466- globalParams.v .size () == character.parameterTransform .numAllModelParameters (),
467- " Input model parameters {} do not match character model parameters {}" ,
468- globalParams.v .size (),
469- character.parameterTransform .numAllModelParameters ());
470482
471- const ParameterTransform& pt = character.parameterTransform ;
472- const size_t numMarkers = markerData[0 ].size ();
473-
474- // pose parameters need to exclude "locators"
475- ParameterSet poseParams = pt.getPoseParameters ();
476- const auto & locatorSet = pt.parameterSets .find (" locators" );
477- if (locatorSet != pt.parameterSets .end ()) {
478- poseParams &= ~locatorSet->second ;
483+ // Generate frame indices from stride
484+ std::vector<size_t > frameIndices;
485+ for (size_t iFrame = 0 ; iFrame < numFrames; iFrame += frameStride) {
486+ frameIndices.push_back (iFrame);
479487 }
480488
481- // set up the solver
482- auto solverFunc = SkeletonSolverFunction (character, pt);
483- GaussNewtonSolverOptions solverOptions;
484- solverOptions.maxIterations = config.maxIter ;
485- solverOptions.minIterations = 2 ;
486- solverOptions.doLineSearch = false ;
487- solverOptions.verbose = config.debug ;
488- solverOptions.threshold = 1 .f ;
489- solverOptions.regularization = config.regularization ;
490- auto solver = GaussNewtonSolver (solverOptions, &solverFunc);
491- solver.setEnabledParameters (poseParams);
492-
493- // parameter limits constraint
494- auto limitConstrFunc = std::make_shared<LimitErrorFunction>(character);
495- limitConstrFunc->setWeight (0.1 );
496- solverFunc.addErrorFunction (limitConstrFunc);
497-
498- // positional constraint function for markers
499- auto posConstrFunc = std::make_shared<PositionErrorFunction>(character, config.lossAlpha );
500- posConstrFunc->setWeight (PositionErrorFunction::kLegacyWeight );
501- solverFunc.addErrorFunction (posConstrFunc);
502-
503- std::shared_ptr<SkinnedLocatorErrorFunction> skinnedLocatorPosConstrFunc =
504- std::make_shared<SkinnedLocatorErrorFunction>(character);
505- skinnedLocatorPosConstrFunc->setWeight (PositionErrorFunction::kLegacyWeight );
506- solverFunc.addErrorFunction (skinnedLocatorPosConstrFunc);
507-
508- // floor penetration constraint data; we assume the world is y-up and floor is y=0 for mocap data.
509- const auto & floorConstraints = createFloorConstraints<float >(
510- " Floor_" ,
511- character.locators ,
512- Vector3f::UnitY (),
513- /* y offset */ 0 .0f ,
514- /* weight */ 5 .0f );
515- auto halfPlaneConstrFunc = std::make_shared<PlaneErrorFunction>(character, /* half plane*/ true );
516- halfPlaneConstrFunc->setConstraints (floorConstraints);
517- halfPlaneConstrFunc->setWeight (PlaneErrorFunction::kLegacyWeight );
518- solverFunc.addErrorFunction (halfPlaneConstrFunc);
519-
520- // marker constraint data
521- auto constrData = createConstraintData (markerData, character.locators );
522- auto skinnedConstrData = createSkinnedConstraintData (markerData, character.skinnedLocators );
523-
524- // smoothness constraint only for the joints and exclude global dofs because the global transform
525- // needs to be accurate (may not matter in practice?)
526- auto smoothConstrFunc = std::make_shared<ModelParametersErrorFunction>(
527- character, poseParams & ~pt.getRigidParameters ());
528- smoothConstrFunc->setWeight (config.smoothing );
529- solverFunc.addErrorFunction (smoothConstrFunc);
530-
531- // add collision error
532- std::shared_ptr<CollisionErrorFunction> collisionErrorFunction;
533- if (config.collisionErrorWeight != 0 && character.collision != nullptr ) {
534- collisionErrorFunction = std::make_shared<CollisionErrorFunction>(character);
535- collisionErrorFunction->setWeight (config.collisionErrorWeight );
536- solverFunc.addErrorFunction (collisionErrorFunction);
489+ // Convert globalParams to initial motion matrix
490+ MatrixXf initialMotion (globalParams.v .size (), numFrames);
491+ for (size_t i = 0 ; i < numFrames; ++i) {
492+ initialMotion.col (i) = globalParams.v ;
537493 }
538494
539- MatrixXf motion (pt.numAllModelParameters (), numFrames);
540- // initialize parameters to contain identity information
541- // the identity fields will be used but untouched during optimization
542- // globalParams could also be repurposed to pass in initial pose value
543- Eigen::VectorXf dof = globalParams.v ;
544- size_t solverFrame = 0 ;
545- double error = 0.0 ;
546- // Use the initial global transform is it's not zero
547- bool needsInit = dof.head (6 ).isZero (0 ); // TODO: assume first six dofs are global dofs
548-
549- // When the frames are not continuous, we sometimes run into an issue when the desired joint
550- // rotation between two consecutive frames is large (eg. larger than 180). If we initialize from
551- // the previous result, the smaller rotation will be wrongly chosen, and we cannot recover from
552- // this mistake. To prevent this, we will solve each frame completely independently when they are
553- // not continuous.
554- bool continuous = (frameStride < 5 );
555- if (!continuous) {
556- needsInit = true ;
557- }
495+ // Determine if tracking should be continuous (temporal coherence)
496+ bool isContinuous = (frameStride < 5 );
558497
559- { // scope the ProgressBar so it returns
560- ProgressBar progress (" " , numFrames);
561- for (size_t iFrame = 0 ; iFrame < numFrames; iFrame += frameStride) {
562- // reinitialize if not continuous
563- if (!continuous) {
564- dof = globalParams.v ;
565- }
566-
567- if ((constrData.at (iFrame).size () + skinnedConstrData.at (iFrame).size ()) >
568- config.minVisPercent * numMarkers) {
569- // add positional constraints
570- posConstrFunc->clearConstraints (); // clear constraint data from the previous frame
571- posConstrFunc->setConstraints (constrData.at (iFrame));
572-
573- skinnedLocatorPosConstrFunc->clearConstraints ();
574- skinnedLocatorPosConstrFunc->setConstraints (skinnedConstrData.at (iFrame));
575-
576- // initialization
577- // TODO: run on first frame or tracking failure
578- if (needsInit) { // solve only for the rigid parameters as preprocessing
579- MT_LOGI_IF (
580- config.debug && continuous, " Solving for an initial rigid pose at frame {}" , iFrame);
581-
582- // Set up different config for initialization
583- solverOptions.maxIterations = 50 ; // make sure it converges
584- solver.setOptions (solverOptions);
585- solver.setEnabledParameters (pt.getRigidParameters ());
586- smoothConstrFunc->setWeight (0.0 ); // turn off smoothing - it doesn't affect rigid dofs
587-
588- solver.solve (dof);
589-
590- // Recover solver config
591- solverOptions.maxIterations = config.maxIter ;
592- solver.setOptions (solverOptions);
593- solver.setEnabledParameters (poseParams);
594- smoothConstrFunc->setWeight (config.smoothing );
595-
596- if (continuous) {
597- needsInit = false ;
598- }
599- }
600-
601- // set smoothness target as the last pose -- dof holds parameter values from last (good)
602- // frame it will serve as a small regularization to rest pose for the first frame
603- // TODO: API needs improvement
604- smoothConstrFunc->setTargetParameters (dof, smoothConstrFunc->getTargetWeights ());
605-
606- error += solver.solve (dof);
607- ++solverFrame;
608- }
609-
610- // set result to output; fill in frames within a stride
611- // note that dof contains complete parameter info with identity
612- for (size_t jDelta = 0 ; jDelta < frameStride && iFrame + jDelta < numFrames; ++jDelta) {
613- motion.col (iFrame + jDelta) = dof;
614- }
615- progress.increment (frameStride);
616- }
617- }
618- if (config.debug ) {
619- if (solverFrame > 0 ) {
620- MT_LOGI (" Average per-frame residual: {}" , error / solverFrame);
621- } else {
622- MT_LOGW (" no valid frames to solve" );
623- }
624- }
625- return motion;
498+ return trackPosesForFrames (
499+ markerData, character, initialMotion, config, frameIndices, isContinuous);
626500}
627501
628502// / Track poses independently for specific frame indices with fixed character identity.
@@ -637,13 +511,15 @@ Eigen::MatrixXf trackPosesPerframe(
637511// / @param initialMotion Initial parameter values (parameters x frames)
638512// / @param config Tracking configuration settings
639513// / @param frameIndices Vector of specific frame indices to solve
514+ // / @param isContinuous Whether to use temporal coherence between frames
640515// / @return Solved motion parameters matrix (parameters x frames) with poses for selected frames
641516Eigen::MatrixXf trackPosesForFrames (
642517 const std::span<const std::vector<Marker>> markerData,
643518 const Character& character,
644519 const MatrixXf& initialMotion,
645520 const TrackingConfig& config,
646- const std::vector<size_t >& frameIndices) {
521+ const std::vector<size_t >& frameIndices,
522+ bool isContinuous) {
647523 const size_t numFrames = markerData.size ();
648524 MT_CHECK (numFrames > 0 , " Input data is empty." );
649525 MT_CHECK (
@@ -707,6 +583,17 @@ Eigen::MatrixXf trackPosesForFrames(
707583 auto constrData = createConstraintData (markerData, character.locators );
708584 auto skinnedConstrData = createSkinnedConstraintData (markerData, character.skinnedLocators );
709585
586+ // smoothness constraint only for the joints and exclude global dofs because the global transform
587+ // needs to be accurate (may not matter in practice?)
588+ // Only use temporal smoothness if isContinuous is true
589+ std::shared_ptr<ModelParametersErrorFunction> smoothConstrFunc;
590+ if (isContinuous) {
591+ smoothConstrFunc = std::make_shared<ModelParametersErrorFunction>(
592+ character, poseParams & ~pt.getRigidParameters ());
593+ smoothConstrFunc->setWeight (config.smoothing );
594+ solverFunc.addErrorFunction (smoothConstrFunc);
595+ }
596+
710597 // add collision error
711598 std::shared_ptr<CollisionErrorFunction> collisionErrorFunction;
712599 if (config.collisionErrorWeight != 0 && character.collision != nullptr ) {
@@ -718,18 +605,26 @@ Eigen::MatrixXf trackPosesForFrames(
718605 // initialize parameters to contain identity information
719606 // the identity fields will be used but untouched during optimization
720607 // globalParams could also be repurposed to pass in initial pose value
721- std::vector<Eigen::VectorXf> poses (frameIndices.size ());
722608 Eigen::VectorXf dof = initialMotion.col (sortedFrames.empty () ? 0 : sortedFrames[0 ]);
723609 size_t solverFrame = 0 ;
724610 double priorError = 0.0 ;
725611 double error = 0.0 ;
726612
727- MatrixXf outMotion (pt.numAllModelParameters (), numFrames);
613+ // Use the initial global transform if it's not zero
614+ bool needsInit = isGlobalTransformZero (dof, pt.getRigidParameters ());
615+
616+ MatrixXf outMotion = initialMotion;
617+ Eigen::Index outputIndex = 0 ;
728618 { // scope the ProgressBar so it returns
729- ProgressBar progress (" " , sortedFrames.size ());
730- for (size_t fi = 0 ; fi < sortedFrames.size (); fi++) {
731- const size_t & iFrame = sortedFrames[fi];
732- dof = initialMotion.col (iFrame);
619+ ProgressBar progress (" Tracking per-frame" , sortedFrames.size ());
620+ for (const auto iFrame : sortedFrames) {
621+ // For continuous tracking, keep the solved dof from previous frame (temporal coherence)
622+ // For non-continuous tracking, always start from initial motion (independent solving)
623+ if (!isContinuous) {
624+ dof = initialMotion.col (iFrame);
625+ needsInit = true ;
626+ }
627+ // For continuous tracking, dof is preserved from previous iteration (or initial value)
733628
734629 if ((constrData.at (iFrame).size () + skinnedConstrData.at (iFrame).size ()) >
735630 numMarkers * config.minVisPercent ) {
@@ -741,34 +636,52 @@ Eigen::MatrixXf trackPosesForFrames(
741636 skinnedLocatorPosConstrFunc->setConstraints (skinnedConstrData.at (iFrame));
742637
743638 // initialization
744- solverOptions.maxIterations = 50 ; // make sure it converges
745- solver.setOptions (solverOptions);
746- solver.setEnabledParameters (pt.getRigidParameters ());
639+ if (needsInit) { // solve only for the rigid parameters as preprocessing
640+ MT_LOGI_IF (
641+ config.debug && isContinuous,
642+ " Solving for an initial rigid pose at frame {}" ,
643+ iFrame);
747644
748- solver.solve (dof);
645+ // Set up different config for initialization
646+ solverOptions.maxIterations = 50 ; // make sure it converges
647+ solver.setOptions (solverOptions);
648+ solver.setEnabledParameters (pt.getRigidParameters ());
649+ if (smoothConstrFunc) {
650+ smoothConstrFunc->setWeight (0.0 ); // turn off smoothing - it doesn't affect rigid dofs
651+ }
652+
653+ solver.solve (dof);
654+
655+ // Recover solver config
656+ solverOptions.maxIterations = config.maxIter ;
657+ solver.setOptions (solverOptions);
658+ solver.setEnabledParameters (poseParams);
659+ if (smoothConstrFunc) {
660+ smoothConstrFunc->setWeight (config.smoothing );
661+ }
749662
750- // Recover solver config
751- solverOptions.maxIterations = config.maxIter ;
752- solver.setOptions (solverOptions);
753- solver.setEnabledParameters (poseParams);
663+ needsInit = false ;
664+ }
665+
666+ // set smoothness target as the last pose for continuous tracking
667+ if (smoothConstrFunc) {
668+ smoothConstrFunc->setTargetParameters (dof, smoothConstrFunc->getTargetWeights ());
669+ }
754670
755671 priorError += solverFunc.getError (dof);
756672 error += solver.solve (dof);
757673 ++solverFrame;
758674 }
759675
760676 // store result
761- poses[fi] = dof;
677+ while (outputIndex <= iFrame) {
678+ outMotion.col (outputIndex++) = dof;
679+ }
762680 progress.increment ();
763681 }
764682
765- // set results to output
766- size_t sortedIndex = 0 ;
767- for (size_t fi = 0 ; fi < numFrames; fi++) {
768- if (sortedIndex < sortedFrames.size () - 1 && fi == sortedFrames[sortedIndex + 1 ]) {
769- sortedIndex++;
770- }
771- outMotion.col (fi) = poses[sortedIndex];
683+ while (outputIndex < numFrames) {
684+ outMotion.col (outputIndex++) = dof;
772685 }
773686 }
774687 if (config.debug ) {
@@ -881,7 +794,8 @@ void calibrateModel(
881794 character,
882795 motion.topRows (transform.numAllModelParameters ()),
883796 trackingConfig,
884- firstFrame);
797+ firstFrame,
798+ false ); // Not continuous for calibration keyframes
885799 motion.topRows (transform.numAllModelParameters ()) = trackSequence (
886800 markerData,
887801 character,
@@ -990,7 +904,8 @@ void calibrateModel(
990904 character,
991905 motion.topRows (transform.numAllModelParameters ()),
992906 trackingConfig,
993- frameIndices);
907+ frameIndices,
908+ false ); // Not continuous for calibration keyframes
994909 } else {
995910 const VectorXf initPose = motion.col (0 ).head (transform.numAllModelParameters ());
996911 motion.topRows (transform.numAllModelParameters ()) =
@@ -1119,7 +1034,8 @@ void calibrateLocators(
11191034 character,
11201035 motion.topRows (transform.numAllModelParameters ()),
11211036 trackingConfig,
1122- frameIndices);
1037+ frameIndices,
1038+ false ); // Not continuous for calibration keyframes
11231039
11241040 // Solve for both markers and poses.
11251041 // TODO: add a small regularization to prevent too large a change
0 commit comments