From 68ece0e91efbf0061a2da757ffd7eba8fe6da652 Mon Sep 17 00:00:00 2001 From: scloke Date: Sun, 31 Oct 2021 16:29:05 +1300 Subject: [PATCH 01/44] New superpixel algorithm (F-DBSCAN) Implementation of a new superpixel algorithm, "Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm". --- modules/ximgproc/include/opencv2/ximgproc.hpp | 1 + .../include/opencv2/ximgproc/scansegment.hpp | 128 +++ modules/ximgproc/src/scansegment.cpp | 929 ++++++++++++++++++ 3 files changed, 1058 insertions(+) create mode 100644 modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp create mode 100644 modules/ximgproc/src/scansegment.cpp diff --git a/modules/ximgproc/include/opencv2/ximgproc.hpp b/modules/ximgproc/include/opencv2/ximgproc.hpp index 65e3dda8c5..671af16706 100644 --- a/modules/ximgproc/include/opencv2/ximgproc.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc.hpp @@ -43,6 +43,7 @@ #include "ximgproc/structured_edge_detection.hpp" #include "ximgproc/edgeboxes.hpp" #include "ximgproc/edge_drawing.hpp" +#include "ximgproc/scansegment.hpp" #include "ximgproc/seeds.hpp" #include "ximgproc/segmentation.hpp" #include "ximgproc/fast_hough_transform.hpp" diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp new file mode 100644 index 0000000000..20906f05ce --- /dev/null +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -0,0 +1,128 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2021, Dr Seng Cheong Loke (lokesengcheong@gmail.com) +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// + + /* + * BibTeX reference +@article{loke2021accelerated, + title={Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm}, + author={Loke, Seng Cheong and MacDonald, Bruce A and Parsons, Matthew and W{\"u}nsche, Burkhard Claus}, + journal={Journal of Real-Time Image Processing}, + pages={1--16}, + year={2021}, + publisher={Springer} +} + */ + +#ifndef __OPENCV_SCANSEGMENT_HPP__ +#define __OPENCV_SCANSEGMENT_HPP__ +#ifdef __cplusplus + +#include +#include +#include + +namespace cv +{ + namespace ximgproc + { +/** @brief Class implementing the F-DBSCAN (Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm) superpixels +algorithm by Loke SC, et al. +The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than +existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s +with a Boundary Recall of 0.797 and an Achievable Segmentation Accuracy of 0.944. The computational complexity is quadratic O(n2) and +more suited to smaller images, but can still process a 2MP colour image faster than the SEEDS algorithm in OpenCV. The output is deterministic +when the number of processing threads is fixed, and requires the source image to be in Lab colour format. + */ + class CV_EXPORTS_W ScanSegment : public Algorithm + { + public: + /** @brief Returns the actual superpixel segmentation from the last image processed using iterate. + Returns zero if no image has been processed. + */ + CV_WRAP virtual int getNumberOfSuperpixels() = 0; + + /** @brief Calculates the superpixel segmentation on a given image with the initialized + parameters in the ScanSegment object. This function can be called again for other images + without the need of initializing the algorithm with createScanSegment(). This save the + computational cost of allocating memory for all the structures of the algorithm. + @param img Input image. Supported format: CV_8UC3. Image size must match with the initialized + image size with the function createScanSegment(). It MUST be in Lab color space. + */ + CV_WRAP virtual void iterate(InputArray img) = 0; + + /** @brief Returns the segmentation labeling of the image. Each label represents a superpixel, + and each pixel is assigned to one superpixel label. + @param labels_out Return: A CV_32UC1 integer array containing the labels of the superpixel + segmentation. The labels are in the range [0, getNumberOfSuperpixels()]. + */ + CV_WRAP virtual void getLabels(OutputArray labels_out) = 0; + + /** @brief Returns the mask of the superpixel segmentation stored in the ScanSegment object. + @param image Return: CV_8UC1 image mask where -1 indicates that the pixel is a superpixel border, + and 0 otherwise. + @param thick_line If false, the border is only one pixel wide, otherwise all pixels at the border + are masked. + The function return the boundaries of the superpixel segmentation. + */ + CV_WRAP virtual void getLabelContourMask(OutputArray image, bool thick_line = false) = 0; + + virtual ~ScanSegment() {} + }; + + /** @brief Initializes a ScanSegment object. + @param image_width Image width. + @param image_height Image height. + @param num_superpixels Desired number of superpixels. Note that the actual number may be smaller + due to restrictions (depending on the image size). Use getNumberOfSuperpixels() to + get the actual number. + @param threads Number of processing threads for parallelisation. Default -1 uses the maximum number + of threads. In practice, four threads is enough for smaller images and eight threads for larger ones. + @param merge_small merge small segments to give the desired number of superpixels. Processing is + much faster without merging, but many small segments will be left in the image. + The function initializes a ScanSegment object for the input image. It stores the parameters of + the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel + algorithm, which are: num_superpixels, threads, and merge_small. + */ + CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads = -1, bool merge_small = true); + } +} +#endif +#endif \ No newline at end of file diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp new file mode 100644 index 0000000000..3c84088657 --- /dev/null +++ b/modules/ximgproc/src/scansegment.cpp @@ -0,0 +1,929 @@ +//////////////////////////////////////////////////////////////////////////////////////// +// +// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. +// +// By downloading, copying, installing or using the software you agree to this license. +// If you do not agree to this license, do not download, install, +// copy or use the software. +// +// +// License Agreement +// For Open Source Computer Vision Library +// +// Copyright (C) 2021, Dr Seng Cheong Loke (lokesengcheong@gmail.com) +// Third party copyrights are property of their respective owners. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistribution's of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * Redistribution's in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation +// and/or other materials provided with the distribution. +// +// * The name of the copyright holders may not be used to endorse or promote products +// derived from this software without specific prior written permission. +// +// This software is provided by the copyright holders and contributors "as is" and +// any express or implied warranties, including, but not limited to, the implied +// warranties of merchantability and fitness for a particular purpose are disclaimed. +// In no event shall the Intel Corporation or contributors be liable for any direct, +// indirect, incidental, special, exemplary, or consequential damages +// (including, but not limited to, procurement of substitute goods or services; +// loss of use, data, or profits; or business interruption) however caused +// and on any theory of liability, whether in contract, strict liability, +// or tort (including negligence or otherwise) arising in any way out of +// the use of this software, even if advised of the possibility of such damage. +// +// + + /* + * BibTeX reference +@article{loke2021accelerated, + title={Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm}, + author={Loke, Seng Cheong and MacDonald, Bruce A and Parsons, Matthew and W{\"u}nsche, Burkhard Claus}, + journal={Journal of Real-Time Image Processing}, + pages={1--16}, + year={2021}, + publisher={Springer} +} + */ + +#include "precomp.hpp" + +#include +#include + +namespace cv { + namespace ximgproc { + + class ScanSegmentImpl : public ScanSegment + { +#define UNKNOWN 0 +#define BORDER -1 +#define UNCLASSIFIED -2 +#define NONE -3 + + public: + + ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int threads, bool merge_small); + + virtual ~ScanSegmentImpl(); + + virtual int getNumberOfSuperpixels() CV_OVERRIDE { return clusterCount; } + + virtual void iterate(InputArray img) CV_OVERRIDE; + + virtual void getLabels(OutputArray labels_out) CV_OVERRIDE; + + virtual void getLabelContourMask(OutputArray image, bool thick_line = false) CV_OVERRIDE; + + private: + static const int neighbourCount = 8; // number of pixel neighbours + static const int smallClustersDiv = 10000; // divide total pixels by this to give smallClusters + const float tolerance100 = 10.0f; // colour tolerance for image size of 100x100px + + int processthreads; // concurrent threads for parallel processing + int width, height; // image size + int superpixels; // number of superpixels + bool merge; // merge small superpixels + int indexSize; // size of label mat vector + int clusterSize; // max size of clusters + bool setupComplete; // is setup complete + int clusterCount; // number of superpixels from the most recent iterate + float adjTolerance; // adjusted colour tolerance + + int horzDiv, vertDiv; // number of horizontal and vertical segments + float horzLength, vertLength; // length of each segment + int effectivethreads; // effective number of concurrent threads + int smallClusters; // clusters below this pixel count are considered small for merging + cv::Rect* seedRects; // array of seed rectangles + cv::Rect* seedRectsExt; // array of extended seed rectangles + cv::Rect* offsetRects; // array of offset rectangles + cv::Point* neighbourLoc; // neighbour locations + + std::vector indexNeighbourVec; // indices for parallel processing + std::vector> indexProcessVec; + + int* labelsBuffer; // label buffer + int* clusterBuffer; // cluster buffer + cv::Vec3b* labBuffer; // lab buffer + uchar* pixelBuffer; // pixel buffer + int neighbourLocBuffer[neighbourCount]; // neighbour locations + std::vector offsetVec; // vector of offsets + + std::atomic clusterIndex, locationIndex, clusterID; // atomic indices + + cv::Mat src, labelsMat; // mats + + struct WSNode + { + int next; + int mask_ofs; + int img_ofs; + }; + + // Queue for WSNodes + struct WSQueue + { + WSQueue() { first = last = 0; } + int first, last; + }; + + class PP1 : public cv::ParallelLoopBody + { + public: + PP1(ScanSegmentImpl* const scanSegment) + : ss(scanSegment) {} + virtual ~PP1() {} + + virtual void operator()(const cv::Range& range) const + { + for (int v = range.start; v < range.end; v++) + { + ss->OP1(v); + } + } + private: + ScanSegmentImpl* const ss; + }; + + class PP2 : public cv::ParallelLoopBody + { + public: + PP2(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) + : ss(scanSegment), ctv(countVec) {} + virtual ~PP2() {} + + virtual void operator()(const cv::Range& range) const + { + for (int v = range.start; v < range.end; v++) + { + ss->OP2((*ctv)[v]); + } + } + private: + ScanSegmentImpl* const ss; + std::vector>* ctv; + }; + + class PP3 : public cv::ParallelLoopBody + { + public: + PP3(ScanSegmentImpl* const scanSegment) + : ss(scanSegment) {} + virtual ~PP3() {} + + virtual void operator()(const cv::Range& range) const + { + for (int v = range.start; v < range.end; v++) + { + ss->OP3(v); + } + } + private: + ScanSegmentImpl* const ss; + }; + + class PP4 : public cv::ParallelLoopBody + { + public: + PP4(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) + : ss(scanSegment), ctv(countVec) {} + virtual ~PP4() {} + + virtual void operator()(const cv::Range& range) const + { + for (int v = range.start; v < range.end; v++) + { + ss->OP4((*ctv)[v]); + } + } + private: + ScanSegmentImpl* const ss; + std::vector>* ctv; + }; + + void OP1(int v); + void OP2(std::pair const& p); + void OP3(int v); + void OP4(std::pair const& p); + void expandCluster(int* labelsBuffer, int* neighbourLocBuffer, int* clusterBuffer, int* offsetBuffer, const cv::Point& point, int adjTolerance, std::atomic* clusterIndex, std::atomic* locationIndex, std::atomic* clusterID); + void calculateCluster(int* labelsBuffer, int* neighbourLocBuffer, int* offsetBuffer, int* offsetEnd, int pointIndex, int adjTolerance, int currentClusterID); + static int allocWSNodes(std::vector& storage); + static void watershedEx(const cv::Mat& src, cv::Mat& dst); + }; + + CV_EXPORTS Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads, bool merge_small) + { + return makePtr(image_width, image_height, num_superpixels, threads, merge_small); + } + + ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int threads, bool merge_small) + { + // set the number of process threads + processthreads = std::thread::hardware_concurrency(); + if (threads > 0) { + processthreads = MIN(processthreads, threads); + } + + width = image_width; + height = image_height; + superpixels = num_superpixels; + merge = merge_small; + indexSize = height * width; + clusterSize = (int)(1.1f * (float)(width * height) / (float)superpixels); + clusterCount = 0; + labelsMat = cv::Mat(height, width, CV_32SC1); + + // divide bounds area into uniformly distributed rectangular segments + int shortCount = (int)floorf(sqrtf((float)processthreads)); + int longCount = processthreads / shortCount; + horzDiv = width > height ? longCount : shortCount; + vertDiv = width > height ? shortCount : longCount; + horzLength = (float)width / (float)horzDiv; + vertLength = (float)height / (float)vertDiv; + effectivethreads = horzDiv * vertDiv; + smallClusters = 0; + + // get array of seed rects + seedRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + seedRectsExt = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + offsetRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + for (int y = 0; y < vertDiv; y++) { + for (int x = 0; x < horzDiv; x++) { + int xStart = (int)((float)x * horzLength); + int yStart = (int)((float)y * vertLength); + cv::Rect seedRect = cv::Rect(xStart, yStart, (int)(x == horzDiv - 1 ? width - xStart : horzLength), (int)(y == vertDiv - 1 ? height - yStart : vertLength)); + + int bnd_l = seedRect.x; + int bnd_t = seedRect.y; + int bnd_r = seedRect.x + seedRect.width - 1; + int bnd_b = seedRect.y + seedRect.height - 1; + if (bnd_l > 0) { + bnd_l -= 1; + } + if (bnd_t > 0) { + bnd_t -= 1; + } + if (bnd_r < width - 1) { + bnd_r += 1; + } + if (bnd_b < height - 1) { + bnd_b += 1; + } + + seedRects[(y * horzDiv) + x] = seedRect; + seedRectsExt[(y * horzDiv) + x] = cv::Rect(bnd_l, bnd_t, bnd_r - bnd_l + 1, bnd_b - bnd_t + 1); + offsetRects[(y * horzDiv) + x] = cv::Rect(seedRect.x - bnd_l, seedRect.y - bnd_t, seedRect.width, seedRect.height); + } + } + + // get adjusted tolerance = (100 / average length (horz/vert)) x sqrt(3) [ie. euclidean lab colour distance sqrt(l2 + a2 + b2)] x tolerance100 + adjTolerance = (200.0f / (width + height)) * sqrtf(3) * tolerance100; + adjTolerance = adjTolerance * adjTolerance; + + // create neighbour vector + indexNeighbourVec = std::vector(effectivethreads); + std::iota(indexNeighbourVec.begin(), indexNeighbourVec.end(), 0); + + // create process vector + indexProcessVec = std::vector>(processthreads); + int processDiv = indexSize / processthreads; + int processCurrent = 0; + for (int i = 0; i < processthreads - 1; i++) { + indexProcessVec[i] = std::make_pair(processCurrent, processCurrent + processDiv); + processCurrent += processDiv; + } + indexProcessVec[processthreads - 1] = std::make_pair(processCurrent, indexSize); + + // create buffers and initialise + std::vector tempLoc{ cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; + neighbourLoc = static_cast(malloc(8 * sizeof(cv::Point))); + memcpy(neighbourLoc, tempLoc.data(), 8 * sizeof(cv::Point)); + + labelsBuffer = static_cast(malloc(indexSize * sizeof(int))); + clusterBuffer = static_cast(malloc(indexSize * sizeof(int))); + pixelBuffer = static_cast(malloc(indexSize)); + offsetVec = std::vector(effectivethreads); + int offsetSize = (clusterSize + 1) * sizeof(int); + bool offsetAllocated = true; + for (int i = 0; i < effectivethreads; i++) { + offsetVec[i] = static_cast(malloc(offsetSize)); + if (offsetVec[i] == NULL) { + offsetAllocated = false; + } + } + for (int i = 0; i < neighbourCount; i++) { + neighbourLocBuffer[i] = (neighbourLoc[i].y * width) + neighbourLoc[i].x; + } + + if (labelsBuffer != NULL && clusterBuffer != NULL && pixelBuffer != NULL && offsetAllocated) { + setupComplete = true; + } + else { + setupComplete = false; + + if (labelsBuffer == NULL) { + CV_Error(Error::StsInternal, "Cannot initialise labels buffer"); + } + if (clusterBuffer == NULL) { + CV_Error(Error::StsInternal, "Cannot initialise cluster buffer"); + } + if (pixelBuffer == NULL) { + CV_Error(Error::StsInternal, "Cannot initialise pixel buffer"); + } + if (!offsetAllocated) { + CV_Error(Error::StsInternal, "Cannot initialise offset buffers"); + } + } + } + + ScanSegmentImpl::~ScanSegmentImpl() + { + // clean up + if (neighbourLoc != NULL) { + free(neighbourLoc); + } + if (seedRects != NULL) { + free(seedRects); + } + if (seedRectsExt != NULL) { + free(seedRectsExt); + } + if (offsetRects != NULL) { + free(offsetRects); + } + if (labelsBuffer != NULL) { + free(labelsBuffer); + } + if (clusterBuffer != NULL) { + free(clusterBuffer); + } + if (pixelBuffer != NULL) { + free(pixelBuffer); + } + for (int i = 0; i < effectivethreads; i++) { + if (offsetVec[i] != NULL) { + free(offsetVec[i]); + } + } + if (!src.empty()) { + src.release(); + } + if (!labelsMat.empty()) { + labelsMat.release(); + } + } + + void ScanSegmentImpl::iterate(InputArray img) + { + // ensure setup successfully completed + CV_Assert(setupComplete); + + if (img.isMat()) + { + // get Mat + src = img.getMat(); + + // image should be valid + CV_Assert(!src.empty()); + } + else if (img.isMatVector()) + { + std::vector vec; + + // get vector Mat + img.getMatVector(vec); + + // array should be valid + CV_Assert(!vec.empty()); + + // merge into Mat + cv::merge(vec, src); + } + else + CV_Error(Error::StsInternal, "Invalid InputArray."); + + int depth = src.depth(); + + CV_Assert(src.size().width == width && src.size().height == height); + CV_Assert(depth == CV_8U); + CV_Assert(src.channels() == 3); + + clusterCount = 0; + clusterIndex.store(0); + locationIndex.store(0); + clusterID.store(1); + + smallClusters = indexSize / smallClustersDiv; + + // set labels to NONE + labelsMat.setTo(NONE); + + // set labels buffer to UNCLASSIFIED + std::fill(labelsBuffer, labelsBuffer + indexSize, UNCLASSIFIED); + + // apply light blur + cv::medianBlur(src, src, 3); + + // start at the center of the rect, then run through the remainder + labBuffer = reinterpret_cast(src.data); + cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP1(reinterpret_cast(this))); + + if (merge) { + // get cutoff size for clusters + std::vector> countVec; + int clusterIndexSize = clusterIndex.load(); + countVec.reserve(clusterIndexSize / 2); + for (int i = 1; i < clusterIndexSize; i += 2) { + int count = clusterBuffer[i]; + if (count >= smallClusters) { + int clusterID = clusterBuffer[i - 1]; + countVec.push_back(std::make_pair(clusterID, count)); + } + } + + // sort descending + std::sort(countVec.begin(), countVec.end(), [](auto& left, auto& right) { + return left.second > right.second; + }); + + int countSize = (int)countVec.size(); + int cutoff = MAX(smallClusters, countVec[MIN(countSize - 1, superpixels - 1)].second); + clusterCount = (int)std::count_if(countVec.begin(), countVec.end(), [&cutoff](std::pair p) {return p.second > cutoff; }); + + // change labels to 1 -> clusterCount, 0 = UNKNOWN, reuse clusterbuffer + std::fill_n(clusterBuffer, indexSize, UNKNOWN); + int countLimit = cutoff == -1 ? (int)countVec.size() : clusterCount; + for (int i = 0; i < countLimit; i++) { + clusterBuffer[countVec[i].first] = i + 1; + } + + cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP2(reinterpret_cast(this), &indexProcessVec)); + + // make copy of labels buffer + memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); + + // run watershed + cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP3(reinterpret_cast(this))); + + // copy back to labels mat + cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP4(reinterpret_cast(this), &indexProcessVec)); + } + else { + memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); + } + + src.release(); + } + + void ScanSegmentImpl::OP1(int v) + { + cv::Rect seedRect = seedRects[v]; + for (int y = seedRect.y; y < seedRect.y + seedRect.height; y++) { + for (int x = seedRect.x; x < seedRect.x + seedRect.width; x++) { + expandCluster(labelsBuffer, neighbourLocBuffer, clusterBuffer, offsetVec[v], cv::Point(x, y), (int)adjTolerance, &clusterIndex, &locationIndex, &clusterID); + } + } + } + + void ScanSegmentImpl::OP2(std::pair const& p) + { + std::pair& q = const_cast&>(p); + for (int i = q.first; i < q.second; i++) { + labelsBuffer[i] = clusterBuffer[labelsBuffer[i]]; + if (labelsBuffer[i] == UNKNOWN) { + pixelBuffer[i] = 255; + } + else { + pixelBuffer[i] = 0; + } + } + } + + void ScanSegmentImpl::OP3(int v) + { + cv::Rect seedRect = seedRects[v]; + cv::Rect seedRectExt = seedRectsExt[v]; + + cv::Mat seedLabels = labelsMat(seedRectExt).clone(); + watershedEx(src(seedRectExt), seedLabels); + seedLabels(offsetRects[v]).copyTo(labelsMat(seedRects[v])); + seedLabels.release(); + } + + void ScanSegmentImpl::OP4(std::pair const& p) + { + std::pair& q = const_cast&>(p); + for (int i = q.first; i < q.second; i++) { + if (pixelBuffer[i] == 0) { + ((int*)labelsMat.data)[i] = labelsBuffer[i] - 1; + } + else { + ((int*)labelsMat.data)[i] -= 1; + } + } + } + + // expand clusters from a point + void ScanSegmentImpl::expandCluster(int* labelsBuffer, int* neighbourLocBuffer, int* clusterBuffer, int* offsetBuffer, const cv::Point& point, int adjTolerance, std::atomic* clusterIndex, std::atomic* locationIndex, std::atomic* clusterID) + { + int pointIndex = (point.y * width) + point.x; + if (labelsBuffer[pointIndex] == UNCLASSIFIED) { + int offsetStart = 0; + int offsetEnd = 0; + int currentClusterID = clusterID->fetch_add(1); + + calculateCluster(labelsBuffer, neighbourLocBuffer, offsetBuffer, &offsetEnd, pointIndex, adjTolerance, currentClusterID); + + if (offsetStart == offsetEnd) { + labelsBuffer[pointIndex] = UNKNOWN; + } + else { + // set cluster id and get core point index + labelsBuffer[pointIndex] = currentClusterID; + + while (offsetStart < offsetEnd) { + int intoffset2 = *(offsetBuffer + offsetStart); + offsetStart++; + calculateCluster(labelsBuffer, neighbourLocBuffer, offsetBuffer, &offsetEnd, intoffset2, adjTolerance, currentClusterID); + } + + // add origin point + offsetBuffer[offsetEnd] = pointIndex; + offsetEnd++; + + // store to buffer + int currentClusterIndex = clusterIndex->fetch_add(2); + clusterBuffer[currentClusterIndex] = currentClusterID; + clusterBuffer[currentClusterIndex + 1] = offsetEnd; + } + } + } + + void ScanSegmentImpl::calculateCluster(int* labelsBuffer, int* neighbourLocBuffer, int* offsetBuffer, int* offsetEnd, int pointIndex, int adjTolerance, int currentClusterID) + { + for (int i = 0; i < neighbourCount; i++) { + if (*offsetEnd < clusterSize) { + int intoffset2 = pointIndex + neighbourLocBuffer[i]; + if (intoffset2 >= 0 && intoffset2 < indexSize && labelsBuffer[intoffset2] == UNCLASSIFIED) { + int diff1 = (int)labBuffer[pointIndex][0] - (int)labBuffer[intoffset2][0]; + int diff2 = (int)labBuffer[pointIndex][1] - (int)labBuffer[intoffset2][1]; + int diff3 = (int)labBuffer[pointIndex][2] - (int)labBuffer[intoffset2][2]; + + if ((diff1 * diff1) + (diff2 * diff2) + (diff3 * diff3) <= adjTolerance) { + labelsBuffer[intoffset2] = currentClusterID; + offsetBuffer[*offsetEnd] = intoffset2; + (*offsetEnd)++; + } + } + } + else { break; } + } + } + + int ScanSegmentImpl::allocWSNodes(std::vector& storage) + { + int sz = (int)storage.size(); + int newsz = MAX(128, sz * 3 / 2); + + storage.resize(newsz); + if (sz == 0) + { + storage[0].next = 0; + sz = 1; + } + for (int i = sz; i < newsz - 1; i++) + storage[i].next = i + 1; + storage[newsz - 1].next = 0; + return sz; + } + + //the modified version of watershed algorithm from OpenCV + void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) + { + // https://github.com/Seaball/watershed_with_mask + + // Labels for pixels + const int IN_QUEUE = -2; // Pixel visited + // possible bit values = 2^8 + const int NQ = 256; + + cv::Size size = src.size(); + int channel = 3; + // Vector of every created node + std::vector storage; + int free_node = 0, node; + // Priority queue of queues of nodes + // from high priority (0) to low priority (255) + WSQueue q[NQ]; + // Non-empty queue with highest priority + int active_queue; + int i, j; + // Color differences + int db, dg, dr; + int subs_tab[513]; + + // MAX(a,b) = b + MAX(a-b,0) +#define ws_max(a,b) ((b) + subs_tab[(a)-(b)+NQ]) + // MIN(a,b) = a - MAX(a-b,0) +#define ws_min(a,b) ((a) - subs_tab[(a)-(b)+NQ]) + + // Create a new node with offsets mofs and iofs in queue idx +#define ws_push(idx,mofs,iofs) \ + { \ + if (!free_node) \ + free_node = allocWSNodes(storage); \ + node = free_node; \ + free_node = storage[free_node].next; \ + storage[node].next = 0; \ + storage[node].mask_ofs = mofs; \ + storage[node].img_ofs = iofs; \ + if (q[idx].last) \ + storage[q[idx].last].next = node; \ + else \ + q[idx].first = node; \ + q[idx].last = node; \ + } + + // Get next node from queue idx +#define ws_pop(idx,mofs,iofs) \ + { \ + node = q[idx].first; \ + q[idx].first = storage[node].next; \ + if (!storage[node].next) \ + q[idx].last = 0; \ + storage[node].next = free_node; \ + free_node = node; \ + mofs = storage[node].mask_ofs; \ + iofs = storage[node].img_ofs; \ + } + + // Get highest absolute channel difference in diff +#define c_diff(ptr1,ptr2,diff) \ + { \ + db = std::abs((ptr1)[0] - (ptr2)[0]); \ + dg = std::abs((ptr1)[1] - (ptr2)[1]); \ + dr = std::abs((ptr1)[2] - (ptr2)[2]); \ + diff = ws_max(db, dg); \ + diff = ws_max(diff, dr); \ + assert(0 <= diff && diff <= 255); \ + } + + CV_Assert(src.type() == CV_8UC3 || src.type() == CV_8UC1 && dst.type() == CV_32SC1); + CV_Assert(src.size() == dst.size()); + + // Current pixel in input image + const uchar* img = src.ptr(); + // Step size to next row in input image + int istep = int(src.step / sizeof(img[0])); + + // Current pixel in mask image + int* mask = dst.ptr(); + // Step size to next row in mask image + int mstep = int(dst.step / sizeof(mask[0])); + + for (i = 0; i < 256; i++) + subs_tab[i] = 0; + for (i = 256; i <= 512; i++) + subs_tab[i] = i - 256; + + //for (j = 0; j < size.width; j++) + //mask[j] = mask[j + mstep*(size.height - 1)] = 0; + + // initial phase: put all the neighbor pixels of each marker to the ordered queue - + // determine the initial boundaries of the basins + for (i = 1; i < size.height - 1; i++) { + img += istep; mask += mstep; + mask[0] = mask[size.width - 1] = 0; // boundary pixels + + for (j = 1; j < size.width - 1; j++) { + int* m = mask + j; + if (m[0] < 0) + m[0] = 0; + if (m[0] == 0 && (m[-1] > 0 || m[1] > 0 || m[-mstep] > 0 || m[mstep] > 0)) + { + // Find smallest difference to adjacent markers + const uchar* ptr = img + j * channel; + int idx = 256, t; + if (m[-1] > 0) { + c_diff(ptr, ptr - channel, idx); + } + if (m[1] > 0) { + c_diff(ptr, ptr + channel, t); + idx = ws_min(idx, t); + } + if (m[-mstep] > 0) { + c_diff(ptr, ptr - istep, t); + idx = ws_min(idx, t); + } + if (m[mstep] > 0) { + c_diff(ptr, ptr + istep, t); + idx = ws_min(idx, t); + } + + // Add to according queue + assert(0 <= idx && idx <= 255); + ws_push(idx, i * mstep + j, i * istep + j * channel); + m[0] = IN_QUEUE;//initial unvisited + } + } + } + // find the first non-empty queue + for (i = 0; i < NQ; i++) + if (q[i].first) + break; + + // if there is no markers, exit immediately + if (i == NQ) + return; + + active_queue = i;//first non-empty priority queue + img = src.ptr(); + mask = dst.ptr(); + + // recursively fill the basins + for (;;) + { + int mofs, iofs; + int lab = 0, t; + int* m; + const uchar* ptr; + + // Get non-empty queue with highest priority + // Exit condition: empty priority queue + if (q[active_queue].first == 0) + { + for (i = active_queue + 1; i < NQ; i++) + if (q[i].first) + break; + if (i == NQ) + { + std::vector().swap(storage); + break; + } + active_queue = i; + } + + // Get next node + ws_pop(active_queue, mofs, iofs); + int top = 1, bottom = 1, left = 1, right = 1; + if (0 <= mofs && mofs < mstep)//pixel on the top + top = 0; + if ((mofs % mstep) == 0)//pixel in the left column + left = 0; + if ((mofs + 1) % mstep == 0)//pixel in the right column + right = 0; + if (mstep * (size.height - 1) <= mofs && mofs < mstep * size.height)//pixel on the bottom + bottom = 0; + + // Calculate pointer to current pixel in input and marker image + m = mask + mofs; + ptr = img + iofs; + int diff, temp; + // Check surrounding pixels for labels to determine label for current pixel + if (left) {//the left point can be visited + t = m[-1]; + if (t > 0) { + lab = t; + c_diff(ptr, ptr - channel, diff); + } + } + if (right) {// Right point can be visited + t = m[1]; + if (t > 0) { + if (lab == 0) {//and this point didn't be labeled before + lab = t; + c_diff(ptr, ptr + channel, diff); + } + else if (t != lab) { + c_diff(ptr, ptr + channel, temp); + diff = ws_min(diff, temp); + if (diff == temp) + lab = t; + } + } + } + if (top) { + t = m[-mstep]; // Top + if (t > 0) { + if (lab == 0) {//and this point didn't be labeled before + lab = t; + c_diff(ptr, ptr - istep, diff); + } + else if (t != lab) { + c_diff(ptr, ptr - istep, temp); + diff = ws_min(diff, temp); + if (diff == temp) + lab = t; + } + } + } + if (bottom) { + t = m[mstep]; // Bottom + if (t > 0) { + if (lab == 0) { + lab = t; + } + else if (t != lab) { + c_diff(ptr, ptr + istep, temp); + diff = ws_min(diff, temp); + if (diff == temp) + lab = t; + } + } + } + // Set label to current pixel in marker image + assert(lab != 0);//lab must be labeled with a nonzero number + m[0] = lab; + + // Add adjacent, unlabeled pixels to corresponding queue + if (left) { + if (m[-1] == 0)//left pixel with marker 0 + { + c_diff(ptr, ptr - channel, t); + ws_push(t, mofs - 1, iofs - channel); + active_queue = ws_min(active_queue, t); + m[-1] = IN_QUEUE; + } + } + + if (right) + { + if (m[1] == 0)//right pixel with marker 0 + { + c_diff(ptr, ptr + channel, t); + ws_push(t, mofs + 1, iofs + channel); + active_queue = ws_min(active_queue, t); + m[1] = IN_QUEUE; + } + } + + if (top) + { + if (m[-mstep] == 0)//top pixel with marker 0 + { + c_diff(ptr, ptr - istep, t); + ws_push(t, mofs - mstep, iofs - istep); + active_queue = ws_min(active_queue, t); + m[-mstep] = IN_QUEUE; + } + } + + if (bottom) { + if (m[mstep] == 0)//down pixel with marker 0 + { + c_diff(ptr, ptr + istep, t); + ws_push(t, mofs + mstep, iofs + istep); + active_queue = ws_min(active_queue, t); + m[mstep] = IN_QUEUE; + } + } + } + } + + void ScanSegmentImpl::getLabels(OutputArray labels_out) + { + labels_out.assign(labelsMat); + } + + void ScanSegmentImpl::getLabelContourMask(OutputArray image, bool thick_line) + { + image.create(height, width, CV_8UC1); + cv::Mat dst = image.getMat(); + dst.setTo(cv::Scalar(0)); + + const int dx8[8] = { -1, -1, 0, 1, 1, 1, 0, -1 }; + const int dy8[8] = { 0, -1, -1, -1, 0, 1, 1, 1 }; + + for (int j = 0; j < height; j++) + { + for (int k = 0; k < width; k++) + { + int neighbors = 0; + for (int i = 0; i < 8; i++) + { + int x = k + dx8[i]; + int y = j + dy8[i]; + + if ((x >= 0 && x < width) && (y >= 0 && y < height)) + { + int index = y * width + x; + int mainindex = j * width + k; + if (((int*)labelsMat.data)[mainindex] != ((int*)labelsMat.data)[index]) + { + if (thick_line || !*dst.ptr(y, x)) + neighbors++; + } + } + } + if (neighbors > 1) + *dst.ptr(j, k) = (uchar)255; + } + } + } + } // namespace ximgproc +} // namespace cv \ No newline at end of file From 79f8436f2017384200fbe2f6c80d95466b033a30 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 02:21:07 +1300 Subject: [PATCH 02/44] Update scansegment.hpp added newline at end of file --- modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 20906f05ce..d22b71246b 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -125,4 +125,4 @@ when the number of processing threads is fixed, and requires the source image to } } #endif -#endif \ No newline at end of file +#endif From 3e6aaae585266a8ca7672600cd38e31eb4de32da Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 02:22:06 +1300 Subject: [PATCH 03/44] Update scansegment.cpp added newline at end of file --- modules/ximgproc/src/scansegment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 3c84088657..692d6afcf6 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -926,4 +926,4 @@ namespace cv { } } } // namespace ximgproc -} // namespace cv \ No newline at end of file +} // namespace cv From 5d8616e068d0ae02e35482cc2fd618d2c7ea7348 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 12:02:01 +1300 Subject: [PATCH 04/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 37 ++++++++++++++-------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 692d6afcf6..bba1a1d637 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -114,7 +114,7 @@ namespace cv { int neighbourLocBuffer[neighbourCount]; // neighbour locations std::vector offsetVec; // vector of offsets - std::atomic clusterIndex, locationIndex, clusterID; // atomic indices + std::atomic clusterIndex, clusterID; // atomic indices cv::Mat src, labelsMat; // mats @@ -139,7 +139,7 @@ namespace cv { : ss(scanSegment) {} virtual ~PP1() {} - virtual void operator()(const cv::Range& range) const + void operator()(const cv::Range& range) const { for (int v = range.start; v < range.end; v++) { @@ -157,7 +157,7 @@ namespace cv { : ss(scanSegment), ctv(countVec) {} virtual ~PP2() {} - virtual void operator()(const cv::Range& range) const + void operator()(const cv::Range& range) const { for (int v = range.start; v < range.end; v++) { @@ -176,7 +176,7 @@ namespace cv { : ss(scanSegment) {} virtual ~PP3() {} - virtual void operator()(const cv::Range& range) const + void operator()(const cv::Range& range) const { for (int v = range.start; v < range.end; v++) { @@ -194,7 +194,7 @@ namespace cv { : ss(scanSegment), ctv(countVec) {} virtual ~PP4() {} - virtual void operator()(const cv::Range& range) const + void operator()(const cv::Range& range) const { for (int v = range.start; v < range.end; v++) { @@ -210,8 +210,8 @@ namespace cv { void OP2(std::pair const& p); void OP3(int v); void OP4(std::pair const& p); - void expandCluster(int* labelsBuffer, int* neighbourLocBuffer, int* clusterBuffer, int* offsetBuffer, const cv::Point& point, int adjTolerance, std::atomic* clusterIndex, std::atomic* locationIndex, std::atomic* clusterID); - void calculateCluster(int* labelsBuffer, int* neighbourLocBuffer, int* offsetBuffer, int* offsetEnd, int pointIndex, int adjTolerance, int currentClusterID); + void expandCluster(int* offsetBuffer, const cv::Point& point); + void calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID); static int allocWSNodes(std::vector& storage); static void watershedEx(const cv::Mat& src, cv::Mat& dst); }; @@ -415,7 +415,6 @@ namespace cv { clusterCount = 0; clusterIndex.store(0); - locationIndex.store(0); clusterID.store(1); smallClusters = indexSize / smallClustersDiv; @@ -441,8 +440,8 @@ namespace cv { for (int i = 1; i < clusterIndexSize; i += 2) { int count = clusterBuffer[i]; if (count >= smallClusters) { - int clusterID = clusterBuffer[i - 1]; - countVec.push_back(std::make_pair(clusterID, count)); + int currentID = clusterBuffer[i - 1]; + countVec.push_back(std::make_pair(currentID, count)); } } @@ -485,7 +484,7 @@ namespace cv { cv::Rect seedRect = seedRects[v]; for (int y = seedRect.y; y < seedRect.y + seedRect.height; y++) { for (int x = seedRect.x; x < seedRect.x + seedRect.width; x++) { - expandCluster(labelsBuffer, neighbourLocBuffer, clusterBuffer, offsetVec[v], cv::Point(x, y), (int)adjTolerance, &clusterIndex, &locationIndex, &clusterID); + expandCluster(offsetVec[v], cv::Point(x, y)); } } } @@ -529,15 +528,15 @@ namespace cv { } // expand clusters from a point - void ScanSegmentImpl::expandCluster(int* labelsBuffer, int* neighbourLocBuffer, int* clusterBuffer, int* offsetBuffer, const cv::Point& point, int adjTolerance, std::atomic* clusterIndex, std::atomic* locationIndex, std::atomic* clusterID) + void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) { int pointIndex = (point.y * width) + point.x; if (labelsBuffer[pointIndex] == UNCLASSIFIED) { int offsetStart = 0; int offsetEnd = 0; - int currentClusterID = clusterID->fetch_add(1); + int currentClusterID = clusterID.fetch_add(1); - calculateCluster(labelsBuffer, neighbourLocBuffer, offsetBuffer, &offsetEnd, pointIndex, adjTolerance, currentClusterID); + calculateCluster(offsetBuffer, &offsetEnd, pointIndex, currentClusterID); if (offsetStart == offsetEnd) { labelsBuffer[pointIndex] = UNKNOWN; @@ -549,7 +548,7 @@ namespace cv { while (offsetStart < offsetEnd) { int intoffset2 = *(offsetBuffer + offsetStart); offsetStart++; - calculateCluster(labelsBuffer, neighbourLocBuffer, offsetBuffer, &offsetEnd, intoffset2, adjTolerance, currentClusterID); + calculateCluster(offsetBuffer, &offsetEnd, intoffset2, currentClusterID); } // add origin point @@ -557,14 +556,14 @@ namespace cv { offsetEnd++; // store to buffer - int currentClusterIndex = clusterIndex->fetch_add(2); + int currentClusterIndex = clusterIndex.fetch_add(2); clusterBuffer[currentClusterIndex] = currentClusterID; clusterBuffer[currentClusterIndex + 1] = offsetEnd; } } } - void ScanSegmentImpl::calculateCluster(int* labelsBuffer, int* neighbourLocBuffer, int* offsetBuffer, int* offsetEnd, int pointIndex, int adjTolerance, int currentClusterID) + void ScanSegmentImpl::calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID) { for (int i = 0; i < neighbourCount; i++) { if (*offsetEnd < clusterSize) { @@ -574,7 +573,7 @@ namespace cv { int diff2 = (int)labBuffer[pointIndex][1] - (int)labBuffer[intoffset2][1]; int diff3 = (int)labBuffer[pointIndex][2] - (int)labBuffer[intoffset2][2]; - if ((diff1 * diff1) + (diff2 * diff2) + (diff3 * diff3) <= adjTolerance) { + if ((diff1 * diff1) + (diff2 * diff2) + (diff3 * diff3) <= (int)adjTolerance) { labelsBuffer[intoffset2] = currentClusterID; offsetBuffer[*offsetEnd] = intoffset2; (*offsetEnd)++; @@ -673,7 +672,7 @@ namespace cv { assert(0 <= diff && diff <= 255); \ } - CV_Assert(src.type() == CV_8UC3 || src.type() == CV_8UC1 && dst.type() == CV_32SC1); + CV_Assert(src.type() == CV_8UC3 && dst.type() == CV_32SC1); CV_Assert(src.size() == dst.size()); // Current pixel in input image From d74bde53ce3e9dee6c8a1f71275045549d5f6c91 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 16:57:54 +1300 Subject: [PATCH 05/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index bba1a1d637..993a9f100b 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -139,7 +139,7 @@ namespace cv { : ss(scanSegment) {} virtual ~PP1() {} - void operator()(const cv::Range& range) const + void operator()(const cv::Range& range) const CV_OVERRIDE { for (int v = range.start; v < range.end; v++) { @@ -157,7 +157,7 @@ namespace cv { : ss(scanSegment), ctv(countVec) {} virtual ~PP2() {} - void operator()(const cv::Range& range) const + void operator()(const cv::Range& range) const CV_OVERRIDE { for (int v = range.start; v < range.end; v++) { @@ -176,7 +176,7 @@ namespace cv { : ss(scanSegment) {} virtual ~PP3() {} - void operator()(const cv::Range& range) const + void operator()(const cv::Range& range) const CV_OVERRIDE { for (int v = range.start; v < range.end; v++) { @@ -194,7 +194,7 @@ namespace cv { : ss(scanSegment), ctv(countVec) {} virtual ~PP4() {} - void operator()(const cv::Range& range) const + void operator()(const cv::Range& range) const CV_OVERRIDE { for (int v = range.start; v < range.end; v++) { From 9dfe1509e7500453743b2953f35959d83f9430cc Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 17:09:20 +1300 Subject: [PATCH 06/44] Update scansegment.hpp bug fixes --- modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index d22b71246b..992a7a3ca9 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -56,8 +56,6 @@ #ifdef __cplusplus #include -#include -#include namespace cv { From 3e4f25dd2e8528be5ddc38a8559a41d511a059ff Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 17:24:14 +1300 Subject: [PATCH 07/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 993a9f100b..3aa8148736 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -54,7 +54,8 @@ #include "precomp.hpp" #include -#include +#include +#include namespace cv { namespace ximgproc { @@ -446,7 +447,7 @@ namespace cv { } // sort descending - std::sort(countVec.begin(), countVec.end(), [](auto& left, auto& right) { + std::sort(countVec.begin(), countVec.end(), [](std::pair& left, std::pair& right) { return left.second > right.second; }); From 5a2e1531815c0a18a6799c985a9d985473ed1d02 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 17:27:11 +1300 Subject: [PATCH 08/44] Update scansegment.hpp trailing whitespace removal --- .../include/opencv2/ximgproc/scansegment.hpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 992a7a3ca9..3a469a1826 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -63,10 +63,10 @@ namespace cv { /** @brief Class implementing the F-DBSCAN (Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm) superpixels algorithm by Loke SC, et al. -The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than -existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s -with a Boundary Recall of 0.797 and an Achievable Segmentation Accuracy of 0.944. The computational complexity is quadratic O(n2) and -more suited to smaller images, but can still process a 2MP colour image faster than the SEEDS algorithm in OpenCV. The output is deterministic +The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than +existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s +with a Boundary Recall of 0.797 and an Achievable Segmentation Accuracy of 0.944. The computational complexity is quadratic O(n2) and +more suited to smaller images, but can still process a 2MP colour image faster than the SEEDS algorithm in OpenCV. The output is deterministic when the number of processing threads is fixed, and requires the source image to be in Lab colour format. */ class CV_EXPORTS_W ScanSegment : public Algorithm @@ -78,15 +78,15 @@ when the number of processing threads is fixed, and requires the source image to CV_WRAP virtual int getNumberOfSuperpixels() = 0; /** @brief Calculates the superpixel segmentation on a given image with the initialized - parameters in the ScanSegment object. This function can be called again for other images - without the need of initializing the algorithm with createScanSegment(). This save the + parameters in the ScanSegment object. This function can be called again for other images + without the need of initializing the algorithm with createScanSegment(). This save the computational cost of allocating memory for all the structures of the algorithm. - @param img Input image. Supported format: CV_8UC3. Image size must match with the initialized + @param img Input image. Supported format: CV_8UC3. Image size must match with the initialized image size with the function createScanSegment(). It MUST be in Lab color space. */ CV_WRAP virtual void iterate(InputArray img) = 0; - /** @brief Returns the segmentation labeling of the image. Each label represents a superpixel, + /** @brief Returns the segmentation labeling of the image. Each label represents a superpixel, and each pixel is assigned to one superpixel label. @param labels_out Return: A CV_32UC1 integer array containing the labels of the superpixel segmentation. The labels are in the range [0, getNumberOfSuperpixels()]. @@ -111,12 +111,12 @@ when the number of processing threads is fixed, and requires the source image to @param num_superpixels Desired number of superpixels. Note that the actual number may be smaller due to restrictions (depending on the image size). Use getNumberOfSuperpixels() to get the actual number. - @param threads Number of processing threads for parallelisation. Default -1 uses the maximum number + @param threads Number of processing threads for parallelisation. Default -1 uses the maximum number of threads. In practice, four threads is enough for smaller images and eight threads for larger ones. - @param merge_small merge small segments to give the desired number of superpixels. Processing is + @param merge_small merge small segments to give the desired number of superpixels. Processing is much faster without merging, but many small segments will be left in the image. The function initializes a ScanSegment object for the input image. It stores the parameters of - the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel + the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel algorithm, which are: num_superpixels, threads, and merge_small. */ CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads = -1, bool merge_small = true); From 7130d257312d7ff13ad461e85990762fc42d5807 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 18:04:40 +1300 Subject: [PATCH 09/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 3aa8148736..f8bb89e966 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -506,9 +506,7 @@ namespace cv { void ScanSegmentImpl::OP3(int v) { - cv::Rect seedRect = seedRects[v]; cv::Rect seedRectExt = seedRectsExt[v]; - cv::Mat seedLabels = labelsMat(seedRectExt).clone(); watershedEx(src(seedRectExt), seedLabels); seedLabels(offsetRects[v]).copyTo(labelsMat(seedRects[v])); From ee702c613c31e43f55a744cc37eb121f4129b066 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 18:34:36 +1300 Subject: [PATCH 10/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index f8bb89e966..6d729fca0b 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -744,6 +744,7 @@ namespace cv { mask = dst.ptr(); // recursively fill the basins + int diff = 0, temp = 0; for (;;) { int mofs, iofs; @@ -781,7 +782,6 @@ namespace cv { // Calculate pointer to current pixel in input and marker image m = mask + mofs; ptr = img + iofs; - int diff, temp; // Check surrounding pixels for labels to determine label for current pixel if (left) {//the left point can be visited t = m[-1]; From c4155b0e22defc70e97a6f12a992448a54949a02 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 19:20:57 +1300 Subject: [PATCH 11/44] Update scansegment.cpp editing changes --- modules/ximgproc/src/scansegment.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 6d729fca0b..c04de9fe3c 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -39,7 +39,7 @@ // // - /* +/* * BibTeX reference @article{loke2021accelerated, title={Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm}, @@ -49,7 +49,7 @@ year={2021}, publisher={Springer} } - */ +*/ #include "precomp.hpp" From ec789379ef2f6dba5fd1ec0152718d198531497c Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 19:22:19 +1300 Subject: [PATCH 12/44] Update scansegment.hpp editing changes --- modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 3a469a1826..2248e35ce1 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -39,7 +39,7 @@ // // - /* +/* * BibTeX reference @article{loke2021accelerated, title={Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm}, @@ -49,7 +49,7 @@ year={2021}, publisher={Springer} } - */ +*/ #ifndef __OPENCV_SCANSEGMENT_HPP__ #define __OPENCV_SCANSEGMENT_HPP__ From 4de0e315271903715b9e1bb9989910f1528e31ae Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 1 Nov 2021 20:08:45 +1300 Subject: [PATCH 13/44] Update scansegment.hpp minor edits --- modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 2248e35ce1..2461c7fe84 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -51,6 +51,7 @@ } */ + #ifndef __OPENCV_SCANSEGMENT_HPP__ #define __OPENCV_SCANSEGMENT_HPP__ #ifdef __cplusplus From c6a918c8f227076cebd21c735517255a05d33cac Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 3 Nov 2021 13:53:30 +1300 Subject: [PATCH 14/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index c04de9fe3c..8f3453c46c 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -447,7 +447,7 @@ namespace cv { } // sort descending - std::sort(countVec.begin(), countVec.end(), [](std::pair& left, std::pair& right) { + std::sort(countVec.begin(), countVec.end(), [](const std::pair& left, const std::pair& right) { return left.second > right.second; }); From 67d91432c60c26f1886e7cc8e0ff105cb77945fb Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 3 Nov 2021 15:02:52 +1300 Subject: [PATCH 15/44] Update scansegment.cpp inserted @addtogroup block --- modules/ximgproc/src/scansegment.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 8f3453c46c..5fe1292ae6 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -59,6 +59,8 @@ namespace cv { namespace ximgproc { + //! @addtogroup ximgproc_superpixel + //! @{ class ScanSegmentImpl : public ScanSegment { @@ -923,5 +925,7 @@ namespace cv { } } } + + //! @} } // namespace ximgproc } // namespace cv From 0fbdc2f4bb1b24ac0a0070a713a1bdc0c1c1e84b Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 3 Nov 2021 23:28:13 +1300 Subject: [PATCH 16/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 5fe1292ae6..cda5676490 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -59,9 +59,6 @@ namespace cv { namespace ximgproc { - //! @addtogroup ximgproc_superpixel - //! @{ - class ScanSegmentImpl : public ScanSegment { #define UNKNOWN 0 @@ -925,7 +922,5 @@ namespace cv { } } } - - //! @} } // namespace ximgproc } // namespace cv From 74c6cf5900f31ce3e2ee3cf054fa85e77e031aa0 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 3 Nov 2021 23:28:21 +1300 Subject: [PATCH 17/44] Update scansegment.hpp bug fixes --- modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 2461c7fe84..50fe12434d 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -62,6 +62,9 @@ namespace cv { namespace ximgproc { + //! @addtogroup ximgproc_superpixel + //! @{ + /** @brief Class implementing the F-DBSCAN (Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm) superpixels algorithm by Loke SC, et al. The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than @@ -121,6 +124,8 @@ when the number of processing threads is fixed, and requires the source image to algorithm, which are: num_superpixels, threads, and merge_small. */ CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads = -1, bool merge_small = true); + + //! @} } } #endif From 650f9c4be7a5d9032f2014f719107503d73b7cb7 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 15:33:38 +1300 Subject: [PATCH 18/44] Update scansegment.hpp indents removed --- .../include/opencv2/ximgproc/scansegment.hpp | 116 +++++++++--------- 1 file changed, 55 insertions(+), 61 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 50fe12434d..7f20fa0ea1 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -39,7 +39,7 @@ // // -/* + /* * BibTeX reference @article{loke2021accelerated, title={Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm}, @@ -51,7 +51,6 @@ } */ - #ifndef __OPENCV_SCANSEGMENT_HPP__ #define __OPENCV_SCANSEGMENT_HPP__ #ifdef __cplusplus @@ -60,73 +59,68 @@ namespace cv { - namespace ximgproc - { - //! @addtogroup ximgproc_superpixel - //! @{ - +namespace ximgproc +{ /** @brief Class implementing the F-DBSCAN (Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm) superpixels algorithm by Loke SC, et al. -The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than -existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s -with a Boundary Recall of 0.797 and an Achievable Segmentation Accuracy of 0.944. The computational complexity is quadratic O(n2) and -more suited to smaller images, but can still process a 2MP colour image faster than the SEEDS algorithm in OpenCV. The output is deterministic +The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than +existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s +with a Boundary Recall of 0.797 and an Achievable Segmentation Accuracy of 0.944. The computational complexity is quadratic O(n2) and +more suited to smaller images, but can still process a 2MP colour image faster than the SEEDS algorithm in OpenCV. The output is deterministic when the number of processing threads is fixed, and requires the source image to be in Lab colour format. - */ - class CV_EXPORTS_W ScanSegment : public Algorithm - { - public: - /** @brief Returns the actual superpixel segmentation from the last image processed using iterate. - Returns zero if no image has been processed. - */ - CV_WRAP virtual int getNumberOfSuperpixels() = 0; - - /** @brief Calculates the superpixel segmentation on a given image with the initialized - parameters in the ScanSegment object. This function can be called again for other images - without the need of initializing the algorithm with createScanSegment(). This save the - computational cost of allocating memory for all the structures of the algorithm. - @param img Input image. Supported format: CV_8UC3. Image size must match with the initialized - image size with the function createScanSegment(). It MUST be in Lab color space. - */ - CV_WRAP virtual void iterate(InputArray img) = 0; +*/ +class CV_EXPORTS_W ScanSegment : public Algorithm +{ +public: + /** @brief Returns the actual superpixel segmentation from the last image processed using iterate. + Returns zero if no image has been processed. + */ + CV_WRAP virtual int getNumberOfSuperpixels() = 0; - /** @brief Returns the segmentation labeling of the image. Each label represents a superpixel, - and each pixel is assigned to one superpixel label. - @param labels_out Return: A CV_32UC1 integer array containing the labels of the superpixel - segmentation. The labels are in the range [0, getNumberOfSuperpixels()]. - */ - CV_WRAP virtual void getLabels(OutputArray labels_out) = 0; + /** @brief Calculates the superpixel segmentation on a given image with the initialized + parameters in the ScanSegment object. This function can be called again for other images + without the need of initializing the algorithm with createScanSegment(). This save the + computational cost of allocating memory for all the structures of the algorithm. + @param img Input image. Supported format: CV_8UC3. Image size must match with the initialized + image size with the function createScanSegment(). It MUST be in Lab color space. + */ + CV_WRAP virtual void iterate(InputArray img) = 0; - /** @brief Returns the mask of the superpixel segmentation stored in the ScanSegment object. - @param image Return: CV_8UC1 image mask where -1 indicates that the pixel is a superpixel border, - and 0 otherwise. - @param thick_line If false, the border is only one pixel wide, otherwise all pixels at the border - are masked. - The function return the boundaries of the superpixel segmentation. - */ - CV_WRAP virtual void getLabelContourMask(OutputArray image, bool thick_line = false) = 0; + /** @brief Returns the segmentation labeling of the image. Each label represents a superpixel, + and each pixel is assigned to one superpixel label. + @param labels_out Return: A CV_32UC1 integer array containing the labels of the superpixel + segmentation. The labels are in the range [0, getNumberOfSuperpixels()]. + */ + CV_WRAP virtual void getLabels(OutputArray labels_out) = 0; - virtual ~ScanSegment() {} - }; + /** @brief Returns the mask of the superpixel segmentation stored in the ScanSegment object. + @param image Return: CV_8UC1 image mask where -1 indicates that the pixel is a superpixel border, + and 0 otherwise. + @param thick_line If false, the border is only one pixel wide, otherwise all pixels at the border + are masked. + The function return the boundaries of the superpixel segmentation. + */ + CV_WRAP virtual void getLabelContourMask(OutputArray image, bool thick_line = false) = 0; - /** @brief Initializes a ScanSegment object. - @param image_width Image width. - @param image_height Image height. - @param num_superpixels Desired number of superpixels. Note that the actual number may be smaller - due to restrictions (depending on the image size). Use getNumberOfSuperpixels() to - get the actual number. - @param threads Number of processing threads for parallelisation. Default -1 uses the maximum number - of threads. In practice, four threads is enough for smaller images and eight threads for larger ones. - @param merge_small merge small segments to give the desired number of superpixels. Processing is - much faster without merging, but many small segments will be left in the image. - The function initializes a ScanSegment object for the input image. It stores the parameters of - the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel - algorithm, which are: num_superpixels, threads, and merge_small. - */ - CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads = -1, bool merge_small = true); + virtual ~ScanSegment() {} +}; - //! @} - } +/** @brief Initializes a ScanSegment object. +@param image_width Image width. +@param image_height Image height. +@param num_superpixels Desired number of superpixels. Note that the actual number may be smaller +due to restrictions (depending on the image size). Use getNumberOfSuperpixels() to +get the actual number. +@param threads Number of processing threads for parallelisation. Default -1 uses the maximum number +of threads. In practice, four threads is enough for smaller images and eight threads for larger ones. +@param merge_small merge small segments to give the desired number of superpixels. Processing is +much faster without merging, but many small segments will be left in the image. +The function initializes a ScanSegment object for the input image. It stores the parameters of +the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel +algorithm, which are: num_superpixels, threads, and merge_small. +*/ +CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads = -1, bool merge_small = true); +} } #endif #endif From e0a4048a1b44ad3bae95865e5c9bac67aa35869f Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 15:34:59 +1300 Subject: [PATCH 19/44] Update scansegment.cpp extra indents removed --- modules/ximgproc/src/scansegment.cpp | 1529 +++++++++++++------------- 1 file changed, 767 insertions(+), 762 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index cda5676490..ffc45bf1e9 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -39,7 +39,7 @@ // // -/* + /* * BibTeX reference @article{loke2021accelerated, title={Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm}, @@ -49,7 +49,7 @@ year={2021}, publisher={Springer} } -*/ + */ #include "precomp.hpp" @@ -58,869 +58,874 @@ #include namespace cv { - namespace ximgproc { - class ScanSegmentImpl : public ScanSegment - { +namespace ximgproc { +//! @addtogroup ximgproc_superpixel +//! @{ + +class ScanSegmentImpl : public ScanSegment +{ #define UNKNOWN 0 #define BORDER -1 #define UNCLASSIFIED -2 #define NONE -3 - public: +public: - ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int threads, bool merge_small); + ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int threads, bool merge_small); - virtual ~ScanSegmentImpl(); + virtual ~ScanSegmentImpl(); - virtual int getNumberOfSuperpixels() CV_OVERRIDE { return clusterCount; } + virtual int getNumberOfSuperpixels() CV_OVERRIDE { return clusterCount; } - virtual void iterate(InputArray img) CV_OVERRIDE; + virtual void iterate(InputArray img) CV_OVERRIDE; - virtual void getLabels(OutputArray labels_out) CV_OVERRIDE; + virtual void getLabels(OutputArray labels_out) CV_OVERRIDE; - virtual void getLabelContourMask(OutputArray image, bool thick_line = false) CV_OVERRIDE; + virtual void getLabelContourMask(OutputArray image, bool thick_line = false) CV_OVERRIDE; - private: - static const int neighbourCount = 8; // number of pixel neighbours - static const int smallClustersDiv = 10000; // divide total pixels by this to give smallClusters - const float tolerance100 = 10.0f; // colour tolerance for image size of 100x100px +private: + static const int neighbourCount = 8; // number of pixel neighbours + static const int smallClustersDiv = 10000; // divide total pixels by this to give smallClusters + const float tolerance100 = 10.0f; // colour tolerance for image size of 100x100px - int processthreads; // concurrent threads for parallel processing - int width, height; // image size - int superpixels; // number of superpixels - bool merge; // merge small superpixels - int indexSize; // size of label mat vector - int clusterSize; // max size of clusters - bool setupComplete; // is setup complete - int clusterCount; // number of superpixels from the most recent iterate - float adjTolerance; // adjusted colour tolerance + int processthreads; // concurrent threads for parallel processing + int width, height; // image size + int superpixels; // number of superpixels + bool merge; // merge small superpixels + int indexSize; // size of label mat vector + int clusterSize; // max size of clusters + bool setupComplete; // is setup complete + int clusterCount; // number of superpixels from the most recent iterate + float adjTolerance; // adjusted colour tolerance - int horzDiv, vertDiv; // number of horizontal and vertical segments - float horzLength, vertLength; // length of each segment - int effectivethreads; // effective number of concurrent threads - int smallClusters; // clusters below this pixel count are considered small for merging - cv::Rect* seedRects; // array of seed rectangles - cv::Rect* seedRectsExt; // array of extended seed rectangles - cv::Rect* offsetRects; // array of offset rectangles - cv::Point* neighbourLoc; // neighbour locations + int horzDiv, vertDiv; // number of horizontal and vertical segments + float horzLength, vertLength; // length of each segment + int effectivethreads; // effective number of concurrent threads + int smallClusters; // clusters below this pixel count are considered small for merging + cv::Rect* seedRects; // array of seed rectangles + cv::Rect* seedRectsExt; // array of extended seed rectangles + cv::Rect* offsetRects; // array of offset rectangles + cv::Point* neighbourLoc; // neighbour locations - std::vector indexNeighbourVec; // indices for parallel processing - std::vector> indexProcessVec; + std::vector indexNeighbourVec; // indices for parallel processing + std::vector> indexProcessVec; - int* labelsBuffer; // label buffer - int* clusterBuffer; // cluster buffer - cv::Vec3b* labBuffer; // lab buffer - uchar* pixelBuffer; // pixel buffer - int neighbourLocBuffer[neighbourCount]; // neighbour locations - std::vector offsetVec; // vector of offsets + int* labelsBuffer; // label buffer + int* clusterBuffer; // cluster buffer + cv::Vec3b* labBuffer; // lab buffer + uchar* pixelBuffer; // pixel buffer + int neighbourLocBuffer[neighbourCount]; // neighbour locations + std::vector offsetVec; // vector of offsets - std::atomic clusterIndex, clusterID; // atomic indices + std::atomic clusterIndex, clusterID; // atomic indices - cv::Mat src, labelsMat; // mats - - struct WSNode - { - int next; - int mask_ofs; - int img_ofs; - }; + cv::Mat src, labelsMat; // mats - // Queue for WSNodes - struct WSQueue - { - WSQueue() { first = last = 0; } - int first, last; - }; - - class PP1 : public cv::ParallelLoopBody - { - public: - PP1(ScanSegmentImpl* const scanSegment) - : ss(scanSegment) {} - virtual ~PP1() {} + struct WSNode + { + int next; + int mask_ofs; + int img_ofs; + }; - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP1(v); - } - } - private: - ScanSegmentImpl* const ss; - }; - - class PP2 : public cv::ParallelLoopBody - { - public: - PP2(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) - : ss(scanSegment), ctv(countVec) {} - virtual ~PP2() {} + // Queue for WSNodes + struct WSQueue + { + WSQueue() { first = last = 0; } + int first, last; + }; - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP2((*ctv)[v]); - } - } - private: - ScanSegmentImpl* const ss; - std::vector>* ctv; - }; + class PP1 : public cv::ParallelLoopBody + { + public: + PP1(ScanSegmentImpl* const scanSegment) + : ss(scanSegment) {} + virtual ~PP1() {} - class PP3 : public cv::ParallelLoopBody + void operator()(const cv::Range& range) const CV_OVERRIDE + { + for (int v = range.start; v < range.end; v++) { - public: - PP3(ScanSegmentImpl* const scanSegment) - : ss(scanSegment) {} - virtual ~PP3() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP3(v); - } - } - private: - ScanSegmentImpl* const ss; - }; - - class PP4 : public cv::ParallelLoopBody + ss->OP1(v); + } + } + private: + ScanSegmentImpl* const ss; + }; + + class PP2 : public cv::ParallelLoopBody + { + public: + PP2(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) + : ss(scanSegment), ctv(countVec) {} + virtual ~PP2() {} + + void operator()(const cv::Range& range) const CV_OVERRIDE + { + for (int v = range.start; v < range.end; v++) { - public: - PP4(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) - : ss(scanSegment), ctv(countVec) {} - virtual ~PP4() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP4((*ctv)[v]); - } - } - private: - ScanSegmentImpl* const ss; - std::vector>* ctv; - }; - - void OP1(int v); - void OP2(std::pair const& p); - void OP3(int v); - void OP4(std::pair const& p); - void expandCluster(int* offsetBuffer, const cv::Point& point); - void calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID); - static int allocWSNodes(std::vector& storage); - static void watershedEx(const cv::Mat& src, cv::Mat& dst); - }; - - CV_EXPORTS Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads, bool merge_small) + ss->OP2((*ctv)[v]); + } + } + private: + ScanSegmentImpl* const ss; + std::vector>* ctv; + }; + + class PP3 : public cv::ParallelLoopBody + { + public: + PP3(ScanSegmentImpl* const scanSegment) + : ss(scanSegment) {} + virtual ~PP3() {} + + void operator()(const cv::Range& range) const CV_OVERRIDE { - return makePtr(image_width, image_height, num_superpixels, threads, merge_small); + for (int v = range.start; v < range.end; v++) + { + ss->OP3(v); + } } - - ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int threads, bool merge_small) + private: + ScanSegmentImpl* const ss; + }; + + class PP4 : public cv::ParallelLoopBody + { + public: + PP4(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) + : ss(scanSegment), ctv(countVec) {} + virtual ~PP4() {} + + void operator()(const cv::Range& range) const CV_OVERRIDE { - // set the number of process threads - processthreads = std::thread::hardware_concurrency(); - if (threads > 0) { - processthreads = MIN(processthreads, threads); + for (int v = range.start; v < range.end; v++) + { + ss->OP4((*ctv)[v]); } + } + private: + ScanSegmentImpl* const ss; + std::vector>* ctv; + }; + + void OP1(int v); + void OP2(std::pair const& p); + void OP3(int v); + void OP4(std::pair const& p); + void expandCluster(int* offsetBuffer, const cv::Point& point); + void calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID); + static int allocWSNodes(std::vector& storage); + static void watershedEx(const cv::Mat& src, cv::Mat& dst); +}; + +CV_EXPORTS Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads, bool merge_small) +{ + return makePtr(image_width, image_height, num_superpixels, threads, merge_small); +} - width = image_width; - height = image_height; - superpixels = num_superpixels; - merge = merge_small; - indexSize = height * width; - clusterSize = (int)(1.1f * (float)(width * height) / (float)superpixels); - clusterCount = 0; - labelsMat = cv::Mat(height, width, CV_32SC1); - - // divide bounds area into uniformly distributed rectangular segments - int shortCount = (int)floorf(sqrtf((float)processthreads)); - int longCount = processthreads / shortCount; - horzDiv = width > height ? longCount : shortCount; - vertDiv = width > height ? shortCount : longCount; - horzLength = (float)width / (float)horzDiv; - vertLength = (float)height / (float)vertDiv; - effectivethreads = horzDiv * vertDiv; - smallClusters = 0; - - // get array of seed rects - seedRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); - seedRectsExt = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); - offsetRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); - for (int y = 0; y < vertDiv; y++) { - for (int x = 0; x < horzDiv; x++) { - int xStart = (int)((float)x * horzLength); - int yStart = (int)((float)y * vertLength); - cv::Rect seedRect = cv::Rect(xStart, yStart, (int)(x == horzDiv - 1 ? width - xStart : horzLength), (int)(y == vertDiv - 1 ? height - yStart : vertLength)); - - int bnd_l = seedRect.x; - int bnd_t = seedRect.y; - int bnd_r = seedRect.x + seedRect.width - 1; - int bnd_b = seedRect.y + seedRect.height - 1; - if (bnd_l > 0) { - bnd_l -= 1; - } - if (bnd_t > 0) { - bnd_t -= 1; - } - if (bnd_r < width - 1) { - bnd_r += 1; - } - if (bnd_b < height - 1) { - bnd_b += 1; - } - - seedRects[(y * horzDiv) + x] = seedRect; - seedRectsExt[(y * horzDiv) + x] = cv::Rect(bnd_l, bnd_t, bnd_r - bnd_l + 1, bnd_b - bnd_t + 1); - offsetRects[(y * horzDiv) + x] = cv::Rect(seedRect.x - bnd_l, seedRect.y - bnd_t, seedRect.width, seedRect.height); - } +ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int threads, bool merge_small) +{ + // set the number of process threads + processthreads = std::thread::hardware_concurrency(); + if (threads > 0) { + processthreads = MIN(processthreads, threads); + } + + width = image_width; + height = image_height; + superpixels = num_superpixels; + merge = merge_small; + indexSize = height * width; + clusterSize = (int)(1.1f * (float)(width * height) / (float)superpixels); + clusterCount = 0; + labelsMat = cv::Mat(height, width, CV_32SC1); + + // divide bounds area into uniformly distributed rectangular segments + int shortCount = (int)floorf(sqrtf((float)processthreads)); + int longCount = processthreads / shortCount; + horzDiv = width > height ? longCount : shortCount; + vertDiv = width > height ? shortCount : longCount; + horzLength = (float)width / (float)horzDiv; + vertLength = (float)height / (float)vertDiv; + effectivethreads = horzDiv * vertDiv; + smallClusters = 0; + + // get array of seed rects + seedRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + seedRectsExt = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + offsetRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + for (int y = 0; y < vertDiv; y++) { + for (int x = 0; x < horzDiv; x++) { + int xStart = (int)((float)x * horzLength); + int yStart = (int)((float)y * vertLength); + cv::Rect seedRect = cv::Rect(xStart, yStart, (int)(x == horzDiv - 1 ? width - xStart : horzLength), (int)(y == vertDiv - 1 ? height - yStart : vertLength)); + + int bnd_l = seedRect.x; + int bnd_t = seedRect.y; + int bnd_r = seedRect.x + seedRect.width - 1; + int bnd_b = seedRect.y + seedRect.height - 1; + if (bnd_l > 0) { + bnd_l -= 1; } - - // get adjusted tolerance = (100 / average length (horz/vert)) x sqrt(3) [ie. euclidean lab colour distance sqrt(l2 + a2 + b2)] x tolerance100 - adjTolerance = (200.0f / (width + height)) * sqrtf(3) * tolerance100; - adjTolerance = adjTolerance * adjTolerance; - - // create neighbour vector - indexNeighbourVec = std::vector(effectivethreads); - std::iota(indexNeighbourVec.begin(), indexNeighbourVec.end(), 0); - - // create process vector - indexProcessVec = std::vector>(processthreads); - int processDiv = indexSize / processthreads; - int processCurrent = 0; - for (int i = 0; i < processthreads - 1; i++) { - indexProcessVec[i] = std::make_pair(processCurrent, processCurrent + processDiv); - processCurrent += processDiv; + if (bnd_t > 0) { + bnd_t -= 1; } - indexProcessVec[processthreads - 1] = std::make_pair(processCurrent, indexSize); - - // create buffers and initialise - std::vector tempLoc{ cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; - neighbourLoc = static_cast(malloc(8 * sizeof(cv::Point))); - memcpy(neighbourLoc, tempLoc.data(), 8 * sizeof(cv::Point)); - - labelsBuffer = static_cast(malloc(indexSize * sizeof(int))); - clusterBuffer = static_cast(malloc(indexSize * sizeof(int))); - pixelBuffer = static_cast(malloc(indexSize)); - offsetVec = std::vector(effectivethreads); - int offsetSize = (clusterSize + 1) * sizeof(int); - bool offsetAllocated = true; - for (int i = 0; i < effectivethreads; i++) { - offsetVec[i] = static_cast(malloc(offsetSize)); - if (offsetVec[i] == NULL) { - offsetAllocated = false; - } + if (bnd_r < width - 1) { + bnd_r += 1; } - for (int i = 0; i < neighbourCount; i++) { - neighbourLocBuffer[i] = (neighbourLoc[i].y * width) + neighbourLoc[i].x; + if (bnd_b < height - 1) { + bnd_b += 1; } - if (labelsBuffer != NULL && clusterBuffer != NULL && pixelBuffer != NULL && offsetAllocated) { - setupComplete = true; - } - else { - setupComplete = false; + seedRects[(y * horzDiv) + x] = seedRect; + seedRectsExt[(y * horzDiv) + x] = cv::Rect(bnd_l, bnd_t, bnd_r - bnd_l + 1, bnd_b - bnd_t + 1); + offsetRects[(y * horzDiv) + x] = cv::Rect(seedRect.x - bnd_l, seedRect.y - bnd_t, seedRect.width, seedRect.height); + } + } + + // get adjusted tolerance = (100 / average length (horz/vert)) x sqrt(3) [ie. euclidean lab colour distance sqrt(l2 + a2 + b2)] x tolerance100 + adjTolerance = (200.0f / (width + height)) * sqrtf(3) * tolerance100; + adjTolerance = adjTolerance * adjTolerance; + + // create neighbour vector + indexNeighbourVec = std::vector(effectivethreads); + std::iota(indexNeighbourVec.begin(), indexNeighbourVec.end(), 0); + + // create process vector + indexProcessVec = std::vector>(processthreads); + int processDiv = indexSize / processthreads; + int processCurrent = 0; + for (int i = 0; i < processthreads - 1; i++) { + indexProcessVec[i] = std::make_pair(processCurrent, processCurrent + processDiv); + processCurrent += processDiv; + } + indexProcessVec[processthreads - 1] = std::make_pair(processCurrent, indexSize); + + // create buffers and initialise + std::vector tempLoc{ cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; + neighbourLoc = static_cast(malloc(8 * sizeof(cv::Point))); + memcpy(neighbourLoc, tempLoc.data(), 8 * sizeof(cv::Point)); + + labelsBuffer = static_cast(malloc(indexSize * sizeof(int))); + clusterBuffer = static_cast(malloc(indexSize * sizeof(int))); + pixelBuffer = static_cast(malloc(indexSize)); + offsetVec = std::vector(effectivethreads); + int offsetSize = (clusterSize + 1) * sizeof(int); + bool offsetAllocated = true; + for (int i = 0; i < effectivethreads; i++) { + offsetVec[i] = static_cast(malloc(offsetSize)); + if (offsetVec[i] == NULL) { + offsetAllocated = false; + } + } + for (int i = 0; i < neighbourCount; i++) { + neighbourLocBuffer[i] = (neighbourLoc[i].y * width) + neighbourLoc[i].x; + } + + if (labelsBuffer != NULL && clusterBuffer != NULL && pixelBuffer != NULL && offsetAllocated) { + setupComplete = true; + } + else { + setupComplete = false; + + if (labelsBuffer == NULL) { + CV_Error(Error::StsInternal, "Cannot initialise labels buffer"); + } + if (clusterBuffer == NULL) { + CV_Error(Error::StsInternal, "Cannot initialise cluster buffer"); + } + if (pixelBuffer == NULL) { + CV_Error(Error::StsInternal, "Cannot initialise pixel buffer"); + } + if (!offsetAllocated) { + CV_Error(Error::StsInternal, "Cannot initialise offset buffers"); + } + } +} - if (labelsBuffer == NULL) { - CV_Error(Error::StsInternal, "Cannot initialise labels buffer"); - } - if (clusterBuffer == NULL) { - CV_Error(Error::StsInternal, "Cannot initialise cluster buffer"); - } - if (pixelBuffer == NULL) { - CV_Error(Error::StsInternal, "Cannot initialise pixel buffer"); - } - if (!offsetAllocated) { - CV_Error(Error::StsInternal, "Cannot initialise offset buffers"); - } - } +ScanSegmentImpl::~ScanSegmentImpl() +{ + // clean up + if (neighbourLoc != NULL) { + free(neighbourLoc); + } + if (seedRects != NULL) { + free(seedRects); + } + if (seedRectsExt != NULL) { + free(seedRectsExt); + } + if (offsetRects != NULL) { + free(offsetRects); + } + if (labelsBuffer != NULL) { + free(labelsBuffer); + } + if (clusterBuffer != NULL) { + free(clusterBuffer); + } + if (pixelBuffer != NULL) { + free(pixelBuffer); + } + for (int i = 0; i < effectivethreads; i++) { + if (offsetVec[i] != NULL) { + free(offsetVec[i]); } + } + if (!src.empty()) { + src.release(); + } + if (!labelsMat.empty()) { + labelsMat.release(); + } +} - ScanSegmentImpl::~ScanSegmentImpl() - { - // clean up - if (neighbourLoc != NULL) { - free(neighbourLoc); - } - if (seedRects != NULL) { - free(seedRects); - } - if (seedRectsExt != NULL) { - free(seedRectsExt); - } - if (offsetRects != NULL) { - free(offsetRects); - } - if (labelsBuffer != NULL) { - free(labelsBuffer); - } - if (clusterBuffer != NULL) { - free(clusterBuffer); - } - if (pixelBuffer != NULL) { - free(pixelBuffer); - } - for (int i = 0; i < effectivethreads; i++) { - if (offsetVec[i] != NULL) { - free(offsetVec[i]); - } - } - if (!src.empty()) { - src.release(); - } - if (!labelsMat.empty()) { - labelsMat.release(); +void ScanSegmentImpl::iterate(InputArray img) +{ + // ensure setup successfully completed + CV_Assert(setupComplete); + + if (img.isMat()) + { + // get Mat + src = img.getMat(); + + // image should be valid + CV_Assert(!src.empty()); + } + else if (img.isMatVector()) + { + std::vector vec; + + // get vector Mat + img.getMatVector(vec); + + // array should be valid + CV_Assert(!vec.empty()); + + // merge into Mat + cv::merge(vec, src); + } + else + CV_Error(Error::StsInternal, "Invalid InputArray."); + + int depth = src.depth(); + + CV_Assert(src.size().width == width && src.size().height == height); + CV_Assert(depth == CV_8U); + CV_Assert(src.channels() == 3); + + clusterCount = 0; + clusterIndex.store(0); + clusterID.store(1); + + smallClusters = indexSize / smallClustersDiv; + + // set labels to NONE + labelsMat.setTo(NONE); + + // set labels buffer to UNCLASSIFIED + std::fill(labelsBuffer, labelsBuffer + indexSize, UNCLASSIFIED); + + // apply light blur + cv::medianBlur(src, src, 3); + + // start at the center of the rect, then run through the remainder + labBuffer = reinterpret_cast(src.data); + cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP1(reinterpret_cast(this))); + + if (merge) { + // get cutoff size for clusters + std::vector> countVec; + int clusterIndexSize = clusterIndex.load(); + countVec.reserve(clusterIndexSize / 2); + for (int i = 1; i < clusterIndexSize; i += 2) { + int count = clusterBuffer[i]; + if (count >= smallClusters) { + int currentID = clusterBuffer[i - 1]; + countVec.push_back(std::make_pair(currentID, count)); } } - void ScanSegmentImpl::iterate(InputArray img) - { - // ensure setup successfully completed - CV_Assert(setupComplete); + // sort descending + std::sort(countVec.begin(), countVec.end(), [](const std::pair& left, const std::pair& right) { + return left.second > right.second; + }); - if (img.isMat()) - { - // get Mat - src = img.getMat(); + int countSize = (int)countVec.size(); + int cutoff = MAX(smallClusters, countVec[MIN(countSize - 1, superpixels - 1)].second); + clusterCount = (int)std::count_if(countVec.begin(), countVec.end(), [&cutoff](std::pair p) {return p.second > cutoff; }); - // image should be valid - CV_Assert(!src.empty()); - } - else if (img.isMatVector()) - { - std::vector vec; + // change labels to 1 -> clusterCount, 0 = UNKNOWN, reuse clusterbuffer + std::fill_n(clusterBuffer, indexSize, UNKNOWN); + int countLimit = cutoff == -1 ? (int)countVec.size() : clusterCount; + for (int i = 0; i < countLimit; i++) { + clusterBuffer[countVec[i].first] = i + 1; + } - // get vector Mat - img.getMatVector(vec); + cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP2(reinterpret_cast(this), &indexProcessVec)); - // array should be valid - CV_Assert(!vec.empty()); + // make copy of labels buffer + memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); - // merge into Mat - cv::merge(vec, src); - } - else - CV_Error(Error::StsInternal, "Invalid InputArray."); + // run watershed + cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP3(reinterpret_cast(this))); - int depth = src.depth(); + // copy back to labels mat + cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP4(reinterpret_cast(this), &indexProcessVec)); + } + else { + memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); + } - CV_Assert(src.size().width == width && src.size().height == height); - CV_Assert(depth == CV_8U); - CV_Assert(src.channels() == 3); + src.release(); +} - clusterCount = 0; - clusterIndex.store(0); - clusterID.store(1); +void ScanSegmentImpl::OP1(int v) +{ + cv::Rect seedRect = seedRects[v]; + for (int y = seedRect.y; y < seedRect.y + seedRect.height; y++) { + for (int x = seedRect.x; x < seedRect.x + seedRect.width; x++) { + expandCluster(offsetVec[v], cv::Point(x, y)); + } + } +} - smallClusters = indexSize / smallClustersDiv; +void ScanSegmentImpl::OP2(std::pair const& p) +{ + std::pair& q = const_cast&>(p); + for (int i = q.first; i < q.second; i++) { + labelsBuffer[i] = clusterBuffer[labelsBuffer[i]]; + if (labelsBuffer[i] == UNKNOWN) { + pixelBuffer[i] = 255; + } + else { + pixelBuffer[i] = 0; + } + } +} - // set labels to NONE - labelsMat.setTo(NONE); +void ScanSegmentImpl::OP3(int v) +{ + cv::Rect seedRectExt = seedRectsExt[v]; + cv::Mat seedLabels = labelsMat(seedRectExt).clone(); + watershedEx(src(seedRectExt), seedLabels); + seedLabels(offsetRects[v]).copyTo(labelsMat(seedRects[v])); + seedLabels.release(); +} - // set labels buffer to UNCLASSIFIED - std::fill(labelsBuffer, labelsBuffer + indexSize, UNCLASSIFIED); +void ScanSegmentImpl::OP4(std::pair const& p) +{ + std::pair& q = const_cast&>(p); + for (int i = q.first; i < q.second; i++) { + if (pixelBuffer[i] == 0) { + ((int*)labelsMat.data)[i] = labelsBuffer[i] - 1; + } + else { + ((int*)labelsMat.data)[i] -= 1; + } + } +} - // apply light blur - cv::medianBlur(src, src, 3); +// expand clusters from a point +void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) +{ + int pointIndex = (point.y * width) + point.x; + if (labelsBuffer[pointIndex] == UNCLASSIFIED) { + int offsetStart = 0; + int offsetEnd = 0; + int currentClusterID = clusterID->fetch_add(1); - // start at the center of the rect, then run through the remainder - labBuffer = reinterpret_cast(src.data); - cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP1(reinterpret_cast(this))); + calculateCluster(offsetBuffer, &offsetEnd, pointIndex, currentClusterID); - if (merge) { - // get cutoff size for clusters - std::vector> countVec; - int clusterIndexSize = clusterIndex.load(); - countVec.reserve(clusterIndexSize / 2); - for (int i = 1; i < clusterIndexSize; i += 2) { - int count = clusterBuffer[i]; - if (count >= smallClusters) { - int currentID = clusterBuffer[i - 1]; - countVec.push_back(std::make_pair(currentID, count)); - } - } + if (offsetStart == offsetEnd) { + labelsBuffer[pointIndex] = UNKNOWN; + } + else { + // set cluster id and get core point index + labelsBuffer[pointIndex] = currentClusterID; + + while (offsetStart < offsetEnd) { + int intoffset2 = *(offsetBuffer + offsetStart); + offsetStart++; + calculateCluster(offsetBuffer, &offsetEnd, intoffset2, currentClusterID); + } - // sort descending - std::sort(countVec.begin(), countVec.end(), [](const std::pair& left, const std::pair& right) { - return left.second > right.second; - }); + // add origin point + offsetBuffer[offsetEnd] = pointIndex; + offsetEnd++; - int countSize = (int)countVec.size(); - int cutoff = MAX(smallClusters, countVec[MIN(countSize - 1, superpixels - 1)].second); - clusterCount = (int)std::count_if(countVec.begin(), countVec.end(), [&cutoff](std::pair p) {return p.second > cutoff; }); + // store to buffer + int currentClusterIndex = clusterIndex->fetch_add(2); + clusterBuffer[currentClusterIndex] = currentClusterID; + clusterBuffer[currentClusterIndex + 1] = offsetEnd; + } + } +} - // change labels to 1 -> clusterCount, 0 = UNKNOWN, reuse clusterbuffer - std::fill_n(clusterBuffer, indexSize, UNKNOWN); - int countLimit = cutoff == -1 ? (int)countVec.size() : clusterCount; - for (int i = 0; i < countLimit; i++) { - clusterBuffer[countVec[i].first] = i + 1; +void ScanSegmentImpl::calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID) +{ + for (int i = 0; i < neighbourCount; i++) { + if (*offsetEnd < clusterSize) { + int intoffset2 = pointIndex + neighbourLocBuffer[i]; + if (intoffset2 >= 0 && intoffset2 < indexSize && labelsBuffer[intoffset2] == UNCLASSIFIED) { + int diff1 = (int)labBuffer[pointIndex][0] - (int)labBuffer[intoffset2][0]; + int diff2 = (int)labBuffer[pointIndex][1] - (int)labBuffer[intoffset2][1]; + int diff3 = (int)labBuffer[pointIndex][2] - (int)labBuffer[intoffset2][2]; + + if ((diff1 * diff1) + (diff2 * diff2) + (diff3 * diff3) <= (int)adjTolerance) { + labelsBuffer[intoffset2] = currentClusterID; + offsetBuffer[*offsetEnd] = intoffset2; + (*offsetEnd)++; } + } + } + else { break; } + } +} - cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP2(reinterpret_cast(this), &indexProcessVec)); +int ScanSegmentImpl::allocWSNodes(std::vector& storage) +{ + int sz = (int)storage.size(); + int newsz = MAX(128, sz * 3 / 2); + + storage.resize(newsz); + if (sz == 0) + { + storage[0].next = 0; + sz = 1; + } + for (int i = sz; i < newsz - 1; i++) + storage[i].next = i + 1; + storage[newsz - 1].next = 0; + return sz; +} - // make copy of labels buffer - memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); +//the modified version of watershed algorithm from OpenCV +void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) +{ + // https://github.com/Seaball/watershed_with_mask + + // Labels for pixels + const int IN_QUEUE = -2; // Pixel visited + // possible bit values = 2^8 + const int NQ = 256; + + cv::Size size = src.size(); + int channel = 3; + // Vector of every created node + std::vector storage; + int free_node = 0, node; + // Priority queue of queues of nodes + // from high priority (0) to low priority (255) + WSQueue q[NQ]; + // Non-empty queue with highest priority + int active_queue; + int i, j; + // Color differences + int db, dg, dr; + int subs_tab[513]; + + // MAX(a,b) = b + MAX(a-b,0) +#define ws_max(a,b) ((b) + subs_tab[(a)-(b)+NQ]) +// MIN(a,b) = a - MAX(a-b,0) +#define ws_min(a,b) ((a) - subs_tab[(a)-(b)+NQ]) - // run watershed - cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP3(reinterpret_cast(this))); +// Create a new node with offsets mofs and iofs in queue idx +#define ws_push(idx,mofs,iofs) \ +{ \ +if (!free_node) \ +free_node = allocWSNodes(storage); \ +node = free_node; \ +free_node = storage[free_node].next; \ +storage[node].next = 0; \ +storage[node].mask_ofs = mofs; \ +storage[node].img_ofs = iofs; \ +if (q[idx].last) \ +storage[q[idx].last].next = node; \ +else \ +q[idx].first = node; \ +q[idx].last = node; \ +} - // copy back to labels mat - cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP4(reinterpret_cast(this), &indexProcessVec)); - } - else { - memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); - } +// Get next node from queue idx +#define ws_pop(idx,mofs,iofs) \ +{ \ +node = q[idx].first; \ +q[idx].first = storage[node].next; \ +if (!storage[node].next) \ +q[idx].last = 0; \ +storage[node].next = free_node; \ +free_node = node; \ +mofs = storage[node].mask_ofs; \ +iofs = storage[node].img_ofs; \ +} - src.release(); - } +// Get highest absolute channel difference in diff +#define c_diff(ptr1,ptr2,diff) \ +{ \ +db = std::abs((ptr1)[0] - (ptr2)[0]); \ +dg = std::abs((ptr1)[1] - (ptr2)[1]); \ +dr = std::abs((ptr1)[2] - (ptr2)[2]); \ +diff = ws_max(db, dg); \ +diff = ws_max(diff, dr); \ +assert(0 <= diff && diff <= 255); \ +} - void ScanSegmentImpl::OP1(int v) - { - cv::Rect seedRect = seedRects[v]; - for (int y = seedRect.y; y < seedRect.y + seedRect.height; y++) { - for (int x = seedRect.x; x < seedRect.x + seedRect.width; x++) { - expandCluster(offsetVec[v], cv::Point(x, y)); + CV_Assert(src.type() == CV_8UC3 && dst.type() == CV_32SC1); + CV_Assert(src.size() == dst.size()); + + // Current pixel in input image + const uchar* img = src.ptr(); + // Step size to next row in input image + int istep = int(src.step / sizeof(img[0])); + + // Current pixel in mask image + int* mask = dst.ptr(); + // Step size to next row in mask image + int mstep = int(dst.step / sizeof(mask[0])); + + for (i = 0; i < 256; i++) + subs_tab[i] = 0; + for (i = 256; i <= 512; i++) + subs_tab[i] = i - 256; + + //for (j = 0; j < size.width; j++) + //mask[j] = mask[j + mstep*(size.height - 1)] = 0; + + // initial phase: put all the neighbor pixels of each marker to the ordered queue - + // determine the initial boundaries of the basins + for (i = 1; i < size.height - 1; i++) { + img += istep; mask += mstep; + mask[0] = mask[size.width - 1] = 0; // boundary pixels + + for (j = 1; j < size.width - 1; j++) { + int* m = mask + j; + if (m[0] < 0) + m[0] = 0; + if (m[0] == 0 && (m[-1] > 0 || m[1] > 0 || m[-mstep] > 0 || m[mstep] > 0)) + { + // Find smallest difference to adjacent markers + const uchar* ptr = img + j * channel; + int idx = 256, t; + if (m[-1] > 0) { + c_diff(ptr, ptr - channel, idx); } - } - } - - void ScanSegmentImpl::OP2(std::pair const& p) - { - std::pair& q = const_cast&>(p); - for (int i = q.first; i < q.second; i++) { - labelsBuffer[i] = clusterBuffer[labelsBuffer[i]]; - if (labelsBuffer[i] == UNKNOWN) { - pixelBuffer[i] = 255; + if (m[1] > 0) { + c_diff(ptr, ptr + channel, t); + idx = ws_min(idx, t); } - else { - pixelBuffer[i] = 0; + if (m[-mstep] > 0) { + c_diff(ptr, ptr - istep, t); + idx = ws_min(idx, t); } + if (m[mstep] > 0) { + c_diff(ptr, ptr + istep, t); + idx = ws_min(idx, t); + } + + // Add to according queue + assert(0 <= idx && idx <= 255); + ws_push(idx, i * mstep + j, i * istep + j * channel); + m[0] = IN_QUEUE;//initial unvisited } } - - void ScanSegmentImpl::OP3(int v) + } + // find the first non-empty queue + for (i = 0; i < NQ; i++) + if (q[i].first) + break; + + // if there is no markers, exit immediately + if (i == NQ) + return; + + active_queue = i;//first non-empty priority queue + img = src.ptr(); + mask = dst.ptr(); + + // recursively fill the basins + int diff = 0, temp = 0; + for (;;) + { + int mofs, iofs; + int lab = 0, t; + int* m; + const uchar* ptr; + + // Get non-empty queue with highest priority + // Exit condition: empty priority queue + if (q[active_queue].first == 0) { - cv::Rect seedRectExt = seedRectsExt[v]; - cv::Mat seedLabels = labelsMat(seedRectExt).clone(); - watershedEx(src(seedRectExt), seedLabels); - seedLabels(offsetRects[v]).copyTo(labelsMat(seedRects[v])); - seedLabels.release(); + for (i = active_queue + 1; i < NQ; i++) + if (q[i].first) + break; + if (i == NQ) + { + std::vector().swap(storage); + break; + } + active_queue = i; } - void ScanSegmentImpl::OP4(std::pair const& p) - { - std::pair& q = const_cast&>(p); - for (int i = q.first; i < q.second; i++) { - if (pixelBuffer[i] == 0) { - ((int*)labelsMat.data)[i] = labelsBuffer[i] - 1; - } - else { - ((int*)labelsMat.data)[i] -= 1; + // Get next node + ws_pop(active_queue, mofs, iofs); + int top = 1, bottom = 1, left = 1, right = 1; + if (0 <= mofs && mofs < mstep)//pixel on the top + top = 0; + if ((mofs % mstep) == 0)//pixel in the left column + left = 0; + if ((mofs + 1) % mstep == 0)//pixel in the right column + right = 0; + if (mstep * (size.height - 1) <= mofs && mofs < mstep * size.height)//pixel on the bottom + bottom = 0; + + // Calculate pointer to current pixel in input and marker image + m = mask + mofs; + ptr = img + iofs; + // Check surrounding pixels for labels to determine label for current pixel + if (left) {//the left point can be visited + t = m[-1]; + if (t > 0) { + lab = t; + c_diff(ptr, ptr - channel, diff); + } + } + if (right) {// Right point can be visited + t = m[1]; + if (t > 0) { + if (lab == 0) {//and this point didn't be labeled before + lab = t; + c_diff(ptr, ptr + channel, diff); + } + else if (t != lab) { + c_diff(ptr, ptr + channel, temp); + diff = ws_min(diff, temp); + if (diff == temp) + lab = t; } } } - - // expand clusters from a point - void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) - { - int pointIndex = (point.y * width) + point.x; - if (labelsBuffer[pointIndex] == UNCLASSIFIED) { - int offsetStart = 0; - int offsetEnd = 0; - int currentClusterID = clusterID.fetch_add(1); - - calculateCluster(offsetBuffer, &offsetEnd, pointIndex, currentClusterID); - - if (offsetStart == offsetEnd) { - labelsBuffer[pointIndex] = UNKNOWN; + if (top) { + t = m[-mstep]; // Top + if (t > 0) { + if (lab == 0) {//and this point didn't be labeled before + lab = t; + c_diff(ptr, ptr - istep, diff); + } + else if (t != lab) { + c_diff(ptr, ptr - istep, temp); + diff = ws_min(diff, temp); + if (diff == temp) + lab = t; } - else { - // set cluster id and get core point index - labelsBuffer[pointIndex] = currentClusterID; - - while (offsetStart < offsetEnd) { - int intoffset2 = *(offsetBuffer + offsetStart); - offsetStart++; - calculateCluster(offsetBuffer, &offsetEnd, intoffset2, currentClusterID); - } - - // add origin point - offsetBuffer[offsetEnd] = pointIndex; - offsetEnd++; - - // store to buffer - int currentClusterIndex = clusterIndex.fetch_add(2); - clusterBuffer[currentClusterIndex] = currentClusterID; - clusterBuffer[currentClusterIndex + 1] = offsetEnd; + } + } + if (bottom) { + t = m[mstep]; // Bottom + if (t > 0) { + if (lab == 0) { + lab = t; + } + else if (t != lab) { + c_diff(ptr, ptr + istep, temp); + diff = ws_min(diff, temp); + if (diff == temp) + lab = t; } } } + // Set label to current pixel in marker image + assert(lab != 0);//lab must be labeled with a nonzero number + m[0] = lab; - void ScanSegmentImpl::calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID) - { - for (int i = 0; i < neighbourCount; i++) { - if (*offsetEnd < clusterSize) { - int intoffset2 = pointIndex + neighbourLocBuffer[i]; - if (intoffset2 >= 0 && intoffset2 < indexSize && labelsBuffer[intoffset2] == UNCLASSIFIED) { - int diff1 = (int)labBuffer[pointIndex][0] - (int)labBuffer[intoffset2][0]; - int diff2 = (int)labBuffer[pointIndex][1] - (int)labBuffer[intoffset2][1]; - int diff3 = (int)labBuffer[pointIndex][2] - (int)labBuffer[intoffset2][2]; - - if ((diff1 * diff1) + (diff2 * diff2) + (diff3 * diff3) <= (int)adjTolerance) { - labelsBuffer[intoffset2] = currentClusterID; - offsetBuffer[*offsetEnd] = intoffset2; - (*offsetEnd)++; - } - } - } - else { break; } + // Add adjacent, unlabeled pixels to corresponding queue + if (left) { + if (m[-1] == 0)//left pixel with marker 0 + { + c_diff(ptr, ptr - channel, t); + ws_push(t, mofs - 1, iofs - channel); + active_queue = ws_min(active_queue, t); + m[-1] = IN_QUEUE; } } - int ScanSegmentImpl::allocWSNodes(std::vector& storage) + if (right) { - int sz = (int)storage.size(); - int newsz = MAX(128, sz * 3 / 2); - - storage.resize(newsz); - if (sz == 0) + if (m[1] == 0)//right pixel with marker 0 { - storage[0].next = 0; - sz = 1; + c_diff(ptr, ptr + channel, t); + ws_push(t, mofs + 1, iofs + channel); + active_queue = ws_min(active_queue, t); + m[1] = IN_QUEUE; } - for (int i = sz; i < newsz - 1; i++) - storage[i].next = i + 1; - storage[newsz - 1].next = 0; - return sz; } - //the modified version of watershed algorithm from OpenCV - void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) + if (top) { - // https://github.com/Seaball/watershed_with_mask - - // Labels for pixels - const int IN_QUEUE = -2; // Pixel visited - // possible bit values = 2^8 - const int NQ = 256; - - cv::Size size = src.size(); - int channel = 3; - // Vector of every created node - std::vector storage; - int free_node = 0, node; - // Priority queue of queues of nodes - // from high priority (0) to low priority (255) - WSQueue q[NQ]; - // Non-empty queue with highest priority - int active_queue; - int i, j; - // Color differences - int db, dg, dr; - int subs_tab[513]; - - // MAX(a,b) = b + MAX(a-b,0) -#define ws_max(a,b) ((b) + subs_tab[(a)-(b)+NQ]) - // MIN(a,b) = a - MAX(a-b,0) -#define ws_min(a,b) ((a) - subs_tab[(a)-(b)+NQ]) - - // Create a new node with offsets mofs and iofs in queue idx -#define ws_push(idx,mofs,iofs) \ - { \ - if (!free_node) \ - free_node = allocWSNodes(storage); \ - node = free_node; \ - free_node = storage[free_node].next; \ - storage[node].next = 0; \ - storage[node].mask_ofs = mofs; \ - storage[node].img_ofs = iofs; \ - if (q[idx].last) \ - storage[q[idx].last].next = node; \ - else \ - q[idx].first = node; \ - q[idx].last = node; \ - } - - // Get next node from queue idx -#define ws_pop(idx,mofs,iofs) \ - { \ - node = q[idx].first; \ - q[idx].first = storage[node].next; \ - if (!storage[node].next) \ - q[idx].last = 0; \ - storage[node].next = free_node; \ - free_node = node; \ - mofs = storage[node].mask_ofs; \ - iofs = storage[node].img_ofs; \ - } - - // Get highest absolute channel difference in diff -#define c_diff(ptr1,ptr2,diff) \ - { \ - db = std::abs((ptr1)[0] - (ptr2)[0]); \ - dg = std::abs((ptr1)[1] - (ptr2)[1]); \ - dr = std::abs((ptr1)[2] - (ptr2)[2]); \ - diff = ws_max(db, dg); \ - diff = ws_max(diff, dr); \ - assert(0 <= diff && diff <= 255); \ - } - - CV_Assert(src.type() == CV_8UC3 && dst.type() == CV_32SC1); - CV_Assert(src.size() == dst.size()); - - // Current pixel in input image - const uchar* img = src.ptr(); - // Step size to next row in input image - int istep = int(src.step / sizeof(img[0])); - - // Current pixel in mask image - int* mask = dst.ptr(); - // Step size to next row in mask image - int mstep = int(dst.step / sizeof(mask[0])); - - for (i = 0; i < 256; i++) - subs_tab[i] = 0; - for (i = 256; i <= 512; i++) - subs_tab[i] = i - 256; - - //for (j = 0; j < size.width; j++) - //mask[j] = mask[j + mstep*(size.height - 1)] = 0; - - // initial phase: put all the neighbor pixels of each marker to the ordered queue - - // determine the initial boundaries of the basins - for (i = 1; i < size.height - 1; i++) { - img += istep; mask += mstep; - mask[0] = mask[size.width - 1] = 0; // boundary pixels - - for (j = 1; j < size.width - 1; j++) { - int* m = mask + j; - if (m[0] < 0) - m[0] = 0; - if (m[0] == 0 && (m[-1] > 0 || m[1] > 0 || m[-mstep] > 0 || m[mstep] > 0)) - { - // Find smallest difference to adjacent markers - const uchar* ptr = img + j * channel; - int idx = 256, t; - if (m[-1] > 0) { - c_diff(ptr, ptr - channel, idx); - } - if (m[1] > 0) { - c_diff(ptr, ptr + channel, t); - idx = ws_min(idx, t); - } - if (m[-mstep] > 0) { - c_diff(ptr, ptr - istep, t); - idx = ws_min(idx, t); - } - if (m[mstep] > 0) { - c_diff(ptr, ptr + istep, t); - idx = ws_min(idx, t); - } - - // Add to according queue - assert(0 <= idx && idx <= 255); - ws_push(idx, i * mstep + j, i * istep + j * channel); - m[0] = IN_QUEUE;//initial unvisited - } - } + if (m[-mstep] == 0)//top pixel with marker 0 + { + c_diff(ptr, ptr - istep, t); + ws_push(t, mofs - mstep, iofs - istep); + active_queue = ws_min(active_queue, t); + m[-mstep] = IN_QUEUE; } - // find the first non-empty queue - for (i = 0; i < NQ; i++) - if (q[i].first) - break; - - // if there is no markers, exit immediately - if (i == NQ) - return; - - active_queue = i;//first non-empty priority queue - img = src.ptr(); - mask = dst.ptr(); + } - // recursively fill the basins - int diff = 0, temp = 0; - for (;;) + if (bottom) { + if (m[mstep] == 0)//down pixel with marker 0 { - int mofs, iofs; - int lab = 0, t; - int* m; - const uchar* ptr; - - // Get non-empty queue with highest priority - // Exit condition: empty priority queue - if (q[active_queue].first == 0) - { - for (i = active_queue + 1; i < NQ; i++) - if (q[i].first) - break; - if (i == NQ) - { - std::vector().swap(storage); - break; - } - active_queue = i; - } - - // Get next node - ws_pop(active_queue, mofs, iofs); - int top = 1, bottom = 1, left = 1, right = 1; - if (0 <= mofs && mofs < mstep)//pixel on the top - top = 0; - if ((mofs % mstep) == 0)//pixel in the left column - left = 0; - if ((mofs + 1) % mstep == 0)//pixel in the right column - right = 0; - if (mstep * (size.height - 1) <= mofs && mofs < mstep * size.height)//pixel on the bottom - bottom = 0; - - // Calculate pointer to current pixel in input and marker image - m = mask + mofs; - ptr = img + iofs; - // Check surrounding pixels for labels to determine label for current pixel - if (left) {//the left point can be visited - t = m[-1]; - if (t > 0) { - lab = t; - c_diff(ptr, ptr - channel, diff); - } - } - if (right) {// Right point can be visited - t = m[1]; - if (t > 0) { - if (lab == 0) {//and this point didn't be labeled before - lab = t; - c_diff(ptr, ptr + channel, diff); - } - else if (t != lab) { - c_diff(ptr, ptr + channel, temp); - diff = ws_min(diff, temp); - if (diff == temp) - lab = t; - } - } - } - if (top) { - t = m[-mstep]; // Top - if (t > 0) { - if (lab == 0) {//and this point didn't be labeled before - lab = t; - c_diff(ptr, ptr - istep, diff); - } - else if (t != lab) { - c_diff(ptr, ptr - istep, temp); - diff = ws_min(diff, temp); - if (diff == temp) - lab = t; - } - } - } - if (bottom) { - t = m[mstep]; // Bottom - if (t > 0) { - if (lab == 0) { - lab = t; - } - else if (t != lab) { - c_diff(ptr, ptr + istep, temp); - diff = ws_min(diff, temp); - if (diff == temp) - lab = t; - } - } - } - // Set label to current pixel in marker image - assert(lab != 0);//lab must be labeled with a nonzero number - m[0] = lab; - - // Add adjacent, unlabeled pixels to corresponding queue - if (left) { - if (m[-1] == 0)//left pixel with marker 0 - { - c_diff(ptr, ptr - channel, t); - ws_push(t, mofs - 1, iofs - channel); - active_queue = ws_min(active_queue, t); - m[-1] = IN_QUEUE; - } - } - - if (right) - { - if (m[1] == 0)//right pixel with marker 0 - { - c_diff(ptr, ptr + channel, t); - ws_push(t, mofs + 1, iofs + channel); - active_queue = ws_min(active_queue, t); - m[1] = IN_QUEUE; - } - } - - if (top) - { - if (m[-mstep] == 0)//top pixel with marker 0 - { - c_diff(ptr, ptr - istep, t); - ws_push(t, mofs - mstep, iofs - istep); - active_queue = ws_min(active_queue, t); - m[-mstep] = IN_QUEUE; - } - } - - if (bottom) { - if (m[mstep] == 0)//down pixel with marker 0 - { - c_diff(ptr, ptr + istep, t); - ws_push(t, mofs + mstep, iofs + istep); - active_queue = ws_min(active_queue, t); - m[mstep] = IN_QUEUE; - } - } + c_diff(ptr, ptr + istep, t); + ws_push(t, mofs + mstep, iofs + istep); + active_queue = ws_min(active_queue, t); + m[mstep] = IN_QUEUE; } } + } +} - void ScanSegmentImpl::getLabels(OutputArray labels_out) - { - labels_out.assign(labelsMat); - } +void ScanSegmentImpl::getLabels(OutputArray labels_out) +{ + labels_out.assign(labelsMat); +} - void ScanSegmentImpl::getLabelContourMask(OutputArray image, bool thick_line) - { - image.create(height, width, CV_8UC1); - cv::Mat dst = image.getMat(); - dst.setTo(cv::Scalar(0)); +void ScanSegmentImpl::getLabelContourMask(OutputArray image, bool thick_line) +{ + image.create(height, width, CV_8UC1); + cv::Mat dst = image.getMat(); + dst.setTo(cv::Scalar(0)); - const int dx8[8] = { -1, -1, 0, 1, 1, 1, 0, -1 }; - const int dy8[8] = { 0, -1, -1, -1, 0, 1, 1, 1 }; + const int dx8[8] = { -1, -1, 0, 1, 1, 1, 0, -1 }; + const int dy8[8] = { 0, -1, -1, -1, 0, 1, 1, 1 }; - for (int j = 0; j < height; j++) + for (int j = 0; j < height; j++) + { + for (int k = 0; k < width; k++) + { + int neighbors = 0; + for (int i = 0; i < 8; i++) { - for (int k = 0; k < width; k++) + int x = k + dx8[i]; + int y = j + dy8[i]; + + if ((x >= 0 && x < width) && (y >= 0 && y < height)) { - int neighbors = 0; - for (int i = 0; i < 8; i++) + int index = y * width + x; + int mainindex = j * width + k; + if (((int*)labelsMat.data)[mainindex] != ((int*)labelsMat.data)[index]) { - int x = k + dx8[i]; - int y = j + dy8[i]; - - if ((x >= 0 && x < width) && (y >= 0 && y < height)) - { - int index = y * width + x; - int mainindex = j * width + k; - if (((int*)labelsMat.data)[mainindex] != ((int*)labelsMat.data)[index]) - { - if (thick_line || !*dst.ptr(y, x)) - neighbors++; - } - } + if (thick_line || !*dst.ptr(y, x)) + neighbors++; } - if (neighbors > 1) - *dst.ptr(j, k) = (uchar)255; } } + if (neighbors > 1) + *dst.ptr(j, k) = (uchar)255; } - } // namespace ximgproc + } +} + +//! @} +} // namespace ximgproc } // namespace cv From a9680961347ddc8cbe16ecc8f3b8f68cc340c4d1 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 15:37:43 +1300 Subject: [PATCH 20/44] Update scansegment.cpp license agreement updated --- modules/ximgproc/src/scansegment.cpp | 37 +++------------------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index ffc45bf1e9..6ad95b33e5 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -1,41 +1,10 @@ //////////////////////////////////////////////////////////////////////////////////////// // -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library +// 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. // // Copyright (C) 2021, Dr Seng Cheong Loke (lokesengcheong@gmail.com) -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. // // From d98fa2c813206871c8ba1f3219edb8246c9b88d4 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 15:38:17 +1300 Subject: [PATCH 21/44] Update scansegment.hpp license agreement updated --- .../include/opencv2/ximgproc/scansegment.hpp | 37 ++----------------- 1 file changed, 3 insertions(+), 34 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 7f20fa0ea1..8df53416c5 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -1,41 +1,10 @@ //////////////////////////////////////////////////////////////////////////////////////// // -// IMPORTANT: READ BEFORE DOWNLOADING, COPYING, INSTALLING OR USING. -// -// By downloading, copying, installing or using the software you agree to this license. -// If you do not agree to this license, do not download, install, -// copy or use the software. -// -// -// License Agreement -// For Open Source Computer Vision Library +// 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. // // Copyright (C) 2021, Dr Seng Cheong Loke (lokesengcheong@gmail.com) -// Third party copyrights are property of their respective owners. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistribution's of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * Redistribution's in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation -// and/or other materials provided with the distribution. -// -// * The name of the copyright holders may not be used to endorse or promote products -// derived from this software without specific prior written permission. -// -// This software is provided by the copyright holders and contributors "as is" and -// any express or implied warranties, including, but not limited to, the implied -// warranties of merchantability and fitness for a particular purpose are disclaimed. -// In no event shall the Intel Corporation or contributors be liable for any direct, -// indirect, incidental, special, exemplary, or consequential damages -// (including, but not limited to, procurement of substitute goods or services; -// loss of use, data, or profits; or business interruption) however caused -// and on any theory of liability, whether in contract, strict liability, -// or tort (including negligence or otherwise) arising in any way out of -// the use of this software, even if advised of the possibility of such damage. // // From a2bdd041743c69919accfd6bd1eb3c4a2958de65 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 15:46:29 +1300 Subject: [PATCH 22/44] Update ximgproc.bib --- modules/ximgproc/doc/ximgproc.bib | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/modules/ximgproc/doc/ximgproc.bib b/modules/ximgproc/doc/ximgproc.bib index 39776cb987..c521b69aa9 100644 --- a/modules/ximgproc/doc/ximgproc.bib +++ b/modules/ximgproc/doc/ximgproc.bib @@ -380,3 +380,12 @@ @article{akinlar2013edcircles year={2013}, publisher={Elsevier} } + +@article{loke2021accelerated, + title={Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm}, + author={Loke, Seng Cheong and MacDonald, Bruce A and Parsons, Matthew and W{\"u}nsche, Burkhard Claus}, + journal={Journal of Real-Time Image Processing}, + pages={1--16}, + year={2021}, + publisher={Springer} +} From 0b3bfe40c4bf6bacbafbcd435ea76da1f20c9a53 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 15:48:32 +1300 Subject: [PATCH 23/44] Update scansegment.hpp reference moved to ximgproc.bib --- .../include/opencv2/ximgproc/scansegment.hpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 8df53416c5..9666e17276 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -8,18 +8,6 @@ // // - /* - * BibTeX reference -@article{loke2021accelerated, - title={Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm}, - author={Loke, Seng Cheong and MacDonald, Bruce A and Parsons, Matthew and W{\"u}nsche, Burkhard Claus}, - journal={Journal of Real-Time Image Processing}, - pages={1--16}, - year={2021}, - publisher={Springer} -} -*/ - #ifndef __OPENCV_SCANSEGMENT_HPP__ #define __OPENCV_SCANSEGMENT_HPP__ #ifdef __cplusplus From a2d5fe872d5144c2017a6b7b88a9d7a0f8273926 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 15:49:06 +1300 Subject: [PATCH 24/44] Update scansegment.cpp reference moved to ximgproc.bib --- modules/ximgproc/src/scansegment.cpp | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 6ad95b33e5..554042c0b8 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -8,18 +8,6 @@ // // - /* - * BibTeX reference -@article{loke2021accelerated, - title={Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm}, - author={Loke, Seng Cheong and MacDonald, Bruce A and Parsons, Matthew and W{\"u}nsche, Burkhard Claus}, - journal={Journal of Real-Time Image Processing}, - pages={1--16}, - year={2021}, - publisher={Springer} -} - */ - #include "precomp.hpp" #include From 5e950ee11e3526e987274efc53ccb79b1ab41f84 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 15:51:20 +1300 Subject: [PATCH 25/44] Update scansegment.hpp c++ def removed --- modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 9666e17276..43206fa1c3 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -10,7 +10,6 @@ #ifndef __OPENCV_SCANSEGMENT_HPP__ #define __OPENCV_SCANSEGMENT_HPP__ -#ifdef __cplusplus #include @@ -80,4 +79,3 @@ CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_h } } #endif -#endif From d09220327a81123a6ada06cae4d173508605355c Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 15:59:54 +1300 Subject: [PATCH 26/44] Update scansegment.hpp changed threads param --- modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 43206fa1c3..c3c3897c94 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -67,7 +67,7 @@ class CV_EXPORTS_W ScanSegment : public Algorithm @param num_superpixels Desired number of superpixels. Note that the actual number may be smaller due to restrictions (depending on the image size). Use getNumberOfSuperpixels() to get the actual number. -@param threads Number of processing threads for parallelisation. Default -1 uses the maximum number +@param slices Number of processing threads for parallelisation. Setting -1 uses the maximum number of threads. In practice, four threads is enough for smaller images and eight threads for larger ones. @param merge_small merge small segments to give the desired number of superpixels. Processing is much faster without merging, but many small segments will be left in the image. @@ -75,7 +75,7 @@ The function initializes a ScanSegment object for the input image. It stores the the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel algorithm, which are: num_superpixels, threads, and merge_small. */ -CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads = -1, bool merge_small = true); +CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int slices = 8, bool merge_small = true); } } #endif From 86521fcdfd318d6b1ebc2a835a991be7acf92e51 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 16:03:12 +1300 Subject: [PATCH 27/44] Update scansegment.cpp changed threads param --- modules/ximgproc/src/scansegment.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 554042c0b8..73aebe4ba7 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -28,7 +28,7 @@ class ScanSegmentImpl : public ScanSegment public: - ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int threads, bool merge_small); + ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int slices, bool merge_small); virtual ~ScanSegmentImpl(); @@ -176,17 +176,17 @@ class ScanSegmentImpl : public ScanSegment static void watershedEx(const cv::Mat& src, cv::Mat& dst); }; -CV_EXPORTS Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int threads, bool merge_small) +CV_EXPORTS Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int slices, bool merge_small) { - return makePtr(image_width, image_height, num_superpixels, threads, merge_small); + return makePtr(image_width, image_height, num_superpixels, slices, merge_small); } -ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int threads, bool merge_small) +ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int slices, bool merge_small) { // set the number of process threads processthreads = std::thread::hardware_concurrency(); - if (threads > 0) { - processthreads = MIN(processthreads, threads); + if (slices > 0) { + processthreads = MIN(processthreads, slices); } width = image_width; From bcbfdc323cab7a888e01bccd76779449fb6ca39d Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 16:12:54 +1300 Subject: [PATCH 28/44] Update scansegment.cpp tab indents replaced with 4 spaces --- modules/ximgproc/src/scansegment.cpp | 1486 +++++++++++++------------- 1 file changed, 743 insertions(+), 743 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 73aebe4ba7..11dde4548c 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -28,563 +28,563 @@ class ScanSegmentImpl : public ScanSegment public: - ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int slices, bool merge_small); + ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int slices, bool merge_small); - virtual ~ScanSegmentImpl(); + virtual ~ScanSegmentImpl(); - virtual int getNumberOfSuperpixels() CV_OVERRIDE { return clusterCount; } + virtual int getNumberOfSuperpixels() CV_OVERRIDE { return clusterCount; } - virtual void iterate(InputArray img) CV_OVERRIDE; + virtual void iterate(InputArray img) CV_OVERRIDE; - virtual void getLabels(OutputArray labels_out) CV_OVERRIDE; + virtual void getLabels(OutputArray labels_out) CV_OVERRIDE; - virtual void getLabelContourMask(OutputArray image, bool thick_line = false) CV_OVERRIDE; + virtual void getLabelContourMask(OutputArray image, bool thick_line = false) CV_OVERRIDE; private: - static const int neighbourCount = 8; // number of pixel neighbours - static const int smallClustersDiv = 10000; // divide total pixels by this to give smallClusters - const float tolerance100 = 10.0f; // colour tolerance for image size of 100x100px - - int processthreads; // concurrent threads for parallel processing - int width, height; // image size - int superpixels; // number of superpixels - bool merge; // merge small superpixels - int indexSize; // size of label mat vector - int clusterSize; // max size of clusters - bool setupComplete; // is setup complete - int clusterCount; // number of superpixels from the most recent iterate - float adjTolerance; // adjusted colour tolerance - - int horzDiv, vertDiv; // number of horizontal and vertical segments - float horzLength, vertLength; // length of each segment - int effectivethreads; // effective number of concurrent threads - int smallClusters; // clusters below this pixel count are considered small for merging - cv::Rect* seedRects; // array of seed rectangles - cv::Rect* seedRectsExt; // array of extended seed rectangles - cv::Rect* offsetRects; // array of offset rectangles - cv::Point* neighbourLoc; // neighbour locations - - std::vector indexNeighbourVec; // indices for parallel processing - std::vector> indexProcessVec; - - int* labelsBuffer; // label buffer - int* clusterBuffer; // cluster buffer - cv::Vec3b* labBuffer; // lab buffer - uchar* pixelBuffer; // pixel buffer - int neighbourLocBuffer[neighbourCount]; // neighbour locations - std::vector offsetVec; // vector of offsets - - std::atomic clusterIndex, clusterID; // atomic indices - - cv::Mat src, labelsMat; // mats - - struct WSNode - { - int next; - int mask_ofs; - int img_ofs; - }; - - // Queue for WSNodes - struct WSQueue - { - WSQueue() { first = last = 0; } - int first, last; - }; - - class PP1 : public cv::ParallelLoopBody - { - public: - PP1(ScanSegmentImpl* const scanSegment) - : ss(scanSegment) {} - virtual ~PP1() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP1(v); - } - } - private: - ScanSegmentImpl* const ss; - }; - - class PP2 : public cv::ParallelLoopBody - { - public: - PP2(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) - : ss(scanSegment), ctv(countVec) {} - virtual ~PP2() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP2((*ctv)[v]); - } - } - private: - ScanSegmentImpl* const ss; - std::vector>* ctv; - }; - - class PP3 : public cv::ParallelLoopBody - { - public: - PP3(ScanSegmentImpl* const scanSegment) - : ss(scanSegment) {} - virtual ~PP3() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP3(v); - } - } - private: - ScanSegmentImpl* const ss; - }; - - class PP4 : public cv::ParallelLoopBody - { - public: - PP4(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) - : ss(scanSegment), ctv(countVec) {} - virtual ~PP4() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP4((*ctv)[v]); - } - } - private: - ScanSegmentImpl* const ss; - std::vector>* ctv; - }; - - void OP1(int v); - void OP2(std::pair const& p); - void OP3(int v); - void OP4(std::pair const& p); - void expandCluster(int* offsetBuffer, const cv::Point& point); - void calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID); - static int allocWSNodes(std::vector& storage); - static void watershedEx(const cv::Mat& src, cv::Mat& dst); + static const int neighbourCount = 8; // number of pixel neighbours + static const int smallClustersDiv = 10000; // divide total pixels by this to give smallClusters + const float tolerance100 = 10.0f; // colour tolerance for image size of 100x100px + + int processthreads; // concurrent threads for parallel processing + int width, height; // image size + int superpixels; // number of superpixels + bool merge; // merge small superpixels + int indexSize; // size of label mat vector + int clusterSize; // max size of clusters + bool setupComplete; // is setup complete + int clusterCount; // number of superpixels from the most recent iterate + float adjTolerance; // adjusted colour tolerance + + int horzDiv, vertDiv; // number of horizontal and vertical segments + float horzLength, vertLength; // length of each segment + int effectivethreads; // effective number of concurrent threads + int smallClusters; // clusters below this pixel count are considered small for merging + cv::Rect* seedRects; // array of seed rectangles + cv::Rect* seedRectsExt; // array of extended seed rectangles + cv::Rect* offsetRects; // array of offset rectangles + cv::Point* neighbourLoc; // neighbour locations + + std::vector indexNeighbourVec; // indices for parallel processing + std::vector> indexProcessVec; + + int* labelsBuffer; // label buffer + int* clusterBuffer; // cluster buffer + cv::Vec3b* labBuffer; // lab buffer + uchar* pixelBuffer; // pixel buffer + int neighbourLocBuffer[neighbourCount]; // neighbour locations + std::vector offsetVec; // vector of offsets + + std::atomic clusterIndex, clusterID; // atomic indices + + cv::Mat src, labelsMat; // mats + + struct WSNode + { + int next; + int mask_ofs; + int img_ofs; + }; + + // Queue for WSNodes + struct WSQueue + { + WSQueue() { first = last = 0; } + int first, last; + }; + + class PP1 : public cv::ParallelLoopBody + { + public: + PP1(ScanSegmentImpl* const scanSegment) + : ss(scanSegment) {} + virtual ~PP1() {} + + void operator()(const cv::Range& range) const CV_OVERRIDE + { + for (int v = range.start; v < range.end; v++) + { + ss->OP1(v); + } + } + private: + ScanSegmentImpl* const ss; + }; + + class PP2 : public cv::ParallelLoopBody + { + public: + PP2(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) + : ss(scanSegment), ctv(countVec) {} + virtual ~PP2() {} + + void operator()(const cv::Range& range) const CV_OVERRIDE + { + for (int v = range.start; v < range.end; v++) + { + ss->OP2((*ctv)[v]); + } + } + private: + ScanSegmentImpl* const ss; + std::vector>* ctv; + }; + + class PP3 : public cv::ParallelLoopBody + { + public: + PP3(ScanSegmentImpl* const scanSegment) + : ss(scanSegment) {} + virtual ~PP3() {} + + void operator()(const cv::Range& range) const CV_OVERRIDE + { + for (int v = range.start; v < range.end; v++) + { + ss->OP3(v); + } + } + private: + ScanSegmentImpl* const ss; + }; + + class PP4 : public cv::ParallelLoopBody + { + public: + PP4(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) + : ss(scanSegment), ctv(countVec) {} + virtual ~PP4() {} + + void operator()(const cv::Range& range) const CV_OVERRIDE + { + for (int v = range.start; v < range.end; v++) + { + ss->OP4((*ctv)[v]); + } + } + private: + ScanSegmentImpl* const ss; + std::vector>* ctv; + }; + + void OP1(int v); + void OP2(std::pair const& p); + void OP3(int v); + void OP4(std::pair const& p); + void expandCluster(int* offsetBuffer, const cv::Point& point); + void calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID); + static int allocWSNodes(std::vector& storage); + static void watershedEx(const cv::Mat& src, cv::Mat& dst); }; CV_EXPORTS Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int slices, bool merge_small) { - return makePtr(image_width, image_height, num_superpixels, slices, merge_small); + return makePtr(image_width, image_height, num_superpixels, slices, merge_small); } ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int slices, bool merge_small) { - // set the number of process threads - processthreads = std::thread::hardware_concurrency(); - if (slices > 0) { - processthreads = MIN(processthreads, slices); - } - - width = image_width; - height = image_height; - superpixels = num_superpixels; - merge = merge_small; - indexSize = height * width; - clusterSize = (int)(1.1f * (float)(width * height) / (float)superpixels); - clusterCount = 0; - labelsMat = cv::Mat(height, width, CV_32SC1); - - // divide bounds area into uniformly distributed rectangular segments - int shortCount = (int)floorf(sqrtf((float)processthreads)); - int longCount = processthreads / shortCount; - horzDiv = width > height ? longCount : shortCount; - vertDiv = width > height ? shortCount : longCount; - horzLength = (float)width / (float)horzDiv; - vertLength = (float)height / (float)vertDiv; - effectivethreads = horzDiv * vertDiv; - smallClusters = 0; - - // get array of seed rects - seedRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); - seedRectsExt = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); - offsetRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); - for (int y = 0; y < vertDiv; y++) { - for (int x = 0; x < horzDiv; x++) { - int xStart = (int)((float)x * horzLength); - int yStart = (int)((float)y * vertLength); - cv::Rect seedRect = cv::Rect(xStart, yStart, (int)(x == horzDiv - 1 ? width - xStart : horzLength), (int)(y == vertDiv - 1 ? height - yStart : vertLength)); - - int bnd_l = seedRect.x; - int bnd_t = seedRect.y; - int bnd_r = seedRect.x + seedRect.width - 1; - int bnd_b = seedRect.y + seedRect.height - 1; - if (bnd_l > 0) { - bnd_l -= 1; - } - if (bnd_t > 0) { - bnd_t -= 1; - } - if (bnd_r < width - 1) { - bnd_r += 1; - } - if (bnd_b < height - 1) { - bnd_b += 1; - } - - seedRects[(y * horzDiv) + x] = seedRect; - seedRectsExt[(y * horzDiv) + x] = cv::Rect(bnd_l, bnd_t, bnd_r - bnd_l + 1, bnd_b - bnd_t + 1); - offsetRects[(y * horzDiv) + x] = cv::Rect(seedRect.x - bnd_l, seedRect.y - bnd_t, seedRect.width, seedRect.height); - } - } - - // get adjusted tolerance = (100 / average length (horz/vert)) x sqrt(3) [ie. euclidean lab colour distance sqrt(l2 + a2 + b2)] x tolerance100 - adjTolerance = (200.0f / (width + height)) * sqrtf(3) * tolerance100; - adjTolerance = adjTolerance * adjTolerance; - - // create neighbour vector - indexNeighbourVec = std::vector(effectivethreads); - std::iota(indexNeighbourVec.begin(), indexNeighbourVec.end(), 0); - - // create process vector - indexProcessVec = std::vector>(processthreads); - int processDiv = indexSize / processthreads; - int processCurrent = 0; - for (int i = 0; i < processthreads - 1; i++) { - indexProcessVec[i] = std::make_pair(processCurrent, processCurrent + processDiv); - processCurrent += processDiv; - } - indexProcessVec[processthreads - 1] = std::make_pair(processCurrent, indexSize); - - // create buffers and initialise - std::vector tempLoc{ cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; - neighbourLoc = static_cast(malloc(8 * sizeof(cv::Point))); - memcpy(neighbourLoc, tempLoc.data(), 8 * sizeof(cv::Point)); - - labelsBuffer = static_cast(malloc(indexSize * sizeof(int))); - clusterBuffer = static_cast(malloc(indexSize * sizeof(int))); - pixelBuffer = static_cast(malloc(indexSize)); - offsetVec = std::vector(effectivethreads); - int offsetSize = (clusterSize + 1) * sizeof(int); - bool offsetAllocated = true; - for (int i = 0; i < effectivethreads; i++) { - offsetVec[i] = static_cast(malloc(offsetSize)); - if (offsetVec[i] == NULL) { - offsetAllocated = false; - } - } - for (int i = 0; i < neighbourCount; i++) { - neighbourLocBuffer[i] = (neighbourLoc[i].y * width) + neighbourLoc[i].x; - } - - if (labelsBuffer != NULL && clusterBuffer != NULL && pixelBuffer != NULL && offsetAllocated) { - setupComplete = true; - } - else { - setupComplete = false; - - if (labelsBuffer == NULL) { - CV_Error(Error::StsInternal, "Cannot initialise labels buffer"); - } - if (clusterBuffer == NULL) { - CV_Error(Error::StsInternal, "Cannot initialise cluster buffer"); - } - if (pixelBuffer == NULL) { - CV_Error(Error::StsInternal, "Cannot initialise pixel buffer"); - } - if (!offsetAllocated) { - CV_Error(Error::StsInternal, "Cannot initialise offset buffers"); - } - } + // set the number of process threads + processthreads = std::thread::hardware_concurrency(); + if (slices > 0) { + processthreads = MIN(processthreads, slices); + } + + width = image_width; + height = image_height; + superpixels = num_superpixels; + merge = merge_small; + indexSize = height * width; + clusterSize = (int)(1.1f * (float)(width * height) / (float)superpixels); + clusterCount = 0; + labelsMat = cv::Mat(height, width, CV_32SC1); + + // divide bounds area into uniformly distributed rectangular segments + int shortCount = (int)floorf(sqrtf((float)processthreads)); + int longCount = processthreads / shortCount; + horzDiv = width > height ? longCount : shortCount; + vertDiv = width > height ? shortCount : longCount; + horzLength = (float)width / (float)horzDiv; + vertLength = (float)height / (float)vertDiv; + effectivethreads = horzDiv * vertDiv; + smallClusters = 0; + + // get array of seed rects + seedRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + seedRectsExt = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + offsetRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + for (int y = 0; y < vertDiv; y++) { + for (int x = 0; x < horzDiv; x++) { + int xStart = (int)((float)x * horzLength); + int yStart = (int)((float)y * vertLength); + cv::Rect seedRect = cv::Rect(xStart, yStart, (int)(x == horzDiv - 1 ? width - xStart : horzLength), (int)(y == vertDiv - 1 ? height - yStart : vertLength)); + + int bnd_l = seedRect.x; + int bnd_t = seedRect.y; + int bnd_r = seedRect.x + seedRect.width - 1; + int bnd_b = seedRect.y + seedRect.height - 1; + if (bnd_l > 0) { + bnd_l -= 1; + } + if (bnd_t > 0) { + bnd_t -= 1; + } + if (bnd_r < width - 1) { + bnd_r += 1; + } + if (bnd_b < height - 1) { + bnd_b += 1; + } + + seedRects[(y * horzDiv) + x] = seedRect; + seedRectsExt[(y * horzDiv) + x] = cv::Rect(bnd_l, bnd_t, bnd_r - bnd_l + 1, bnd_b - bnd_t + 1); + offsetRects[(y * horzDiv) + x] = cv::Rect(seedRect.x - bnd_l, seedRect.y - bnd_t, seedRect.width, seedRect.height); + } + } + + // get adjusted tolerance = (100 / average length (horz/vert)) x sqrt(3) [ie. euclidean lab colour distance sqrt(l2 + a2 + b2)] x tolerance100 + adjTolerance = (200.0f / (width + height)) * sqrtf(3) * tolerance100; + adjTolerance = adjTolerance * adjTolerance; + + // create neighbour vector + indexNeighbourVec = std::vector(effectivethreads); + std::iota(indexNeighbourVec.begin(), indexNeighbourVec.end(), 0); + + // create process vector + indexProcessVec = std::vector>(processthreads); + int processDiv = indexSize / processthreads; + int processCurrent = 0; + for (int i = 0; i < processthreads - 1; i++) { + indexProcessVec[i] = std::make_pair(processCurrent, processCurrent + processDiv); + processCurrent += processDiv; + } + indexProcessVec[processthreads - 1] = std::make_pair(processCurrent, indexSize); + + // create buffers and initialise + std::vector tempLoc{ cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; + neighbourLoc = static_cast(malloc(8 * sizeof(cv::Point))); + memcpy(neighbourLoc, tempLoc.data(), 8 * sizeof(cv::Point)); + + labelsBuffer = static_cast(malloc(indexSize * sizeof(int))); + clusterBuffer = static_cast(malloc(indexSize * sizeof(int))); + pixelBuffer = static_cast(malloc(indexSize)); + offsetVec = std::vector(effectivethreads); + int offsetSize = (clusterSize + 1) * sizeof(int); + bool offsetAllocated = true; + for (int i = 0; i < effectivethreads; i++) { + offsetVec[i] = static_cast(malloc(offsetSize)); + if (offsetVec[i] == NULL) { + offsetAllocated = false; + } + } + for (int i = 0; i < neighbourCount; i++) { + neighbourLocBuffer[i] = (neighbourLoc[i].y * width) + neighbourLoc[i].x; + } + + if (labelsBuffer != NULL && clusterBuffer != NULL && pixelBuffer != NULL && offsetAllocated) { + setupComplete = true; + } + else { + setupComplete = false; + + if (labelsBuffer == NULL) { + CV_Error(Error::StsInternal, "Cannot initialise labels buffer"); + } + if (clusterBuffer == NULL) { + CV_Error(Error::StsInternal, "Cannot initialise cluster buffer"); + } + if (pixelBuffer == NULL) { + CV_Error(Error::StsInternal, "Cannot initialise pixel buffer"); + } + if (!offsetAllocated) { + CV_Error(Error::StsInternal, "Cannot initialise offset buffers"); + } + } } ScanSegmentImpl::~ScanSegmentImpl() { - // clean up - if (neighbourLoc != NULL) { - free(neighbourLoc); - } - if (seedRects != NULL) { - free(seedRects); - } - if (seedRectsExt != NULL) { - free(seedRectsExt); - } - if (offsetRects != NULL) { - free(offsetRects); - } - if (labelsBuffer != NULL) { - free(labelsBuffer); - } - if (clusterBuffer != NULL) { - free(clusterBuffer); - } - if (pixelBuffer != NULL) { - free(pixelBuffer); - } - for (int i = 0; i < effectivethreads; i++) { - if (offsetVec[i] != NULL) { - free(offsetVec[i]); - } - } - if (!src.empty()) { - src.release(); - } - if (!labelsMat.empty()) { - labelsMat.release(); - } + // clean up + if (neighbourLoc != NULL) { + free(neighbourLoc); + } + if (seedRects != NULL) { + free(seedRects); + } + if (seedRectsExt != NULL) { + free(seedRectsExt); + } + if (offsetRects != NULL) { + free(offsetRects); + } + if (labelsBuffer != NULL) { + free(labelsBuffer); + } + if (clusterBuffer != NULL) { + free(clusterBuffer); + } + if (pixelBuffer != NULL) { + free(pixelBuffer); + } + for (int i = 0; i < effectivethreads; i++) { + if (offsetVec[i] != NULL) { + free(offsetVec[i]); + } + } + if (!src.empty()) { + src.release(); + } + if (!labelsMat.empty()) { + labelsMat.release(); + } } void ScanSegmentImpl::iterate(InputArray img) { - // ensure setup successfully completed - CV_Assert(setupComplete); - - if (img.isMat()) - { - // get Mat - src = img.getMat(); - - // image should be valid - CV_Assert(!src.empty()); - } - else if (img.isMatVector()) - { - std::vector vec; - - // get vector Mat - img.getMatVector(vec); - - // array should be valid - CV_Assert(!vec.empty()); - - // merge into Mat - cv::merge(vec, src); - } - else - CV_Error(Error::StsInternal, "Invalid InputArray."); - - int depth = src.depth(); - - CV_Assert(src.size().width == width && src.size().height == height); - CV_Assert(depth == CV_8U); - CV_Assert(src.channels() == 3); - - clusterCount = 0; - clusterIndex.store(0); - clusterID.store(1); - - smallClusters = indexSize / smallClustersDiv; - - // set labels to NONE - labelsMat.setTo(NONE); - - // set labels buffer to UNCLASSIFIED - std::fill(labelsBuffer, labelsBuffer + indexSize, UNCLASSIFIED); - - // apply light blur - cv::medianBlur(src, src, 3); - - // start at the center of the rect, then run through the remainder - labBuffer = reinterpret_cast(src.data); - cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP1(reinterpret_cast(this))); - - if (merge) { - // get cutoff size for clusters - std::vector> countVec; - int clusterIndexSize = clusterIndex.load(); - countVec.reserve(clusterIndexSize / 2); - for (int i = 1; i < clusterIndexSize; i += 2) { - int count = clusterBuffer[i]; - if (count >= smallClusters) { - int currentID = clusterBuffer[i - 1]; - countVec.push_back(std::make_pair(currentID, count)); - } - } - - // sort descending - std::sort(countVec.begin(), countVec.end(), [](const std::pair& left, const std::pair& right) { - return left.second > right.second; - }); - - int countSize = (int)countVec.size(); - int cutoff = MAX(smallClusters, countVec[MIN(countSize - 1, superpixels - 1)].second); - clusterCount = (int)std::count_if(countVec.begin(), countVec.end(), [&cutoff](std::pair p) {return p.second > cutoff; }); - - // change labels to 1 -> clusterCount, 0 = UNKNOWN, reuse clusterbuffer - std::fill_n(clusterBuffer, indexSize, UNKNOWN); - int countLimit = cutoff == -1 ? (int)countVec.size() : clusterCount; - for (int i = 0; i < countLimit; i++) { - clusterBuffer[countVec[i].first] = i + 1; - } - - cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP2(reinterpret_cast(this), &indexProcessVec)); - - // make copy of labels buffer - memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); - - // run watershed - cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP3(reinterpret_cast(this))); - - // copy back to labels mat - cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP4(reinterpret_cast(this), &indexProcessVec)); - } - else { - memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); - } - - src.release(); + // ensure setup successfully completed + CV_Assert(setupComplete); + + if (img.isMat()) + { + // get Mat + src = img.getMat(); + + // image should be valid + CV_Assert(!src.empty()); + } + else if (img.isMatVector()) + { + std::vector vec; + + // get vector Mat + img.getMatVector(vec); + + // array should be valid + CV_Assert(!vec.empty()); + + // merge into Mat + cv::merge(vec, src); + } + else + CV_Error(Error::StsInternal, "Invalid InputArray."); + + int depth = src.depth(); + + CV_Assert(src.size().width == width && src.size().height == height); + CV_Assert(depth == CV_8U); + CV_Assert(src.channels() == 3); + + clusterCount = 0; + clusterIndex.store(0); + clusterID.store(1); + + smallClusters = indexSize / smallClustersDiv; + + // set labels to NONE + labelsMat.setTo(NONE); + + // set labels buffer to UNCLASSIFIED + std::fill(labelsBuffer, labelsBuffer + indexSize, UNCLASSIFIED); + + // apply light blur + cv::medianBlur(src, src, 3); + + // start at the center of the rect, then run through the remainder + labBuffer = reinterpret_cast(src.data); + cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP1(reinterpret_cast(this))); + + if (merge) { + // get cutoff size for clusters + std::vector> countVec; + int clusterIndexSize = clusterIndex.load(); + countVec.reserve(clusterIndexSize / 2); + for (int i = 1; i < clusterIndexSize; i += 2) { + int count = clusterBuffer[i]; + if (count >= smallClusters) { + int currentID = clusterBuffer[i - 1]; + countVec.push_back(std::make_pair(currentID, count)); + } + } + + // sort descending + std::sort(countVec.begin(), countVec.end(), [](const std::pair& left, const std::pair& right) { + return left.second > right.second; + }); + + int countSize = (int)countVec.size(); + int cutoff = MAX(smallClusters, countVec[MIN(countSize - 1, superpixels - 1)].second); + clusterCount = (int)std::count_if(countVec.begin(), countVec.end(), [&cutoff](std::pair p) {return p.second > cutoff; }); + + // change labels to 1 -> clusterCount, 0 = UNKNOWN, reuse clusterbuffer + std::fill_n(clusterBuffer, indexSize, UNKNOWN); + int countLimit = cutoff == -1 ? (int)countVec.size() : clusterCount; + for (int i = 0; i < countLimit; i++) { + clusterBuffer[countVec[i].first] = i + 1; + } + + cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP2(reinterpret_cast(this), &indexProcessVec)); + + // make copy of labels buffer + memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); + + // run watershed + cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP3(reinterpret_cast(this))); + + // copy back to labels mat + cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP4(reinterpret_cast(this), &indexProcessVec)); + } + else { + memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); + } + + src.release(); } void ScanSegmentImpl::OP1(int v) { - cv::Rect seedRect = seedRects[v]; - for (int y = seedRect.y; y < seedRect.y + seedRect.height; y++) { - for (int x = seedRect.x; x < seedRect.x + seedRect.width; x++) { - expandCluster(offsetVec[v], cv::Point(x, y)); - } - } + cv::Rect seedRect = seedRects[v]; + for (int y = seedRect.y; y < seedRect.y + seedRect.height; y++) { + for (int x = seedRect.x; x < seedRect.x + seedRect.width; x++) { + expandCluster(offsetVec[v], cv::Point(x, y)); + } + } } void ScanSegmentImpl::OP2(std::pair const& p) { - std::pair& q = const_cast&>(p); - for (int i = q.first; i < q.second; i++) { - labelsBuffer[i] = clusterBuffer[labelsBuffer[i]]; - if (labelsBuffer[i] == UNKNOWN) { - pixelBuffer[i] = 255; - } - else { - pixelBuffer[i] = 0; - } - } + std::pair& q = const_cast&>(p); + for (int i = q.first; i < q.second; i++) { + labelsBuffer[i] = clusterBuffer[labelsBuffer[i]]; + if (labelsBuffer[i] == UNKNOWN) { + pixelBuffer[i] = 255; + } + else { + pixelBuffer[i] = 0; + } + } } void ScanSegmentImpl::OP3(int v) { - cv::Rect seedRectExt = seedRectsExt[v]; - cv::Mat seedLabels = labelsMat(seedRectExt).clone(); - watershedEx(src(seedRectExt), seedLabels); - seedLabels(offsetRects[v]).copyTo(labelsMat(seedRects[v])); - seedLabels.release(); + cv::Rect seedRectExt = seedRectsExt[v]; + cv::Mat seedLabels = labelsMat(seedRectExt).clone(); + watershedEx(src(seedRectExt), seedLabels); + seedLabels(offsetRects[v]).copyTo(labelsMat(seedRects[v])); + seedLabels.release(); } void ScanSegmentImpl::OP4(std::pair const& p) { - std::pair& q = const_cast&>(p); - for (int i = q.first; i < q.second; i++) { - if (pixelBuffer[i] == 0) { - ((int*)labelsMat.data)[i] = labelsBuffer[i] - 1; - } - else { - ((int*)labelsMat.data)[i] -= 1; - } - } + std::pair& q = const_cast&>(p); + for (int i = q.first; i < q.second; i++) { + if (pixelBuffer[i] == 0) { + ((int*)labelsMat.data)[i] = labelsBuffer[i] - 1; + } + else { + ((int*)labelsMat.data)[i] -= 1; + } + } } // expand clusters from a point void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) { - int pointIndex = (point.y * width) + point.x; - if (labelsBuffer[pointIndex] == UNCLASSIFIED) { - int offsetStart = 0; - int offsetEnd = 0; - int currentClusterID = clusterID->fetch_add(1); - - calculateCluster(offsetBuffer, &offsetEnd, pointIndex, currentClusterID); - - if (offsetStart == offsetEnd) { - labelsBuffer[pointIndex] = UNKNOWN; - } - else { - // set cluster id and get core point index - labelsBuffer[pointIndex] = currentClusterID; - - while (offsetStart < offsetEnd) { - int intoffset2 = *(offsetBuffer + offsetStart); - offsetStart++; - calculateCluster(offsetBuffer, &offsetEnd, intoffset2, currentClusterID); - } - - // add origin point - offsetBuffer[offsetEnd] = pointIndex; - offsetEnd++; - - // store to buffer - int currentClusterIndex = clusterIndex->fetch_add(2); - clusterBuffer[currentClusterIndex] = currentClusterID; - clusterBuffer[currentClusterIndex + 1] = offsetEnd; - } - } + int pointIndex = (point.y * width) + point.x; + if (labelsBuffer[pointIndex] == UNCLASSIFIED) { + int offsetStart = 0; + int offsetEnd = 0; + int currentClusterID = clusterID->fetch_add(1); + + calculateCluster(offsetBuffer, &offsetEnd, pointIndex, currentClusterID); + + if (offsetStart == offsetEnd) { + labelsBuffer[pointIndex] = UNKNOWN; + } + else { + // set cluster id and get core point index + labelsBuffer[pointIndex] = currentClusterID; + + while (offsetStart < offsetEnd) { + int intoffset2 = *(offsetBuffer + offsetStart); + offsetStart++; + calculateCluster(offsetBuffer, &offsetEnd, intoffset2, currentClusterID); + } + + // add origin point + offsetBuffer[offsetEnd] = pointIndex; + offsetEnd++; + + // store to buffer + int currentClusterIndex = clusterIndex->fetch_add(2); + clusterBuffer[currentClusterIndex] = currentClusterID; + clusterBuffer[currentClusterIndex + 1] = offsetEnd; + } + } } void ScanSegmentImpl::calculateCluster(int* offsetBuffer, int* offsetEnd, int pointIndex, int currentClusterID) { - for (int i = 0; i < neighbourCount; i++) { - if (*offsetEnd < clusterSize) { - int intoffset2 = pointIndex + neighbourLocBuffer[i]; - if (intoffset2 >= 0 && intoffset2 < indexSize && labelsBuffer[intoffset2] == UNCLASSIFIED) { - int diff1 = (int)labBuffer[pointIndex][0] - (int)labBuffer[intoffset2][0]; - int diff2 = (int)labBuffer[pointIndex][1] - (int)labBuffer[intoffset2][1]; - int diff3 = (int)labBuffer[pointIndex][2] - (int)labBuffer[intoffset2][2]; - - if ((diff1 * diff1) + (diff2 * diff2) + (diff3 * diff3) <= (int)adjTolerance) { - labelsBuffer[intoffset2] = currentClusterID; - offsetBuffer[*offsetEnd] = intoffset2; - (*offsetEnd)++; - } - } - } - else { break; } - } + for (int i = 0; i < neighbourCount; i++) { + if (*offsetEnd < clusterSize) { + int intoffset2 = pointIndex + neighbourLocBuffer[i]; + if (intoffset2 >= 0 && intoffset2 < indexSize && labelsBuffer[intoffset2] == UNCLASSIFIED) { + int diff1 = (int)labBuffer[pointIndex][0] - (int)labBuffer[intoffset2][0]; + int diff2 = (int)labBuffer[pointIndex][1] - (int)labBuffer[intoffset2][1]; + int diff3 = (int)labBuffer[pointIndex][2] - (int)labBuffer[intoffset2][2]; + + if ((diff1 * diff1) + (diff2 * diff2) + (diff3 * diff3) <= (int)adjTolerance) { + labelsBuffer[intoffset2] = currentClusterID; + offsetBuffer[*offsetEnd] = intoffset2; + (*offsetEnd)++; + } + } + } + else { break; } + } } int ScanSegmentImpl::allocWSNodes(std::vector& storage) { - int sz = (int)storage.size(); - int newsz = MAX(128, sz * 3 / 2); - - storage.resize(newsz); - if (sz == 0) - { - storage[0].next = 0; - sz = 1; - } - for (int i = sz; i < newsz - 1; i++) - storage[i].next = i + 1; - storage[newsz - 1].next = 0; - return sz; + int sz = (int)storage.size(); + int newsz = MAX(128, sz * 3 / 2); + + storage.resize(newsz); + if (sz == 0) + { + storage[0].next = 0; + sz = 1; + } + for (int i = sz; i < newsz - 1; i++) + storage[i].next = i + 1; + storage[newsz - 1].next = 0; + return sz; } //the modified version of watershed algorithm from OpenCV void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) { - // https://github.com/Seaball/watershed_with_mask - - // Labels for pixels - const int IN_QUEUE = -2; // Pixel visited - // possible bit values = 2^8 - const int NQ = 256; - - cv::Size size = src.size(); - int channel = 3; - // Vector of every created node - std::vector storage; - int free_node = 0, node; - // Priority queue of queues of nodes - // from high priority (0) to low priority (255) - WSQueue q[NQ]; - // Non-empty queue with highest priority - int active_queue; - int i, j; - // Color differences - int db, dg, dr; - int subs_tab[513]; - - // MAX(a,b) = b + MAX(a-b,0) + // https://github.com/Seaball/watershed_with_mask + + // Labels for pixels + const int IN_QUEUE = -2; // Pixel visited + // possible bit values = 2^8 + const int NQ = 256; + + cv::Size size = src.size(); + int channel = 3; + // Vector of every created node + std::vector storage; + int free_node = 0, node; + // Priority queue of queues of nodes + // from high priority (0) to low priority (255) + WSQueue q[NQ]; + // Non-empty queue with highest priority + int active_queue; + int i, j; + // Color differences + int db, dg, dr; + int subs_tab[513]; + + // MAX(a,b) = b + MAX(a-b,0) #define ws_max(a,b) ((b) + subs_tab[(a)-(b)+NQ]) // MIN(a,b) = a - MAX(a-b,0) #define ws_min(a,b) ((a) - subs_tab[(a)-(b)+NQ]) @@ -630,257 +630,257 @@ diff = ws_max(diff, dr); \ assert(0 <= diff && diff <= 255); \ } - CV_Assert(src.type() == CV_8UC3 && dst.type() == CV_32SC1); - CV_Assert(src.size() == dst.size()); - - // Current pixel in input image - const uchar* img = src.ptr(); - // Step size to next row in input image - int istep = int(src.step / sizeof(img[0])); - - // Current pixel in mask image - int* mask = dst.ptr(); - // Step size to next row in mask image - int mstep = int(dst.step / sizeof(mask[0])); - - for (i = 0; i < 256; i++) - subs_tab[i] = 0; - for (i = 256; i <= 512; i++) - subs_tab[i] = i - 256; - - //for (j = 0; j < size.width; j++) - //mask[j] = mask[j + mstep*(size.height - 1)] = 0; - - // initial phase: put all the neighbor pixels of each marker to the ordered queue - - // determine the initial boundaries of the basins - for (i = 1; i < size.height - 1; i++) { - img += istep; mask += mstep; - mask[0] = mask[size.width - 1] = 0; // boundary pixels - - for (j = 1; j < size.width - 1; j++) { - int* m = mask + j; - if (m[0] < 0) - m[0] = 0; - if (m[0] == 0 && (m[-1] > 0 || m[1] > 0 || m[-mstep] > 0 || m[mstep] > 0)) - { - // Find smallest difference to adjacent markers - const uchar* ptr = img + j * channel; - int idx = 256, t; - if (m[-1] > 0) { - c_diff(ptr, ptr - channel, idx); - } - if (m[1] > 0) { - c_diff(ptr, ptr + channel, t); - idx = ws_min(idx, t); - } - if (m[-mstep] > 0) { - c_diff(ptr, ptr - istep, t); - idx = ws_min(idx, t); - } - if (m[mstep] > 0) { - c_diff(ptr, ptr + istep, t); - idx = ws_min(idx, t); - } - - // Add to according queue - assert(0 <= idx && idx <= 255); - ws_push(idx, i * mstep + j, i * istep + j * channel); - m[0] = IN_QUEUE;//initial unvisited - } - } - } - // find the first non-empty queue - for (i = 0; i < NQ; i++) - if (q[i].first) - break; - - // if there is no markers, exit immediately - if (i == NQ) - return; - - active_queue = i;//first non-empty priority queue - img = src.ptr(); - mask = dst.ptr(); - - // recursively fill the basins - int diff = 0, temp = 0; - for (;;) - { - int mofs, iofs; - int lab = 0, t; - int* m; - const uchar* ptr; - - // Get non-empty queue with highest priority - // Exit condition: empty priority queue - if (q[active_queue].first == 0) - { - for (i = active_queue + 1; i < NQ; i++) - if (q[i].first) - break; - if (i == NQ) - { - std::vector().swap(storage); - break; - } - active_queue = i; - } - - // Get next node - ws_pop(active_queue, mofs, iofs); - int top = 1, bottom = 1, left = 1, right = 1; - if (0 <= mofs && mofs < mstep)//pixel on the top - top = 0; - if ((mofs % mstep) == 0)//pixel in the left column - left = 0; - if ((mofs + 1) % mstep == 0)//pixel in the right column - right = 0; - if (mstep * (size.height - 1) <= mofs && mofs < mstep * size.height)//pixel on the bottom - bottom = 0; - - // Calculate pointer to current pixel in input and marker image - m = mask + mofs; - ptr = img + iofs; - // Check surrounding pixels for labels to determine label for current pixel - if (left) {//the left point can be visited - t = m[-1]; - if (t > 0) { - lab = t; - c_diff(ptr, ptr - channel, diff); - } - } - if (right) {// Right point can be visited - t = m[1]; - if (t > 0) { - if (lab == 0) {//and this point didn't be labeled before - lab = t; - c_diff(ptr, ptr + channel, diff); - } - else if (t != lab) { - c_diff(ptr, ptr + channel, temp); - diff = ws_min(diff, temp); - if (diff == temp) - lab = t; - } - } - } - if (top) { - t = m[-mstep]; // Top - if (t > 0) { - if (lab == 0) {//and this point didn't be labeled before - lab = t; - c_diff(ptr, ptr - istep, diff); - } - else if (t != lab) { - c_diff(ptr, ptr - istep, temp); - diff = ws_min(diff, temp); - if (diff == temp) - lab = t; - } - } - } - if (bottom) { - t = m[mstep]; // Bottom - if (t > 0) { - if (lab == 0) { - lab = t; - } - else if (t != lab) { - c_diff(ptr, ptr + istep, temp); - diff = ws_min(diff, temp); - if (diff == temp) - lab = t; - } - } - } - // Set label to current pixel in marker image - assert(lab != 0);//lab must be labeled with a nonzero number - m[0] = lab; - - // Add adjacent, unlabeled pixels to corresponding queue - if (left) { - if (m[-1] == 0)//left pixel with marker 0 - { - c_diff(ptr, ptr - channel, t); - ws_push(t, mofs - 1, iofs - channel); - active_queue = ws_min(active_queue, t); - m[-1] = IN_QUEUE; - } - } - - if (right) - { - if (m[1] == 0)//right pixel with marker 0 - { - c_diff(ptr, ptr + channel, t); - ws_push(t, mofs + 1, iofs + channel); - active_queue = ws_min(active_queue, t); - m[1] = IN_QUEUE; - } - } - - if (top) - { - if (m[-mstep] == 0)//top pixel with marker 0 - { - c_diff(ptr, ptr - istep, t); - ws_push(t, mofs - mstep, iofs - istep); - active_queue = ws_min(active_queue, t); - m[-mstep] = IN_QUEUE; - } - } - - if (bottom) { - if (m[mstep] == 0)//down pixel with marker 0 - { - c_diff(ptr, ptr + istep, t); - ws_push(t, mofs + mstep, iofs + istep); - active_queue = ws_min(active_queue, t); - m[mstep] = IN_QUEUE; - } - } - } + CV_Assert(src.type() == CV_8UC3 && dst.type() == CV_32SC1); + CV_Assert(src.size() == dst.size()); + + // Current pixel in input image + const uchar* img = src.ptr(); + // Step size to next row in input image + int istep = int(src.step / sizeof(img[0])); + + // Current pixel in mask image + int* mask = dst.ptr(); + // Step size to next row in mask image + int mstep = int(dst.step / sizeof(mask[0])); + + for (i = 0; i < 256; i++) + subs_tab[i] = 0; + for (i = 256; i <= 512; i++) + subs_tab[i] = i - 256; + + //for (j = 0; j < size.width; j++) + //mask[j] = mask[j + mstep*(size.height - 1)] = 0; + + // initial phase: put all the neighbor pixels of each marker to the ordered queue - + // determine the initial boundaries of the basins + for (i = 1; i < size.height - 1; i++) { + img += istep; mask += mstep; + mask[0] = mask[size.width - 1] = 0; // boundary pixels + + for (j = 1; j < size.width - 1; j++) { + int* m = mask + j; + if (m[0] < 0) + m[0] = 0; + if (m[0] == 0 && (m[-1] > 0 || m[1] > 0 || m[-mstep] > 0 || m[mstep] > 0)) + { + // Find smallest difference to adjacent markers + const uchar* ptr = img + j * channel; + int idx = 256, t; + if (m[-1] > 0) { + c_diff(ptr, ptr - channel, idx); + } + if (m[1] > 0) { + c_diff(ptr, ptr + channel, t); + idx = ws_min(idx, t); + } + if (m[-mstep] > 0) { + c_diff(ptr, ptr - istep, t); + idx = ws_min(idx, t); + } + if (m[mstep] > 0) { + c_diff(ptr, ptr + istep, t); + idx = ws_min(idx, t); + } + + // Add to according queue + assert(0 <= idx && idx <= 255); + ws_push(idx, i * mstep + j, i * istep + j * channel); + m[0] = IN_QUEUE;//initial unvisited + } + } + } + // find the first non-empty queue + for (i = 0; i < NQ; i++) + if (q[i].first) + break; + + // if there is no markers, exit immediately + if (i == NQ) + return; + + active_queue = i;//first non-empty priority queue + img = src.ptr(); + mask = dst.ptr(); + + // recursively fill the basins + int diff = 0, temp = 0; + for (;;) + { + int mofs, iofs; + int lab = 0, t; + int* m; + const uchar* ptr; + + // Get non-empty queue with highest priority + // Exit condition: empty priority queue + if (q[active_queue].first == 0) + { + for (i = active_queue + 1; i < NQ; i++) + if (q[i].first) + break; + if (i == NQ) + { + std::vector().swap(storage); + break; + } + active_queue = i; + } + + // Get next node + ws_pop(active_queue, mofs, iofs); + int top = 1, bottom = 1, left = 1, right = 1; + if (0 <= mofs && mofs < mstep)//pixel on the top + top = 0; + if ((mofs % mstep) == 0)//pixel in the left column + left = 0; + if ((mofs + 1) % mstep == 0)//pixel in the right column + right = 0; + if (mstep * (size.height - 1) <= mofs && mofs < mstep * size.height)//pixel on the bottom + bottom = 0; + + // Calculate pointer to current pixel in input and marker image + m = mask + mofs; + ptr = img + iofs; + // Check surrounding pixels for labels to determine label for current pixel + if (left) {//the left point can be visited + t = m[-1]; + if (t > 0) { + lab = t; + c_diff(ptr, ptr - channel, diff); + } + } + if (right) {// Right point can be visited + t = m[1]; + if (t > 0) { + if (lab == 0) {//and this point didn't be labeled before + lab = t; + c_diff(ptr, ptr + channel, diff); + } + else if (t != lab) { + c_diff(ptr, ptr + channel, temp); + diff = ws_min(diff, temp); + if (diff == temp) + lab = t; + } + } + } + if (top) { + t = m[-mstep]; // Top + if (t > 0) { + if (lab == 0) {//and this point didn't be labeled before + lab = t; + c_diff(ptr, ptr - istep, diff); + } + else if (t != lab) { + c_diff(ptr, ptr - istep, temp); + diff = ws_min(diff, temp); + if (diff == temp) + lab = t; + } + } + } + if (bottom) { + t = m[mstep]; // Bottom + if (t > 0) { + if (lab == 0) { + lab = t; + } + else if (t != lab) { + c_diff(ptr, ptr + istep, temp); + diff = ws_min(diff, temp); + if (diff == temp) + lab = t; + } + } + } + // Set label to current pixel in marker image + assert(lab != 0);//lab must be labeled with a nonzero number + m[0] = lab; + + // Add adjacent, unlabeled pixels to corresponding queue + if (left) { + if (m[-1] == 0)//left pixel with marker 0 + { + c_diff(ptr, ptr - channel, t); + ws_push(t, mofs - 1, iofs - channel); + active_queue = ws_min(active_queue, t); + m[-1] = IN_QUEUE; + } + } + + if (right) + { + if (m[1] == 0)//right pixel with marker 0 + { + c_diff(ptr, ptr + channel, t); + ws_push(t, mofs + 1, iofs + channel); + active_queue = ws_min(active_queue, t); + m[1] = IN_QUEUE; + } + } + + if (top) + { + if (m[-mstep] == 0)//top pixel with marker 0 + { + c_diff(ptr, ptr - istep, t); + ws_push(t, mofs - mstep, iofs - istep); + active_queue = ws_min(active_queue, t); + m[-mstep] = IN_QUEUE; + } + } + + if (bottom) { + if (m[mstep] == 0)//down pixel with marker 0 + { + c_diff(ptr, ptr + istep, t); + ws_push(t, mofs + mstep, iofs + istep); + active_queue = ws_min(active_queue, t); + m[mstep] = IN_QUEUE; + } + } + } } void ScanSegmentImpl::getLabels(OutputArray labels_out) { - labels_out.assign(labelsMat); + labels_out.assign(labelsMat); } void ScanSegmentImpl::getLabelContourMask(OutputArray image, bool thick_line) { - image.create(height, width, CV_8UC1); - cv::Mat dst = image.getMat(); - dst.setTo(cv::Scalar(0)); - - const int dx8[8] = { -1, -1, 0, 1, 1, 1, 0, -1 }; - const int dy8[8] = { 0, -1, -1, -1, 0, 1, 1, 1 }; - - for (int j = 0; j < height; j++) - { - for (int k = 0; k < width; k++) - { - int neighbors = 0; - for (int i = 0; i < 8; i++) - { - int x = k + dx8[i]; - int y = j + dy8[i]; - - if ((x >= 0 && x < width) && (y >= 0 && y < height)) - { - int index = y * width + x; - int mainindex = j * width + k; - if (((int*)labelsMat.data)[mainindex] != ((int*)labelsMat.data)[index]) - { - if (thick_line || !*dst.ptr(y, x)) - neighbors++; - } - } - } - if (neighbors > 1) - *dst.ptr(j, k) = (uchar)255; - } - } + image.create(height, width, CV_8UC1); + cv::Mat dst = image.getMat(); + dst.setTo(cv::Scalar(0)); + + const int dx8[8] = { -1, -1, 0, 1, 1, 1, 0, -1 }; + const int dy8[8] = { 0, -1, -1, -1, 0, 1, 1, 1 }; + + for (int j = 0; j < height; j++) + { + for (int k = 0; k < width; k++) + { + int neighbors = 0; + for (int i = 0; i < 8; i++) + { + int x = k + dx8[i]; + int y = j + dy8[i]; + + if ((x >= 0 && x < width) && (y >= 0 && y < height)) + { + int index = y * width + x; + int mainindex = j * width + k; + if (((int*)labelsMat.data)[mainindex] != ((int*)labelsMat.data)[index]) + { + if (thick_line || !*dst.ptr(y, x)) + neighbors++; + } + } + } + if (neighbors > 1) + *dst.ptr(j, k) = (uchar)255; + } + } } //! @} From c64cbf6879b1754aedc7223aed6b1a17c332a894 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 17:00:37 +1300 Subject: [PATCH 29/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 11dde4548c..b178933776 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -492,7 +492,7 @@ void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) if (labelsBuffer[pointIndex] == UNCLASSIFIED) { int offsetStart = 0; int offsetEnd = 0; - int currentClusterID = clusterID->fetch_add(1); + int currentClusterID = clusterID.fetch_add(1); calculateCluster(offsetBuffer, &offsetEnd, pointIndex, currentClusterID); @@ -514,7 +514,7 @@ void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) offsetEnd++; // store to buffer - int currentClusterIndex = clusterIndex->fetch_add(2); + int currentClusterIndex = clusterIndex.fetch_add(2); clusterBuffer[currentClusterIndex] = currentClusterID; clusterBuffer[currentClusterIndex + 1] = offsetEnd; } From 56112ad18244a7c4c83fdae428947d1188b4a84c Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 10 Nov 2021 17:10:11 +1300 Subject: [PATCH 30/44] Update scansegment.hpp removed trailing whitespace --- .../include/opencv2/ximgproc/scansegment.hpp | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index c3c3897c94..7c3b802b39 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -19,10 +19,10 @@ namespace ximgproc { /** @brief Class implementing the F-DBSCAN (Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm) superpixels algorithm by Loke SC, et al. -The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than -existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s -with a Boundary Recall of 0.797 and an Achievable Segmentation Accuracy of 0.944. The computational complexity is quadratic O(n2) and -more suited to smaller images, but can still process a 2MP colour image faster than the SEEDS algorithm in OpenCV. The output is deterministic +The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than +existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s +with a Boundary Recall of 0.797 and an Achievable Segmentation Accuracy of 0.944. The computational complexity is quadratic O(n2) and +more suited to smaller images, but can still process a 2MP colour image faster than the SEEDS algorithm in OpenCV. The output is deterministic when the number of processing threads is fixed, and requires the source image to be in Lab colour format. */ class CV_EXPORTS_W ScanSegment : public Algorithm @@ -34,15 +34,15 @@ class CV_EXPORTS_W ScanSegment : public Algorithm CV_WRAP virtual int getNumberOfSuperpixels() = 0; /** @brief Calculates the superpixel segmentation on a given image with the initialized - parameters in the ScanSegment object. This function can be called again for other images - without the need of initializing the algorithm with createScanSegment(). This save the + parameters in the ScanSegment object. This function can be called again for other images + without the need of initializing the algorithm with createScanSegment(). This save the computational cost of allocating memory for all the structures of the algorithm. - @param img Input image. Supported format: CV_8UC3. Image size must match with the initialized + @param img Input image. Supported format: CV_8UC3. Image size must match with the initialized image size with the function createScanSegment(). It MUST be in Lab color space. */ CV_WRAP virtual void iterate(InputArray img) = 0; - /** @brief Returns the segmentation labeling of the image. Each label represents a superpixel, + /** @brief Returns the segmentation labeling of the image. Each label represents a superpixel, and each pixel is assigned to one superpixel label. @param labels_out Return: A CV_32UC1 integer array containing the labels of the superpixel segmentation. The labels are in the range [0, getNumberOfSuperpixels()]. @@ -67,12 +67,12 @@ class CV_EXPORTS_W ScanSegment : public Algorithm @param num_superpixels Desired number of superpixels. Note that the actual number may be smaller due to restrictions (depending on the image size). Use getNumberOfSuperpixels() to get the actual number. -@param slices Number of processing threads for parallelisation. Setting -1 uses the maximum number +@param slices Number of processing threads for parallelisation. Setting -1 uses the maximum number of threads. In practice, four threads is enough for smaller images and eight threads for larger ones. -@param merge_small merge small segments to give the desired number of superpixels. Processing is +@param merge_small merge small segments to give the desired number of superpixels. Processing is much faster without merging, but many small segments will be left in the image. The function initializes a ScanSegment object for the input image. It stores the parameters of -the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel +the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel algorithm, which are: num_superpixels, threads, and merge_small. */ CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int slices = 8, bool merge_small = true); From a04879262438f3006b1a37eaaf8ee1a2ecd877cb Mon Sep 17 00:00:00 2001 From: scloke Date: Thu, 11 Nov 2021 10:19:34 +1300 Subject: [PATCH 31/44] Update scansegment.cpp replace malloc with autobuffer --- modules/ximgproc/src/scansegment.cpp | 60 ++++++++++++---------------- 1 file changed, 26 insertions(+), 34 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index b178933776..624226fe24 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -59,6 +59,11 @@ class ScanSegmentImpl : public ScanSegment float horzLength, vertLength; // length of each segment int effectivethreads; // effective number of concurrent threads int smallClusters; // clusters below this pixel count are considered small for merging + + cv::AutoBuffer _seedRects; // autobuffer of seed rectangles + cv::AutoBuffer _seedRectsExt; // autobuffer of extended seed rectangles + cv::AutoBuffer _offsetRects; // autobuffer of offset rectangles + cv::AutoBuffer _neighbourLoc;// autobuffer of neighbour locations cv::Rect* seedRects; // array of seed rectangles cv::Rect* seedRectsExt; // array of extended seed rectangles cv::Rect* offsetRects; // array of offset rectangles @@ -67,6 +72,10 @@ class ScanSegmentImpl : public ScanSegment std::vector indexNeighbourVec; // indices for parallel processing std::vector> indexProcessVec; + cv::AutoBuffer _labelsBuffer; // label autobuffer + cv::AutoBuffer _clusterBuffer; // cluster autobuffer + cv::AutoBuffer _pixelBuffer; // pixel autobuffer + std::vector> _offsetVec; // vector of offset autobuffers int* labelsBuffer; // label buffer int* clusterBuffer; // cluster buffer cv::Vec3b* labBuffer; // lab buffer @@ -209,9 +218,12 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe smallClusters = 0; // get array of seed rects - seedRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); - seedRectsExt = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); - offsetRects = static_cast(malloc(horzDiv * vertDiv * sizeof(cv::Rect))); + _seedRects = cv::AutoBuffer(horzDiv * vertDiv); + _seedRectsExt = cv::AutoBuffer(horzDiv * vertDiv); + _offsetRects = cv::AutoBuffer(horzDiv * vertDiv); + seedRects = _seedRects.data(); + seedRectsExt = _seedRectsExt.data(); + offsetRects = _offsetRects.data(); for (int y = 0; y < vertDiv; y++) { for (int x = 0; x < horzDiv; x++) { int xStart = (int)((float)x * horzLength); @@ -261,17 +273,23 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe // create buffers and initialise std::vector tempLoc{ cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; - neighbourLoc = static_cast(malloc(8 * sizeof(cv::Point))); + _neighbourLoc = cv::AutoBuffer(8); + neighbourLoc = _neighbourLoc.data(); memcpy(neighbourLoc, tempLoc.data(), 8 * sizeof(cv::Point)); - labelsBuffer = static_cast(malloc(indexSize * sizeof(int))); - clusterBuffer = static_cast(malloc(indexSize * sizeof(int))); - pixelBuffer = static_cast(malloc(indexSize)); + _labelsBuffer = cv::AutoBuffer(indexSize); + _clusterBuffer = cv::AutoBuffer(indexSize); + _pixelBuffer = cv::AutoBuffer(indexSize); + _offsetVec = std::vector>(effectivethreads); + labelsBuffer = _labelsBuffer.data(); + clusterBuffer = _clusterBuffer.data(); + pixelBuffer = _pixelBuffer.data(); offsetVec = std::vector(effectivethreads); int offsetSize = (clusterSize + 1) * sizeof(int); bool offsetAllocated = true; for (int i = 0; i < effectivethreads; i++) { - offsetVec[i] = static_cast(malloc(offsetSize)); + _offsetVec[i] = cv::AutoBuffer(offsetSize); + offsetVec[i] = _offsetVec[i].data(); if (offsetVec[i] == NULL) { offsetAllocated = false; } @@ -304,32 +322,6 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe ScanSegmentImpl::~ScanSegmentImpl() { // clean up - if (neighbourLoc != NULL) { - free(neighbourLoc); - } - if (seedRects != NULL) { - free(seedRects); - } - if (seedRectsExt != NULL) { - free(seedRectsExt); - } - if (offsetRects != NULL) { - free(offsetRects); - } - if (labelsBuffer != NULL) { - free(labelsBuffer); - } - if (clusterBuffer != NULL) { - free(clusterBuffer); - } - if (pixelBuffer != NULL) { - free(pixelBuffer); - } - for (int i = 0; i < effectivethreads; i++) { - if (offsetVec[i] != NULL) { - free(offsetVec[i]); - } - } if (!src.empty()) { src.release(); } From f106c54c707ec1f86f038cccf890f3e85953f1dd Mon Sep 17 00:00:00 2001 From: scloke Date: Thu, 11 Nov 2021 10:26:44 +1300 Subject: [PATCH 32/44] Update scansegment.hpp updated header guard --- modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 7c3b802b39..91d33273f3 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -8,8 +8,8 @@ // // -#ifndef __OPENCV_SCANSEGMENT_HPP__ -#define __OPENCV_SCANSEGMENT_HPP__ +#ifndef __OPENCV_XIMGPROC_SCANSEGMENT_HPP__ +#define __OPENCV_XIMGPROC_SCANSEGMENT_HPP__ #include From 6635ef25bb1435de60d2bc1f08d10b900e427f16 Mon Sep 17 00:00:00 2001 From: scloke Date: Thu, 11 Nov 2021 10:43:34 +1300 Subject: [PATCH 33/44] Update scansegment.cpp bug fix --- modules/ximgproc/src/scansegment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 624226fe24..fa0307c30e 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -279,7 +279,7 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe _labelsBuffer = cv::AutoBuffer(indexSize); _clusterBuffer = cv::AutoBuffer(indexSize); - _pixelBuffer = cv::AutoBuffer(indexSize); + _pixelBuffer = cv::AutoBuffer(indexSize); _offsetVec = std::vector>(effectivethreads); labelsBuffer = _labelsBuffer.data(); clusterBuffer = _clusterBuffer.data(); From e9413ad7f48e020a6237a93549fb6f99ebb72272 Mon Sep 17 00:00:00 2001 From: scloke Date: Thu, 11 Nov 2021 10:48:45 +1300 Subject: [PATCH 34/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index fa0307c30e..03cac745d5 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -74,7 +74,7 @@ class ScanSegmentImpl : public ScanSegment cv::AutoBuffer _labelsBuffer; // label autobuffer cv::AutoBuffer _clusterBuffer; // cluster autobuffer - cv::AutoBuffer _pixelBuffer; // pixel autobuffer + cv::AutoBuffer _pixelBuffer; // pixel autobuffer std::vector> _offsetVec; // vector of offset autobuffers int* labelsBuffer; // label buffer int* clusterBuffer; // cluster buffer From 8e86fc093a07d3848b9ed215e237ba59c513cd54 Mon Sep 17 00:00:00 2001 From: scloke Date: Fri, 19 Nov 2021 16:28:36 +1300 Subject: [PATCH 35/44] Update scansegment.cpp fixed process threads to the number of slices --- modules/ximgproc/src/scansegment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 03cac745d5..b43d6e0476 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -195,7 +195,7 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe // set the number of process threads processthreads = std::thread::hardware_concurrency(); if (slices > 0) { - processthreads = MIN(processthreads, slices); + processthreads = slices; } width = image_width; From ab87fa10994b0a967281433f46b0b2c29b216ee1 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 22 Nov 2021 12:18:32 +1300 Subject: [PATCH 36/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 61 ++++++++++++++-------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index b43d6e0476..af78c02f15 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -12,14 +12,13 @@ #include #include -#include namespace cv { namespace ximgproc { //! @addtogroup ximgproc_superpixel //! @{ -class ScanSegmentImpl : public ScanSegment +class ScanSegmentImpl CV_FINAL : public ScanSegment { #define UNKNOWN 0 #define BORDER -1 @@ -193,7 +192,7 @@ CV_EXPORTS Ptr createScanSegment(int image_width, int image_height, ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int slices, bool merge_small) { // set the number of process threads - processthreads = std::thread::hardware_concurrency(); + processthreads = cv::getNumThreads(); if (slices > 0) { processthreads = slices; } @@ -584,42 +583,42 @@ void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) // Create a new node with offsets mofs and iofs in queue idx #define ws_push(idx,mofs,iofs) \ { \ -if (!free_node) \ -free_node = allocWSNodes(storage); \ -node = free_node; \ -free_node = storage[free_node].next; \ -storage[node].next = 0; \ -storage[node].mask_ofs = mofs; \ -storage[node].img_ofs = iofs; \ -if (q[idx].last) \ -storage[q[idx].last].next = node; \ -else \ -q[idx].first = node; \ -q[idx].last = node; \ + if (!free_node) \ + free_node = allocWSNodes(storage); \ + node = free_node; \ + free_node = storage[free_node].next; \ + storage[node].next = 0; \ + storage[node].mask_ofs = mofs; \ + storage[node].img_ofs = iofs; \ + if (q[idx].last) \ + storage[q[idx].last].next = node; \ + else \ + q[idx].first = node; \ + q[idx].last = node; \ } // Get next node from queue idx #define ws_pop(idx,mofs,iofs) \ { \ -node = q[idx].first; \ -q[idx].first = storage[node].next; \ -if (!storage[node].next) \ -q[idx].last = 0; \ -storage[node].next = free_node; \ -free_node = node; \ -mofs = storage[node].mask_ofs; \ -iofs = storage[node].img_ofs; \ + node = q[idx].first; \ + q[idx].first = storage[node].next; \ + if (!storage[node].next) \ + q[idx].last = 0; \ + storage[node].next = free_node; \ + free_node = node; \ + mofs = storage[node].mask_ofs; \ + iofs = storage[node].img_ofs; \ } // Get highest absolute channel difference in diff #define c_diff(ptr1,ptr2,diff) \ { \ -db = std::abs((ptr1)[0] - (ptr2)[0]); \ -dg = std::abs((ptr1)[1] - (ptr2)[1]); \ -dr = std::abs((ptr1)[2] - (ptr2)[2]); \ -diff = ws_max(db, dg); \ -diff = ws_max(diff, dr); \ -assert(0 <= diff && diff <= 255); \ + db = std::abs((ptr1)[0] - (ptr2)[0]); \ + dg = std::abs((ptr1)[1] - (ptr2)[1]); \ + dr = std::abs((ptr1)[2] - (ptr2)[2]); \ + diff = ws_max(db, dg); \ + diff = ws_max(diff, dr); \ + CV_Assert(0 <= diff && diff <= 255); \ } CV_Assert(src.type() == CV_8UC3 && dst.type() == CV_32SC1); @@ -675,7 +674,7 @@ assert(0 <= diff && diff <= 255); \ } // Add to according queue - assert(0 <= idx && idx <= 255); + CV_Assert(0 <= idx && idx <= 255); ws_push(idx, i * mstep + j, i * istep + j * channel); m[0] = IN_QUEUE;//initial unvisited } @@ -786,7 +785,7 @@ assert(0 <= diff && diff <= 255); \ } } // Set label to current pixel in marker image - assert(lab != 0);//lab must be labeled with a nonzero number + CV_Assert(lab != 0);//lab must be labeled with a nonzero number m[0] = lab; // Add adjacent, unlabeled pixels to corresponding queue From 1b586b3e29c7c30ac0b8af74a5b87ef59155e3eb Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 22 Nov 2021 12:40:07 +1300 Subject: [PATCH 37/44] Update scansegment.cpp C++ 11 lambdas used instead of cv::ParallelLoopBody --- modules/ximgproc/src/scansegment.cpp | 100 ++++++--------------------- 1 file changed, 21 insertions(+), 79 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index af78c02f15..72b7e657f5 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -99,81 +99,7 @@ class ScanSegmentImpl CV_FINAL : public ScanSegment WSQueue() { first = last = 0; } int first, last; }; - - class PP1 : public cv::ParallelLoopBody - { - public: - PP1(ScanSegmentImpl* const scanSegment) - : ss(scanSegment) {} - virtual ~PP1() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP1(v); - } - } - private: - ScanSegmentImpl* const ss; - }; - - class PP2 : public cv::ParallelLoopBody - { - public: - PP2(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) - : ss(scanSegment), ctv(countVec) {} - virtual ~PP2() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP2((*ctv)[v]); - } - } - private: - ScanSegmentImpl* const ss; - std::vector>* ctv; - }; - - class PP3 : public cv::ParallelLoopBody - { - public: - PP3(ScanSegmentImpl* const scanSegment) - : ss(scanSegment) {} - virtual ~PP3() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP3(v); - } - } - private: - ScanSegmentImpl* const ss; - }; - - class PP4 : public cv::ParallelLoopBody - { - public: - PP4(ScanSegmentImpl* const scanSegment, std::vector>* const countVec) - : ss(scanSegment), ctv(countVec) {} - virtual ~PP4() {} - - void operator()(const cv::Range& range) const CV_OVERRIDE - { - for (int v = range.start; v < range.end; v++) - { - ss->OP4((*ctv)[v]); - } - } - private: - ScanSegmentImpl* const ss; - std::vector>* ctv; - }; - + void OP1(int v); void OP2(std::pair const& p); void OP3(int v); @@ -381,7 +307,11 @@ void ScanSegmentImpl::iterate(InputArray img) // start at the center of the rect, then run through the remainder labBuffer = reinterpret_cast(src.data); - cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP1(reinterpret_cast(this))); + cv::parallel_for_(Range(0, (int)indexNeighbourVec.size()), [&](const Range& range) { + for (int i = range.start; i < range.end; i++) { + OP1(i); + } + }); if (merge) { // get cutoff size for clusters @@ -412,16 +342,28 @@ void ScanSegmentImpl::iterate(InputArray img) clusterBuffer[countVec[i].first] = i + 1; } - cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP2(reinterpret_cast(this), &indexProcessVec)); + parallel_for_(Range(0, (int)indexProcessVec.size()), [&](const Range& range) { + for (int i = range.start; i < range.end; i++) { + OP2(indexProcessVec[i]); + } + }); // make copy of labels buffer memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); // run watershed - cv::parallel_for_(cv::Range(0, (int)indexNeighbourVec.size()), PP3(reinterpret_cast(this))); + cv::parallel_for_(Range(0, (int)indexNeighbourVec.size()), [&](const Range& range) { + for (int i = range.start; i < range.end; i++) { + OP3(i); + } + }); // copy back to labels mat - cv::parallel_for_(cv::Range(0, (int)indexProcessVec.size()), PP4(reinterpret_cast(this), &indexProcessVec)); + parallel_for_(Range(0, (int)indexProcessVec.size()), [&](const Range& range) { + for (int i = range.start; i < range.end; i++) { + OP4(indexProcessVec[i]); + } + }); } else { memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); From a766bb479601b6453b700970def6a024bd16c634 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 22 Nov 2021 14:46:04 +1300 Subject: [PATCH 38/44] Update scansegment.cpp changed neighbours location buffer to array --- modules/ximgproc/src/scansegment.cpp | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 72b7e657f5..6757ba4dce 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -62,11 +62,10 @@ class ScanSegmentImpl CV_FINAL : public ScanSegment cv::AutoBuffer _seedRects; // autobuffer of seed rectangles cv::AutoBuffer _seedRectsExt; // autobuffer of extended seed rectangles cv::AutoBuffer _offsetRects; // autobuffer of offset rectangles - cv::AutoBuffer _neighbourLoc;// autobuffer of neighbour locations cv::Rect* seedRects; // array of seed rectangles cv::Rect* seedRectsExt; // array of extended seed rectangles cv::Rect* offsetRects; // array of offset rectangles - cv::Point* neighbourLoc; // neighbour locations + cv::Point neighbourLoc[8] = { cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; // neighbour locations std::vector indexNeighbourVec; // indices for parallel processing std::vector> indexProcessVec; @@ -197,11 +196,6 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe indexProcessVec[processthreads - 1] = std::make_pair(processCurrent, indexSize); // create buffers and initialise - std::vector tempLoc{ cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; - _neighbourLoc = cv::AutoBuffer(8); - neighbourLoc = _neighbourLoc.data(); - memcpy(neighbourLoc, tempLoc.data(), 8 * sizeof(cv::Point)); - _labelsBuffer = cv::AutoBuffer(indexSize); _clusterBuffer = cv::AutoBuffer(indexSize); _pixelBuffer = cv::AutoBuffer(indexSize); From 67fa9e5507663e65b3daf63dee38b2c1933b293b Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 22 Nov 2021 17:38:12 +1300 Subject: [PATCH 39/44] Update scansegment.cpp remove whitespace --- modules/ximgproc/src/scansegment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 6757ba4dce..3a867c99e0 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -98,7 +98,7 @@ class ScanSegmentImpl CV_FINAL : public ScanSegment WSQueue() { first = last = 0; } int first, last; }; - + void OP1(int v); void OP2(std::pair const& p); void OP3(int v); From 2234e6ac82d1fd691f3dc079e185c02dabdf2397 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 24 Nov 2021 16:29:50 +1300 Subject: [PATCH 40/44] Update scansegment.cpp RAW pointers removed --- modules/ximgproc/src/scansegment.cpp | 140 ++++++++++----------------- 1 file changed, 50 insertions(+), 90 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 3a867c99e0..5513caf549 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -50,7 +50,6 @@ class ScanSegmentImpl CV_FINAL : public ScanSegment bool merge; // merge small superpixels int indexSize; // size of label mat vector int clusterSize; // max size of clusters - bool setupComplete; // is setup complete int clusterCount; // number of superpixels from the most recent iterate float adjTolerance; // adjusted colour tolerance @@ -59,27 +58,20 @@ class ScanSegmentImpl CV_FINAL : public ScanSegment int effectivethreads; // effective number of concurrent threads int smallClusters; // clusters below this pixel count are considered small for merging - cv::AutoBuffer _seedRects; // autobuffer of seed rectangles - cv::AutoBuffer _seedRectsExt; // autobuffer of extended seed rectangles - cv::AutoBuffer _offsetRects; // autobuffer of offset rectangles - cv::Rect* seedRects; // array of seed rectangles - cv::Rect* seedRectsExt; // array of extended seed rectangles - cv::Rect* offsetRects; // array of offset rectangles + cv::AutoBuffer seedRects; // autobuffer of seed rectangles + cv::AutoBuffer seedRectsExt; // autobuffer of extended seed rectangles + cv::AutoBuffer offsetRects; // autobuffer of offset rectangles cv::Point neighbourLoc[8] = { cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; // neighbour locations std::vector indexNeighbourVec; // indices for parallel processing std::vector> indexProcessVec; - cv::AutoBuffer _labelsBuffer; // label autobuffer - cv::AutoBuffer _clusterBuffer; // cluster autobuffer - cv::AutoBuffer _pixelBuffer; // pixel autobuffer - std::vector> _offsetVec; // vector of offset autobuffers - int* labelsBuffer; // label buffer - int* clusterBuffer; // cluster buffer + cv::AutoBuffer labelsBuffer; // label autobuffer + cv::AutoBuffer clusterBuffer; // cluster autobuffer + cv::AutoBuffer pixelBuffer; // pixel autobuffer + std::vector> offsetVec; // vector of offset autobuffers cv::Vec3b* labBuffer; // lab buffer - uchar* pixelBuffer; // pixel buffer int neighbourLocBuffer[neighbourCount]; // neighbour locations - std::vector offsetVec; // vector of offsets std::atomic clusterIndex, clusterID; // atomic indices @@ -142,12 +134,9 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe smallClusters = 0; // get array of seed rects - _seedRects = cv::AutoBuffer(horzDiv * vertDiv); - _seedRectsExt = cv::AutoBuffer(horzDiv * vertDiv); - _offsetRects = cv::AutoBuffer(horzDiv * vertDiv); - seedRects = _seedRects.data(); - seedRectsExt = _seedRectsExt.data(); - offsetRects = _offsetRects.data(); + seedRects = cv::AutoBuffer(horzDiv * vertDiv); + seedRectsExt = cv::AutoBuffer(horzDiv * vertDiv); + offsetRects = cv::AutoBuffer(horzDiv * vertDiv); for (int y = 0; y < vertDiv; y++) { for (int x = 0; x < horzDiv; x++) { int xStart = (int)((float)x * horzLength); @@ -171,9 +160,9 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe bnd_b += 1; } - seedRects[(y * horzDiv) + x] = seedRect; - seedRectsExt[(y * horzDiv) + x] = cv::Rect(bnd_l, bnd_t, bnd_r - bnd_l + 1, bnd_b - bnd_t + 1); - offsetRects[(y * horzDiv) + x] = cv::Rect(seedRect.x - bnd_l, seedRect.y - bnd_t, seedRect.width, seedRect.height); + seedRects.data()[(y * horzDiv) + x] = seedRect; + seedRectsExt.data()[(y * horzDiv) + x] = cv::Rect(bnd_l, bnd_t, bnd_r - bnd_l + 1, bnd_b - bnd_t + 1); + offsetRects.data()[(y * horzDiv) + x] = cv::Rect(seedRect.x - bnd_l, seedRect.y - bnd_t, seedRect.width, seedRect.height); } } @@ -196,46 +185,17 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe indexProcessVec[processthreads - 1] = std::make_pair(processCurrent, indexSize); // create buffers and initialise - _labelsBuffer = cv::AutoBuffer(indexSize); - _clusterBuffer = cv::AutoBuffer(indexSize); - _pixelBuffer = cv::AutoBuffer(indexSize); - _offsetVec = std::vector>(effectivethreads); - labelsBuffer = _labelsBuffer.data(); - clusterBuffer = _clusterBuffer.data(); - pixelBuffer = _pixelBuffer.data(); - offsetVec = std::vector(effectivethreads); + labelsBuffer = cv::AutoBuffer(indexSize); + clusterBuffer = cv::AutoBuffer(indexSize); + pixelBuffer = cv::AutoBuffer(indexSize); + offsetVec = std::vector>(effectivethreads); int offsetSize = (clusterSize + 1) * sizeof(int); - bool offsetAllocated = true; for (int i = 0; i < effectivethreads; i++) { - _offsetVec[i] = cv::AutoBuffer(offsetSize); - offsetVec[i] = _offsetVec[i].data(); - if (offsetVec[i] == NULL) { - offsetAllocated = false; - } + offsetVec[i] = cv::AutoBuffer(offsetSize); } for (int i = 0; i < neighbourCount; i++) { neighbourLocBuffer[i] = (neighbourLoc[i].y * width) + neighbourLoc[i].x; } - - if (labelsBuffer != NULL && clusterBuffer != NULL && pixelBuffer != NULL && offsetAllocated) { - setupComplete = true; - } - else { - setupComplete = false; - - if (labelsBuffer == NULL) { - CV_Error(Error::StsInternal, "Cannot initialise labels buffer"); - } - if (clusterBuffer == NULL) { - CV_Error(Error::StsInternal, "Cannot initialise cluster buffer"); - } - if (pixelBuffer == NULL) { - CV_Error(Error::StsInternal, "Cannot initialise pixel buffer"); - } - if (!offsetAllocated) { - CV_Error(Error::StsInternal, "Cannot initialise offset buffers"); - } - } } ScanSegmentImpl::~ScanSegmentImpl() @@ -294,7 +254,7 @@ void ScanSegmentImpl::iterate(InputArray img) labelsMat.setTo(NONE); // set labels buffer to UNCLASSIFIED - std::fill(labelsBuffer, labelsBuffer + indexSize, UNCLASSIFIED); + std::fill(labelsBuffer.data(), labelsBuffer.data() + indexSize, UNCLASSIFIED); // apply light blur cv::medianBlur(src, src, 3); @@ -305,7 +265,7 @@ void ScanSegmentImpl::iterate(InputArray img) for (int i = range.start; i < range.end; i++) { OP1(i); } - }); + }); if (merge) { // get cutoff size for clusters @@ -313,9 +273,9 @@ void ScanSegmentImpl::iterate(InputArray img) int clusterIndexSize = clusterIndex.load(); countVec.reserve(clusterIndexSize / 2); for (int i = 1; i < clusterIndexSize; i += 2) { - int count = clusterBuffer[i]; + int count = clusterBuffer.data()[i]; if (count >= smallClusters) { - int currentID = clusterBuffer[i - 1]; + int currentID = clusterBuffer.data()[i - 1]; countVec.push_back(std::make_pair(currentID, count)); } } @@ -330,20 +290,20 @@ void ScanSegmentImpl::iterate(InputArray img) clusterCount = (int)std::count_if(countVec.begin(), countVec.end(), [&cutoff](std::pair p) {return p.second > cutoff; }); // change labels to 1 -> clusterCount, 0 = UNKNOWN, reuse clusterbuffer - std::fill_n(clusterBuffer, indexSize, UNKNOWN); + std::fill_n(clusterBuffer.data(), indexSize, UNKNOWN); int countLimit = cutoff == -1 ? (int)countVec.size() : clusterCount; for (int i = 0; i < countLimit; i++) { - clusterBuffer[countVec[i].first] = i + 1; + clusterBuffer.data()[countVec[i].first] = i + 1; } parallel_for_(Range(0, (int)indexProcessVec.size()), [&](const Range& range) { for (int i = range.start; i < range.end; i++) { OP2(indexProcessVec[i]); } - }); + }); // make copy of labels buffer - memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); + memcpy(labelsMat.data, labelsBuffer.data(), indexSize * sizeof(int)); // run watershed cv::parallel_for_(Range(0, (int)indexNeighbourVec.size()), [&](const Range& range) { @@ -360,7 +320,7 @@ void ScanSegmentImpl::iterate(InputArray img) }); } else { - memcpy(labelsMat.data, labelsBuffer, indexSize * sizeof(int)); + memcpy(labelsMat.data, labelsBuffer.data(), indexSize * sizeof(int)); } src.release(); @@ -368,10 +328,10 @@ void ScanSegmentImpl::iterate(InputArray img) void ScanSegmentImpl::OP1(int v) { - cv::Rect seedRect = seedRects[v]; + cv::Rect seedRect = seedRects.data()[v]; for (int y = seedRect.y; y < seedRect.y + seedRect.height; y++) { for (int x = seedRect.x; x < seedRect.x + seedRect.width; x++) { - expandCluster(offsetVec[v], cv::Point(x, y)); + expandCluster(offsetVec.data()[v], cv::Point(x, y)); } } } @@ -380,22 +340,22 @@ void ScanSegmentImpl::OP2(std::pair const& p) { std::pair& q = const_cast&>(p); for (int i = q.first; i < q.second; i++) { - labelsBuffer[i] = clusterBuffer[labelsBuffer[i]]; - if (labelsBuffer[i] == UNKNOWN) { - pixelBuffer[i] = 255; + labelsBuffer.data()[i] = clusterBuffer.data()[labelsBuffer.data()[i]]; + if (labelsBuffer.data()[i] == UNKNOWN) { + pixelBuffer.data()[i] = 255; } else { - pixelBuffer[i] = 0; + pixelBuffer.data()[i] = 0; } } } void ScanSegmentImpl::OP3(int v) { - cv::Rect seedRectExt = seedRectsExt[v]; + cv::Rect seedRectExt = seedRectsExt.data()[v]; cv::Mat seedLabels = labelsMat(seedRectExt).clone(); watershedEx(src(seedRectExt), seedLabels); - seedLabels(offsetRects[v]).copyTo(labelsMat(seedRects[v])); + seedLabels(offsetRects.data()[v]).copyTo(labelsMat(seedRects.data()[v])); seedLabels.release(); } @@ -403,8 +363,8 @@ void ScanSegmentImpl::OP4(std::pair const& p) { std::pair& q = const_cast&>(p); for (int i = q.first; i < q.second; i++) { - if (pixelBuffer[i] == 0) { - ((int*)labelsMat.data)[i] = labelsBuffer[i] - 1; + if (pixelBuffer.data()[i] == 0) { + ((int*)labelsMat.data)[i] = labelsBuffer.data()[i] - 1; } else { ((int*)labelsMat.data)[i] -= 1; @@ -416,7 +376,7 @@ void ScanSegmentImpl::OP4(std::pair const& p) void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) { int pointIndex = (point.y * width) + point.x; - if (labelsBuffer[pointIndex] == UNCLASSIFIED) { + if (labelsBuffer.data()[pointIndex] == UNCLASSIFIED) { int offsetStart = 0; int offsetEnd = 0; int currentClusterID = clusterID.fetch_add(1); @@ -424,11 +384,11 @@ void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) calculateCluster(offsetBuffer, &offsetEnd, pointIndex, currentClusterID); if (offsetStart == offsetEnd) { - labelsBuffer[pointIndex] = UNKNOWN; + labelsBuffer.data()[pointIndex] = UNKNOWN; } else { // set cluster id and get core point index - labelsBuffer[pointIndex] = currentClusterID; + labelsBuffer.data()[pointIndex] = currentClusterID; while (offsetStart < offsetEnd) { int intoffset2 = *(offsetBuffer + offsetStart); @@ -442,8 +402,8 @@ void ScanSegmentImpl::expandCluster(int* offsetBuffer, const cv::Point& point) // store to buffer int currentClusterIndex = clusterIndex.fetch_add(2); - clusterBuffer[currentClusterIndex] = currentClusterID; - clusterBuffer[currentClusterIndex + 1] = offsetEnd; + clusterBuffer.data()[currentClusterIndex] = currentClusterID; + clusterBuffer.data()[currentClusterIndex + 1] = offsetEnd; } } } @@ -453,13 +413,13 @@ void ScanSegmentImpl::calculateCluster(int* offsetBuffer, int* offsetEnd, int po for (int i = 0; i < neighbourCount; i++) { if (*offsetEnd < clusterSize) { int intoffset2 = pointIndex + neighbourLocBuffer[i]; - if (intoffset2 >= 0 && intoffset2 < indexSize && labelsBuffer[intoffset2] == UNCLASSIFIED) { + if (intoffset2 >= 0 && intoffset2 < indexSize && labelsBuffer.data()[intoffset2] == UNCLASSIFIED) { int diff1 = (int)labBuffer[pointIndex][0] - (int)labBuffer[intoffset2][0]; int diff2 = (int)labBuffer[pointIndex][1] - (int)labBuffer[intoffset2][1]; int diff3 = (int)labBuffer[pointIndex][2] - (int)labBuffer[intoffset2][2]; if ((diff1 * diff1) + (diff2 * diff2) + (diff3 * diff3) <= (int)adjTolerance) { - labelsBuffer[intoffset2] = currentClusterID; + labelsBuffer.data()[intoffset2] = currentClusterID; offsetBuffer[*offsetEnd] = intoffset2; (*offsetEnd)++; } @@ -516,9 +476,9 @@ void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) // MIN(a,b) = a - MAX(a-b,0) #define ws_min(a,b) ((a) - subs_tab[(a)-(b)+NQ]) -// Create a new node with offsets mofs and iofs in queue idx + // Create a new node with offsets mofs and iofs in queue idx #define ws_push(idx,mofs,iofs) \ -{ \ + { \ if (!free_node) \ free_node = allocWSNodes(storage); \ node = free_node; \ @@ -531,11 +491,11 @@ void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) else \ q[idx].first = node; \ q[idx].last = node; \ -} + } -// Get next node from queue idx + // Get next node from queue idx #define ws_pop(idx,mofs,iofs) \ -{ \ + { \ node = q[idx].first; \ q[idx].first = storage[node].next; \ if (!storage[node].next) \ @@ -544,7 +504,7 @@ void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) free_node = node; \ mofs = storage[node].mask_ofs; \ iofs = storage[node].img_ofs; \ -} + } // Get highest absolute channel difference in diff #define c_diff(ptr1,ptr2,diff) \ From 3f398a5efb54ec7bfe88473f6ded1bbfe09c13d6 Mon Sep 17 00:00:00 2001 From: scloke Date: Wed, 24 Nov 2021 16:43:10 +1300 Subject: [PATCH 41/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 5513caf549..1049e9ddca 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -211,9 +211,6 @@ ScanSegmentImpl::~ScanSegmentImpl() void ScanSegmentImpl::iterate(InputArray img) { - // ensure setup successfully completed - CV_Assert(setupComplete); - if (img.isMat()) { // get Mat @@ -331,7 +328,7 @@ void ScanSegmentImpl::OP1(int v) cv::Rect seedRect = seedRects.data()[v]; for (int y = seedRect.y; y < seedRect.y + seedRect.height; y++) { for (int x = seedRect.x; x < seedRect.x + seedRect.width; x++) { - expandCluster(offsetVec.data()[v], cv::Point(x, y)); + expandCluster(offsetVec[v].data(), cv::Point(x, y)); } } } From 8b132b5c73edd4ce6483b5f8e4fa50a3e0e2e0f9 Mon Sep 17 00:00:00 2001 From: Alexander Alekhin Date: Sun, 28 Nov 2021 03:28:40 +0000 Subject: [PATCH 42/44] ximgproc(ScanSegment): coding style, add smoke test --- .../include/opencv2/ximgproc/scansegment.hpp | 90 ++++++++++--------- modules/ximgproc/src/scansegment.cpp | 68 +++++++------- modules/ximgproc/test/test_scansegment.cpp | 35 ++++++++ 3 files changed, 115 insertions(+), 78 deletions(-) create mode 100644 modules/ximgproc/test/test_scansegment.cpp diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 91d33273f3..1d4e754b6c 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -1,24 +1,19 @@ -//////////////////////////////////////////////////////////////////////////////////////// -// // 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. // // Copyright (C) 2021, Dr Seng Cheong Loke (lokesengcheong@gmail.com) -// -// #ifndef __OPENCV_XIMGPROC_SCANSEGMENT_HPP__ #define __OPENCV_XIMGPROC_SCANSEGMENT_HPP__ #include -namespace cv -{ -namespace ximgproc -{ +namespace cv { namespace ximgproc { + /** @brief Class implementing the F-DBSCAN (Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm) superpixels algorithm by Loke SC, et al. + The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s with a Boundary Recall of 0.797 and an Achievable Segmentation Accuracy of 0.944. The computational complexity is quadratic O(n2) and @@ -28,40 +23,50 @@ when the number of processing threads is fixed, and requires the source image to class CV_EXPORTS_W ScanSegment : public Algorithm { public: - /** @brief Returns the actual superpixel segmentation from the last image processed using iterate. - Returns zero if no image has been processed. - */ - CV_WRAP virtual int getNumberOfSuperpixels() = 0; - - /** @brief Calculates the superpixel segmentation on a given image with the initialized - parameters in the ScanSegment object. This function can be called again for other images - without the need of initializing the algorithm with createScanSegment(). This save the - computational cost of allocating memory for all the structures of the algorithm. - @param img Input image. Supported format: CV_8UC3. Image size must match with the initialized - image size with the function createScanSegment(). It MUST be in Lab color space. - */ - CV_WRAP virtual void iterate(InputArray img) = 0; - - /** @brief Returns the segmentation labeling of the image. Each label represents a superpixel, - and each pixel is assigned to one superpixel label. - @param labels_out Return: A CV_32UC1 integer array containing the labels of the superpixel - segmentation. The labels are in the range [0, getNumberOfSuperpixels()]. - */ - CV_WRAP virtual void getLabels(OutputArray labels_out) = 0; - - /** @brief Returns the mask of the superpixel segmentation stored in the ScanSegment object. - @param image Return: CV_8UC1 image mask where -1 indicates that the pixel is a superpixel border, - and 0 otherwise. - @param thick_line If false, the border is only one pixel wide, otherwise all pixels at the border - are masked. - The function return the boundaries of the superpixel segmentation. - */ - CV_WRAP virtual void getLabelContourMask(OutputArray image, bool thick_line = false) = 0; - - virtual ~ScanSegment() {} + virtual ~ScanSegment(); + + /** @brief Returns the actual superpixel segmentation from the last image processed using iterate. + + Returns zero if no image has been processed. + */ + CV_WRAP virtual int getNumberOfSuperpixels() = 0; + + /** @brief Calculates the superpixel segmentation on a given image with the initialized + parameters in the ScanSegment object. + + This function can be called again for other images without the need of initializing the algorithm with createScanSegment(). + This save the computational cost of allocating memory for all the structures of the algorithm. + + @param img Input image. Supported format: CV_8UC3. Image size must match with the initialized + image size with the function createScanSegment(). It MUST be in Lab color space. + */ + CV_WRAP virtual void iterate(InputArray img) = 0; + + /** @brief Returns the segmentation labeling of the image. + + Each label represents a superpixel, and each pixel is assigned to one superpixel label. + + @param labels_out Return: A CV_32UC1 integer array containing the labels of the superpixel + segmentation. The labels are in the range [0, getNumberOfSuperpixels()]. + */ + CV_WRAP virtual void getLabels(OutputArray labels_out) = 0; + + /** @brief Returns the mask of the superpixel segmentation stored in the ScanSegment object. + + The function return the boundaries of the superpixel segmentation. + + @param image Return: CV_8UC1 image mask where -1 indicates that the pixel is a superpixel border, and 0 otherwise. + @param thick_line If false, the border is only one pixel wide, otherwise all pixels at the border are masked. + */ + CV_WRAP virtual void getLabelContourMask(OutputArray image, bool thick_line = false) = 0; }; /** @brief Initializes a ScanSegment object. + +The function initializes a ScanSegment object for the input image. It stores the parameters of +the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel +algorithm, which are: num_superpixels, threads, and merge_small. + @param image_width Image width. @param image_height Image height. @param num_superpixels Desired number of superpixels. Note that the actual number may be smaller @@ -71,11 +76,8 @@ get the actual number. of threads. In practice, four threads is enough for smaller images and eight threads for larger ones. @param merge_small merge small segments to give the desired number of superpixels. Processing is much faster without merging, but many small segments will be left in the image. -The function initializes a ScanSegment object for the input image. It stores the parameters of -the image: image_width and image_height. It also sets the parameters of the F-DBSCAN superpixel -algorithm, which are: num_superpixels, threads, and merge_small. */ CV_EXPORTS_W cv::Ptr createScanSegment(int image_width, int image_height, int num_superpixels, int slices = 8, bool merge_small = true); -} -} + +}} // namespace #endif diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 1049e9ddca..116084e023 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -15,8 +15,11 @@ namespace cv { namespace ximgproc { -//! @addtogroup ximgproc_superpixel -//! @{ + +ScanSegment::~ScanSegment() +{ + // nothing +} class ScanSegmentImpl CV_FINAL : public ScanSegment { @@ -44,38 +47,38 @@ class ScanSegmentImpl CV_FINAL : public ScanSegment static const int smallClustersDiv = 10000; // divide total pixels by this to give smallClusters const float tolerance100 = 10.0f; // colour tolerance for image size of 100x100px - int processthreads; // concurrent threads for parallel processing + int processthreads; // concurrent threads for parallel processing int width, height; // image size int superpixels; // number of superpixels bool merge; // merge small superpixels int indexSize; // size of label mat vector int clusterSize; // max size of clusters int clusterCount; // number of superpixels from the most recent iterate - float adjTolerance; // adjusted colour tolerance + float adjTolerance; // adjusted colour tolerance int horzDiv, vertDiv; // number of horizontal and vertical segments float horzLength, vertLength; // length of each segment int effectivethreads; // effective number of concurrent threads int smallClusters; // clusters below this pixel count are considered small for merging - cv::AutoBuffer seedRects; // autobuffer of seed rectangles - cv::AutoBuffer seedRectsExt; // autobuffer of extended seed rectangles - cv::AutoBuffer offsetRects; // autobuffer of offset rectangles - cv::Point neighbourLoc[8] = { cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; // neighbour locations + cv::AutoBuffer seedRects; // autobuffer of seed rectangles + cv::AutoBuffer seedRectsExt; // autobuffer of extended seed rectangles + cv::AutoBuffer offsetRects; // autobuffer of offset rectangles + cv::Point neighbourLoc[8] = { cv::Point(-1, -1), cv::Point(0, -1), cv::Point(1, -1), cv::Point(-1, 0), cv::Point(1, 0), cv::Point(-1, 1), cv::Point(0, 1), cv::Point(1, 1) }; // neighbour locations - std::vector indexNeighbourVec; // indices for parallel processing + std::vector indexNeighbourVec; // indices for parallel processing std::vector> indexProcessVec; - cv::AutoBuffer labelsBuffer; // label autobuffer - cv::AutoBuffer clusterBuffer; // cluster autobuffer + cv::AutoBuffer labelsBuffer; // label autobuffer + cv::AutoBuffer clusterBuffer; // cluster autobuffer cv::AutoBuffer pixelBuffer; // pixel autobuffer std::vector> offsetVec; // vector of offset autobuffers - cv::Vec3b* labBuffer; // lab buffer + cv::Vec3b* labBuffer; // lab buffer int neighbourLocBuffer[neighbourCount]; // neighbour locations - std::atomic clusterIndex, clusterID; // atomic indices + std::atomic clusterIndex, clusterID; // atomic indices - cv::Mat src, labelsMat; // mats + cv::Mat src, labelsMat; // mats struct WSNode { @@ -109,22 +112,19 @@ CV_EXPORTS Ptr createScanSegment(int image_width, int image_height, ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_superpixels, int slices, bool merge_small) { // set the number of process threads - processthreads = cv::getNumThreads(); - if (slices > 0) { - processthreads = slices; - } + processthreads = (slices > 0) ? slices : cv::getNumThreads(); width = image_width; height = image_height; superpixels = num_superpixels; merge = merge_small; indexSize = height * width; - clusterSize = (int)(1.1f * (float)(width * height) / (float)superpixels); + clusterSize = cvRound(1.1f * (float)(width * height) / (float)superpixels); clusterCount = 0; labelsMat = cv::Mat(height, width, CV_32SC1); // divide bounds area into uniformly distributed rectangular segments - int shortCount = (int)floorf(sqrtf((float)processthreads)); + int shortCount = cvFloor(sqrtf((float)processthreads)); int longCount = processthreads / shortCount; horzDiv = width > height ? longCount : shortCount; vertDiv = width > height ? shortCount : longCount; @@ -139,8 +139,8 @@ ScanSegmentImpl::ScanSegmentImpl(int image_width, int image_height, int num_supe offsetRects = cv::AutoBuffer(horzDiv * vertDiv); for (int y = 0; y < vertDiv; y++) { for (int x = 0; x < horzDiv; x++) { - int xStart = (int)((float)x * horzLength); - int yStart = (int)((float)y * vertLength); + int xStart = cvFloor((float)x * horzLength); + int yStart = cvFloor((float)y * vertLength); cv::Rect seedRect = cv::Rect(xStart, yStart, (int)(x == horzDiv - 1 ? width - xStart : horzLength), (int)(y == vertDiv - 1 ? height - yStart : vertLength)); int bnd_l = seedRect.x; @@ -262,7 +262,7 @@ void ScanSegmentImpl::iterate(InputArray img) for (int i = range.start; i < range.end; i++) { OP1(i); } - }); + }); if (merge) { // get cutoff size for clusters @@ -280,7 +280,7 @@ void ScanSegmentImpl::iterate(InputArray img) // sort descending std::sort(countVec.begin(), countVec.end(), [](const std::pair& left, const std::pair& right) { return left.second > right.second; - }); + }); int countSize = (int)countVec.size(); int cutoff = MAX(smallClusters, countVec[MIN(countSize - 1, superpixels - 1)].second); @@ -297,7 +297,7 @@ void ScanSegmentImpl::iterate(InputArray img) for (int i = range.start; i < range.end; i++) { OP2(indexProcessVec[i]); } - }); + }); // make copy of labels buffer memcpy(labelsMat.data, labelsBuffer.data(), indexSize * sizeof(int)); @@ -307,16 +307,17 @@ void ScanSegmentImpl::iterate(InputArray img) for (int i = range.start; i < range.end; i++) { OP3(i); } - }); + }); // copy back to labels mat parallel_for_(Range(0, (int)indexProcessVec.size()), [&](const Range& range) { for (int i = range.start; i < range.end; i++) { OP4(indexProcessVec[i]); } - }); + }); } - else { + else + { memcpy(labelsMat.data, labelsBuffer.data(), indexSize * sizeof(int)); } @@ -475,8 +476,8 @@ void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) // Create a new node with offsets mofs and iofs in queue idx #define ws_push(idx,mofs,iofs) \ - { \ - if (!free_node) \ +{ \ + if (!free_node) \ free_node = allocWSNodes(storage); \ node = free_node; \ free_node = storage[free_node].next; \ @@ -488,11 +489,11 @@ void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) else \ q[idx].first = node; \ q[idx].last = node; \ - } +} // Get next node from queue idx #define ws_pop(idx,mofs,iofs) \ - { \ +{ \ node = q[idx].first; \ q[idx].first = storage[node].next; \ if (!storage[node].next) \ @@ -501,7 +502,7 @@ void ScanSegmentImpl::watershedEx(const cv::Mat& src, cv::Mat& dst) free_node = node; \ mofs = storage[node].mask_ofs; \ iofs = storage[node].img_ofs; \ - } +} // Get highest absolute channel difference in diff #define c_diff(ptr1,ptr2,diff) \ @@ -767,6 +768,5 @@ void ScanSegmentImpl::getLabelContourMask(OutputArray image, bool thick_line) } } -//! @} } // namespace ximgproc } // namespace cv diff --git a/modules/ximgproc/test/test_scansegment.cpp b/modules/ximgproc/test/test_scansegment.cpp new file mode 100644 index 0000000000..fecda0fd35 --- /dev/null +++ b/modules/ximgproc/test/test_scansegment.cpp @@ -0,0 +1,35 @@ +// 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 "test_precomp.hpp" + +namespace opencv_test { namespace { + +static void runScanSegment(int slices) + +{ + Mat img = imread(cvtest::findDataFile("cv/shared/lena.png"), IMREAD_COLOR); + Mat labImg; + cvtColor(img, labImg, COLOR_BGR2Lab); + Ptr ss = createScanSegment(labImg.cols, labImg.rows, 500, slices, true); + ss->iterate(labImg); + int numSuperpixels = ss->getNumberOfSuperpixels(); + EXPECT_GT(numSuperpixels, 100); + EXPECT_LE(numSuperpixels, 500); + Mat res; + ss->getLabelContourMask(res, false); + EXPECT_GE(cvtest::norm(res, NORM_L1), 1000000); + + if (cvtest::debugLevel >= 10) + { + imshow("ScanSegment", res); + waitKey(); + } +} + +TEST(ximgproc_ScanSegment, smoke) { runScanSegment(1); } +TEST(ximgproc_ScanSegment, smoke4) { runScanSegment(4); } +TEST(ximgproc_ScanSegment, smoke8) { runScanSegment(8); } + +}} // namespace From 30e7b5b7bb0adc90c9eec82faf5fb16d98b0c7aa Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 29 Nov 2021 16:45:24 +1300 Subject: [PATCH 43/44] Update scansegment.hpp added citation --- modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp index 1d4e754b6c..5e0a673f9b 100644 --- a/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp +++ b/modules/ximgproc/include/opencv2/ximgproc/scansegment.hpp @@ -12,7 +12,7 @@ namespace cv { namespace ximgproc { /** @brief Class implementing the F-DBSCAN (Accelerated superpixel image segmentation with a parallelized DBSCAN algorithm) superpixels -algorithm by Loke SC, et al. +algorithm by Loke SC, et al. @cite loke2021accelerated for original paper. The algorithm uses a parallelised DBSCAN cluster search that is resistant to noise, competitive in segmentation quality, and faster than existing superpixel segmentation methods. When tested on the Berkeley Segmentation Dataset, the average processing speed is 175 frames/s From acc879b70dbe85a956d4c3c0895c4285290a1870 Mon Sep 17 00:00:00 2001 From: scloke Date: Mon, 29 Nov 2021 17:06:11 +1300 Subject: [PATCH 44/44] Update scansegment.cpp bug fixes --- modules/ximgproc/src/scansegment.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/ximgproc/src/scansegment.cpp b/modules/ximgproc/src/scansegment.cpp index 116084e023..037ea69e14 100644 --- a/modules/ximgproc/src/scansegment.cpp +++ b/modules/ximgproc/src/scansegment.cpp @@ -336,8 +336,7 @@ void ScanSegmentImpl::OP1(int v) void ScanSegmentImpl::OP2(std::pair const& p) { - std::pair& q = const_cast&>(p); - for (int i = q.first; i < q.second; i++) { + for (int i = p.first; i < p.second; i++) { labelsBuffer.data()[i] = clusterBuffer.data()[labelsBuffer.data()[i]]; if (labelsBuffer.data()[i] == UNKNOWN) { pixelBuffer.data()[i] = 255; @@ -359,8 +358,7 @@ void ScanSegmentImpl::OP3(int v) void ScanSegmentImpl::OP4(std::pair const& p) { - std::pair& q = const_cast&>(p); - for (int i = q.first; i < q.second; i++) { + for (int i = p.first; i < p.second; i++) { if (pixelBuffer.data()[i] == 0) { ((int*)labelsMat.data)[i] = labelsBuffer.data()[i] - 1; }