Files
ANSLibs/QRCode/RegressionLine.h

163 lines
4.6 KiB
C
Raw Normal View History

/*
* 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
auto end = std::remove_if(points.begin(), points.end(), [this, maxSignedDist](auto p) {
auto sd = this->signedDistance(p);
return sd > maxSignedDist || sd < -2 * maxSignedDist;
});
points.erase(end, points.end());
// 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