You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
161 lines
4.5 KiB
161 lines
4.5 KiB
/* |
|
* Copyright 2020 Axel Waggershauser |
|
*/ |
|
// SPDX-License-Identifier: Apache-2.0 |
|
|
|
#pragma once |
|
|
|
#include "Point.h" |
|
#include "ZXAlgorithms.h" |
|
|
|
#include <algorithm> |
|
#include <cmath> |
|
#include <vector> |
|
|
|
#ifdef PRINT_DEBUG |
|
#include <cstdio> |
|
#endif |
|
|
|
namespace ZXing { |
|
|
|
class RegressionLine |
|
{ |
|
protected: |
|
std::vector<PointF> _points; |
|
PointF _directionInward; |
|
PointF::value_t a = NAN, b = NAN, c = NAN; |
|
|
|
friend PointF intersect(const RegressionLine& l1, const RegressionLine& l2); |
|
|
|
template<typename T> bool evaluate(const PointT<T>* begin, const PointT<T>* end) |
|
{ |
|
auto mean = Reduce(begin, end, PointF()) / std::distance(begin, end); |
|
PointF::value_t sumXX = 0, sumYY = 0, sumXY = 0; |
|
for (auto p = begin; p != end; ++p) { |
|
auto d = *p - mean; |
|
sumXX += d.x * d.x; |
|
sumYY += d.y * d.y; |
|
sumXY += d.x * d.y; |
|
} |
|
if (sumYY >= sumXX) { |
|
auto l = std::sqrt(sumYY * sumYY + sumXY * sumXY); |
|
a = +sumYY / l; |
|
b = -sumXY / l; |
|
} else { |
|
auto l = std::sqrt(sumXX * sumXX + sumXY * sumXY); |
|
a = +sumXY / l; |
|
b = -sumXX / l; |
|
} |
|
if (dot(_directionInward, normal()) < 0) { |
|
a = -a; |
|
b = -b; |
|
} |
|
c = dot(normal(), mean); // (a*mean.x + b*mean.y); |
|
return dot(_directionInward, normal()) > 0.5f; // angle between original and new direction is at most 60 degree |
|
} |
|
|
|
template <typename T> bool evaluate(const std::vector<PointT<T>>& points) { return evaluate(&points.front(), &points.back() + 1); } |
|
|
|
template <typename T> static auto distance(PointT<T> a, PointT<T> b) { return ZXing::distance(a, b); } |
|
|
|
public: |
|
RegressionLine() { _points.reserve(16); } // arbitrary but plausible start size (tiny performance improvement) |
|
|
|
template<typename T> RegressionLine(PointT<T> a, PointT<T> b) |
|
{ |
|
evaluate(std::vector{a, b}); |
|
} |
|
|
|
template<typename T> RegressionLine(const PointT<T>* b, const PointT<T>* e) |
|
{ |
|
evaluate(b, e); |
|
} |
|
|
|
const auto& points() const { return _points; } |
|
int length() const { return _points.size() >= 2 ? int(distance(_points.front(), _points.back())) : 0; } |
|
bool isValid() const { return !std::isnan(a); } |
|
PointF normal() const { return isValid() ? PointF(a, b) : _directionInward; } |
|
auto signedDistance(PointF p) const { return dot(normal(), p) - c; } |
|
template <typename T> auto distance(PointT<T> p) const { return std::abs(signedDistance(PointF(p))); } |
|
PointF project(PointF p) const { return p - signedDistance(p) * normal(); } |
|
PointF centroid() const { return Reduce(_points) / _points.size(); } |
|
|
|
void reset() |
|
{ |
|
_points.clear(); |
|
_directionInward = {}; |
|
a = b = c = NAN; |
|
} |
|
|
|
void add(PointF p) { |
|
assert(_directionInward != PointF()); |
|
_points.push_back(p); |
|
if (_points.size() == 1) |
|
c = dot(normal(), p); |
|
} |
|
|
|
void pop_back() { _points.pop_back(); } |
|
void pop_front() |
|
{ |
|
std::rotate(_points.begin(), _points.begin() + 1, _points.end()); |
|
_points.pop_back(); |
|
} |
|
|
|
void setDirectionInward(PointF d) { _directionInward = normalized(d); } |
|
|
|
bool evaluate(double maxSignedDist = -1, bool updatePoints = false) |
|
{ |
|
bool ret = evaluate(_points); |
|
if (maxSignedDist > 0) { |
|
auto points = _points; |
|
while (true) { |
|
auto old_points_size = points.size(); |
|
// remove points that are further 'inside' than maxSignedDist or further 'outside' than 2 x maxSignedDist |
|
std::erase_if(points, [this, maxSignedDist](auto p) { |
|
auto sd = this->signedDistance(p); |
|
return sd > maxSignedDist || sd < -2 * maxSignedDist; |
|
}); |
|
// if we threw away too many points, something is off with the line to begin with |
|
if (points.size() < old_points_size / 2 || points.size() < 2) |
|
return false; |
|
if (old_points_size == points.size()) |
|
break; |
|
#ifdef PRINT_DEBUG |
|
printf("removed %zu points -> %zu remaining\n", old_points_size - points.size(), points.size()); |
|
fflush(stdout); |
|
#endif |
|
ret = evaluate(points); |
|
} |
|
|
|
if (updatePoints) |
|
_points = std::move(points); |
|
} |
|
return ret; |
|
} |
|
|
|
bool isHighRes() const |
|
{ |
|
PointF min = _points.front(), max = _points.front(); |
|
for (auto p : _points) { |
|
UpdateMinMax(min.x, max.x, p.x); |
|
UpdateMinMax(min.y, max.y, p.y); |
|
} |
|
auto diff = max - min; |
|
auto len = maxAbsComponent(diff); |
|
auto steps = std::min(std::abs(diff.x), std::abs(diff.y)); |
|
// due to aliasing we get bad extrapolations if the line is short and too close to vertical/horizontal |
|
return steps > 2 || len > 50; |
|
} |
|
}; |
|
|
|
inline PointF intersect(const RegressionLine& l1, const RegressionLine& l2) |
|
{ |
|
assert(l1.isValid() && l2.isValid()); |
|
auto d = l1.a * l2.b - l1.b * l2.a; |
|
auto x = (l1.c * l2.b - l1.b * l2.c) / d; |
|
auto y = (l1.a * l2.c - l1.c * l2.a) / d; |
|
return {x, y}; |
|
} |
|
|
|
} // ZXing |
|
|
|
|