Skip to content

Commit

Permalink
Merge pull request opencv#18857 from OrestChura:oc/kmeans
Browse files Browse the repository at this point in the history
[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)
  • Loading branch information
OrestChura authored Nov 30, 2020
1 parent 95ce8f4 commit 986ad4f
Show file tree
Hide file tree
Showing 10 changed files with 599 additions and 15 deletions.
145 changes: 145 additions & 0 deletions modules/gapi/include/opencv2/gapi/core.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>;
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down
21 changes: 21 additions & 0 deletions modules/gapi/include/opencv2/gapi/gmat.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
32 changes: 17 additions & 15 deletions modules/gapi/include/opencv2/gapi/imgproc.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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();
}
};
Expand All @@ -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();
}
};
Expand Down Expand Up @@ -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();
}
};
Expand Down
32 changes: 32 additions & 0 deletions modules/gapi/src/api/gmat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,38 @@ const cv::GOrigin& cv::GMat::priv() const
return *m_priv;
}

static std::vector<int> 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<int>(n))[0];
}

std::vector<int> cv::gapi::detail::checkVector(const cv::GMatDesc& in)
{
return checkVectorImpl(in.size.width, in.size.height, in.chan, -1);
}

namespace{
template <typename T> cv::GMetaArgs vec_descr_of(const std::vector<T> &vec)
{
Expand Down
34 changes: 34 additions & 0 deletions modules/gapi/src/api/kernels_core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<GOpaque<double>,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<GOpaque<double>,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<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)
{
return core::GKMeans2D::on(data, K, bestLabels, criteria, attempts, flags);
}

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)
{
return core::GKMeans3D::on(data, K, bestLabels, criteria, attempts, flags);
}

GOpaque<Size> streaming::size(const GMat& src)
{
return streaming::GSize::on(src);
Expand Down
Loading

0 comments on commit 986ad4f

Please sign in to comment.