mirror of
https://github.com/opencv/opencv.git
synced 2025-08-01 10:26:53 +08:00

Find contours speedup #26834 It is an attempt, as suggested by #26775, to restore lost speed when migrating `findContours()` implementation from C to C++ The patch adds an "Arena" (a pool) of pre-allocated memory so that contours points (and TreeNodes) can be picked from the Arena. The code of `findContours()` is mostly unchanged, the arena usage being implicit through a utility class Arena::Item that provides C++ overloaded operators and construct/destruct logic. As mentioned in #26775, the contour points are allocated and released in order, and can be represented by ranges of indices in their arena. No range subset will be released and drill a hole, that's why the internal representation as a range of indices makes sense. The TreeNodes use another Arena class that does not comply to that range logic. Currently, there is a significant improvement of the run-time on the test mentioned in #26775, but it is still far from the `findContours_legacy()` performance. - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [X] The PR is proposed to the proper branch - [X] There is a reference to the original bug report and related work - [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [ ] The feature is well documented and sample code can be built with the project CMake
354 lines
9.6 KiB
C++
354 lines
9.6 KiB
C++
// This file is part of OpenCV project.
|
|
// It is subject to the license terms in the LICENSE file found in the top-level directory
|
|
// of this distribution and at http://opencv.org/license.html
|
|
|
|
#include "opencv2/core/base.hpp"
|
|
#include "opencv2/core/types.hpp"
|
|
#include "opencv2/imgproc.hpp"
|
|
#include "contours_common.hpp"
|
|
#include <vector>
|
|
|
|
using namespace std;
|
|
using namespace cv;
|
|
|
|
namespace {
|
|
|
|
struct ApproxItem
|
|
{
|
|
Point pt;
|
|
size_t k; // support region
|
|
int s; // 1-curvature
|
|
bool removed;
|
|
ApproxItem() : k(0), s(0), removed(false) {}
|
|
ApproxItem(const Point& pt_, int s_) : pt(pt_), k(0), s(s_), removed(false) {}
|
|
};
|
|
|
|
static const schar abs_diff[16] = {1, 2, 3, 4, 3, 2, 1, 0, 1, 2, 3, 4, 3, 2, 1};
|
|
static const Point chainCodeDeltas[8] =
|
|
{{1, 0}, {1, -1}, {0, -1}, {-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}};
|
|
|
|
// Pass 0.
|
|
// Restores all the digital curve points from the chain code.
|
|
// Removes the points (from the resultant polygon)
|
|
// that have zero 1-curvature
|
|
static vector<ApproxItem> pass_0(const ContourCodesStorage& chain, Point pt, bool isApprox, bool isFull)
|
|
{
|
|
vector<ApproxItem> res;
|
|
const size_t len = chain.size();
|
|
res.reserve(len / 2);
|
|
for (size_t i = 0; i < len; ++i)
|
|
{
|
|
const schar prev = (i == 0) ? chain[len - 1] : chain[i - 1];
|
|
const schar cur = chain[i];
|
|
const schar s = abs_diff[cur - prev + 7];
|
|
if ((!isApprox && (isFull || s != 0)) || isApprox)
|
|
{
|
|
res.push_back(ApproxItem(pt, s));
|
|
if (s == 0)
|
|
(res.end() - 1)->removed = true;
|
|
}
|
|
pt += chainCodeDeltas[cur];
|
|
}
|
|
return res;
|
|
}
|
|
|
|
static void gatherPoints(const vector<ApproxItem>& ares, ContourPointsStorage& output)
|
|
{
|
|
output.clear();
|
|
for (const ApproxItem& item : ares)
|
|
{
|
|
if (!item.removed)
|
|
output.push_back(item.pt);
|
|
}
|
|
}
|
|
|
|
static size_t calc_support(const vector<ApproxItem>& ares, size_t i)
|
|
{
|
|
const size_t len = ares.size();
|
|
/* determine support region */
|
|
int d_num = 0;
|
|
int l = 0;
|
|
size_t k = 1;
|
|
for (;; k++)
|
|
{
|
|
CV_Assert(k <= len);
|
|
/* calc indices */
|
|
const size_t i1 = (i >= k) ? (i - k) : (len - k + i);
|
|
const size_t i2 = (i + k < len) ? (i + k) : (i + k - len);
|
|
|
|
const int dx = ares[i2].pt.x - ares[i1].pt.x;
|
|
const int dy = ares[i2].pt.y - ares[i1].pt.y;
|
|
|
|
/* distance between p_(i - k) and p_(i + k) */
|
|
const int lk = dx * dx + dy * dy;
|
|
|
|
/* distance between p_i and the line (p_(i-k), p_(i+k)) */
|
|
const int dk_num =
|
|
(ares[i].pt.x - ares[i1].pt.x) * dy - (ares[i].pt.y - ares[i1].pt.y) * dx;
|
|
|
|
union
|
|
{
|
|
int i;
|
|
float f;
|
|
} d;
|
|
d.f = (float)(((double)d_num) * lk - ((double)dk_num) * l);
|
|
|
|
if (k > 1 && (l >= lk || ((d_num > 0 && d.i <= 0) || (d_num < 0 && d.i >= 0))))
|
|
break;
|
|
|
|
d_num = dk_num;
|
|
l = lk;
|
|
}
|
|
return k - 1;
|
|
}
|
|
|
|
static int calc_cosine(const vector<ApproxItem>& ares, size_t i)
|
|
{
|
|
const size_t k = ares[i].k;
|
|
size_t j;
|
|
int s;
|
|
const size_t len = ares.size();
|
|
/* calc k-cosine curvature */
|
|
for (j = k, s = 0; j > 0; j--)
|
|
{
|
|
const size_t i1 = (i >= j) ? (i - j) : (len - j + i);
|
|
const size_t i2 = (i + j < len) ? (i + j) : (i + j - len);
|
|
|
|
const int dx1 = ares[i1].pt.x - ares[i].pt.x;
|
|
const int dy1 = ares[i1].pt.y - ares[i].pt.y;
|
|
const int dx2 = ares[i2].pt.x - ares[i].pt.x;
|
|
const int dy2 = ares[i2].pt.y - ares[i].pt.y;
|
|
|
|
if ((dx1 | dy1) == 0 || (dx2 | dy2) == 0)
|
|
break;
|
|
|
|
double temp_num = dx1 * dx2 + dy1 * dy2;
|
|
temp_num = (float)(temp_num / sqrt(((double)dx1 * dx1 + (double)dy1 * dy1) *
|
|
((double)dx2 * dx2 + (double)dy2 * dy2)));
|
|
Cv32suf sk;
|
|
sk.f = (float)(temp_num + 1.1);
|
|
|
|
CV_Assert(0 <= sk.f && sk.f <= 2.2);
|
|
if (j < k && sk.i <= s)
|
|
break;
|
|
|
|
s = sk.i;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
static bool calc_nms_cleanup(const vector<ApproxItem>& ares, size_t i)
|
|
{
|
|
const size_t k2 = ares[i].k >> 1;
|
|
const int s = ares[i].s;
|
|
const size_t len = ares.size();
|
|
size_t j;
|
|
for (j = 1; j <= k2; j++)
|
|
{
|
|
const size_t i1 = (i >= j) ? (i - j) : (len - j + i);
|
|
const size_t i2 = (i + j < len) ? (i + j) : (i + j - len);
|
|
if (ares[i1].s > s || ares[i2].s > s)
|
|
break;
|
|
}
|
|
return j <= k2;
|
|
}
|
|
|
|
static bool calc_dominance(const vector<ApproxItem>& ares, size_t i)
|
|
{
|
|
const size_t len = ares.size();
|
|
CV_Assert(len > 0);
|
|
const size_t i1 = (i >= 1) ? (i - 1) : (len - 1 + i);
|
|
const size_t i2 = (i + 1 < len) ? (i + 1) : (i + 1 - len);
|
|
return ares[i].s <= ares[i1].s || ares[i].s <= ares[i2].s;
|
|
}
|
|
|
|
inline size_t get_next_idx(const vector<ApproxItem>& ares, const size_t start)
|
|
{
|
|
const size_t len = ares.size();
|
|
size_t res = start + 1;
|
|
for (; res < len; ++res)
|
|
{
|
|
if (!ares[res].removed)
|
|
break;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
inline void clear_until(vector<ApproxItem>& ares, const size_t start, const size_t finish)
|
|
{
|
|
const size_t len = ares.size();
|
|
for (size_t i = start + 1; i < finish && i < len; ++i)
|
|
{
|
|
ares[i].removed = true;
|
|
}
|
|
}
|
|
|
|
static bool calc_new_start(vector<ApproxItem>& ares, size_t& res)
|
|
{
|
|
const size_t len = ares.size();
|
|
CV_Assert(len > 0);
|
|
size_t i1;
|
|
// remove all previous items from the beginning
|
|
for (i1 = 1; i1 < len && ares[i1].s != 0; i1++)
|
|
{
|
|
ares[i1 - 1].s = 0;
|
|
}
|
|
if (i1 == len)
|
|
{
|
|
// all points survived - skip to the end
|
|
return false;
|
|
}
|
|
i1--;
|
|
|
|
size_t i2;
|
|
// remove all following items from the end
|
|
for (i2 = len - 2; i2 > 0 && ares[i2].s != 0; i2--)
|
|
{
|
|
clear_until(ares, i2, len);
|
|
ares[i2 + 1].s = 0;
|
|
}
|
|
i2++;
|
|
|
|
// only two points left
|
|
if (i1 == 0 && i2 == len - 1)
|
|
{
|
|
// find first non-removed element from the start
|
|
i1 = get_next_idx(ares, 0);
|
|
// append first item to the end
|
|
ares.push_back(ares[0]);
|
|
(ares.end() - 1)->removed = false;
|
|
}
|
|
res = i1;
|
|
return true;
|
|
}
|
|
|
|
static void pass_cleanup(vector<ApproxItem>& ares, size_t start_idx)
|
|
{
|
|
int count = 1;
|
|
|
|
const size_t len = ares.size();
|
|
size_t first = start_idx;
|
|
for (size_t i = start_idx, prev = start_idx; i < len; ++i)
|
|
{
|
|
ApproxItem& item = ares[i];
|
|
if (item.removed)
|
|
continue;
|
|
size_t next_idx = get_next_idx(ares, i);
|
|
if (next_idx == len || next_idx - i != 1)
|
|
{
|
|
if (count >= 2)
|
|
{
|
|
if (count == 2)
|
|
{
|
|
const int s1 = ares[prev].s;
|
|
const int s2 = ares[i].s;
|
|
|
|
if (s1 > s2 || (s1 == s2 && ares[prev].k <= ares[i].k))
|
|
/* remove second */
|
|
ares[i].removed = true;
|
|
else
|
|
/* remove first */
|
|
ares[prev].removed = true;
|
|
}
|
|
else
|
|
{
|
|
first = get_next_idx(ares, first);
|
|
clear_until(ares, first, i);
|
|
}
|
|
}
|
|
first = i;
|
|
count = 1;
|
|
}
|
|
else
|
|
{
|
|
++count;
|
|
}
|
|
prev = i;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
void cv::approximateChainTC89(const ContourCodesStorage& chain, const Point& origin, const int method,
|
|
ContourPointsStorage& output)
|
|
{
|
|
if (chain.size() == 0)
|
|
{
|
|
output.clear();
|
|
output.push_back(origin);
|
|
return;
|
|
}
|
|
|
|
const bool isApprox = method == CHAIN_APPROX_TC89_L1 || method == CHAIN_APPROX_TC89_KCOS;
|
|
|
|
ApproxItem root;
|
|
vector<ApproxItem> ares = pass_0(chain, origin, isApprox, method == CHAIN_APPROX_NONE);
|
|
|
|
if (isApprox)
|
|
{
|
|
CV_DbgAssert(ares.size() < (size_t)numeric_limits<int>::max());
|
|
|
|
// Pass 1.
|
|
// Determines support region for all the remained points */
|
|
for (size_t i = 0; i < ares.size(); ++i)
|
|
{
|
|
ApproxItem& item = ares[i];
|
|
if (item.removed)
|
|
continue;
|
|
item.k = calc_support(ares, i);
|
|
|
|
if (method == CHAIN_APPROX_TC89_KCOS)
|
|
item.s = calc_cosine(ares, i);
|
|
}
|
|
|
|
// Pass 2.
|
|
// Performs non-maxima suppression
|
|
for (size_t i = 0; i < ares.size(); ++i)
|
|
{
|
|
ApproxItem& item = ares[i];
|
|
if (calc_nms_cleanup(ares, i))
|
|
{
|
|
item.s = 0; // "clear"
|
|
item.removed = true;
|
|
}
|
|
}
|
|
|
|
// Pass 3.
|
|
// Removes non-dominant points with 1-length support region */
|
|
for (size_t i = 0; i < ares.size(); ++i)
|
|
{
|
|
ApproxItem& item = ares[i];
|
|
if (item.removed)
|
|
continue;
|
|
if (item.k == 1 && calc_dominance(ares, i))
|
|
{
|
|
item.s = 0;
|
|
item.removed = true;
|
|
}
|
|
}
|
|
|
|
if (method == cv::CHAIN_APPROX_TC89_L1)
|
|
{
|
|
// Pass 4.
|
|
// Cleans remained couples of points
|
|
bool skip = false;
|
|
size_t new_start_idx = 0;
|
|
const size_t len = ares.size();
|
|
if (ares[0].s != 0 && ares[len - 1].s != 0)
|
|
{
|
|
if (!calc_new_start(ares, new_start_idx))
|
|
{
|
|
skip = true;
|
|
}
|
|
}
|
|
if (!skip)
|
|
{
|
|
pass_cleanup(ares, new_start_idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
gatherPoints(ares, output);
|
|
}
|