@@ -460,167 +460,24 @@ Eigen::MatrixXf trackPosesPerframe(
460460 const size_t frameStride) {
461461 const size_t numFrames = markerData.size ();
462462 MT_CHECK (numFrames > 0 , " Input data is empty." );
463- MT_CHECK (
464- globalParams.v .size () == character.parameterTransform .numAllModelParameters (),
465- " Input model parameters {} do not match character model parameters {}" ,
466- globalParams.v .size (),
467- character.parameterTransform .numAllModelParameters ());
468-
469- const ParameterTransform& pt = character.parameterTransform ;
470- const size_t numMarkers = markerData[0 ].size ();
471-
472- // pose parameters need to exclude "locators"
473- ParameterSet poseParams = pt.getPoseParameters ();
474- const auto & locatorSet = pt.parameterSets .find (" locators" );
475- if (locatorSet != pt.parameterSets .end ()) {
476- poseParams &= ~locatorSet->second ;
477- }
478-
479- // set up the solver
480- auto solverFunc = SkeletonSolverFunction (&character.skeleton , &pt);
481- GaussNewtonSolverOptions solverOptions;
482- solverOptions.maxIterations = config.maxIter ;
483- solverOptions.minIterations = 2 ;
484- solverOptions.doLineSearch = false ;
485- solverOptions.verbose = config.debug ;
486- solverOptions.threshold = 1 .f ;
487- solverOptions.regularization = config.regularization ;
488- auto solver = GaussNewtonSolver (solverOptions, &solverFunc);
489- solver.setEnabledParameters (poseParams);
490-
491- // parameter limits constraint
492- auto limitConstrFunc = std::make_shared<LimitErrorFunction>(character);
493- limitConstrFunc->setWeight (0.1 );
494- solverFunc.addErrorFunction (limitConstrFunc);
495-
496- // positional constraint function for markers
497- auto posConstrFunc = std::make_shared<PositionErrorFunction>(character, config.lossAlpha );
498- posConstrFunc->setWeight (PositionErrorFunction::kLegacyWeight );
499- solverFunc.addErrorFunction (posConstrFunc);
500-
501- std::shared_ptr<SkinnedLocatorErrorFunction> skinnedLocatorPosConstrFunc =
502- std::make_shared<SkinnedLocatorErrorFunction>(character);
503- skinnedLocatorPosConstrFunc->setWeight (PositionErrorFunction::kLegacyWeight );
504- solverFunc.addErrorFunction (skinnedLocatorPosConstrFunc);
505463
506- // floor penetration constraint data; we assume the world is y-up and floor is y=0 for mocap data.
507- const auto & floorConstraints = createFloorConstraints<float >(
508- " Floor_" ,
509- character.locators ,
510- Vector3f::UnitY (),
511- /* y offset */ 0 .0f ,
512- /* weight */ 5 .0f );
513- auto halfPlaneConstrFunc = std::make_shared<PlaneErrorFunction>(character, /* half plane*/ true );
514- halfPlaneConstrFunc->setConstraints (floorConstraints);
515- halfPlaneConstrFunc->setWeight (PlaneErrorFunction::kLegacyWeight );
516- solverFunc.addErrorFunction (halfPlaneConstrFunc);
517-
518- // marker constraint data
519- auto constrData = createConstraintData (markerData, character.locators );
520- auto skinnedConstrData = createSkinnedConstraintData (markerData, character.skinnedLocators );
521-
522- // smoothness constraint only for the joints and exclude global dofs because the global transform
523- // needs to be accurate (may not matter in practice?)
524- auto smoothConstrFunc = std::make_shared<ModelParametersErrorFunction>(
525- character, poseParams & ~pt.getRigidParameters ());
526- smoothConstrFunc->setWeight (config.smoothing );
527- solverFunc.addErrorFunction (smoothConstrFunc);
528-
529- // add collision error
530- std::shared_ptr<CollisionErrorFunction> collisionErrorFunction;
531- if (config.collisionErrorWeight != 0 && character.collision != nullptr ) {
532- collisionErrorFunction = std::make_shared<CollisionErrorFunction>(character);
533- collisionErrorFunction->setWeight (config.collisionErrorWeight );
534- solverFunc.addErrorFunction (collisionErrorFunction);
464+ // Generate frame indices from stride
465+ std::vector<size_t > frameIndices;
466+ for (size_t iFrame = 0 ; iFrame < numFrames; iFrame += frameStride) {
467+ frameIndices.push_back (iFrame);
535468 }
536469
537- MatrixXf motion (pt.numAllModelParameters (), numFrames);
538- // initialize parameters to contain identity information
539- // the identity fields will be used but untouched during optimization
540- // globalParams could also be repurposed to pass in initial pose value
541- Eigen::VectorXf dof = globalParams.v ;
542- size_t solverFrame = 0 ;
543- double error = 0.0 ;
544- // Use the initial global transform is it's not zero
545- bool needsInit = dof.head (6 ).isZero (0 ); // TODO: assume first six dofs are global dofs
546-
547- // When the frames are not continuous, we sometimes run into an issue when the desired joint
548- // rotation between two consecutive frames is large (eg. larger than 180). If we initialize from
549- // the previous result, the smaller rotation will be wrongly chosen, and we cannot recover from
550- // this mistake. To prevent this, we will solve each frame completely independently when they are
551- // not continuous.
552- bool continuous = (frameStride < 5 );
553- if (!continuous) {
554- needsInit = true ;
470+ // Convert globalParams to initial motion matrix
471+ MatrixXf initialMotion (globalParams.v .size (), numFrames);
472+ for (size_t i = 0 ; i < numFrames; ++i) {
473+ initialMotion.col (i) = globalParams.v ;
555474 }
556475
557- { // scope the ProgressBar so it returns
558- ProgressBar progress (" " , numFrames);
559- for (size_t iFrame = 0 ; iFrame < numFrames; iFrame += frameStride) {
560- // reinitialize if not continuous
561- if (!continuous) {
562- dof = globalParams.v ;
563- }
476+ // Determine if tracking should be continuous (temporal coherence)
477+ bool isContinuous = (frameStride < 5 );
564478
565- if ((constrData.at (iFrame).size () + skinnedConstrData.at (iFrame).size ()) >
566- config.minVisPercent * numMarkers) {
567- // add positional constraints
568- posConstrFunc->clearConstraints (); // clear constraint data from the previous frame
569- posConstrFunc->setConstraints (constrData.at (iFrame));
570-
571- skinnedLocatorPosConstrFunc->clearConstraints ();
572- skinnedLocatorPosConstrFunc->setConstraints (skinnedConstrData.at (iFrame));
573-
574- // initialization
575- // TODO: run on first frame or tracking failure
576- if (needsInit) { // solve only for the rigid parameters as preprocessing
577- MT_LOGI_IF (
578- config.debug && continuous, " Solving for an initial rigid pose at frame {}" , iFrame);
579-
580- // Set up different config for initialization
581- solverOptions.maxIterations = 50 ; // make sure it converges
582- solver.setOptions (solverOptions);
583- solver.setEnabledParameters (pt.getRigidParameters ());
584- smoothConstrFunc->setWeight (0.0 ); // turn off smoothing - it doesn't affect rigid dofs
585-
586- solver.solve (dof);
587-
588- // Recover solver config
589- solverOptions.maxIterations = config.maxIter ;
590- solver.setOptions (solverOptions);
591- solver.setEnabledParameters (poseParams);
592- smoothConstrFunc->setWeight (config.smoothing );
593-
594- if (continuous) {
595- needsInit = false ;
596- }
597- }
598-
599- // set smoothness target as the last pose -- dof holds parameter values from last (good)
600- // frame it will serve as a small regularization to rest pose for the first frame
601- // TODO: API needs improvement
602- smoothConstrFunc->setTargetParameters (dof, smoothConstrFunc->getTargetWeights ());
603-
604- error += solver.solve (dof);
605- ++solverFrame;
606- }
607-
608- // set result to output; fill in frames within a stride
609- // note that dof contains complete parameter info with identity
610- for (size_t jDelta = 0 ; jDelta < frameStride && iFrame + jDelta < numFrames; ++jDelta) {
611- motion.col (iFrame + jDelta) = dof;
612- }
613- progress.increment (frameStride);
614- }
615- }
616- if (config.debug ) {
617- if (solverFrame > 0 ) {
618- MT_LOGI (" Average per-frame residual: {}" , error / solverFrame);
619- } else {
620- MT_LOGW (" no valid frames to solve" );
621- }
622- }
623- return motion;
479+ return trackPosesForFrames (
480+ markerData, character, initialMotion, config, frameIndices, isContinuous);
624481}
625482
626483// / Track poses independently for specific frame indices with fixed character identity.
@@ -635,13 +492,15 @@ Eigen::MatrixXf trackPosesPerframe(
635492// / @param initialMotion Initial parameter values (parameters x frames)
636493// / @param config Tracking configuration settings
637494// / @param frameIndices Vector of specific frame indices to solve
495+ // / @param isContinuous Whether to use temporal coherence between frames
638496// / @return Solved motion parameters matrix (parameters x frames) with poses for selected frames
639497Eigen::MatrixXf trackPosesForFrames (
640498 const gsl::span<const std::vector<Marker>> markerData,
641499 const Character& character,
642500 const MatrixXf& initialMotion,
643501 const TrackingConfig& config,
644- const std::vector<size_t >& frameIndices) {
502+ const std::vector<size_t >& frameIndices,
503+ bool isContinuous) {
645504 const size_t numFrames = markerData.size ();
646505 MT_CHECK (numFrames > 0 , " Input data is empty." );
647506 MT_CHECK (
@@ -705,6 +564,17 @@ Eigen::MatrixXf trackPosesForFrames(
705564 auto constrData = createConstraintData (markerData, character.locators );
706565 auto skinnedConstrData = createSkinnedConstraintData (markerData, character.skinnedLocators );
707566
567+ // smoothness constraint only for the joints and exclude global dofs because the global transform
568+ // needs to be accurate (may not matter in practice?)
569+ // Only use temporal smoothness if isContinuous is true
570+ std::shared_ptr<ModelParametersErrorFunction> smoothConstrFunc;
571+ if (isContinuous) {
572+ smoothConstrFunc = std::make_shared<ModelParametersErrorFunction>(
573+ character, poseParams & ~pt.getRigidParameters ());
574+ smoothConstrFunc->setWeight (config.smoothing );
575+ solverFunc.addErrorFunction (smoothConstrFunc);
576+ }
577+
708578 // add collision error
709579 std::shared_ptr<CollisionErrorFunction> collisionErrorFunction;
710580 if (config.collisionErrorWeight != 0 && character.collision != nullptr ) {
@@ -716,18 +586,32 @@ Eigen::MatrixXf trackPosesForFrames(
716586 // initialize parameters to contain identity information
717587 // the identity fields will be used but untouched during optimization
718588 // globalParams could also be repurposed to pass in initial pose value
719- std::vector<Eigen::VectorXf> poses (frameIndices.size ());
720589 Eigen::VectorXf dof = initialMotion.col (sortedFrames.empty () ? 0 : sortedFrames[0 ]);
721590 size_t solverFrame = 0 ;
722591 double priorError = 0.0 ;
723592 double error = 0.0 ;
724593
725- MatrixXf outMotion (pt.numAllModelParameters (), numFrames);
594+ // Use the initial global transform is it's not zero
595+ bool needsInit = true ;
596+ const auto rigidParams = pt.getRigidParameters ();
597+ for (Eigen::Index i = 0 ; i < dof.size (); ++i) {
598+ if (rigidParams.test (i) && dof[i] != 0 .0f ) {
599+ needsInit = false ;
600+ }
601+ }
602+
603+ MatrixXf outMotion = initialMotion;
604+ Eigen::Index outputIndex = 0 ;
726605 { // scope the ProgressBar so it returns
727- ProgressBar progress (" " , sortedFrames.size ());
728- for (size_t fi = 0 ; fi < sortedFrames.size (); fi++) {
729- const size_t & iFrame = sortedFrames[fi];
730- dof = initialMotion.col (iFrame);
606+ ProgressBar progress (" Tracking per-frame" , sortedFrames.size ());
607+ for (const auto iFrame : sortedFrames) {
608+ // For continuous tracking, keep the solved dof from previous frame (temporal coherence)
609+ // For non-continuous tracking, always start from initial motion (independent solving)
610+ if (!isContinuous) {
611+ dof = initialMotion.col (iFrame);
612+ needsInit = true ;
613+ }
614+ // For continuous tracking, dof is preserved from previous iteration (or initial value)
731615
732616 if ((constrData.at (iFrame).size () + skinnedConstrData.at (iFrame).size ()) >
733617 numMarkers * config.minVisPercent ) {
@@ -739,34 +623,52 @@ Eigen::MatrixXf trackPosesForFrames(
739623 skinnedLocatorPosConstrFunc->setConstraints (skinnedConstrData.at (iFrame));
740624
741625 // initialization
742- solverOptions.maxIterations = 50 ; // make sure it converges
743- solver.setOptions (solverOptions);
744- solver.setEnabledParameters (pt.getRigidParameters ());
626+ if (needsInit) { // solve only for the rigid parameters as preprocessing
627+ MT_LOGI_IF (
628+ config.debug && isContinuous,
629+ " Solving for an initial rigid pose at frame {}" ,
630+ iFrame);
631+
632+ // Set up different config for initialization
633+ solverOptions.maxIterations = 50 ; // make sure it converges
634+ solver.setOptions (solverOptions);
635+ solver.setEnabledParameters (pt.getRigidParameters ());
636+ if (smoothConstrFunc) {
637+ smoothConstrFunc->setWeight (0.0 ); // turn off smoothing - it doesn't affect rigid dofs
638+ }
639+
640+ solver.solve (dof);
641+
642+ // Recover solver config
643+ solverOptions.maxIterations = config.maxIter ;
644+ solver.setOptions (solverOptions);
645+ solver.setEnabledParameters (poseParams);
646+ if (smoothConstrFunc) {
647+ smoothConstrFunc->setWeight (config.smoothing );
648+ }
745649
746- solver.solve (dof);
650+ needsInit = false ;
651+ }
747652
748- // Recover solver config
749- solverOptions. maxIterations = config. maxIter ;
750- solver. setOptions (solverOptions );
751- solver. setEnabledParameters (poseParams);
653+ // set smoothness target as the last pose for continuous tracking
654+ if (smoothConstrFunc) {
655+ smoothConstrFunc-> setTargetParameters (dof, smoothConstrFunc-> getTargetWeights () );
656+ }
752657
753658 priorError += solverFunc.getError (dof);
754659 error += solver.solve (dof);
755660 ++solverFrame;
756661 }
757662
758663 // store result
759- poses[fi] = dof;
664+ while (outputIndex <= iFrame) {
665+ outMotion.col (outputIndex++) = dof;
666+ }
760667 progress.increment ();
761668 }
762669
763- // set results to output
764- size_t sortedIndex = 0 ;
765- for (size_t fi = 0 ; fi < numFrames; fi++) {
766- if (sortedIndex < sortedFrames.size () - 1 && fi == sortedFrames[sortedIndex + 1 ]) {
767- sortedIndex++;
768- }
769- outMotion.col (fi) = poses[sortedIndex];
670+ while (outputIndex < numFrames) {
671+ outMotion.col (outputIndex++) = dof;
770672 }
771673 }
772674 if (config.debug ) {
@@ -879,7 +781,8 @@ void calibrateModel(
879781 character,
880782 motion.topRows (transform.numAllModelParameters ()),
881783 trackingConfig,
882- firstFrame);
784+ firstFrame,
785+ false ); // Not continuous for calibration keyframes
883786 motion.topRows (transform.numAllModelParameters ()) = trackSequence (
884787 markerData,
885788 character,
@@ -988,7 +891,8 @@ void calibrateModel(
988891 character,
989892 motion.topRows (transform.numAllModelParameters ()),
990893 trackingConfig,
991- frameIndices);
894+ frameIndices,
895+ false ); // Not continuous for calibration keyframes
992896 } else {
993897 const VectorXf initPose = motion.col (0 ).head (transform.numAllModelParameters ());
994898 motion.topRows (transform.numAllModelParameters ()) =
@@ -1117,7 +1021,8 @@ void calibrateLocators(
11171021 character,
11181022 motion.topRows (transform.numAllModelParameters ()),
11191023 trackingConfig,
1120- frameIndices);
1024+ frameIndices,
1025+ false ); // Not continuous for calibration keyframes
11211026
11221027 // Solve for both markers and poses.
11231028 // TODO: add a small regularization to prevent too large a change
0 commit comments