// Copyright 2019 The MediaPipe Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef MEDIAPIPE_UTIL_TRACKING_MOTION_MODELS_H_ #define MEDIAPIPE_UTIL_TRACKING_MOTION_MODELS_H_ #include #include #include #include #include "absl/container/node_hash_map.h" #include "mediapipe/framework/port/logging.h" #include "mediapipe/framework/port/singleton.h" #include "mediapipe/framework/port/vector.h" #include "mediapipe/util/tracking/camera_motion.pb.h" #include "mediapipe/util/tracking/motion_models.pb.h" #include "mediapipe/util/tracking/region_flow.pb.h" // NOLINT namespace mediapipe { // Abstract Camera Model, functionality that each model must support. template class ModelAdapter { public: // Initializes a model from vector to data. // If identity_parametrization is set, it is assumed that for args = 0 -> // Model = identity, else args = 0 -> zero transform. static Model FromFloatPointer(const float* args, bool identity_parametrization); static Model FromDoublePointer(const double* args, bool identity_parametrization); // Transforms points according to model * pt. static Vector2_f TransformPoint(const Model& model, const Vector2_f& pt); // Returns model^(-1), outputs feasibility to variable success (can not be // NULL). If model is not invertible, function should return identity model. static Model InvertChecked(const Model& model, bool* success); // Returns model^(-1), returns identity model if inversion is not possible, // and warns via LOG(ERROR). It is recommended that InvertChecked is used // instead. // Note: Default implementation, motion models only need to supply above // function. static Model Invert(const Model& model); // Concatenates two models. Returns lhs * rhs static Model Compose(const Model& lhs, const Model& rhs); // Debugging function to create plots. Output parameters separated by space. static std::string ToString(const Model& model); // Returns number of DOF. static constexpr int NumParameters(); // Access parameters in a model agnostic way. Order is the order of // specification in the corresponding motion_models.proto file, i.e. // id = proto_id - 1. static float GetParameter(const Model& model, int id); // Sets parameter in a model agonstic way. Same interpretation of id as for // GetParameter. static void SetParameter(int id, float value, Model* model); // Returns normalization transform for specific frame size. Range after // normalization is [0, 1]. static Model NormalizationTransform(float frame_width, float frame_height); // Embeds a lower paramteric model into this model. // Overload for specialization. template static Model Embed(const LowerModel& model); // Projects higher motion model H to lower one L such that for points x_i // \sum_i || H * x - L * x|| == min // For this we transform the models to the center of the specified frame // domain at which degree's of freedom are usually independent. Details can be // found in the individual implementation functions. template static Model ProjectFrom(const HigherModel& model, float frame_width, float frame_height); }; // Specialized implementations, with additional functionality if needed. template <> class ModelAdapter { public: static TranslationModel FromArgs(float dx, float dy); static TranslationModel FromFloatPointer(const float* args, bool identity_parametrization); static TranslationModel FromDoublePointer(const double* args, bool identity_parametrization); static Vector2_f TransformPoint(const TranslationModel& model, const Vector2_f& pt); static TranslationModel Invert(const TranslationModel& model); static TranslationModel InvertChecked(const TranslationModel& model, bool* success); static TranslationModel Compose(const TranslationModel& lhs, const TranslationModel& rhs); static float GetParameter(const TranslationModel& model, int id); static void SetParameter(int id, float value, TranslationModel* model); static std::string ToString(const TranslationModel& model); static constexpr int NumParameters() { return 2; } // Support to convert to and from affine. static AffineModel ToAffine(const TranslationModel& model); // Fails with debug check, if affine model is not a translation. static TranslationModel FromAffine(const AffineModel& model); static Homography ToHomography(const TranslationModel& model); // Fails with debug check if homography is not a translation. static TranslationModel FromHomography(const Homography& model); // Evaluates Jacobian at specified point and parameters set to 0. // Note: This is independent of whether identity parametrization was used // or not via From*Pointer. // Jacobian has to be of size 2 x NumParams(), and is returned in column // major order. static void GetJacobianAtPoint(const Vector2_f& pt, float* jacobian); static TranslationModel NormalizationTransform(float frame_width, float frame_height); static TranslationModel Embed(const TranslationModel& model) { return model; } static TranslationModel ProjectFrom(const TranslationModel& model, float frame_width, float frame_height) { return model; } static TranslationModel ProjectFrom(const LinearSimilarityModel& model, float frame_width, float frame_height); static TranslationModel ProjectFrom(const AffineModel& model, float frame_width, float frame_height); static TranslationModel ProjectFrom(const Homography& model, float frame_width, float frame_height); // Returns parameter wise maximum. static TranslationModel Maximum(const TranslationModel& lhs, const TranslationModel& rhs); // Return determinant of model. static float Determinant(const TranslationModel& unused) { return 1; } }; template <> class ModelAdapter { public: static SimilarityModel FromArgs(float dx, float dy, float scale, float rotation); static SimilarityModel FromFloatPointer(const float* args, bool); static SimilarityModel FromDoublePointer(const double* args, bool); static Vector2_f TransformPoint(const SimilarityModel& model, const Vector2_f& pt); static SimilarityModel Invert(const SimilarityModel& model); static SimilarityModel InvertChecked(const SimilarityModel& model, bool* success); static SimilarityModel Compose(const SimilarityModel& lhs, const SimilarityModel& rhs); static float GetParameter(const SimilarityModel& model, int id); static void SetParameter(int id, float value, SimilarityModel* model); static std::string ToString(const SimilarityModel& model); static constexpr int NumParameters() { return 4; } static SimilarityModel NormalizationTransform(float frame_width, float frame_height); static TranslationModel ProjectToTranslation(const SimilarityModel& model, float frame_width, float frame_height); }; template <> class ModelAdapter { public: static LinearSimilarityModel FromArgs(float dx, float dy, float a, float b); static LinearSimilarityModel FromFloatPointer(const float* args, bool identity_parametrization); static LinearSimilarityModel FromDoublePointer(const double* args, bool identity_parametrization); static Vector2_f TransformPoint(const LinearSimilarityModel& model, const Vector2_f& pt); static LinearSimilarityModel Invert(const LinearSimilarityModel& model); static LinearSimilarityModel InvertChecked(const LinearSimilarityModel& model, bool* success); static LinearSimilarityModel Compose(const LinearSimilarityModel& lhs, const LinearSimilarityModel& rhs); static float GetParameter(const LinearSimilarityModel& model, int id); static void SetParameter(int id, float value, LinearSimilarityModel* model); static std::string ToString(const LinearSimilarityModel& model); static constexpr int NumParameters() { return 4; } // Support to convert to and from affine. static AffineModel ToAffine(const LinearSimilarityModel& model); static Homography ToHomography(const LinearSimilarityModel& model); // Fails with debug check, if homography is not a similarity. static LinearSimilarityModel FromHomography(const Homography& model); // Fails with debug check, if affine model is not a similarity. static LinearSimilarityModel FromAffine(const AffineModel& model); // Conversion from and to non-linear similarity. static SimilarityModel ToSimilarity(const LinearSimilarityModel& model); static LinearSimilarityModel FromSimilarity(const SimilarityModel& model); // Additional functionality: // Composes scaled identity transform with model (sI * model). static LinearSimilarityModel ScaleParameters( const LinearSimilarityModel& model, float scale); // Adds identity I to model (I + model). static LinearSimilarityModel AddIdentity(const LinearSimilarityModel& model); // Evaluates Jacobian at specified point and parameters set to 0. // Note: This is independent of whether identity parametrization was used // or not via From*Pointer. // Jacobian has to be of size 2 x NumParams(), and is returned in column // major order. static void GetJacobianAtPoint(const Vector2_f& pt, float* jacobian); static LinearSimilarityModel NormalizationTransform(float frame_width, float frame_height); static LinearSimilarityModel Embed(const TranslationModel& model) { return FromArgs(model.dx(), model.dy(), 1, 0); } static LinearSimilarityModel Embed(const LinearSimilarityModel& model) { return model; } static TranslationModel ProjectToTranslation( const LinearSimilarityModel& model, float frame_width, float frame_height); static LinearSimilarityModel ProjectFrom(const LinearSimilarityModel& model, float frame_width, float frame_height) { return model; } static LinearSimilarityModel ProjectFrom(const AffineModel& model, float frame_width, float frame_height); static LinearSimilarityModel ProjectFrom(const Homography& model, float frame_width, float frame_height); // Returns parameter wise maximum. static LinearSimilarityModel Maximum(const LinearSimilarityModel& lhs, const LinearSimilarityModel& rhs); // Return determinant of model. static float Determinant(const LinearSimilarityModel& m) { return m.a() * m.a() + m.b() * m.b(); } }; template <> class ModelAdapter { public: static AffineModel FromArgs(float dx, float dy, float a, float b, float c, float d); static AffineModel FromFloatPointer(const float* args, bool identity_parametrization); static AffineModel FromDoublePointer(const double* args, bool identity_parametrization); static Vector2_f TransformPoint(const AffineModel& model, const Vector2_f& pt); static AffineModel Invert(const AffineModel& model); static AffineModel InvertChecked(const AffineModel& model, bool* success); static AffineModel Compose(const AffineModel& lhs, const AffineModel& rhs); static float GetParameter(const AffineModel& model, int id); static void SetParameter(int id, float value, AffineModel* model); static std::string ToString(const AffineModel& model); static constexpr int NumParameters() { return 6; } // Support to convert to and from affine. static AffineModel ToAffine(const AffineModel& model) { return model; } static AffineModel FromAffine(const AffineModel& model) { return model; } static Homography ToHomography(const AffineModel& model); // Fails with debug check, if homography is not affine. static AffineModel FromHomography(const Homography& model); // Additional functionality: // Composes scaled identity transform with model (sI * model). static AffineModel ScaleParameters(const AffineModel& model, float scale); // Adds identity I to model (I + model). static AffineModel AddIdentity(const AffineModel& model); // Evaluates Jacobian at specified point and parameters set to 0. // Note: This is independent of whether identity parametrization was used // or not via From*Pointer. // Jacobian has to be of size 2 x NumParams(), and is returned in column // major order. static void GetJacobianAtPoint(const Vector2_f& pt, float* jacobian); static AffineModel NormalizationTransform(float frame_width, float frame_height); static AffineModel Embed(const TranslationModel& model) { return FromArgs(model.dx(), model.dy(), 1, 0, 0, 1); } static AffineModel Embed(const LinearSimilarityModel& model) { return FromArgs(model.dx(), model.dy(), model.a(), -model.b(), model.b(), model.a()); } static AffineModel Embed(const AffineModel& model) { return model; } static AffineModel ProjectFrom(const AffineModel& model, float frame_width, float frame_height) { return model; } static AffineModel ProjectFrom(const Homography& model, float frame_width, float frame_height); static LinearSimilarityModel ProjectToLinearSimilarity( const AffineModel& model, float frame_width, float frame_height); // Returns parameter wise maximum. static AffineModel Maximum(const AffineModel& lhs, const AffineModel& rhs); static float Determinant(const AffineModel& m) { return m.a() * m.d() - m.b() * m.c(); } }; template <> class ModelAdapter { public: static Homography FromArgs(float h_00, float h_01, float h_02, float h_10, float h_11, float h_12, float h_20, float h_21); static Homography FromFloatPointer(const float* args, bool identity_parametrization); static Homography FromDoublePointer(const double* args, bool identity_parametrization); static Vector2_f TransformPoint(const Homography& model, const Vector2_f& pt); static Vector3_f TransformPoint3(const Homography& model, const Vector3_f& pt); static Homography Invert(const Homography& model); static Homography InvertChecked(const Homography& model, bool* success); static Homography Compose(const Homography& lhs, const Homography& rhs); static float GetParameter(const Homography& model, int id); static void SetParameter(int id, float value, Homography* model); static std::string ToString(const Homography& model); static constexpr int NumParameters() { return 8; } static bool IsAffine(const Homography& model); // Support to convert to and from affine. Debug check that model is actually // an affine model. static AffineModel ToAffine(const Homography& model); static Homography FromAffine(const AffineModel& model); static Homography ToHomography(const Homography& model) { return model; } static Homography FromHomography(const Homography& model) { return model; } // Additional functionality: // Evaluates Jacobian at specified point and parameters set to 0. // Note: This is independent of whether identity parametrization was used // or not via From*Pointer. // Jacobian has to be of size 2 x NumParams(), and is returned in column // major order. static void GetJacobianAtPoint(const Vector2_f& pt, float* jacobian); static Homography NormalizationTransform(float frame_width, float frame_height); static Homography Embed(const Homography& model) { return model; } static Homography Embed(const AffineModel& model) { return FromArgs(model.a(), model.b(), model.dx(), model.c(), model.d(), model.dy(), 0, 0); } static Homography Embed(const LinearSimilarityModel& model) { return FromArgs(model.a(), -model.b(), model.dx(), model.b(), model.a(), model.dy(), 0, 0); } static Homography Embed(const TranslationModel& model) { return FromArgs(1, 0, model.dx(), 0, 1, model.dy(), 0, 0); } static float Determinant(const Homography& m) { // Applying laplace formula to last column. // h_00 h_01 h_02 // h_10 h_11 h_12 // h_20 h_21 1 <-- apply laplace. return m.h_20() * (m.h_01() * m.h_12() - m.h_11() * m.h_02()) + -m.h_21() * (m.h_00() * m.h_12() - m.h_10() * m.h_02()) + 1.0f * (m.h_00() * m.h_11() - m.h_10() * m.h_01()); } static AffineModel ProjectToAffine(const Homography& model, float frame_width, float frame_height); }; // Common algorithms implemented using corresponding ModelAdapter. // Implemented in cc file, explicitly instantiated below. template class ModelMethods { typedef ModelAdapter Adapter; public: // Returns _normalized_ intersection area of rectangle transformed by // model_1 and model_2, respectively. static float NormalizedIntersectionArea(const Model& model_1, const Model& model_2, const Vector2_f& rect); }; // Traits to bind mixture and corresponding base model together. template struct MixtureTraits { typedef BaseModel base_model; typedef MixtureModel model; }; typedef MixtureTraits LinearSimilarityTraits; typedef MixtureTraits AffineTraits; typedef MixtureTraits HomographyTraits; template class MixtureModelAdapterBase { public: typedef typename MixtureTraits::model MixtureModel; typedef typename MixtureTraits::base_model BaseModel; typedef ModelAdapter BaseModelAdapter; // Initializes a model from vector to data. All weights are set to one. // If identity_parametrization is set, it is assumed that for args = 0 -> // Model = identity, else args = 0 -> zero transform. // Adjacent models are assumed to be separated by // NumParameters() + skip elements. static MixtureModel FromFloatPointer(const float* args, bool identity_parametrization, int skip, int num_models); static MixtureModel FromDoublePointer(const double* args, bool identity_parametrization, int skip, int num_models); // Mixture models are not closed under composition and inversion. // Instead, each point has to be transformed via above functions. // However, a mixture model can be composed with a BaseModel from either left // or right, by component-wise composition. // Returns mixture_model * base_model. static MixtureModel ComposeRight(const MixtureModel& mixture_model, const BaseModel& base_model); // Returns base_model * mixture_model. static MixtureModel ComposeLeft(const MixtureModel& mixture_model, const BaseModel& base_model); // Debugging function to create plots. Output parameters separated by delim. static std::string ToString(const MixtureModel& model, std::string delim); // Returns total number of DOF (number of models * BaseModel DOF) static int NumParameters(const MixtureModel& model) { return model.model_size() * BaseModelAdapter::NumParameters(); } // Access parameters in a model agnostic way. Order is the order of // specification in the corresponding motion_models.proto file, i.e. // id = proto_id - 1. static float GetParameter(const MixtureModel& model, int model_id, int param_id); static void SetParameter(int model_id, int param_id, float value, MixtureModel* model); static MixtureModel IdentityModel(int num_mixtures); // Returns average model across mixture, i.e. mean of each parameter across // the mixture. static BaseModel MeanModel(const MixtureModel& model); // Fits a linear model to each parameter across mixture and returns mixture // evaluated across line. static MixtureModel LinearModel(const MixtureModel& model); static MixtureModel Embed(const BaseModel& base_model, int num_mixtures); }; class MixtureRowWeights; template class MixtureModelAdapter : public MixtureModelAdapterBase { public: typedef typename MixtureModelAdapterBase::MixtureModel MixtureModel; typedef typename MixtureModelAdapterBase::BaseModel BaseModel; typedef ModelAdapter BaseModelAdapter; // Returns convex combination of models from supplied mixture_model, // specifically: // \sum_i mixture_model.model(i) * weights[i] // where: // b) weights[i] need to be normalized to sum to one. static BaseModel ToBaseModel(const MixtureModel& mixture_model, const float* weights); // Transforms points according ToBaseModel(model, weights) * pt; // Note: Weights need to sum to one (not checked). static Vector2_f TransformPoint(const MixtureModel& model, const float* weights, const Vector2_f& pt); static Vector2_f TransformPoint(const MixtureModel& model, const MixtureRowWeights& weights, const Vector2_f& pt); // Transforms / solves for points according to // ToBaseModel(model, weights)^(-1) * pt // Fails with CHECK if model is not invertible. // Note: Weights need to sum to one (not checked). static Vector2_f SolveForPoint(const MixtureModel& model, const float* weights, const Vector2_f& pt); // Same as above, indicating if model is invertible in parameter // success. If model is not invertible, passed parameter // pt is returned unchanged. static Vector2_f SolveForPointChecked(const MixtureModel& model, const float* weights, const Vector2_f& pt, bool* success); }; // Re-implemented for speed benefits. template <> class MixtureModelAdapter : public MixtureModelAdapterBase { public: inline static Homography ToBaseModel(const MixtureHomography& model, const float* weights); inline static Vector2_f TransformPoint(const MixtureHomography& model, const float* weights, const Vector2_f& pt); // Overload. OK as only input format for weights changed. inline static Vector2_f TransformPoint(const MixtureHomography& model, const MixtureRowWeights& weights, const Vector2_f& pt); inline static Vector2_f SolveForPoint(const MixtureHomography& model, const float* weights, const Vector2_f& pt); inline static Vector2_f SolveForPointChecked(const MixtureModel& model, const float* weights, const Vector2_f& pt, bool* success); }; // Compositing for multiple models in order of argument list. template Model ModelCompose2(const Model& a, const Model& b) { typedef ModelAdapter Adapter; return Adapter::Compose(a, b); } template Model ModelCompose3(const Model& a, const Model& b, const Model& c) { typedef ModelAdapter Adapter; return Adapter::Compose(a, Adapter::Compose(b, c)); } template Model ModelCompose4(const Model& a, const Model& b, const Model& c, const Model& d) { typedef ModelAdapter Adapter; return Adapter::Compose(a, Adapter::Compose(b, Adapter::Compose(c, d))); } template Model ModelInvert(const Model& model) { typedef ModelAdapter Adapter; return Adapter::Invert(model); } // Returns model according to b^(-1) * a template Model ModelDiff(const Model& a, const Model& b) { typedef ModelAdapter Adapter; return Adapter::Compose(Adapter::Invert(b), a); } template Model ModelDiffChecked(const Model& a, const Model& b, bool* success) { typedef ModelAdapter Adapter; Model b_inv = Adapter::InvertChecked(b, success); return Adapter::Compose(b_inv, a); } template Vector2_f TransformPoint(const Model& m, const Vector2_f& v) { return ModelAdapter::TransformPoint(m, v); } // Epsilon threshold for determinant. Below this threshold we consider // the linear model to be non-invertible. const float kDetInvertibleEps = 1e-10; // Threshold for stability. Used to determine if a particular motion model // is invertible AND likely to be stable after inversion (imposes higher // threshold on determinant than just for invertibility). const float kDetStableEps = 1e-2; template bool IsInverseStable(const Model& model) { return ModelAdapter::Determinant(model) > kDetStableEps; } // Accumulates camera motions in accum: // If motions for frames 1..N are: F1, F2, .. FN, where Fk is the motion that // maps frame k to k-1 (backwards motion), then cumulative motion mapping frame // N to 0 is: // C = F1 * F2 * ... * FN. // // This function computes it recursively: C(k) = C(k-1) * Fk. template void AccumulateModel(const Model& model, Model* accum) { *accum = ModelCompose2(*accum, model); } // Accumulates inverse camera motions in accum_inverted: // We want to compute the inverse motion that maps frame 0 to frame N: // C^-1 = FN^-1 * .... * F2^-1 * F1^-1. // (inverse of C defined above for AccumulateModel). // // This function computes it recursively: C(k)^-1 = Fk^-1 * C(k-1)^-1. // // Return value indicates accumulation was successful (it might fail if model // is not invertible), otherwise accum_inverted is left unchanged. template bool AccumulateInvertedModel(const Model& model, Model* accum_inverted) { bool success = true; const Model inv_model = ModelAdapter::InvertChecked(model, &success); if (success) { *accum_inverted = ModelCompose2(inv_model, *accum_inverted); } return success; } // Returns true if |predicted * ground_truth^(-1)| < bounds (element wise). // Use UniformModel to initialize bounds. template bool ModelDiffWithinBounds(const Model& ground_truth, const Model& predicted, const Model& bounds) { Model diff = ModelAdapter::Compose( predicted, ModelAdapter::Invert(ground_truth)); Model identity; for (int p = 0; p < ModelAdapter::NumParameters(); ++p) { const float bound = ModelAdapter::GetParameter(bounds, p); const float diff_p = fabs(ModelAdapter::GetParameter(diff, p) - ModelAdapter::GetParameter(identity, p)); if (diff_p > bound) { LOG(WARNING) << "Param diff " << p << " out of bounds: " << diff_p << " > " << bound << " bound"; return false; } } return true; } // Returns true if model is identity within floating point accuracy. template bool IsModelIdentity(const Model& model) { Model identity; for (int p = 0; p < ModelAdapter::NumParameters(); ++p) { const float diff_p = fabs(ModelAdapter::GetParameter(model, p) - ModelAdapter::GetParameter(identity, p)); if (diff_p > 1e-6f) { return false; } } return true; } // Expresses input model M w.r.t. new domain given by LinearSimilarityTransform // (scale 0 0 // 0 scale 0) := S -> S M S^(-1) template Model CoordinateTransform(const Model& model, float scale) { return CoordinateTransform( model, ModelAdapter::FromArgs(0, 0, scale, 0)); } // For model M and similarity S returns // S * M * S^(-1). template Model CoordinateTransform(const Model& model, const LinearSimilarityModel& similarity) { return ModelCompose3(ModelAdapter::Embed(similarity), model, ModelAdapter::Embed(ModelInvert(similarity))); } // Returns a model with all parameters set to value. template Model UniformModelParameters(const float value) { std::array::NumParameters()> params; params.fill(value); return ModelAdapter::FromFloatPointer(params.data(), false); } // Returns a blended model: a * (1 - weight_b) + b * weight_b. // Assumes 0 <= weight_b <= 1. // Note that blending the homographies is a non-linear operation if the // intention is to obtain a transform that blends the points transformed by // a and b. However, this is a linear approximation, which ignores the // perspective division, and simply blends the coefficients. template Model BlendModels(const Model& a, const Model& b, float weight_b) { Model blended; DCHECK_GE(weight_b, 0); DCHECK_LE(weight_b, 1); const float weight_a = 1 - weight_b; for (int p = 0; p < ModelAdapter::NumParameters(); ++p) { const float pa = ModelAdapter::GetParameter(a, p); const float pb = ModelAdapter::GetParameter(b, p); ModelAdapter::SetParameter(p, pa * weight_a + pb * weight_b, &blended); } return blended; } template std::string ModelToString(const Model& model) { return ModelAdapter::ToString(model); } // Typedef's. typedef ModelAdapter TranslationAdapter; typedef ModelAdapter SimilarityAdapter; typedef ModelAdapter LinearSimilarityAdapter; typedef ModelAdapter AffineAdapter; typedef ModelAdapter HomographyAdapter; typedef ModelMethods TranslationMethods; typedef ModelMethods SimilarityMethods; typedef ModelMethods LinearSimilarityMethods; typedef ModelMethods AffineMethods; typedef ModelMethods HomographyMethods; typedef MixtureModelAdapter MixtureLinearSimilarityAdapter; typedef MixtureModelAdapter MixtureAffineAdapter; typedef MixtureModelAdapter MixtureHomographyAdapter; // Stores pre-computed normalized mixture weights. // Weights are computed for each scanline, // based on gaussian weighting of y-location to mid-points for each model // (specified in scanlines!). // We use even spacing between mid-points by default. // By supplying a y_scale != 1.f, normalized coordinates can be used as input. // Possible unnormalized y values for RowWeigts are // [-margin, frame_height + margin). class MixtureRowWeights { public: MixtureRowWeights(int frame_height, int margin, float sigma, float y_scale, int num_models); int NumModels() const { return num_models_; } float YScale() const { return y_scale_; } float Sigma() const { return sigma_; } // Test if MixtureRowWeights should be re-initialized (call constructor // again), based on changed options. bool NeedsInitialization(int num_models, float sigma, float y_scale) const { return (num_models != num_models_ || fabs(sigma - sigma_) > 1e-6f || fabs(y_scale - y_scale_) > 1e-6f); } const float* RowWeights(float y) const { int bin_y = y * y_scale_ + 0.5; DCHECK_LT(bin_y, frame_height_ + margin_); DCHECK_GE(bin_y, -margin_); return &weights_[(bin_y + margin_) * num_models_]; } // Same as above but clamps parameter y to be within interval // (-margin, frame_height + margin). const float* RowWeightsClamped(float y) const { int bin_y = y * y_scale_ + 0.5; bin_y = std::max(-margin_, std::min(frame_height_ - 1 + margin_, bin_y)); return &weights_[(bin_y + margin_) * num_models_]; } // Returns weight threshold for fractional block distance, e.g. // parameter 1.5f returns row weight at 1.5f * block_height from block center. float WeightThreshold(float frac_blocks); private: int frame_height_; float y_scale_; int margin_; float sigma_; int num_models_; std::vector mid_points_; std::vector weights_; }; // Returns pointer (caller takes ownership) of initialized MixtureRowWeights. inline MixtureRowWeights* MixtureRowWeightsFromCameraMotion( const CameraMotion& camera_motion, int frame_height) { return new MixtureRowWeights(frame_height, 0, // no margin. camera_motion.mixture_row_sigma(), 1.0, camera_motion.mixture_homography().model_size()); } // Performs element wise smoothing of input models with per parameter sigma's // in time (and optionally bilateral). Parameters of optional // model_sigma that are NOT 0 are interpreted as bilateral smoothing sigma. // Use UniformModelParameters to set all value of sigma_time to the same sigma. template void SmoothModels(const Model& sigma_time_model, const Model* model_sigma, std::vector* models) { CHECK(models); const int num_models = models->size(); std::vector> smoothed_model_data(num_models); for (int param = 0; param < ModelAdapter::NumParameters(); ++param) { const float sigma_time = ModelAdapter::GetParameter(sigma_time_model, param); if (sigma_time == 0) { // Don't perform any smoothing, just copy. for (int i = 0; i < num_models; ++i) { smoothed_model_data[i].push_back( ModelAdapter::GetParameter((*models)[i], param)); } continue; } // Create lookup table for frame weights. const int frame_radius = std::min(num_models - 1, std::ceil(sigma_time * 1.5f)); const int frame_diameter = 2 * frame_radius + 1; // Create lookup table for weights. std::vector frame_weights(frame_diameter); const float frame_coeff = -0.5f / (sigma_time * sigma_time); int frame_idx = 0; for (int i = -frame_radius; i <= frame_radius; ++i, ++frame_idx) { frame_weights[frame_idx] = std::exp(frame_coeff * i * i); } // Create local copy with border. std::vector param_path(num_models + 2 * frame_radius); const float param_sigma = model_sigma != nullptr ? ModelAdapter::GetParameter(*model_sigma, param) : 0; const float param_sigma_denom = param_sigma != 0 ? (-0.5f / (param_sigma * param_sigma)) : 0; for (int model_idx = 0; model_idx < num_models; ++model_idx) { param_path[model_idx + frame_radius] = ModelAdapter::GetParameter((*models)[model_idx], param); } // Copy right. std::copy(param_path.rbegin() + frame_radius, param_path.rbegin() + 2 * frame_radius, param_path.end() - frame_radius); // Copy left. std::copy(param_path.begin() + frame_radius, param_path.begin() + 2 * frame_radius, param_path.rend() - frame_radius); // Apply filter. for (int i = 0; i < num_models; ++i) { float value_sum = 0; float weight_sum = 0; const float curr_value = param_path[i + frame_radius]; for (int k = 0; k < frame_diameter; ++k) { const float value = param_path[i + k]; float weight = frame_weights[k]; if (param_sigma != 0) { // Bilateral filtering. const float value_diff = curr_value - value; weight *= std::exp(value_diff * value_diff * param_sigma_denom); } weight_sum += weight; value_sum += value * weight; } // Weight_sum is always > 0, as sigma is > 0. smoothed_model_data[i].push_back(value_sum / weight_sum); } } for (int i = 0; i < num_models; ++i) { (*models)[i].CopyFrom(ModelAdapter::FromFloatPointer( &smoothed_model_data[i][0], false)); } } // Inline implementations. // Translation model. inline TranslationModel ModelAdapter::FromArgs(float dx, float dy) { TranslationModel model; model.set_dx(dx); model.set_dy(dy); return model; } inline TranslationModel ModelAdapter::FromFloatPointer( const float* args, bool) { DCHECK(args); TranslationModel model; model.set_dx(args[0]); model.set_dy(args[1]); return model; } inline TranslationModel ModelAdapter::FromDoublePointer( const double* args, bool) { DCHECK(args); TranslationModel model; model.set_dx(args[0]); model.set_dy(args[1]); return model; } inline Vector2_f ModelAdapter::TransformPoint( const TranslationModel& model, const Vector2_f& pt) { return Vector2_f(pt.x() + model.dx(), pt.y() + model.dy()); } inline TranslationModel ModelAdapter::Invert( const TranslationModel& model) { bool success = true; TranslationModel result = InvertChecked(model, &success); if (!success) { LOG(ERROR) << "Model not invertible. Returning identity."; return TranslationModel(); } return result; } inline TranslationModel ModelAdapter::InvertChecked( const TranslationModel& model, bool* success) { TranslationModel inv_model; inv_model.set_dx(-model.dx()); inv_model.set_dy(-model.dy()); *success = true; return inv_model; } inline TranslationModel ModelAdapter::Compose( const TranslationModel& lhs, const TranslationModel& rhs) { TranslationModel result; result.set_dx(lhs.dx() + rhs.dx()); result.set_dy(lhs.dy() + rhs.dy()); return result; } inline float ModelAdapter::GetParameter( const TranslationModel& model, int id) { switch (id) { case 0: return model.dx(); case 1: return model.dy(); default: LOG(FATAL) << "Parameter id is out of bounds"; } return 0; } inline void ModelAdapter::SetParameter( int id, float value, TranslationModel* model) { switch (id) { case 0: return model->set_dx(value); case 1: return model->set_dy(value); default: LOG(FATAL) << "Parameter id is out of bounds"; } } // Linear Similarity model. inline LinearSimilarityModel ModelAdapter::FromArgs( float dx, float dy, float a, float b) { LinearSimilarityModel model; model.set_dx(dx); model.set_dy(dy); model.set_a(a); model.set_b(b); return model; } inline LinearSimilarityModel ModelAdapter::FromFloatPointer( const float* args, bool identity_parametrization) { DCHECK(args); LinearSimilarityModel model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_dx(args[0]); model.set_dy(args[1]); model.set_a(id_shift + args[2]); model.set_b(args[3]); return model; } inline LinearSimilarityModel ModelAdapter::FromDoublePointer( const double* args, bool identity_parametrization) { DCHECK(args); LinearSimilarityModel model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_dx(args[0]); model.set_dy(args[1]); model.set_a(id_shift + args[2]); model.set_b(args[3]); return model; } inline Vector2_f ModelAdapter::TransformPoint( const LinearSimilarityModel& model, const Vector2_f& pt) { return Vector2_f(model.a() * pt.x() - model.b() * pt.y() + model.dx(), model.b() * pt.x() + model.a() * pt.y() + model.dy()); } inline LinearSimilarityModel ModelAdapter::Invert( const LinearSimilarityModel& model) { bool success = true; LinearSimilarityModel result = InvertChecked(model, &success); if (!success) { LOG(ERROR) << "Model not invertible. Returning identity."; return LinearSimilarityModel(); } else { return result; } } inline LinearSimilarityModel ModelAdapter::InvertChecked( const LinearSimilarityModel& model, bool* success) { LinearSimilarityModel inv_model; const float det = model.a() * model.a() + model.b() * model.b(); if (fabs(det) < kDetInvertibleEps) { *success = false; VLOG(1) << "Model is not invertible, det is zero."; return LinearSimilarityModel(); } *success = true; const float inv_det = 1.0 / det; inv_model.set_a(model.a() * inv_det); inv_model.set_b(-model.b() * inv_det); // Inverse translation is -A^(-1) * [dx dy]. inv_model.set_dx(-(inv_model.a() * model.dx() - inv_model.b() * model.dy())); inv_model.set_dy(-(inv_model.b() * model.dx() + inv_model.a() * model.dy())); return inv_model; } inline LinearSimilarityModel ModelAdapter::Compose( const LinearSimilarityModel& lhs, const LinearSimilarityModel& rhs) { LinearSimilarityModel result; result.set_a(lhs.a() * rhs.a() - lhs.b() * rhs.b()); result.set_b(lhs.a() * rhs.b() + lhs.b() * rhs.a()); result.set_dx(lhs.a() * rhs.dx() - lhs.b() * rhs.dy() + lhs.dx()); result.set_dy(lhs.b() * rhs.dx() + lhs.a() * rhs.dy() + lhs.dy()); return result; } inline float ModelAdapter::GetParameter( const LinearSimilarityModel& model, int id) { switch (id) { case 0: return model.dx(); case 1: return model.dy(); case 2: return model.a(); case 3: return model.b(); default: LOG(FATAL) << "Parameter id is out of bounds"; } return 0; } inline void ModelAdapter::SetParameter( int id, float value, LinearSimilarityModel* model) { switch (id) { case 0: return model->set_dx(value); case 1: return model->set_dy(value); case 2: return model->set_a(value); case 3: return model->set_b(value); default: LOG(FATAL) << "Parameter id is out of bounds"; } } // Affine model. inline AffineModel ModelAdapter::FromArgs(float dx, float dy, float a, float b, float c, float d) { AffineModel model; model.set_dx(dx); model.set_dy(dy); model.set_a(a); model.set_b(b); model.set_c(c); model.set_d(d); return model; } inline AffineModel ModelAdapter::FromFloatPointer( const float* args, bool identity_parametrization) { DCHECK(args); AffineModel model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_dx(args[0]); model.set_dy(args[1]); model.set_a(id_shift + args[2]); model.set_b(args[3]); model.set_c(args[4]); model.set_d(id_shift + args[5]); return model; } inline AffineModel ModelAdapter::FromDoublePointer( const double* args, bool identity_parametrization) { DCHECK(args); AffineModel model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_dx(args[0]); model.set_dy(args[1]); model.set_a(id_shift + args[2]); model.set_b(args[3]); model.set_c(args[4]); model.set_d(id_shift + args[5]); return model; } inline Vector2_f ModelAdapter::TransformPoint( const AffineModel& model, const Vector2_f& pt) { return Vector2_f(model.a() * pt.x() + model.b() * pt.y() + model.dx(), model.c() * pt.x() + model.d() * pt.y() + model.dy()); } // Use of Invert is discouraged, always use InvertChecked. inline AffineModel ModelAdapter::Invert(const AffineModel& model) { bool success = true; AffineModel result = InvertChecked(model, &success); if (!success) { LOG(ERROR) << "Model not invertible. Returning identity."; return AffineModel(); } else { return result; } } inline AffineModel ModelAdapter::InvertChecked( const AffineModel& model, bool* success) { AffineModel inv_model; const float det = model.a() * model.d() - model.b() * model.c(); if (fabs(det) < kDetInvertibleEps) { *success = false; VLOG(1) << "Model is not invertible, det is zero."; return AffineModel(); } *success = true; const float inv_det = 1.0 / det; inv_model.set_a(model.d() * inv_det); inv_model.set_d(model.a() * inv_det); inv_model.set_c(-model.c() * inv_det); inv_model.set_b(-model.b() * inv_det); // Inverse translation is -A^(-1) * [dx dy]. inv_model.set_dx(-(inv_model.a() * model.dx() + inv_model.b() * model.dy())); inv_model.set_dy(-(inv_model.c() * model.dx() + inv_model.d() * model.dy())); return inv_model; } inline AffineModel ModelAdapter::Compose(const AffineModel& lhs, const AffineModel& rhs) { AffineModel result; result.set_a(lhs.a() * rhs.a() + lhs.b() * rhs.c()); result.set_b(lhs.a() * rhs.b() + lhs.b() * rhs.d()); result.set_c(lhs.c() * rhs.a() + lhs.d() * rhs.c()); result.set_d(lhs.c() * rhs.b() + lhs.d() * rhs.d()); result.set_dx(lhs.a() * rhs.dx() + lhs.b() * rhs.dy() + lhs.dx()); result.set_dy(lhs.c() * rhs.dx() + lhs.d() * rhs.dy() + lhs.dy()); return result; } inline float ModelAdapter::GetParameter(const AffineModel& model, int id) { switch (id) { case 0: return model.dx(); case 1: return model.dy(); case 2: return model.a(); case 3: return model.b(); case 4: return model.c(); case 5: return model.d(); default: LOG(FATAL) << "Parameter id is out of bounds"; } return 0; } inline void ModelAdapter::SetParameter(int id, float value, AffineModel* model) { switch (id) { case 0: return model->set_dx(value); case 1: return model->set_dy(value); case 2: return model->set_a(value); case 3: return model->set_b(value); case 4: return model->set_c(value); case 5: return model->set_d(value); default: LOG(FATAL) << "Parameter id is out of bounds"; } } // Homography model. inline Homography ModelAdapter::FromArgs(float h_00, float h_01, float h_02, float h_10, float h_11, float h_12, float h_20, float h_21) { Homography model; model.set_h_00(h_00); model.set_h_01(h_01); model.set_h_02(h_02); model.set_h_10(h_10); model.set_h_11(h_11); model.set_h_12(h_12); model.set_h_20(h_20); model.set_h_21(h_21); return model; } inline Homography ModelAdapter::FromFloatPointer( const float* args, bool identity_parametrization) { DCHECK(args); Homography model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_h_00(id_shift + args[0]); model.set_h_01(args[1]); model.set_h_02(args[2]); model.set_h_10(args[3]); model.set_h_11(id_shift + args[4]); model.set_h_12(args[5]); model.set_h_20(args[6]); model.set_h_21(args[7]); return model; } inline Homography ModelAdapter::FromDoublePointer( const double* args, bool identity_parametrization) { DCHECK(args); Homography model; const float id_shift = identity_parametrization ? 1.f : 0.f; model.set_h_00(id_shift + args[0]); model.set_h_01(args[1]); model.set_h_02(args[2]); model.set_h_10(args[3]); model.set_h_11(id_shift + args[4]); model.set_h_12(args[5]); model.set_h_20(args[6]); model.set_h_21(args[7]); return model; } inline Vector2_f ModelAdapter::TransformPoint( const Homography& model, const Vector2_f& pt) { const float x = model.h_00() * pt.x() + model.h_01() * pt.y() + model.h_02(); const float y = model.h_10() * pt.x() + model.h_11() * pt.y() + model.h_12(); float z = model.h_20() * pt.x() + model.h_21() * pt.y() + 1.0f; if (z != 1.f) { // Enforce z can not assume very small values. constexpr float eps = 1e-12f; if (fabs(z) < eps) { LOG(ERROR) << "Point mapped to infinity. " << "Degenerate homography. See proto."; z = z >= 0 ? eps : -eps; } return Vector2_f(x / z, y / z); } else { return Vector2_f(x, y); } } inline Vector3_f ModelAdapter::TransformPoint3( const Homography& model, const Vector3_f& pt) { return Vector3_f( model.h_00() * pt.x() + model.h_01() * pt.y() + model.h_02() * pt.z(), model.h_10() * pt.x() + model.h_11() * pt.y() + model.h_12() * pt.z(), model.h_20() * pt.x() + model.h_21() * pt.y() + pt.z()); } inline Homography ModelAdapter::Invert(const Homography& model) { bool success = true; Homography result = InvertChecked(model, &success); if (!success) { LOG(ERROR) << "Model not invertible. Returning identity."; return Homography(); } else { return result; } } inline Homography ModelAdapter::Compose(const Homography& lhs, const Homography& rhs) { Homography result; const float z = lhs.h_20() * rhs.h_02() + lhs.h_21() * rhs.h_12() + 1.0f * 1.0f; CHECK_NE(z, 0) << "Degenerate homography. See proto."; const float inv_z = 1.0 / z; result.set_h_00((lhs.h_00() * rhs.h_00() + lhs.h_01() * rhs.h_10() + lhs.h_02() * rhs.h_20()) * inv_z); result.set_h_01((lhs.h_00() * rhs.h_01() + lhs.h_01() * rhs.h_11() + lhs.h_02() * rhs.h_21()) * inv_z); result.set_h_02( (lhs.h_00() * rhs.h_02() + lhs.h_01() * rhs.h_12() + lhs.h_02() * 1.0f) * inv_z); result.set_h_10((lhs.h_10() * rhs.h_00() + lhs.h_11() * rhs.h_10() + lhs.h_12() * rhs.h_20()) * inv_z); result.set_h_11((lhs.h_10() * rhs.h_01() + lhs.h_11() * rhs.h_11() + lhs.h_12() * rhs.h_21()) * inv_z); result.set_h_12( (lhs.h_10() * rhs.h_02() + lhs.h_11() * rhs.h_12() + lhs.h_12() * 1.0f) * inv_z); result.set_h_20( (lhs.h_20() * rhs.h_00() + lhs.h_21() * rhs.h_10() + 1.0f * rhs.h_20()) * inv_z); result.set_h_21( (lhs.h_20() * rhs.h_01() + lhs.h_21() * rhs.h_11() + 1.f * rhs.h_21()) * inv_z); return result; } inline float ModelAdapter::GetParameter(const Homography& model, int id) { switch (id) { case 0: return model.h_00(); case 1: return model.h_01(); case 2: return model.h_02(); case 3: return model.h_10(); case 4: return model.h_11(); case 5: return model.h_12(); case 6: return model.h_20(); case 7: return model.h_21(); default: LOG(FATAL) << "Parameter id is out of bounds"; } return 0; } inline void ModelAdapter::SetParameter(int id, float value, Homography* model) { switch (id) { case 0: return model->set_h_00(value); case 1: return model->set_h_01(value); case 2: return model->set_h_02(value); case 3: return model->set_h_10(value); case 4: return model->set_h_11(value); case 5: return model->set_h_12(value); case 6: return model->set_h_20(value); case 7: return model->set_h_21(value); default: LOG(FATAL) << "Parameter id is out of bounds"; } } // MixtureModelAdapterBase implementation. template typename MixtureTraits::model MixtureModelAdapterBase::FromFloatPointer( const float* args, bool identity_parametrization, int skip, int num_models) { MixtureModel model; const float* arg_ptr = args; for (int i = 0; i < num_models; ++i, arg_ptr += BaseModelAdapter::NumParameters() + skip) { BaseModel base = BaseModelAdapter::FromFloatPointer(arg_ptr, identity_parametrization); model.add_model()->CopyFrom(base); } return model; } template typename MixtureTraits::model MixtureModelAdapterBase::FromDoublePointer( const double* args, bool identity_parametrization, int skip, int num_models) { MixtureModel model; const double* arg_ptr = args; for (int i = 0; i < num_models; ++i, arg_ptr += BaseModelAdapter::NumParameters() + skip) { BaseModel base = BaseModelAdapter::FromDoublePointer(arg_ptr, identity_parametrization); model.add_model()->CopyFrom(base); } return model; } template typename MixtureTraits::model MixtureModelAdapterBase::ComposeRight( const MixtureModel& mixture_model, const BaseModel& base_model) { const int num_models = mixture_model.model_size(); MixtureModel result; for (int m = 0; m < num_models; ++m) { result.add_model()->CopyFrom( BaseModelAdapter::Compose(mixture_model.model(m), base_model)); } return result; } template typename MixtureTraits::model MixtureModelAdapterBase::ComposeLeft( const MixtureModel& mixture_model, const BaseModel& base_model) { const int num_models = mixture_model.model_size(); MixtureModel result; for (int m = 0; m < num_models; ++m) { result.add_model()->CopyFrom( BaseModelAdapter::Compose(base_model, mixture_model.model(m))); } return result; } template std::string MixtureModelAdapterBase::ToString( const MixtureModel& model, std::string delim) { std::string result = ""; for (int m = 0, size = model.model_size(); m < size; ++m) { result += (m == 0 ? "" : delim) + BaseModelAdapter::ToString(model.model(m)); } return result; } template float MixtureModelAdapterBase::GetParameter( const MixtureModel& model, int model_id, int param_id) { return BaseModelAdapter::GetParameter(model.model(model_id), param_id); } template void MixtureModelAdapterBase::SetParameter(int model_id, int param_id, float value, MixtureModel* model) { BaseModelAdapter::SetParameter(param_id, value, model->mutable_model(model_id)); } template typename MixtureTraits::model MixtureModelAdapterBase::IdentityModel(int num_mixtures) { MixtureModel model; for (int i = 0; i < num_mixtures; ++i) { model.add_model(); } return model; } template typename MixtureTraits::base_model MixtureModelAdapterBase::MeanModel( const MixtureModel& mixture_model) { const int num_models = mixture_model.model_size(); if (num_models == 0) { return BaseModel(); } float params[BaseModelAdapter::NumParameters()]; memset(params, 0, sizeof(params[0]) * BaseModelAdapter::NumParameters()); // Average of models. const float denom = 1.0f / num_models; for (int k = 0; k < BaseModelAdapter::NumParameters(); ++k) { for (int m = 0; m < num_models; ++m) { params[k] += BaseModelAdapter::GetParameter(mixture_model.model(m), k); } params[k] *= denom; } return BaseModelAdapter::FromFloatPointer(params, false); } template typename MixtureTraits::model MixtureModelAdapterBase::LinearModel( const MixtureModel& mixture_model) { // For each parameter: Fit line param_idx -> param value. const int num_models = mixture_model.model_size(); if (num_models <= 1) { return mixture_model; } std::vector result(num_models * BaseModelAdapter::NumParameters()); const double inv_models = 1.0f / num_models; for (int p = 0; p < BaseModelAdapter::NumParameters(); ++p) { // Calculate sum, sq_sum and inner product. double sum_x = 0.0; double sum_y = 0.0; double sum_xx = 0.0; double sum_yy = 0.0; double sum_xy = 0.0; for (int m = 0; m < num_models; ++m) { const float x = m * inv_models; sum_x += x; sum_xx += x * x; const double y = GetParameter(mixture_model, m, p); sum_y += y; sum_yy += y * y; sum_xy += x * y; } const double denom = sum_xx - inv_models * sum_x * sum_x; CHECK_NE(denom, 0); // As num_models > 1. const double a = (sum_xy - inv_models * sum_x * sum_y) * denom; const double b = inv_models * (sum_y - a * sum_x); for (int m = 0; m < num_models; ++m) { const float x = m * inv_models; result[m * BaseModelAdapter::NumParameters() + p] = a * x + b; } } MixtureModel result_model = FromFloatPointer(&result[0], false, 0, num_models); return result_model; } template typename MixtureTraits::model MixtureModelAdapterBase::Embed( const BaseModel& base_model, int num_mixtures) { MixtureModel model; for (int i = 0; i < num_mixtures; ++i) { model.add_model()->CopyFrom(base_model); } return model; } // MixtureModelAdapter implementation. template typename MixtureModelAdapterBase::BaseModel MixtureModelAdapter::ToBaseModel( const MixtureModel& mixture_model, const float* weights) { const int num_models = mixture_model.model_size(); float params[BaseModelAdapter::NumParameters()]; memset(params, 0, sizeof(params[0]) * BaseModelAdapter::NumParameters()); // Weighted combination of mixture models. for (int m = 0; m < num_models; ++m) { for (int k = 0; k < BaseModelAdapter::NumParameters(); ++k) { params[k] += BaseModelAdapter::GetParameter(mixture_model.model(m), k) * weights[m]; } } return BaseModelAdapter::FromFloatPointer(params, false); } template Vector2_f MixtureModelAdapter::TransformPoint( const MixtureModel& model, const float* weights, const Vector2_f& pt) { const int num_models = model.model_size(); const Vector3_f pt3(pt.x(), pt.y(), 1.0f); Vector3_f result(0, 0, 0); for (int i = 0; i < num_models; ++i) { result += BaseModelAdapter::TransformPoint3(model.model(i), pt3 * weights[i]); } DCHECK_NE(result.z(), 0) << "Degenerate mapping."; return Vector2_f(result.x() / result.z(), result.y() / result.z()); } template Vector2_f MixtureModelAdapter::TransformPoint( const MixtureModel& model, const MixtureRowWeights& weights, const Vector2_f& pt) { return TransformPoint(model, weights.RowWeightsClamped(pt.y()), pt); } template Vector2_f MixtureModelAdapter::SolveForPoint( const MixtureModel& model, const float* weights, const Vector2_f& pt) { BaseModel base_model = ToBaseModel(model, weights); return BaseModelAdapter::TransformPoint(BaseModelAdapter::Invert(base_model), pt); } template Vector2_f MixtureModelAdapter::SolveForPointChecked( const MixtureModel& model, const float* weights, const Vector2_f& pt, bool* success) { BaseModel base_model = ToBaseModel(model, weights); BaseModel inv_base_model = BaseModelAdapter::InvertChecked(base_model, success); return BaseModelAdapter::TransformPoint(inv_base_model, pt); } // MixtureModelAdapter implementation. inline Homography MixtureModelAdapter::ToBaseModel( const MixtureHomography& mixture_model, const float* weights) { const int num_models = mixture_model.model_size(); float params[8] = {0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f, 0.f}; const Homography& const_homog = mixture_model.model(0); // Weighted combination of mixture models. switch (mixture_model.dof()) { case MixtureHomography::ALL_DOF: for (int m = 0; m < num_models; ++m) { params[0] += mixture_model.model(m).h_00() * weights[m]; params[1] += mixture_model.model(m).h_01() * weights[m]; params[2] += mixture_model.model(m).h_02() * weights[m]; params[3] += mixture_model.model(m).h_10() * weights[m]; params[4] += mixture_model.model(m).h_11() * weights[m]; params[5] += mixture_model.model(m).h_12() * weights[m]; params[6] += mixture_model.model(m).h_20() * weights[m]; params[7] += mixture_model.model(m).h_21() * weights[m]; } break; case MixtureHomography::TRANSLATION_DOF: params[0] = const_homog.h_00(); params[1] = const_homog.h_01(); params[3] = const_homog.h_10(); params[4] = const_homog.h_11(); params[6] = const_homog.h_20(); params[7] = const_homog.h_21(); for (int m = 0; m < num_models; ++m) { params[2] += mixture_model.model(m).h_02() * weights[m]; params[5] += mixture_model.model(m).h_12() * weights[m]; } break; case MixtureHomography::SKEW_ROTATION_DOF: params[0] = const_homog.h_00(); params[4] = const_homog.h_11(); params[6] = const_homog.h_20(); params[7] = const_homog.h_21(); for (int m = 0; m < num_models; ++m) { params[1] += mixture_model.model(m).h_01() * weights[m]; params[2] += mixture_model.model(m).h_02() * weights[m]; params[3] += mixture_model.model(m).h_10() * weights[m]; params[5] += mixture_model.model(m).h_12() * weights[m]; } break; case MixtureHomography::CONST_DOF: return const_homog; default: LOG(FATAL) << "Unknown type."; } return HomographyAdapter::FromFloatPointer(params, false); } inline Vector2_f MixtureModelAdapter::TransformPoint( const MixtureHomography& model, const float* weights, const Vector2_f& pt) { const int num_models = model.model_size(); const Homography& const_homog = model.model(0); Vector3_f result(0, 0, 0); const Vector3_f pt3(pt.x(), pt.y(), 1.0f); float x; float y; switch (model.dof()) { case MixtureHomography::ALL_DOF: for (int i = 0; i < num_models; ++i) { result += HomographyAdapter::TransformPoint3(model.model(i), pt3 * weights[i]); } break; case MixtureHomography::TRANSLATION_DOF: x = const_homog.h_00() * pt.x() + const_homog.h_01() * pt.y(); y = const_homog.h_10() * pt.x() + const_homog.h_11() * pt.y(); for (int i = 0; i < num_models; ++i) { x += model.model(i).h_02() * weights[i]; y += model.model(i).h_12() * weights[i]; } result = Vector3_f( x, y, const_homog.h_20() * pt.x() + const_homog.h_21() * pt.y() + 1.0f); break; case MixtureHomography::SKEW_ROTATION_DOF: x = const_homog.h_00() * pt.x(); y = const_homog.h_11() * pt.y(); for (int i = 0; i < num_models; ++i) { x += (model.model(i).h_01() * pt.y() + model.model(i).h_02()) * weights[i]; y += (model.model(i).h_10() * pt.x() + model.model(i).h_12()) * weights[i]; } result = Vector3_f( x, y, const_homog.h_20() * pt.x() + const_homog.h_21() * pt.y() + 1.0f); break; case MixtureHomography::CONST_DOF: return HomographyAdapter::TransformPoint(model.model(0), pt); default: LOG(FATAL) << "Unknown type."; } DCHECK_NE(result.z(), 0) << "Degenerate mapping."; return Vector2_f(result.x() / result.z(), result.y() / result.z()); } inline Vector2_f MixtureModelAdapter::TransformPoint( const MixtureHomography& model, const MixtureRowWeights& weights, const Vector2_f& pt) { return TransformPoint(model, weights.RowWeightsClamped(pt.y()), pt); } inline Vector2_f MixtureModelAdapter::SolveForPoint( const MixtureHomography& model, const float* weights, const Vector2_f& pt) { Homography base_model = ToBaseModel(model, weights); return HomographyAdapter::TransformPoint( HomographyAdapter::Invert(base_model), pt); } inline Vector2_f MixtureModelAdapter::SolveForPointChecked( const MixtureHomography& model, const float* weights, const Vector2_f& pt, bool* success) { Homography base_model = ToBaseModel(model, weights); Homography inv_base_model = HomographyAdapter::InvertChecked(base_model, success); return HomographyAdapter::TransformPoint(inv_base_model, pt); } } // namespace mediapipe #endif // MEDIAPIPE_UTIL_TRACKING_MOTION_MODELS_H_