diff --git a/modules/gapi/include/opencv2/gapi/core.hpp b/modules/gapi/include/opencv2/gapi/core.hpp index 8825585696..1e3eee86c3 100644 --- a/modules/gapi/include/opencv2/gapi/core.hpp +++ b/modules/gapi/include/opencv2/gapi/core.hpp @@ -26,6 +26,7 @@ @defgroup gapi_transform Graph API: Image and channel composition functions @} */ + namespace cv { namespace gapi { namespace core { using GMat2 = std::tuple; @@ -508,6 +509,77 @@ namespace core { return in.withType(in.depth, in.chan).withSize(dsize); } }; + + G_TYPED_KERNEL( + GKMeansND, + ,GMat,GMat>(GMat,int,GMat,TermCriteria,int,KmeansFlags)>, + "org.opencv.core.kmeansND") { + + static std::tuple + outMeta(const GMatDesc& in, int K, const GMatDesc& bestLabels, const TermCriteria&, int, + KmeansFlags flags) { + GAPI_Assert(in.depth == CV_32F); + std::vector amount_n_dim = detail::checkVector(in); + int amount = amount_n_dim[0], dim = amount_n_dim[1]; + if (amount == -1) // Mat with height != 1, width != 1, channels != 1 given + { // which means that kmeans will consider the following: + amount = in.size.height; + dim = in.size.width * in.chan; + } + // kmeans sets these labels' sizes when no bestLabels given: + GMatDesc out_labels(CV_32S, 1, Size{1, amount}); + // kmeans always sets these centers' sizes: + GMatDesc centers (CV_32F, 1, Size{dim, K}); + if (flags & KMEANS_USE_INITIAL_LABELS) + { + GAPI_Assert(bestLabels.depth == CV_32S); + int labels_amount = detail::checkVector(bestLabels, 1u); + GAPI_Assert(labels_amount == amount); + out_labels = bestLabels; // kmeans preserves bestLabels' sizes if given + } + return std::make_tuple(empty_gopaque_desc(), out_labels, centers); + } + }; + + G_TYPED_KERNEL( + GKMeansNDNoInit, + ,GMat,GMat>(GMat,int,TermCriteria,int,KmeansFlags)>, + "org.opencv.core.kmeansNDNoInit") { + + static std::tuple + outMeta(const GMatDesc& in, int K, const TermCriteria&, int, KmeansFlags flags) { + GAPI_Assert( !(flags & KMEANS_USE_INITIAL_LABELS) ); + GAPI_Assert(in.depth == CV_32F); + std::vector amount_n_dim = detail::checkVector(in); + int amount = amount_n_dim[0], dim = amount_n_dim[1]; + if (amount == -1) // Mat with height != 1, width != 1, channels != 1 given + { // which means that kmeans will consider the following: + amount = in.size.height; + dim = in.size.width * in.chan; + } + GMatDesc out_labels(CV_32S, 1, Size{1, amount}); + GMatDesc centers (CV_32F, 1, Size{dim, K}); + return std::make_tuple(empty_gopaque_desc(), out_labels, centers); + } + }; + + G_TYPED_KERNEL(GKMeans2D, ,GArray,GArray> + (GArray,int,GArray,TermCriteria,int,KmeansFlags)>, + "org.opencv.core.kmeans2D") { + static std::tuple + outMeta(const GArrayDesc&,int,const GArrayDesc&,const TermCriteria&,int,KmeansFlags) { + return std::make_tuple(empty_gopaque_desc(), empty_array_desc(), empty_array_desc()); + } + }; + + G_TYPED_KERNEL(GKMeans3D, ,GArray,GArray> + (GArray,int,GArray,TermCriteria,int,KmeansFlags)>, + "org.opencv.core.kmeans3D") { + static std::tuple + outMeta(const GArrayDesc&,int,const GArrayDesc&,const TermCriteria&,int,KmeansFlags) { + return std::make_tuple(empty_gopaque_desc(), empty_array_desc(), empty_array_desc()); + } + }; } // namespace core namespace streaming { @@ -1757,6 +1829,79 @@ GAPI_EXPORTS GMat warpAffine(const GMat& src, const Mat& M, const Size& dsize, i int borderMode = cv::BORDER_CONSTANT, const Scalar& borderValue = Scalar()); //! @} gapi_transform +/** @brief Finds centers of clusters and groups input samples around the clusters. + +The function kmeans implements a k-means algorithm that finds the centers of K clusters +and groups the input samples around the clusters. As an output, \f$\texttt{bestLabels}_i\f$ +contains a 0-based cluster index for the \f$i^{th}\f$ sample. + +@note + - Function textual ID is "org.opencv.core.kmeansND" + - In case of an N-dimentional points' set given, input GMat can have the following traits: +2 dimensions, a single row or column if there are N channels, +or N columns if there is a single channel. Mat should have @ref CV_32F depth. + - Although, if GMat with height != 1, width != 1, channels != 1 given as data, n-dimensional +samples are considered given in amount of A, where A = height, n = width * channels. + - In case of GMat given as data: + - the output labels are returned as 1-channel GMat with sizes +width = 1, height = A, where A is samples amount, or width = bestLabels.width, +height = bestLabels.height if bestLabels given; + - the cluster centers are returned as 1-channel GMat with sizes +width = n, height = K, where n is samples' dimentionality and K is clusters' amount. + - As one of possible usages, if you want to control the initial labels for each attempt +by yourself, you can utilize just the core of the function. To do that, set the number +of attempts to 1, initialize labels each time using a custom algorithm, pass them with the +( flags = #KMEANS_USE_INITIAL_LABELS ) flag, and then choose the best (most-compact) clustering. + +@param data Data for clustering. An array of N-Dimensional points with float coordinates is needed. +Function can take GArray, GArray for 2D and 3D cases or GMat for any +dimentionality and channels. +@param K Number of clusters to split the set by. +@param bestLabels Optional input integer array that can store the supposed initial cluster indices +for every sample. Used when ( flags = #KMEANS_USE_INITIAL_LABELS ) flag is set. +@param criteria The algorithm termination criteria, that is, the maximum number of iterations +and/or the desired accuracy. The accuracy is specified as criteria.epsilon. As soon as each of +the cluster centers moves by less than criteria.epsilon on some iteration, the algorithm stops. +@param attempts Flag to specify the number of times the algorithm is executed using different +initial labellings. The algorithm returns the labels that yield the best compactness (see the first +function return value). +@param flags Flag that can take values of cv::KmeansFlags . + +@return + - Compactness measure that is computed as +\f[\sum _i \| \texttt{samples} _i - \texttt{centers} _{ \texttt{labels} _i} \| ^2\f] +after every attempt. The best (minimum) value is chosen and the corresponding labels and the +compactness value are returned by the function. + - Integer array that stores the cluster indices for every sample. + - Array of the cluster centers. +*/ +GAPI_EXPORTS std::tuple,GMat,GMat> +kmeans(const GMat& data, const int K, const GMat& bestLabels, + const TermCriteria& criteria, const int attempts, const KmeansFlags flags); + +/** @overload +@note + - Function textual ID is "org.opencv.core.kmeansNDNoInit" + - #KMEANS_USE_INITIAL_LABELS flag must not be set while using this overload. + */ +GAPI_EXPORTS std::tuple,GMat,GMat> +kmeans(const GMat& data, const int K, const TermCriteria& criteria, const int attempts, + const KmeansFlags flags); + +/** @overload +@note Function textual ID is "org.opencv.core.kmeans2D" + */ +GAPI_EXPORTS std::tuple,GArray,GArray> +kmeans(const GArray& data, const int K, const GArray& bestLabels, + const TermCriteria& criteria, const int attempts, const KmeansFlags flags); + +/** @overload +@note Function textual ID is "org.opencv.core.kmeans3D" + */ +GAPI_EXPORTS std::tuple,GArray,GArray> +kmeans(const GArray& data, const int K, const GArray& bestLabels, + const TermCriteria& criteria, const int attempts, const KmeansFlags flags); + namespace streaming { /** @brief Gets dimensions from Mat. diff --git a/modules/gapi/include/opencv2/gapi/gmat.hpp b/modules/gapi/include/opencv2/gapi/gmat.hpp index f441413be5..20a10db92b 100644 --- a/modules/gapi/include/opencv2/gapi/gmat.hpp +++ b/modules/gapi/include/opencv2/gapi/gmat.hpp @@ -203,6 +203,27 @@ struct GAPI_EXPORTS GMatDesc static inline GMatDesc empty_gmat_desc() { return GMatDesc{-1,-1,{-1,-1}}; } +namespace gapi { namespace detail { +/** Checks GMatDesc fields if the passed matrix is a set of n-dimentional points. +@param in GMatDesc to check. +@param n expected dimensionality. +@return the amount of points. In case input matrix can't be described as vector of points +of expected dimensionality, returns -1. + */ +int checkVector(const GMatDesc& in, const size_t n); + +/** @overload + +Checks GMatDesc fields if the passed matrix can be described as a set of points of any +dimensionality. + +@return array of two elements in form of std::vector: the amount of points +and their calculated dimensionality. In case input matrix can't be described as vector of points, +returns {-1, -1}. + */ +std::vector checkVector(const GMatDesc& in); +}} // namespace gapi::detail + #if !defined(GAPI_STANDALONE) GAPI_EXPORTS GMatDesc descr_of(const cv::UMat &mat); #endif // !defined(GAPI_STANDALONE) diff --git a/modules/gapi/include/opencv2/gapi/imgproc.hpp b/modules/gapi/include/opencv2/gapi/imgproc.hpp index 7435ec1e1d..46b53c0416 100644 --- a/modules/gapi/include/opencv2/gapi/imgproc.hpp +++ b/modules/gapi/include/opencv2/gapi/imgproc.hpp @@ -43,15 +43,6 @@ void validateFindingContoursMeta(const int depth, const int chan, const int mode break; } } - -// Checks if the passed mat is a set of n-dimentional points of the given depth -bool isPointsVector(const int chan, const cv::Size &size, const int depth, - const int n, const int ddepth = -1) -{ - return (ddepth == depth || ddepth < 0) && - ((chan == n && (size.height == 1 || size.width == 1)) || - (chan == 1 && size.width == n)); -} } // anonymous namespace namespace cv { namespace gapi { @@ -212,10 +203,17 @@ namespace imgproc { G_TYPED_KERNEL(GBoundingRectMat, (GMat)>, "org.opencv.imgproc.shape.boundingRectMat") { static GOpaqueDesc outMeta(GMatDesc in) { - GAPI_Assert((in.depth == CV_8U && in.chan == 1) || - (isPointsVector(in.chan, in.size, in.depth, 2, CV_32S) || - isPointsVector(in.chan, in.size, in.depth, 2, CV_32F))); - + if (in.depth == CV_8U) + { + GAPI_Assert(in.chan == 1); + } + else + { + GAPI_Assert (in.depth == CV_32S || in.depth == CV_32F); + int amount = detail::checkVector(in, 2u); + GAPI_Assert(amount != -1 && + "Input Mat can't be described as vector of 2-dimentional points"); + } return empty_gopaque_desc(); } }; @@ -237,7 +235,9 @@ namespace imgproc { G_TYPED_KERNEL(GFitLine2DMat, (GMat,DistanceTypes,double,double,double)>, "org.opencv.imgproc.shape.fitLine2DMat") { static GOpaqueDesc outMeta(GMatDesc in,DistanceTypes,double,double,double) { - GAPI_Assert(isPointsVector(in.chan, in.size, in.depth, 2, -1)); + int amount = detail::checkVector(in, 2u); + GAPI_Assert(amount != -1 && + "Input Mat can't be described as vector of 2-dimentional points"); return empty_gopaque_desc(); } }; @@ -269,7 +269,9 @@ namespace imgproc { G_TYPED_KERNEL(GFitLine3DMat, (GMat,DistanceTypes,double,double,double)>, "org.opencv.imgproc.shape.fitLine3DMat") { static GOpaqueDesc outMeta(GMatDesc in,int,double,double,double) { - GAPI_Assert(isPointsVector(in.chan, in.size, in.depth, 3, -1)); + int amount = detail::checkVector(in, 3u); + GAPI_Assert(amount != -1 && + "Input Mat can't be described as vector of 3-dimentional points"); return empty_gopaque_desc(); } }; diff --git a/modules/gapi/src/api/gmat.cpp b/modules/gapi/src/api/gmat.cpp index 08bb170a86..47a246c293 100644 --- a/modules/gapi/src/api/gmat.cpp +++ b/modules/gapi/src/api/gmat.cpp @@ -36,6 +36,38 @@ const cv::GOrigin& cv::GMat::priv() const return *m_priv; } +static std::vector checkVectorImpl(const int width, const int height, const int chan, + const int n) +{ + if (width == 1 && (n == -1 || n == chan)) + { + return {height, chan}; + } + else if (height == 1 && (n == -1 || n == chan)) + { + return {width, chan}; + } + else if (chan == 1 && (n == -1 || n == width)) + { + return {height, width}; + } + else // input Mat can't be described as vector of points of given dimensionality + { + return {-1, -1}; + } +} + +int cv::gapi::detail::checkVector(const cv::GMatDesc& in, const size_t n) +{ + GAPI_Assert(n != 0u); + return checkVectorImpl(in.size.width, in.size.height, in.chan, static_cast(n))[0]; +} + +std::vector cv::gapi::detail::checkVector(const cv::GMatDesc& in) +{ + return checkVectorImpl(in.size.width, in.size.height, in.chan, -1); +} + namespace{ template cv::GMetaArgs vec_descr_of(const std::vector &vec) { diff --git a/modules/gapi/src/api/kernels_core.cpp b/modules/gapi/src/api/kernels_core.cpp index 82aceb1f26..15af915bdd 100644 --- a/modules/gapi/src/api/kernels_core.cpp +++ b/modules/gapi/src/api/kernels_core.cpp @@ -388,6 +388,40 @@ GMat warpAffine(const GMat& src, const Mat& M, const Size& dsize, int flags, return core::GWarpAffine::on(src, M, dsize, flags, borderMode, borderValue); } +std::tuple,GMat,GMat> kmeans(const GMat& data, const int K, const GMat& bestLabels, + const TermCriteria& criteria, const int attempts, + const KmeansFlags flags) +{ + return core::GKMeansND::on(data, K, bestLabels, criteria, attempts, flags); +} + +std::tuple,GMat,GMat> kmeans(const GMat& data, const int K, + const TermCriteria& criteria, const int attempts, + const KmeansFlags flags) +{ + return core::GKMeansNDNoInit::on(data, K, criteria, attempts, flags); +} + +std::tuple,GArray,GArray> kmeans(const GArray& data, + const int K, + const GArray& bestLabels, + const TermCriteria& criteria, + const int attempts, + const KmeansFlags flags) +{ + return core::GKMeans2D::on(data, K, bestLabels, criteria, attempts, flags); +} + +std::tuple,GArray,GArray> kmeans(const GArray& data, + const int K, + const GArray& bestLabels, + const TermCriteria& criteria, + const int attempts, + const KmeansFlags flags) +{ + return core::GKMeans3D::on(data, K, bestLabels, criteria, attempts, flags); +} + GOpaque streaming::size(const GMat& src) { return streaming::GSize::on(src); diff --git a/modules/gapi/src/backends/cpu/gcpucore.cpp b/modules/gapi/src/backends/cpu/gcpucore.cpp index fc460149c6..3e6ce1c1d4 100644 --- a/modules/gapi/src/backends/cpu/gcpucore.cpp +++ b/modules/gapi/src/backends/cpu/gcpucore.cpp @@ -585,6 +585,63 @@ GAPI_OCV_KERNEL(GCPUWarpAffine, cv::gapi::core::GWarpAffine) } }; +GAPI_OCV_KERNEL(GCPUKMeansND, cv::gapi::core::GKMeansND) +{ + static void run(const cv::Mat& data, const int K, const cv::Mat& inBestLabels, + const cv::TermCriteria& criteria, const int attempts, + const cv::KmeansFlags flags, + double& compactness, cv::Mat& outBestLabels, cv::Mat& centers) + { + if (flags & cv::KMEANS_USE_INITIAL_LABELS) + { + inBestLabels.copyTo(outBestLabels); + } + compactness = cv::kmeans(data, K, outBestLabels, criteria, attempts, flags, centers); + } +}; + +GAPI_OCV_KERNEL(GCPUKMeansNDNoInit, cv::gapi::core::GKMeansNDNoInit) +{ + static void run(const cv::Mat& data, const int K, const cv::TermCriteria& criteria, + const int attempts, const cv::KmeansFlags flags, + double& compactness, cv::Mat& outBestLabels, cv::Mat& centers) + { + compactness = cv::kmeans(data, K, outBestLabels, criteria, attempts, flags, centers); + } +}; + +GAPI_OCV_KERNEL(GCPUKMeans2D, cv::gapi::core::GKMeans2D) +{ + static void run(const std::vector& data, const int K, + const std::vector& inBestLabels, const cv::TermCriteria& criteria, + const int attempts, const cv::KmeansFlags flags, + double& compactness, std::vector& outBestLabels, + std::vector& centers) + { + if (flags & cv::KMEANS_USE_INITIAL_LABELS) + { + outBestLabels = inBestLabels; + } + compactness = cv::kmeans(data, K, outBestLabels, criteria, attempts, flags, centers); + } +}; + +GAPI_OCV_KERNEL(GCPUKMeans3D, cv::gapi::core::GKMeans3D) +{ + static void run(const std::vector& data, const int K, + const std::vector& inBestLabels, const cv::TermCriteria& criteria, + const int attempts, const cv::KmeansFlags flags, + double& compactness, std::vector& outBestLabels, + std::vector& centers) + { + if (flags & cv::KMEANS_USE_INITIAL_LABELS) + { + outBestLabels = inBestLabels; + } + compactness = cv::kmeans(data, K, outBestLabels, criteria, attempts, flags, centers); + } +}; + GAPI_OCV_KERNEL(GCPUParseSSDBL, cv::gapi::nn::parsers::GParseSSDBL) { static void run(const cv::Mat& in_ssd_result, @@ -714,6 +771,10 @@ cv::gapi::GKernelPackage cv::gapi::core::cpu::kernels() , GCPUNormalize , GCPUWarpPerspective , GCPUWarpAffine + , GCPUKMeansND + , GCPUKMeansNDNoInit + , GCPUKMeans2D + , GCPUKMeans3D , GCPUParseSSDBL , GOCVParseSSD , GCPUParseYolo diff --git a/modules/gapi/test/common/gapi_core_tests.hpp b/modules/gapi/test/common/gapi_core_tests.hpp index 889e32f1c1..48ac4482a7 100644 --- a/modules/gapi/test/common/gapi_core_tests.hpp +++ b/modules/gapi/test/common/gapi_core_tests.hpp @@ -151,6 +151,16 @@ GAPI_TEST_FIXTURE(WarpPerspectiveTest, initMatrixRandU, GAPI_TEST_FIXTURE(WarpAffineTest, initMatrixRandU, FIXTURE_API(CompareMats, double , double, int, int, cv::Scalar), 6, cmpF, angle, scale, flags, border_mode, border_value) +GAPI_TEST_FIXTURE(KMeansNDNoInitTest, initMatrixRandU, FIXTURE_API(int, cv::KmeansFlags), + 2, K, flags) +GAPI_TEST_FIXTURE(KMeansNDInitTest, initMatrixRandU, + FIXTURE_API(CompareMats, int, cv::KmeansFlags), 3, cmpF, K, flags) +GAPI_TEST_FIXTURE(KMeans2DNoInitTest, initNothing, FIXTURE_API(int, cv::KmeansFlags), + 2, K, flags) +GAPI_TEST_FIXTURE(KMeans2DInitTest, initNothing, FIXTURE_API(int, cv::KmeansFlags), 2, K, flags) +GAPI_TEST_FIXTURE(KMeans3DNoInitTest, initNothing, FIXTURE_API(int, cv::KmeansFlags), + 2, K, flags) +GAPI_TEST_FIXTURE(KMeans3DInitTest, initNothing, FIXTURE_API(int, cv::KmeansFlags), 2, K, flags) GAPI_TEST_EXT_BASE_FIXTURE(ParseSSDBLTest, ParserSSDTest, initNothing, FIXTURE_API(float, int), 2, confidence_threshold, filter_label) diff --git a/modules/gapi/test/common/gapi_core_tests_inl.hpp b/modules/gapi/test/common/gapi_core_tests_inl.hpp index 045b556369..0e2d5ad474 100644 --- a/modules/gapi/test/common/gapi_core_tests_inl.hpp +++ b/modules/gapi/test/common/gapi_core_tests_inl.hpp @@ -15,6 +15,16 @@ namespace opencv_test { +namespace +{ +template +inline bool compareVectorsAbsExact(const std::vector& outOCV, + const std::vector& outGAPI) +{ + return AbsExactVector().to_compare_f()(outOCV, outGAPI); +} +} + TEST_P(MathOpTest, MatricesAccuracyTest) { // G-API code & corresponding OpenCV code //////////////////////////////// @@ -1377,6 +1387,187 @@ TEST_P(NormalizeTest, Test) } } +TEST_P(KMeansNDNoInitTest, AccuracyTest) +{ + const int amount = sz.height != 1 ? sz.height : sz.width, + dim = sz.height != 1 ? sz.width : (type >> CV_CN_SHIFT) + 1; + // amount of channels + const cv::TermCriteria criteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 30, 0); + const int attempts = 1; + double compact_gapi = -1.; + cv::Mat labels_gapi, centers_gapi; + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in; + cv::GOpaque compactness; + cv::GMat outLabels, centers; + std::tie(compactness, outLabels, centers) = cv::gapi::kmeans(in, K, criteria, attempts, flags); + cv::GComputation c(cv::GIn(in), cv::GOut(compactness, outLabels, centers)); + c.apply(cv::gin(in_mat1), cv::gout(compact_gapi, labels_gapi, centers_gapi), getCompileArgs()); + // Validation ////////////////////////////////////////////////////////////// + { + EXPECT_GE(compact_gapi, 0.); + EXPECT_EQ(labels_gapi.cols, 1); + EXPECT_EQ(labels_gapi.rows, amount); + EXPECT_FALSE(labels_gapi.empty()); + EXPECT_EQ(centers_gapi.cols, dim); + EXPECT_EQ(centers_gapi.rows, K); + EXPECT_FALSE(centers_gapi.empty()); + } +} + +TEST_P(KMeansNDInitTest, AccuracyTest) +{ + const int amount = sz.height != 1 ? sz.height : sz.width; + const cv::TermCriteria criteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 30, 0); + const int attempts = 1; + cv::Mat bestLabels(cv::Size{1, amount}, CV_32SC1); + double compact_ocv = -1., compact_gapi = -1.; + cv::Mat labels_ocv, labels_gapi, centers_ocv, centers_gapi; + cv::randu(bestLabels, 0, K); + bestLabels.copyTo(labels_ocv); + // G-API code ////////////////////////////////////////////////////////////// + cv::GMat in, inLabels; + cv::GOpaque compactness; + cv::GMat outLabels, centers; + std::tie(compactness, outLabels, centers) = + cv::gapi::kmeans(in, K, inLabels, criteria, attempts, flags); + cv::GComputation c(cv::GIn(in, inLabels), cv::GOut(compactness, outLabels, centers)); + c.apply(cv::gin(in_mat1, bestLabels), cv::gout(compact_gapi, labels_gapi, centers_gapi), + getCompileArgs()); + // OpenCV code ///////////////////////////////////////////////////////////// + compact_ocv = cv::kmeans(in_mat1, K, labels_ocv, criteria, attempts, flags, centers_ocv); + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_TRUE(compact_gapi == compact_ocv); + EXPECT_TRUE(cmpF(labels_gapi, labels_ocv)); + EXPECT_TRUE(cmpF(centers_gapi, centers_ocv)); + } +} + +TEST_P(KMeans2DNoInitTest, AccuracyTest) +{ + const int amount = sz.height; + const cv::TermCriteria criteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 30, 0); + const int attempts = 1; + std::vector in_vector{}; + double compact_gapi = -1.; + std::vector labels_gapi{}; + std::vector centers_gapi{}; + initPointsVectorRandU(amount, in_vector); + // G-API code ////////////////////////////////////////////////////////////// + cv::GArray in; + cv::GArray inLabels(std::vector{}); + cv::GOpaque compactness; + cv::GArray outLabels; + cv::GArray centers; + std::tie(compactness, outLabels, centers) = + cv::gapi::kmeans(in, K, inLabels, criteria, attempts, flags); + cv::GComputation c(cv::GIn(in), cv::GOut(compactness, outLabels, centers)); + c.apply(cv::gin(in_vector), cv::gout(compact_gapi, labels_gapi, centers_gapi), getCompileArgs()); + // Validation ////////////////////////////////////////////////////////////// + { + EXPECT_GE(compact_gapi, 0.); + EXPECT_EQ(labels_gapi.size(), static_cast(amount)); + EXPECT_EQ(centers_gapi.size(), static_cast(K)); + } +} + +TEST_P(KMeans2DInitTest, AccuracyTest) +{ + const int amount = sz.height; + const cv::TermCriteria criteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 30, 0); + const int attempts = 1; + std::vector in_vector{}; + std::vector bestLabels(amount); + double compact_ocv = -1., compact_gapi = -1.; + std::vector labels_ocv{}, labels_gapi{}; + std::vector centers_ocv{}, centers_gapi{}; + initPointsVectorRandU(amount, in_vector); + cv::randu(bestLabels, 0, K); + labels_ocv = bestLabels; + // G-API code ////////////////////////////////////////////////////////////// + cv::GArray in; + cv::GArray inLabels; + cv::GOpaque compactness; + cv::GArray outLabels; + cv::GArray centers; + std::tie(compactness, outLabels, centers) = + cv::gapi::kmeans(in, K, inLabels, criteria, attempts, flags); + cv::GComputation c(cv::GIn(in, inLabels), cv::GOut(compactness, outLabels, centers)); + c.apply(cv::gin(in_vector, bestLabels), cv::gout(compact_gapi, labels_gapi, centers_gapi), + getCompileArgs()); + // OpenCV code ///////////////////////////////////////////////////////////// + compact_ocv = cv::kmeans(in_vector, K, labels_ocv, criteria, attempts, flags, centers_ocv); + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_TRUE(compact_gapi == compact_ocv); + EXPECT_TRUE(compareVectorsAbsExact(labels_gapi, labels_ocv)); + EXPECT_TRUE(compareVectorsAbsExact(centers_gapi, centers_ocv)); + } +} + +TEST_P(KMeans3DNoInitTest, AccuracyTest) +{ + const int amount = sz.height; + const cv::TermCriteria criteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 30, 0); + const int attempts = 1; + std::vector in_vector{}; + double compact_gapi = -1.; + std::vector labels_gapi{}; + std::vector centers_gapi{}; + initPointsVectorRandU(amount, in_vector); + // G-API code ////////////////////////////////////////////////////////////// + cv::GArray in; + cv::GArray inLabels(std::vector{}); + cv::GOpaque compactness; + cv::GArray outLabels; + cv::GArray centers; + std::tie(compactness, outLabels, centers) = + cv::gapi::kmeans(in, K, inLabels, criteria, attempts, flags); + cv::GComputation c(cv::GIn(in), cv::GOut(compactness, outLabels, centers)); + c.apply(cv::gin(in_vector), cv::gout(compact_gapi, labels_gapi, centers_gapi), getCompileArgs()); + // Validation ////////////////////////////////////////////////////////////// + { + EXPECT_GE(compact_gapi, 0.); + EXPECT_EQ(labels_gapi.size(), static_cast(amount)); + EXPECT_EQ(centers_gapi.size(), static_cast(K)); + } +} + +TEST_P(KMeans3DInitTest, AccuracyTest) +{ + const int amount = sz.height; + const cv::TermCriteria criteria(TermCriteria::MAX_ITER + TermCriteria::EPS, 30, 0); + const int attempts = 1; + std::vector in_vector{}; + std::vector bestLabels(amount); + double compact_ocv = -1., compact_gapi = -1.; + std::vector labels_ocv{}, labels_gapi{}; + std::vector centers_ocv{}, centers_gapi{}; + initPointsVectorRandU(amount, in_vector); + cv::randu(bestLabels, 0, K); + labels_ocv = bestLabels; + // G-API code ////////////////////////////////////////////////////////////// + cv::GArray in; + cv::GArray inLabels; + cv::GOpaque compactness; + cv::GArray outLabels; + cv::GArray centers; + std::tie(compactness, outLabels, centers) = + cv::gapi::kmeans(in, K, inLabels, criteria, attempts, flags); + cv::GComputation c(cv::GIn(in, inLabels), cv::GOut(compactness, outLabels, centers)); + c.apply(cv::gin(in_vector, bestLabels), cv::gout(compact_gapi, labels_gapi, centers_gapi), + getCompileArgs()); + // OpenCV code ///////////////////////////////////////////////////////////// + compact_ocv = cv::kmeans(in_vector, K, labels_ocv, criteria, attempts, flags, centers_ocv); + // Comparison ////////////////////////////////////////////////////////////// + { + EXPECT_TRUE(compact_gapi == compact_ocv); + EXPECT_TRUE(compareVectorsAbsExact(labels_gapi, labels_ocv)); + EXPECT_TRUE(compareVectorsAbsExact(centers_gapi, centers_ocv)); + } +} + // PLEASE DO NOT PUT NEW ACCURACY TESTS BELOW THIS POINT! ////////////////////// TEST_P(BackendOutputAllocationTest, EmptyOutput) diff --git a/modules/gapi/test/common/gapi_tests_common.hpp b/modules/gapi/test/common/gapi_tests_common.hpp index 514fa2be38..6d11881372 100644 --- a/modules/gapi/test/common/gapi_tests_common.hpp +++ b/modules/gapi/test/common/gapi_tests_common.hpp @@ -1174,6 +1174,28 @@ inline std::ostream& operator<<(std::ostream& os, DistanceTypes op) #undef CASE return os; } + +inline std::ostream& operator<<(std::ostream& os, KmeansFlags op) +{ + int op_(op); + switch (op_) + { + case KmeansFlags::KMEANS_RANDOM_CENTERS: + os << "KMEANS_RANDOM_CENTERS"; + break; + case KmeansFlags::KMEANS_PP_CENTERS: + os << "KMEANS_PP_CENTERS"; + break; + case KmeansFlags::KMEANS_RANDOM_CENTERS | KmeansFlags::KMEANS_USE_INITIAL_LABELS: + os << "KMEANS_RANDOM_CENTERS | KMEANS_USE_INITIAL_LABELS"; + break; + case KmeansFlags::KMEANS_PP_CENTERS | KmeansFlags::KMEANS_USE_INITIAL_LABELS: + os << "KMEANS_PP_CENTERS | KMEANS_USE_INITIAL_LABELS"; + break; + default: GAPI_Assert(false && "unknown KmeansFlags value"); + } + return os; +} } // namespace cv #endif //OPENCV_GAPI_TESTS_COMMON_HPP diff --git a/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp b/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp index 595b63dd1f..fedc7c154f 100644 --- a/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp +++ b/modules/gapi/test/cpu/gapi_core_tests_cpu.cpp @@ -484,6 +484,72 @@ INSTANTIATE_TEST_CASE_P(NormalizeTestCPU, NormalizeTest, Values(NORM_MINMAX, NORM_INF, NORM_L1, NORM_L2), Values(-1, CV_8U, CV_16U, CV_16S, CV_32F))); +INSTANTIATE_TEST_CASE_P(KMeansNDNoInitTestCPU, KMeansNDNoInitTest, + Combine(Values(CV_32FC1), + Values(cv::Size(2, 20)), + Values(-1), + Values(CORE_CPU), + Values(5), + Values(cv::KMEANS_RANDOM_CENTERS, cv::KMEANS_PP_CENTERS))); + +INSTANTIATE_TEST_CASE_P(KMeansNDInitTestCPU, KMeansNDInitTest, + Combine(Values(CV_32FC1, CV_32FC3), + Values(cv::Size(1, 20), + cv::Size(2, 20), + cv::Size(5, 720)), + Values(-1), + Values(CORE_CPU), + Values(AbsTolerance(0.01).to_compare_obj()), + Values(5, 15), + Values(cv::KMEANS_RANDOM_CENTERS | cv::KMEANS_USE_INITIAL_LABELS, + cv::KMEANS_PP_CENTERS | cv::KMEANS_USE_INITIAL_LABELS))); + +INSTANTIATE_TEST_CASE_P(KMeansNDInitReverseTestCPU, KMeansNDInitTest, + Combine(Values(CV_32FC3), + Values(cv::Size(20, 1)), + Values(-1), + Values(CORE_CPU), + Values(AbsTolerance(0.01).to_compare_obj()), + Values(5, 15), + Values(cv::KMEANS_RANDOM_CENTERS | cv::KMEANS_USE_INITIAL_LABELS, + cv::KMEANS_PP_CENTERS | cv::KMEANS_USE_INITIAL_LABELS))); + +INSTANTIATE_TEST_CASE_P(KMeans2DNoInitTestCPU, KMeans2DNoInitTest, + Combine(Values(-1), + Values(cv::Size(-1, 20)), + Values(-1), + Values(CORE_CPU), + Values(5), + Values(cv::KMEANS_RANDOM_CENTERS, cv::KMEANS_PP_CENTERS))); + +INSTANTIATE_TEST_CASE_P(KMeans2DInitTestCPU, KMeans2DInitTest, + Combine(Values(-1), + Values(cv::Size(-1, 720), + cv::Size(-1, 20)), + Values(-1), + Values(CORE_CPU), + Values(5, 15), + Values(cv::KMEANS_RANDOM_CENTERS | cv::KMEANS_USE_INITIAL_LABELS, + cv::KMEANS_PP_CENTERS | cv::KMEANS_USE_INITIAL_LABELS))); + +INSTANTIATE_TEST_CASE_P(KMeans3DNoInitTestCPU, KMeans3DNoInitTest, + Combine(Values(-1), + Values(cv::Size(-1, 20)), + Values(-1), + Values(CORE_CPU), + Values(5), + Values(cv::KMEANS_RANDOM_CENTERS, cv::KMEANS_PP_CENTERS))); + +INSTANTIATE_TEST_CASE_P(KMeans3DInitTestCPU, KMeans3DInitTest, + Combine(Values(-1), + Values(cv::Size(-1, 720), + cv::Size(-1, 20)), + Values(-1), + Values(CORE_CPU), + Values(5, 15), + Values(cv::KMEANS_RANDOM_CENTERS | cv::KMEANS_USE_INITIAL_LABELS, + cv::KMEANS_PP_CENTERS | cv::KMEANS_USE_INITIAL_LABELS))); + // PLEASE DO NOT PUT NEW ACCURACY TESTS BELOW THIS POINT! ////////////////////// INSTANTIATE_TEST_CASE_P(BackendOutputAllocationTestCPU, BackendOutputAllocationTest,