diff --git a/modules/imgproc/src/contours_approx.cpp b/modules/imgproc/src/contours_approx.cpp index 176c490468..bba45f48e7 100644 --- a/modules/imgproc/src/contours_approx.cpp +++ b/modules/imgproc/src/contours_approx.cpp @@ -31,7 +31,7 @@ static const Point chainCodeDeltas[8] = // Restores all the digital curve points from the chain code. // Removes the points (from the resultant polygon) // that have zero 1-curvature -static vector pass_0(const vector& chain, Point pt, bool isApprox, bool isFull) +static vector pass_0(const ContourCodesStorage& chain, Point pt, bool isApprox, bool isFull) { vector res; const size_t len = chain.size(); @@ -52,17 +52,14 @@ static vector pass_0(const vector& chain, Point pt, bool isAp return res; } -static vector gatherPoints(const vector& ares) +static void gatherPoints(const vector& ares, ContourPointsStorage& output) { - vector res; - res.reserve(ares.size() / 2); + output.clear(); for (const ApproxItem& item : ares) { - if (item.removed) - continue; - res.push_back(item.pt); + if (!item.removed) + output.push_back(item.pt); } - return res; } static size_t calc_support(const vector& ares, size_t i) @@ -273,11 +270,14 @@ static void pass_cleanup(vector& ares, size_t start_idx) } // namespace -vector cv::approximateChainTC89(vector chain, const Point& origin, const int method) +void cv::approximateChainTC89(const ContourCodesStorage& chain, const Point& origin, const int method, + ContourPointsStorage& output) { if (chain.size() == 0) { - return vector({origin}); + output.clear(); + output.push_back(origin); + return; } const bool isApprox = method == CHAIN_APPROX_TC89_L1 || method == CHAIN_APPROX_TC89_KCOS; @@ -349,5 +349,5 @@ vector cv::approximateChainTC89(vector chain, const Point& origin, } } - return gatherPoints(ares); + gatherPoints(ares, output); } diff --git a/modules/imgproc/src/contours_blockstorage.hpp b/modules/imgproc/src/contours_blockstorage.hpp new file mode 100644 index 0000000000..7b7c55a72d --- /dev/null +++ b/modules/imgproc/src/contours_blockstorage.hpp @@ -0,0 +1,122 @@ +// 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 + +#ifndef OPENCV_CONTOURS_BLOCKSTORAGE_HPP +#define OPENCV_CONTOURS_BLOCKSTORAGE_HPP + +#include "precomp.hpp" + +#include + +namespace cv { + +// BLOCK_SIZE_ELEM - number of elements in a block +// STATIC_CAPACITY_BYTES - static memory in bytes for preallocated blocks +template +class BlockStorage { + public: + using value_type = T; + typedef struct {value_type data[BLOCK_SIZE_ELEM];} block_type; + + BlockStorage() + { + const size_t minDynamicBlocks = !staticBlocksCount ? 1 : 0; + for(size_t i = 0 ; i operator*(void) const {return get();} + std::pair get(void) const { + const block_type& cur_block = + (blockIndex < owner->staticBlocksCount) ? owner->staticBlocks[blockIndex] : + *owner->dynamicBlocks[blockIndex-owner->staticBlocksCount]; + const value_type* rangeStart = cur_block.data+offset; + const size_t rangeLength = std::min(remaining, BLOCK_SIZE_ELEM-offset); + return std::make_pair(rangeStart, rangeLength); + } + RangeIterator& operator++() { + std::pair range = get(); + remaining -= range.second; + offset = 0; + ++blockIndex; + return *this; + } + }; + RangeIterator getRangeIterator(size_t first, size_t last) const { + return RangeIterator(this, first, last); + } + private: + std::array staticBlocks; + const size_t staticBlocksCount = STATIC_CAPACITY_BYTES/(BLOCK_SIZE_ELEM*sizeof(value_type)); + std::vector dynamicBlocks; + size_t sz = 0; +}; + +} // namespace cv + +#endif // OPENCV_CONTOURS_BLOCKSTORAGE_HPP diff --git a/modules/imgproc/src/contours_common.cpp b/modules/imgproc/src/contours_common.cpp index a8cb12c1a2..8fb1459fc9 100644 --- a/modules/imgproc/src/contours_common.cpp +++ b/modules/imgproc/src/contours_common.cpp @@ -22,12 +22,11 @@ void cv::contourTreeToResults(CTree& tree, return; } - // mapping for indexes (original -> resulting) - map index_mapping; - index_mapping[-1] = -1; - index_mapping[0] = -1; - CV_Assert(tree.size() < (size_t)numeric_limits::max()); + // mapping for indexes (original -> resulting) + // -1 - based indexing + vector index_mapping(tree.size() + 1, -1); + const int total = (int)tree.size() - 1; _contours.create(total, 1, 0, -1, true); { @@ -39,7 +38,7 @@ void cv::contourTreeToResults(CTree& tree, CV_Assert(elem.self() != -1); if (elem.self() == 0) continue; - index_mapping[elem.self()] = i; + index_mapping.at(elem.self() + 1) = i; CV_Assert(elem.body.size() < (size_t)numeric_limits::max()); const int sz = (int)elem.body.size(); _contours.create(sz, 1, res_type, i, true); @@ -65,10 +64,10 @@ void cv::contourTreeToResults(CTree& tree, if (elem.self() == 0) continue; Vec4i& h_vec = h_mat.at(i); - h_vec = Vec4i(index_mapping.at(elem.next), - index_mapping.at(elem.prev), - index_mapping.at(elem.first_child), - index_mapping.at(elem.parent)); + h_vec = Vec4i(index_mapping.at(elem.next + 1), + index_mapping.at(elem.prev + 1), + index_mapping.at(elem.first_child + 1), + index_mapping.at(elem.parent + 1)); ++i; } } diff --git a/modules/imgproc/src/contours_common.hpp b/modules/imgproc/src/contours_common.hpp index b22c5cfd0b..e02945fd01 100644 --- a/modules/imgproc/src/contours_common.hpp +++ b/modules/imgproc/src/contours_common.hpp @@ -6,7 +6,8 @@ #define OPENCV_CONTOURS_COMMON_HPP #include "precomp.hpp" -#include + +#include "contours_blockstorage.hpp" namespace cv { @@ -45,11 +46,15 @@ public: T body; public: - TreeNode(int self) : - self_(self), parent(-1), first_child(-1), prev(-1), next(-1), ctable_next(-1) + TreeNode(int self, T&& body_) : + self_(self), parent(-1), first_child(-1), prev(-1), next(-1), ctable_next(-1), body(std::move(body_)) { CV_Assert(self >= 0); } + TreeNode(const TreeNode&) = delete; + TreeNode(TreeNode&&) noexcept = default; + TreeNode& operator=(const TreeNode&) = delete; + TreeNode& operator=(TreeNode&&) noexcept = default; int self() const { return self_; @@ -59,15 +64,22 @@ public: template class Tree { +public: + Tree() {} + Tree(const Tree&) = delete; + Tree(Tree&&) = delete; + Tree& operator=(const Tree&) = delete; + Tree& operator=(Tree&&) = delete; + ~Tree() = default; private: std::vector> nodes; public: - TreeNode& newElem() + TreeNode& newElem(T && body_) { const size_t idx = nodes.size(); CV_DbgAssert(idx < (size_t)std::numeric_limits::max()); - nodes.push_back(TreeNode((int)idx)); + nodes.emplace_back(std::move(TreeNode((int)idx, std::move(body_)))); return nodes[idx]; } TreeNode& elem(int idx) @@ -101,7 +113,7 @@ public: child.parent = prev_item.parent; if (prev_item.next != -1) { - nodes[prev_item.next].prev = idx; + ((TreeNode&)nodes[prev_item.next]).prev = idx; child.next = prev_item.next; } child.prev = prev; @@ -159,23 +171,80 @@ public: } private: - std::stack levels; Tree& tree; + std::stack levels; }; //============================================================================== +template +class ContourDataStorage +{ +public: + typedef T data_storage_t; + typedef BlockStorage storage_t; +public: + ContourDataStorage(void) = delete; + ContourDataStorage(storage_t* _storage):storage(_storage) {} + ContourDataStorage(const ContourDataStorage&) = delete; + ContourDataStorage(ContourDataStorage&&) noexcept = default; + ~ContourDataStorage() = default; + ContourDataStorage& operator=(const ContourDataStorage&) = delete; + ContourDataStorage& operator=(ContourDataStorage&&) noexcept = default; +public: + typename storage_t::RangeIterator getRangeIterator(void) const {return storage->getRangeIterator(first, last);} +public: + bool empty(void) const {return first == last;} + size_t size(void) const {return last - first;} +public: + void clear(void) {first = last;} + bool resize(size_t newSize) + { + bool ok = (newSize <= size()); + if (ok) + last = first+newSize; + return ok; + } + void push_back(const data_storage_t& value) + { + if (empty()) + { + first = storage->size(); + } + storage->push_back(value); + last = storage->size(); + } + const data_storage_t& at(size_t index) const {return storage->at(first+index);} + data_storage_t& at(size_t index) {return storage->at(first+index);} + const data_storage_t& operator[](size_t index) const {return at(index);} + data_storage_t& operator[](size_t index) {return at(index);} +private: + storage_t* storage = nullptr; + size_t first = 0; + size_t last = 0; +}; + +typedef ContourDataStorage ContourPointsStorage; +typedef ContourDataStorage ContourCodesStorage; + class Contour { public: + ContourPointsStorage pts; cv::Rect brect; cv::Point origin; - std::vector pts; - std::vector codes; - bool isHole; - bool isChain; + ContourCodesStorage codes; + bool isHole = false; + bool isChain = false; - Contour() : isHole(false), isChain(false) {} + explicit Contour(ContourPointsStorage::storage_t* pointStorage_, + ContourCodesStorage::storage_t* codesStorage_) + :pts(pointStorage_),codes(codesStorage_) {} + Contour(const Contour&) = delete; + Contour(Contour&& other) noexcept = default; + Contour& operator=(const Contour&) = delete; + Contour& operator=(Contour&& other) noexcept = default; + ~Contour() = default; void updateBoundingRect() {} bool isEmpty() const { @@ -185,17 +254,37 @@ public: { return isChain ? codes.size() : pts.size(); } + void addPoint(const Point& pt) + { + pts.push_back(pt); + } void copyTo(void* data) const { // NOTE: Mat::copyTo doesn't work because it creates new Mat object // instead of reusing existing vector data if (isChain) { - memcpy(data, &codes[0], codes.size() * sizeof(codes[0])); + /*memcpy(data, codes.data(), codes.size() * sizeof(typename decltype(codes)::value_type));*/ + schar* dst = reinterpret_cast(data); + for(auto rangeIterator = codes.getRangeIterator() ; !rangeIterator.done() ; ++rangeIterator) + { + const auto range = *rangeIterator; + memcpy(dst, range.first, range.second*sizeof(schar)); + dst += range.second; + } } else { - memcpy(data, &pts[0], pts.size() * sizeof(pts[0])); + /*for (size_t i = 0, count = pts.size() ; i < count ; ++i) + ((Point*)data)[i] = pts.at(i); + */ + cv::Point* dst = reinterpret_cast(data); + for(auto rangeIterator = pts.getRangeIterator() ; !rangeIterator.done() ; ++rangeIterator) + { + const auto range = *rangeIterator; + memcpy(dst, range.first, range.second*sizeof(cv::Point)); + dst += range.second; + } } } }; @@ -211,8 +300,8 @@ void contourTreeToResults(CTree& tree, cv::OutputArray& _hierarchy); -std::vector - approximateChainTC89(std::vector chain, const Point& origin, const int method); +void approximateChainTC89(const ContourCodesStorage& chain, const Point& origin, const int method, + ContourPointsStorage& output); } // namespace cv diff --git a/modules/imgproc/src/contours_link.cpp b/modules/imgproc/src/contours_link.cpp index 8df88fc123..667db1be30 100644 --- a/modules/imgproc/src/contours_link.cpp +++ b/modules/imgproc/src/contours_link.cpp @@ -90,10 +90,13 @@ public: vector ext_rns; vector int_rns; + ContourPointsStorage::storage_t pointsStorage; + ContourCodesStorage::storage_t codesStorage; + public: - LinkRunner() + LinkRunner(void) { - tree.newElem(); + tree.newElem(Contour(&pointsStorage, &codesStorage)); rns.reserve(100); } void process(Mat& image); @@ -117,12 +120,12 @@ void LinkRunner::convertLinks(int& first, int& prev, bool isHole) if (rns[cur].link == -1) continue; - CNode& node = tree.newElem(); + CNode& node = tree.newElem(Contour(&pointsStorage, &codesStorage)); node.body.isHole = isHole; do { - node.body.pts.push_back(rns[cur].pt); + node.body.addPoint(rns[cur].pt); int p_temp = cur; cur = rns[cur].link; rns[p_temp].link = -1; diff --git a/modules/imgproc/src/contours_new.cpp b/modules/imgproc/src/contours_new.cpp index 9f8c716fbb..b2f67e13b1 100644 --- a/modules/imgproc/src/contours_new.cpp +++ b/modules/imgproc/src/contours_new.cpp @@ -197,7 +197,7 @@ static void icvFetchContourEx(Mat& image, Trait::setRightFlag(i0, i0, nbd); if (!res_contour.isChain) { - res_contour.pts.push_back(pt); + res_contour.addPoint(pt); } } else @@ -236,7 +236,7 @@ static void icvFetchContourEx(Mat& image, } else if (s != prev_s || isDirect) { - res_contour.pts.push_back(pt); + res_contour.addPoint(pt); } if (s != prev_s) @@ -281,6 +281,9 @@ static void icvFetchContourEx(Mat& image, // It supports both hierarchical and plane variants of Suzuki algorithm. struct ContourScanner_ { + ContourPointsStorage::storage_t& pointsStorage; + ContourCodesStorage::storage_t& codesStorage; + Mat image; Point offset; // ROI offset: coordinates, added to each contour point Point pt; // current scanner position @@ -293,7 +296,9 @@ struct ContourScanner_ array ctable; public: - ContourScanner_() {} + ContourScanner_(ContourPointsStorage::storage_t& _pointsStorage, + ContourCodesStorage::storage_t& _codesStorage) + :pointsStorage(_pointsStorage),codesStorage(_codesStorage) {} ~ContourScanner_() {} inline bool isInt() const { @@ -310,13 +315,13 @@ public: int findNextX(int x, int y, int& prev, int& p); bool findNext(); - static shared_ptr create(Mat img, int mode, int method, Point offset); + static shared_ptr create(ContourPointsStorage::storage_t& pointsStorage, ContourCodesStorage::storage_t& codesStorage, Mat img, int mode, int method, Point offset); }; // class ContourScanner_ typedef shared_ptr ContourScanner; -shared_ptr ContourScanner_::create(Mat img, int mode, int method, Point offset) +shared_ptr ContourScanner_::create(ContourPointsStorage::storage_t& pointsStorage, ContourCodesStorage::storage_t& codesStorage, Mat img, int mode, int method, Point offset) { if (mode == RETR_CCOMP && img.type() == CV_32SC1) mode = RETR_FLOODFILL; @@ -342,14 +347,14 @@ shared_ptr ContourScanner_::create(Mat img, int mode, int metho Size size = img.size(); CV_Assert(size.height >= 1); - shared_ptr scanner = make_shared(); + shared_ptr scanner = make_shared(pointsStorage, codesStorage); scanner->image = img; scanner->mode = mode; scanner->offset = offset; scanner->pt = Point(1, 1); scanner->lnbd = Point(0, 1); scanner->nbd = 2; - CNode& root = scanner->tree.newElem(); + CNode& root = scanner->tree.newElem(Contour(&scanner->pointsStorage, &scanner->codesStorage)); CV_Assert(root.self() == 0); root.body.isHole = true; root.body.brect = Rect(Point(0, 0), size); @@ -367,7 +372,7 @@ CNode& ContourScanner_::makeContour(schar& nbd_, const bool is_hole, const int x const Point start_pt(x - (is_hole ? 1 : 0), y); - CNode& res = tree.newElem(); + CNode& res = tree.newElem(Contour(&pointsStorage, &codesStorage)); res.body.isHole = is_hole; res.body.isChain = isChain; res.body.origin = start_pt + offset; @@ -403,7 +408,7 @@ CNode& ContourScanner_::makeContour(schar& nbd_, const bool is_hole, const int x if (this->approx_method1 != this->approx_method2) { CV_Assert(res.body.isChain); - res.body.pts = approximateChainTC89(res.body.codes, prev_origin, this->approx_method2); + approximateChainTC89(res.body.codes, prev_origin, this->approx_method2, res.body.pts); res.body.isChain = false; } return res; @@ -674,7 +679,9 @@ void cv::findContours(InputArray _image, threshold(image, image, 0, 1, THRESH_BINARY); // find contours - ContourScanner scanner = ContourScanner_::create(image, mode, method, offset + Point(-1, -1)); + ContourPointsStorage::storage_t pointsStorage; + ContourCodesStorage::storage_t codesStorage; + ContourScanner scanner = ContourScanner_::create(pointsStorage, codesStorage, image, mode, method, offset + Point(-1, -1)); while (scanner->findNext()) { }