PaddleOCR/ppocr/postprocess/lanms/lanms.h

235 lines
5.8 KiB
C++

#pragma once
#include "clipper/clipper.hpp"
// locality-aware NMS
namespace lanms {
namespace cl = ClipperLib;
struct Polygon {
cl::Path poly;
float score;
};
float paths_area(const ClipperLib::Paths &ps) {
float area = 0;
for (auto &&p: ps)
area += cl::Area(p);
return area;
}
float poly_iou(const Polygon &a, const Polygon &b) {
cl::Clipper clpr;
clpr.AddPath(a.poly, cl::ptSubject, true);
clpr.AddPath(b.poly, cl::ptClip, true);
cl::Paths inter, uni;
clpr.Execute(cl::ctIntersection, inter, cl::pftEvenOdd);
clpr.Execute(cl::ctUnion, uni, cl::pftEvenOdd);
auto inter_area = paths_area(inter),
uni_area = paths_area(uni);
return std::abs(inter_area) / std::max(std::abs(uni_area), 1.0f);
}
bool should_merge(const Polygon &a, const Polygon &b, float iou_threshold) {
return poly_iou(a, b) > iou_threshold;
}
/**
* Incrementally merge polygons
*/
class PolyMerger {
public:
PolyMerger(): score(0), nr_polys(0) {
memset(data, 0, sizeof(data));
}
/**
* Add a new polygon to be merged.
*/
void add(const Polygon &p_given) {
Polygon p;
if (nr_polys > 0) {
// vertices of two polygons to merge may not in the same order;
// we match their vertices by choosing the ordering that
// minimizes the total squared distance.
// see function normalize_poly for details.
p = normalize_poly(get(), p_given);
} else {
p = p_given;
}
assert(p.poly.size() == 4);
auto &poly = p.poly;
auto s = p.score;
data[0] += poly[0].X * s;
data[1] += poly[0].Y * s;
data[2] += poly[1].X * s;
data[3] += poly[1].Y * s;
data[4] += poly[2].X * s;
data[5] += poly[2].Y * s;
data[6] += poly[3].X * s;
data[7] += poly[3].Y * s;
score += p.score;
nr_polys += 1;
}
inline std::int64_t sqr(std::int64_t x) { return x * x; }
Polygon normalize_poly(
const Polygon &ref,
const Polygon &p) {
std::int64_t min_d = std::numeric_limits<std::int64_t>::max();
size_t best_start = 0, best_order = 0;
for (size_t start = 0; start < 4; start ++) {
size_t j = start;
std::int64_t d = (
sqr(ref.poly[(j + 0) % 4].X - p.poly[(j + 0) % 4].X)
+ sqr(ref.poly[(j + 0) % 4].Y - p.poly[(j + 0) % 4].Y)
+ sqr(ref.poly[(j + 1) % 4].X - p.poly[(j + 1) % 4].X)
+ sqr(ref.poly[(j + 1) % 4].Y - p.poly[(j + 1) % 4].Y)
+ sqr(ref.poly[(j + 2) % 4].X - p.poly[(j + 2) % 4].X)
+ sqr(ref.poly[(j + 2) % 4].Y - p.poly[(j + 2) % 4].Y)
+ sqr(ref.poly[(j + 3) % 4].X - p.poly[(j + 3) % 4].X)
+ sqr(ref.poly[(j + 3) % 4].Y - p.poly[(j + 3) % 4].Y)
);
if (d < min_d) {
min_d = d;
best_start = start;
best_order = 0;
}
d = (
sqr(ref.poly[(j + 0) % 4].X - p.poly[(j + 3) % 4].X)
+ sqr(ref.poly[(j + 0) % 4].Y - p.poly[(j + 3) % 4].Y)
+ sqr(ref.poly[(j + 1) % 4].X - p.poly[(j + 2) % 4].X)
+ sqr(ref.poly[(j + 1) % 4].Y - p.poly[(j + 2) % 4].Y)
+ sqr(ref.poly[(j + 2) % 4].X - p.poly[(j + 1) % 4].X)
+ sqr(ref.poly[(j + 2) % 4].Y - p.poly[(j + 1) % 4].Y)
+ sqr(ref.poly[(j + 3) % 4].X - p.poly[(j + 0) % 4].X)
+ sqr(ref.poly[(j + 3) % 4].Y - p.poly[(j + 0) % 4].Y)
);
if (d < min_d) {
min_d = d;
best_start = start;
best_order = 1;
}
}
Polygon r;
r.poly.resize(4);
auto j = best_start;
if (best_order == 0) {
for (size_t i = 0; i < 4; i ++)
r.poly[i] = p.poly[(j + i) % 4];
} else {
for (size_t i = 0; i < 4; i ++)
r.poly[i] = p.poly[(j + 4 - i - 1) % 4];
}
r.score = p.score;
return r;
}
Polygon get() const {
Polygon p;
auto &poly = p.poly;
poly.resize(4);
auto score_inv = 1.0f / std::max(1e-8f, score);
poly[0].X = data[0] * score_inv;
poly[0].Y = data[1] * score_inv;
poly[1].X = data[2] * score_inv;
poly[1].Y = data[3] * score_inv;
poly[2].X = data[4] * score_inv;
poly[2].Y = data[5] * score_inv;
poly[3].X = data[6] * score_inv;
poly[3].Y = data[7] * score_inv;
assert(score > 0);
p.score = score;
return p;
}
private:
std::int64_t data[8];
float score;
std::int32_t nr_polys;
};
/**
* The standard NMS algorithm.
*/
std::vector<Polygon> standard_nms(std::vector<Polygon> &polys, float iou_threshold) {
size_t n = polys.size();
if (n == 0)
return {};
std::vector<size_t> indices(n);
std::iota(std::begin(indices), std::end(indices), 0);
std::sort(std::begin(indices), std::end(indices), [&](size_t i, size_t j) { return polys[i].score > polys[j].score; });
std::vector<size_t> keep;
while (indices.size()) {
size_t p = 0, cur = indices[0];
keep.emplace_back(cur);
for (size_t i = 1; i < indices.size(); i ++) {
if (!should_merge(polys[cur], polys[indices[i]], iou_threshold)) {
indices[p ++] = indices[i];
}
}
indices.resize(p);
}
std::vector<Polygon> ret;
for (auto &&i: keep) {
ret.emplace_back(polys[i]);
}
return ret;
}
std::vector<Polygon>
merge_quadrangle_n9(const float *data, size_t n, float iou_threshold) {
using cInt = cl::cInt;
// first pass
std::vector<Polygon> polys;
for (size_t i = 0; i < n; i ++) {
auto p = data + i * 9;
Polygon poly{
{
{cInt(p[0]), cInt(p[1])},
{cInt(p[2]), cInt(p[3])},
{cInt(p[4]), cInt(p[5])},
{cInt(p[6]), cInt(p[7])},
},
p[8],
};
if (polys.size()) {
// merge with the last one
auto &bpoly = polys.back();
if (should_merge(poly, bpoly, iou_threshold)) {
PolyMerger merger;
merger.add(bpoly);
merger.add(poly);
bpoly = merger.get();
} else {
polys.emplace_back(poly);
}
} else {
polys.emplace_back(poly);
}
}
return standard_nms(polys, iou_threshold);
}
}