mirror of
https://github.com/mollyim/webrtc.git
synced 2025-05-13 05:40:42 +01:00

Bug: webrtc:12338 Change-Id: Id47ef14982a6f31df6fc2e6d317e14f6e269e706 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/226954 Reviewed-by: Harald Alvestrand <hta@webrtc.org> Commit-Queue: Artem Titov <titovartem@webrtc.org> Cr-Commit-Position: refs/heads/master@{#34571}
206 lines
7.2 KiB
C++
206 lines
7.2 KiB
C++
/*
|
|
* Copyright (c) 2018 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree. An additional intellectual property rights grant can be found
|
|
* in the file PATENTS. All contributing project authors may
|
|
* be found in the AUTHORS file in the root of the source tree.
|
|
*/
|
|
|
|
#include "rtc_tools/frame_analyzer/linear_least_squares.h"
|
|
|
|
#include <math.h>
|
|
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <functional>
|
|
#include <numeric>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
|
|
#include "rtc_base/checks.h"
|
|
#include "rtc_base/logging.h"
|
|
|
|
namespace webrtc {
|
|
namespace test {
|
|
|
|
template <class T>
|
|
using Matrix = std::valarray<std::valarray<T>>;
|
|
|
|
namespace {
|
|
|
|
template <typename R, typename T>
|
|
R DotProduct(const std::valarray<T>& a, const std::valarray<T>& b) {
|
|
RTC_CHECK_EQ(a.size(), b.size());
|
|
return std::inner_product(std::begin(a), std::end(a), std::begin(b), R(0));
|
|
}
|
|
|
|
// Calculates a^T * b.
|
|
template <typename R, typename T>
|
|
Matrix<R> MatrixMultiply(const Matrix<T>& a, const Matrix<T>& b) {
|
|
Matrix<R> result(std::valarray<R>(a.size()), b.size());
|
|
for (size_t i = 0; i < a.size(); ++i) {
|
|
for (size_t j = 0; j < b.size(); ++j)
|
|
result[j][i] = DotProduct<R>(a[i], b[j]);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
template <typename T>
|
|
Matrix<T> Transpose(const Matrix<T>& matrix) {
|
|
if (matrix.size() == 0)
|
|
return Matrix<T>();
|
|
const size_t rows = matrix.size();
|
|
const size_t columns = matrix[0].size();
|
|
Matrix<T> result(std::valarray<T>(rows), columns);
|
|
|
|
for (size_t i = 0; i < rows; ++i) {
|
|
for (size_t j = 0; j < columns; ++j)
|
|
result[j][i] = matrix[i][j];
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// Convert valarray from type T to type R.
|
|
template <typename R, typename T>
|
|
std::valarray<R> ConvertTo(const std::valarray<T>& v) {
|
|
std::valarray<R> result(v.size());
|
|
for (size_t i = 0; i < v.size(); ++i)
|
|
result[i] = static_cast<R>(v[i]);
|
|
return result;
|
|
}
|
|
|
|
// Convert valarray Matrix from type T to type R.
|
|
template <typename R, typename T>
|
|
Matrix<R> ConvertTo(const Matrix<T>& mat) {
|
|
Matrix<R> result(mat.size());
|
|
for (size_t i = 0; i < mat.size(); ++i)
|
|
result[i] = ConvertTo<R>(mat[i]);
|
|
return result;
|
|
}
|
|
|
|
// Convert from valarray Matrix back to the more conventional std::vector.
|
|
template <typename T>
|
|
std::vector<std::vector<T>> ToVectorMatrix(const Matrix<T>& m) {
|
|
std::vector<std::vector<T>> result;
|
|
for (const std::valarray<T>& v : m)
|
|
result.emplace_back(std::begin(v), std::end(v));
|
|
return result;
|
|
}
|
|
|
|
// Create a valarray Matrix from a conventional std::vector.
|
|
template <typename T>
|
|
Matrix<T> FromVectorMatrix(const std::vector<std::vector<T>>& mat) {
|
|
Matrix<T> result(mat.size());
|
|
for (size_t i = 0; i < mat.size(); ++i)
|
|
result[i] = std::valarray<T>(mat[i].data(), mat[i].size());
|
|
return result;
|
|
}
|
|
|
|
// Returns `matrix_to_invert`^-1 * `right_hand_matrix`. `matrix_to_invert` must
|
|
// have square size.
|
|
Matrix<double> GaussianElimination(Matrix<double> matrix_to_invert,
|
|
Matrix<double> right_hand_matrix) {
|
|
// `n` is the width/height of `matrix_to_invert`.
|
|
const size_t n = matrix_to_invert.size();
|
|
// Make sure `matrix_to_invert` has square size.
|
|
for (const std::valarray<double>& column : matrix_to_invert)
|
|
RTC_CHECK_EQ(n, column.size());
|
|
// Make sure `right_hand_matrix` has correct size.
|
|
for (const std::valarray<double>& column : right_hand_matrix)
|
|
RTC_CHECK_EQ(n, column.size());
|
|
|
|
// Transpose the matrices before and after so that we can perform Gaussian
|
|
// elimination on the columns instead of the rows, since that is easier with
|
|
// our representation.
|
|
matrix_to_invert = Transpose(matrix_to_invert);
|
|
right_hand_matrix = Transpose(right_hand_matrix);
|
|
|
|
// Loop over the diagonal of `matrix_to_invert` and perform column reduction.
|
|
// Column reduction is a sequence of elementary column operations that is
|
|
// performed on both `matrix_to_invert` and `right_hand_matrix` until
|
|
// `matrix_to_invert` has been transformed to the identity matrix.
|
|
for (size_t diagonal_index = 0; diagonal_index < n; ++diagonal_index) {
|
|
// Make sure the diagonal element has the highest absolute value by
|
|
// swapping columns if necessary.
|
|
for (size_t column = diagonal_index + 1; column < n; ++column) {
|
|
if (std::abs(matrix_to_invert[column][diagonal_index]) >
|
|
std::abs(matrix_to_invert[diagonal_index][diagonal_index])) {
|
|
std::swap(matrix_to_invert[column], matrix_to_invert[diagonal_index]);
|
|
std::swap(right_hand_matrix[column], right_hand_matrix[diagonal_index]);
|
|
}
|
|
}
|
|
|
|
// Reduce the diagonal element to be 1, by dividing the column with that
|
|
// value. If the diagonal element is 0, it means the system of equations has
|
|
// many solutions, and in that case we will return an arbitrary solution.
|
|
if (matrix_to_invert[diagonal_index][diagonal_index] == 0.0) {
|
|
RTC_LOG(LS_WARNING) << "Matrix is not invertible, ignoring.";
|
|
continue;
|
|
}
|
|
const double diagonal_element =
|
|
matrix_to_invert[diagonal_index][diagonal_index];
|
|
matrix_to_invert[diagonal_index] /= diagonal_element;
|
|
right_hand_matrix[diagonal_index] /= diagonal_element;
|
|
|
|
// Eliminate the other entries in row `diagonal_index` by making them zero.
|
|
for (size_t column = 0; column < n; ++column) {
|
|
if (column == diagonal_index)
|
|
continue;
|
|
const double row_element = matrix_to_invert[column][diagonal_index];
|
|
matrix_to_invert[column] -=
|
|
row_element * matrix_to_invert[diagonal_index];
|
|
right_hand_matrix[column] -=
|
|
row_element * right_hand_matrix[diagonal_index];
|
|
}
|
|
}
|
|
|
|
// Transpose the result before returning it, explained in comment above.
|
|
return Transpose(right_hand_matrix);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
IncrementalLinearLeastSquares::IncrementalLinearLeastSquares() = default;
|
|
IncrementalLinearLeastSquares::~IncrementalLinearLeastSquares() = default;
|
|
|
|
void IncrementalLinearLeastSquares::AddObservations(
|
|
const std::vector<std::vector<uint8_t>>& x,
|
|
const std::vector<std::vector<uint8_t>>& y) {
|
|
if (x.empty() || y.empty())
|
|
return;
|
|
// Make sure all columns are the same size.
|
|
const size_t n = x[0].size();
|
|
for (const std::vector<uint8_t>& column : x)
|
|
RTC_CHECK_EQ(n, column.size());
|
|
for (const std::vector<uint8_t>& column : y)
|
|
RTC_CHECK_EQ(n, column.size());
|
|
|
|
// We will multiply the uint8_t values together, so we need to expand to a
|
|
// type that can safely store those values, i.e. uint16_t.
|
|
const Matrix<uint16_t> unpacked_x = ConvertTo<uint16_t>(FromVectorMatrix(x));
|
|
const Matrix<uint16_t> unpacked_y = ConvertTo<uint16_t>(FromVectorMatrix(y));
|
|
|
|
const Matrix<uint64_t> xx = MatrixMultiply<uint64_t>(unpacked_x, unpacked_x);
|
|
const Matrix<uint64_t> xy = MatrixMultiply<uint64_t>(unpacked_x, unpacked_y);
|
|
if (sum_xx && sum_xy) {
|
|
*sum_xx += xx;
|
|
*sum_xy += xy;
|
|
} else {
|
|
sum_xx = xx;
|
|
sum_xy = xy;
|
|
}
|
|
}
|
|
|
|
std::vector<std::vector<double>>
|
|
IncrementalLinearLeastSquares::GetBestSolution() const {
|
|
RTC_CHECK(sum_xx && sum_xy) << "No observations have been added";
|
|
return ToVectorMatrix(GaussianElimination(ConvertTo<double>(*sum_xx),
|
|
ConvertTo<double>(*sum_xy)));
|
|
}
|
|
|
|
} // namespace test
|
|
} // namespace webrtc
|