mirror of
https://github.com/opencv/opencv.git
synced 2026-07-30 07:43:03 +04:00
Merge pull request #18857 from OrestChura:oc/kmeans
[G-API]: kmeans() Standard Kernel Implementation
* cv::gapi::kmeans kernel implementation
- 4 overloads:
- standard GMat - for any dimensionality
- GMat without bestLabels initialization
- GArray<Point2f> - for 2D
- GArray<Point3f> - for 3D
- Accuracy tests:
- for every input - 2 tests
1) without initializing. In this case, no comparison with cv::kmeans is done as kmeans uses random auto-initialization
2) with initialization
- in both cases, only 1 attempt is done as after first attempt kmeans initializes bestLabels randomly
* Addressing comments
- bestLabels is returned to its original place among parameters
- checkVector and isPointsVector functions are merged into one, shared between core.hpp & imgproc.hpp by placing it into gmat.hpp (and implementation - to gmat.cpp)
- typos corrected
* addressing comments
- unified names in tests
- const added
- typos
* Addressing comments
- fixed the doc note
- ddepth -> expectedDepth, `< 0 ` -> `== -1`
* Fix unsupported cases of input Mat
- supported: multiple channels, reversed width
- added test cases for those
- added notes in docs
- refactored checkVector to return dimentionality along with quantity
* Addressing comments
- makes chackVector smaller and (maybe) clearer
* Addressing comments
* Addressing comments
- cv::checkVector -> cv::gapi::detail
* Addressing comments
- Changed checkVector: returns bool, quantity & dimensionality as references
* Addressing comments
- Polishing checkVector
- FIXME added
* Addressing discussion
- checkVector: added overload, separate two different functionalities
- depth assert - out of the function
* Addressing comments
- quantity -> amount, dimensionality -> dim
- Fix typos
* Addressing comments
- fix docs
- use 2 variable's definitions instead of one (for all non-trivial variables)
This commit is contained in:
@@ -26,6 +26,7 @@
|
||||
@defgroup gapi_transform Graph API: Image and channel composition functions
|
||||
@}
|
||||
*/
|
||||
|
||||
namespace cv { namespace gapi {
|
||||
namespace core {
|
||||
using GMat2 = std::tuple<GMat,GMat>;
|
||||
@@ -508,6 +509,77 @@ namespace core {
|
||||
return in.withType(in.depth, in.chan).withSize(dsize);
|
||||
}
|
||||
};
|
||||
|
||||
G_TYPED_KERNEL(
|
||||
GKMeansND,
|
||||
<std::tuple<GOpaque<double>,GMat,GMat>(GMat,int,GMat,TermCriteria,int,KmeansFlags)>,
|
||||
"org.opencv.core.kmeansND") {
|
||||
|
||||
static std::tuple<GOpaqueDesc,GMatDesc,GMatDesc>
|
||||
outMeta(const GMatDesc& in, int K, const GMatDesc& bestLabels, const TermCriteria&, int,
|
||||
KmeansFlags flags) {
|
||||
GAPI_Assert(in.depth == CV_32F);
|
||||
std::vector<int> 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,
|
||||
<std::tuple<GOpaque<double>,GMat,GMat>(GMat,int,TermCriteria,int,KmeansFlags)>,
|
||||
"org.opencv.core.kmeansNDNoInit") {
|
||||
|
||||
static std::tuple<GOpaqueDesc,GMatDesc,GMatDesc>
|
||||
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<int> 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, <std::tuple<GOpaque<double>,GArray<int>,GArray<Point2f>>
|
||||
(GArray<Point2f>,int,GArray<int>,TermCriteria,int,KmeansFlags)>,
|
||||
"org.opencv.core.kmeans2D") {
|
||||
static std::tuple<GOpaqueDesc,GArrayDesc,GArrayDesc>
|
||||
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, <std::tuple<GOpaque<double>,GArray<int>,GArray<Point3f>>
|
||||
(GArray<Point3f>,int,GArray<int>,TermCriteria,int,KmeansFlags)>,
|
||||
"org.opencv.core.kmeans3D") {
|
||||
static std::tuple<GOpaqueDesc,GArrayDesc,GArrayDesc>
|
||||
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<Point2f>, GArray<Point3f> 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<GOpaque<double>,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<GOpaque<double>,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<GOpaque<double>,GArray<int>,GArray<Point2f>>
|
||||
kmeans(const GArray<Point2f>& data, const int K, const GArray<int>& bestLabels,
|
||||
const TermCriteria& criteria, const int attempts, const KmeansFlags flags);
|
||||
|
||||
/** @overload
|
||||
@note Function textual ID is "org.opencv.core.kmeans3D"
|
||||
*/
|
||||
GAPI_EXPORTS std::tuple<GOpaque<double>,GArray<int>,GArray<Point3f>>
|
||||
kmeans(const GArray<Point3f>& data, const int K, const GArray<int>& bestLabels,
|
||||
const TermCriteria& criteria, const int attempts, const KmeansFlags flags);
|
||||
|
||||
namespace streaming {
|
||||
/** @brief Gets dimensions from Mat.
|
||||
|
||||
|
||||
@@ -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<int>: 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<int> checkVector(const GMatDesc& in);
|
||||
}} // namespace gapi::detail
|
||||
|
||||
#if !defined(GAPI_STANDALONE)
|
||||
GAPI_EXPORTS GMatDesc descr_of(const cv::UMat &mat);
|
||||
#endif // !defined(GAPI_STANDALONE)
|
||||
|
||||
@@ -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, <GOpaque<Rect>(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, <GOpaque<Vec4f>(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, <GOpaque<Vec6f>(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();
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user