123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333 |
- // David Eberly, Geometric Tools, Redmond WA 98052
- // Copyright (c) 1998-2020
- // Distributed under the Boost Software License, Version 1.0.
- // https://www.boost.org/LICENSE_1_0.txt
- // https://www.geometrictools.com/License/Boost/LICENSE_1_0.txt
- // Version: 4.3.2019.12.27
- #pragma once
- // Least-squares fit of two parallel lines to points that presumably are
- // clustered on the lines. The algorithm is described in
- // https://www.geometrictools.com/Documentation/FitParallelLinesToPoints2D.pdf
- #include <Mathematics/Polynomial1.h>
- #include <Mathematics/RootsPolynomial.h>
- #include <Mathematics/Vector2.h>
- namespace WwiseGTE
- {
- template <typename Real>
- class ApprParallelLines2
- {
- public:
- ApprParallelLines2()
- :
- mR0(0), mR1(1), mR2(2), mR3(3), mR4(4), mR5(5), mR6(6)
- {
- // The constants are set here in case Real is a rational type,
- // which avoids construction costs for those types.
- }
- void Fit(std::vector<Vector2<Real>> const& P, unsigned int maxIterations,
- Vector2<Real>& C, Vector2<Real>& V, Real& radius)
- {
- // Compute the average of the samples.
- size_t const n = P.size();
- Real const invN = static_cast<Real>(1) / static_cast<Real>(n);
- std::vector<Vector2<Real>> PAdjust = P;
- Vector2<Real> A{ mR0, mR0 };
- for (auto const& sample : PAdjust)
- {
- A += sample;
- }
- A *= invN;
- // Subtract the average from the samples so that the replacement
- // points have zero average.
- for (auto& sample : PAdjust)
- {
- sample -= A;
- }
- // Compute the Zpq terms.
- ZValues data(PAdjust);
- // Compute F(sigma,gamma) = f0(sigma) + gamma * f1(sigma).
- Polynomial1<Real> f0, f1;
- ComputeF(data, f0, f1);
- Polynomial1<Real> freduced0(4), freduced1(3);
- for (int i = 0; i <= 4; ++i)
- {
- freduced0[i] = f0[2 * i];
- }
- for (int i = 0; i <= 3; ++i)
- {
- freduced1[i] = f1[2 * i + 1];
- }
- // Evaluate the error function at any (sigma,gamma). Choose (0,1)
- // so that we do not have to process a root sigma=0 later.
- Real minSigma = mR0, minGamma = mR1;
- Real minK = data.Z03 / (mR2 * data.Z02);
- Real minKSqr = minK * minK;
- Real minRSqr = minKSqr + data.Z02;
- Real minError = data.Z04 - mR4 * minK * data.Z03 + (mR4 * minKSqr - data.Z02) * data.Z02;
- if (f1 != Polynomial1<Real>{ mR0 })
- {
- Polynomial1<Real> sigmaSqrPoly{ mR0, mR0, mR1 };
- Polynomial1<Real> f0Sqr = f0 * f0, f1Sqr = f1 * f1;
- Polynomial1<Real> h = sigmaSqrPoly * f1Sqr + (f0Sqr - f1Sqr);
- Polynomial1<Real> hreduced(8);
- for (int i = 0; i <= 8; ++i)
- {
- hreduced[i] = h[2 * i];
- }
- std::array<Real, 8> roots;
- int numRoots = RootsPolynomial<Real>::Find(8, &hreduced[0],
- maxIterations, roots.data());
- for (int i = 0; i < numRoots; ++i)
- {
- Real sigmaSqr = roots[i];
- if (sigmaSqr > mR0)
- {
- Real sigma = std::sqrt(sigmaSqr);
- Real gamma = -freduced0(sigmaSqr) / (sigma * freduced1(sigmaSqr));
- UpdateParameters(data, sigma, sigmaSqr, gamma,
- minSigma, minGamma, minK, minRSqr, minError);
- }
- }
- }
- else
- {
- Polynomial1<Real> hreduced(4);
- for (int i = 0; i <= 4; ++i)
- {
- hreduced[i] = f0[2 * i];
- }
- std::array<Real, 4> roots;
- int numRoots = RootsPolynomial<Real>::Find(4, &hreduced[0],
- maxIterations, roots.data());
- for (int i = 0; i < numRoots; ++i)
- {
- Real sigmaSqr = roots[i];
- if (sigmaSqr > mR0)
- {
- Real sigma = std::sqrt(sigmaSqr);
- Real gamma = std::sqrt(sigma);
- UpdateParameters(data, sigma, sigmaSqr, gamma,
- minSigma, minGamma, minK, minRSqr, minError);
- gamma = -gamma;
- UpdateParameters(data, sigma, sigmaSqr, gamma,
- minSigma, minGamma, minK, minRSqr, minError);
- }
- }
- }
- // Compute the minimizers V, C and radius. The center minK*U must have
- // A added to it because the inputs P had A subtracted from them. The
- // addition no longer guarantees that Dot(V,C) = 0, so the V-component
- // of A+minK*U is projected out so that the returned center has only a
- // U-component.
- V = Vector2<Real>{ minGamma, minSigma };
- C = A + minK * Vector2<Real>{ -minSigma, minGamma };
- C -= Dot(C, V) * V;
- radius = std::sqrt(minRSqr);
- }
- private:
- struct ZValues
- {
- ZValues(std::vector<Vector2<Real>> const& P)
- :
- Z20(0), Z11(0), Z02(0),
- Z30(0), Z21(0), Z12(0), Z03(0),
- Z40(0), Z31(0), Z22(0), Z13(0), Z04(0)
- {
- Real const invN = static_cast<Real>(1) / static_cast<Real>(P.size());
- for (auto const& sample : P)
- {
- Real xx = sample[0] * sample[0];
- Real xy = sample[0] * sample[1];
- Real yy = sample[1] * sample[1];
- Real xxx = xx * sample[0];
- Real xxy = xy * sample[0];
- Real xyy = xy * sample[1];
- Real yyy = yy * sample[1];
- Real xxxx = xxx * sample[0];
- Real xxxy = xxx * sample[1];
- Real xxyy = xx * yy;
- Real xyyy = yyy * sample[0];
- Real yyyy = yyy * sample[1];
- Z20 += xx;
- Z11 += xy;
- Z02 += yy;
- Z30 += xxx;
- Z21 += xxy;
- Z12 += xyy;
- Z03 += yyy;
- Z40 += xxxx;
- Z31 += xxxy;
- Z22 += xxyy;
- Z13 += xyyy;
- Z04 += yyyy;
- }
- Z20 *= invN;
- Z11 *= invN;
- Z02 *= invN;
- Z30 *= invN;
- Z21 *= invN;
- Z12 *= invN;
- Z03 *= invN;
- Z40 *= invN;
- Z31 *= invN;
- Z22 *= invN;
- Z13 *= invN;
- Z04 *= invN;
- }
- Real Z20, Z11, Z02;
- Real Z30, Z21, Z12, Z03;
- Real Z40, Z31, Z22, Z13, Z04;
- };
- // Given two polynomials A0+gamma*B0 and A1+gamma*B1, the product is
- // [A0*A1+(1-sigma^2)*B0*B1] + gamma*[A0*B1+B0*A1] = A2+gamma*B2.
- void ComputeProduct(
- Polynomial1<Real> const& A0, Polynomial1<Real> const& B0,
- Polynomial1<Real> const& A1, Polynomial1<Real> const& B1,
- Polynomial1<Real>& A2, Polynomial1<Real>& B2)
- {
- Polynomial1<Real> gammaSqr{ mR1, mR0, -mR1 };
- A2 = A0 * A1 + gammaSqr * B0 * B1;
- B2 = A0 * B1 + B0 * A1;
- }
- void ComputeF(ZValues const& data, Polynomial1<Real>& f0, Polynomial1<Real>& f1)
- {
- // Compute the apq and bpq terms.
- Polynomial1<Real> a11(2);
- a11[0] = data.Z11;
- a11[2] = -mR2 * data.Z11;
- Polynomial1<Real> b11(1);
- b11[1] = data.Z02 - data.Z20;
- Polynomial1<Real> a20(2);
- a20[0] = data.Z02;
- a20[2] = data.Z20 - data.Z02;
- Polynomial1<Real> b20(1);
- b20[1] = -mR2 * data.Z11;
- Polynomial1<Real> a30(3);
- a30[1] = -mR3;
- a30[3] = mR3 * data.Z12 - data.Z30;
- Polynomial1<Real> b30(2);
- b30[0] = data.Z03;
- b30[2] = mR3 * data.Z21 - data.Z03;
- Polynomial1<Real> a21(3);
- a21[1] = data.Z03 - mR2 * data.Z21;
- a21[3] = mR3 * data.Z21 - data.Z03;
- Polynomial1<Real> b21(2);
- b21[0] = data.Z12;
- b21[2] = data.Z30 - mR3 * data.Z12;
- Polynomial1<Real> a40(4);
- a40[0] = data.Z04;
- a40[2] = mR6 * data.Z22 - mR2 * data.Z04;
- a40[4] = data.Z40 - mR6 * data.Z22 + data.Z04;
- Polynomial1<Real> b40(3);
- b40[1] = -mR4 * data.Z13;
- b40[3] = mR4 * (data.Z13 - data.Z31);
- Polynomial1<Real> a31(4);
- a31[0] = data.Z13;
- a31[2] = mR3 * data.Z31 - mR5 * data.Z13;
- a31[4] = mR4 * (data.Z13 - data.Z31);
- Polynomial1<Real> b31(3);
- b31[1] = data.Z04 - mR3 * data.Z22;
- b31[3] = mR6 * data.Z22 - data.Z40 - data.Z04;
- // Compute S20^2 = c0 + gamma*d0.
- Polynomial1<Real> c0, d0;
- ComputeProduct(a20, b20, a20, b20, c0, d0);
- // Compute S31 * S20^2 = c1 + gamma*d1.
- Polynomial1<Real> c1, d1;
- ComputeProduct(a31, b31, c0, d0, c1, d1);
- // Compute S21 * S20 = c2 + gamma*d2.
- Polynomial1<Real> c2, d2;
- ComputeProduct(a21, b21, a20, b20, c2, d2);
- // Compute S30 * (S21 * S20) = c3 + gamma*d3.
- Polynomial1<Real> c3, d3;
- ComputeProduct(a30, b30, c2, d2, c3, d3);
- // Compute S30 * S11 = c4 + gamma*d4.
- Polynomial1<Real> c4, d4;
- ComputeProduct(a30, b30, a11, b11, c4, d4);
- // Compute S30 * (S30 * S11) = c5 + gamma*d5.
- Polynomial1<Real> c5, d5;
- ComputeProduct(a30, b30, c4, d4, c5, d5);
- // Compute S20^2 * S11 = c6 + gamma*d6.
- Polynomial1<Real> c6, d6;
- ComputeProduct(c0, d0, a11, b11, c6, d6);
- // Compute S20 * (S20^2 * S11) = c7 + gamma*d7.
- Polynomial1<Real> c7, d7;
- ComputeProduct(a20, b20, c6, d6, c7, d7);
- // Compute F = 2*S31*S20^2 - 3*S30*S21*S20 + S30^2*S11
- // - 2*S20^3*S11 = f0 + gamma*f1, where f0 is even of degree 8
- // and f1 is odd of degree 7.
- f0 = mR2 * (c1 - c7) - mR3 * c3 + c5;
- f1 = mR2 * (d1 - d7) - mR3 * d3 + d5;
- }
- void UpdateParameters(ZValues const& data, Real const& sigma, Real const& sigmaSqr,
- Real const& gamma, Real& minSigma, Real& minGamma, Real& minK,
- Real& minRSqr, Real& minError)
- {
- // Rather than evaluate apq(sigma) and bpq(sigma), the
- // polynomials are evaluated at sigmaSqr to avoid the
- // rounding errors that are inherent by computing
- // s = sqrt(ssqr); ssqr = s * s;
- Real A20 = data.Z02 + (data.Z20 - data.Z02) * sigmaSqr;
- Real B20 = -mR2 * data.Z11 * sigma;
- Real S20 = A20 + gamma * B20;
- Real A30 = -sigma * (mR3 * data.Z12 + (data.Z30 - mR3 * data.Z12) * sigmaSqr);
- Real B30 = data.Z03 + (mR3 * data.Z21 - data.Z03) * sigmaSqr;
- Real S30 = A30 + gamma * B30;
- Real A40 = data.Z04 + ((mR6 * data.Z22 - mR2 * data.Z04)
- + (data.Z40 - mR6 * data.Z22 + data.Z04) * sigmaSqr) * sigmaSqr;
- Real B40 = -mR4 * sigma * (data.Z13 + (data.Z31 - data.Z13) * sigmaSqr);
- Real S40 = A40 + gamma * B40;
- Real k = S30 / (mR2 * S20);
- Real ksqr = k * k;
- Real rsqr = ksqr + S20;
- Real error = S40 - mR4 * k * S30 + (mR4 * ksqr - S20) * S20;
- if (error < minError)
- {
- minSigma = sigma;
- minGamma = gamma;
- minK = k;
- minRSqr = rsqr;
- minError = error;
- }
- }
- Real const mR0, mR1, mR2, mR3, mR4, mR5, mR6;
- };
- }
|