mirror of
https://github.com/opencv/opencv.git
synced 2026-07-29 07:13:02 +04:00
Merge pull request #27676 from abhishek-gola:unique_layer_add
Added unique layer to new DNN engine #27676 ### Pull Request Readiness Checklist See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request - [x] I agree to contribute to the project under Apache 2 License. - [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV - [x] The PR is proposed to the proper branch - [x] There is a reference to the original bug report and related work - [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable Patch to opencv_extra has the same branch name. - [x] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
@@ -1337,6 +1337,12 @@ CV__DNN_INLINE_NS_BEGIN
|
||||
static Ptr<Tile2Layer> create(const LayerParams& params);
|
||||
};
|
||||
|
||||
class CV_EXPORTS UniqueLayer : public Layer
|
||||
{
|
||||
public:
|
||||
static Ptr<UniqueLayer> create(const LayerParams& params);
|
||||
};
|
||||
|
||||
class CV_EXPORTS LayerNormLayer : public Layer
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -108,6 +108,7 @@ void initializeLayerFactory()
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Split2, Split2Layer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Squeeze, SqueezeLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Tile2, Tile2Layer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Unique, UniqueLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Transpose, TransposeLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(Unsqueeze, UnsqueezeLayer);
|
||||
CV_DNN_REGISTER_LAYER_CLASS(IsNaN, IsNaNLayer);
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
// 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) 2025, BigVision LLC, all rights reserved.
|
||||
// Third party copyrights are property of their respective owners.
|
||||
|
||||
#include "../precomp.hpp"
|
||||
#include "layers_common.hpp"
|
||||
#include <opencv2/dnn/shape_utils.hpp>
|
||||
|
||||
// ONNX operator: Unique
|
||||
// Spec: https://onnx.ai/onnx/operators/onnx__Unique.html
|
||||
// Supported opsets: 11 (axis attribute introduced in opset 11)
|
||||
|
||||
namespace cv {
|
||||
namespace dnn {
|
||||
|
||||
class UniqueLayerImpl CV_FINAL : public UniqueLayer
|
||||
{
|
||||
public:
|
||||
UniqueLayerImpl(const LayerParams& params)
|
||||
{
|
||||
setParamsFrom(params);
|
||||
sorted_ = params.get<bool>("sorted", true);
|
||||
hasAxis_ = params.has("axis");
|
||||
axis_ = hasAxis_ ? params.get<int>("axis") : 0;
|
||||
}
|
||||
|
||||
bool supportBackend(int backendId) CV_OVERRIDE
|
||||
{
|
||||
return backendId == DNN_BACKEND_OPENCV;
|
||||
}
|
||||
|
||||
bool dynamicOutputShapes() const CV_OVERRIDE
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool getMemoryShapes(const std::vector<MatShape>& inputs,
|
||||
const int requiredOutputs,
|
||||
std::vector<MatShape>& outputs,
|
||||
std::vector<MatShape>& /*internals*/) const CV_OVERRIDE
|
||||
{
|
||||
CV_Assert(inputs.size() == 1);
|
||||
outputs.resize(requiredOutputs);
|
||||
return false;
|
||||
}
|
||||
|
||||
void getTypes(const std::vector<MatType>& inputs,
|
||||
const int requiredOutputs,
|
||||
const int /*requiredInternals*/,
|
||||
std::vector<MatType>& outputs,
|
||||
std::vector<MatType>& /*internals*/) const CV_OVERRIDE
|
||||
{
|
||||
CV_Assert(inputs.size() == 1);
|
||||
outputs.resize(requiredOutputs);
|
||||
outputs[0] = inputs[0];
|
||||
for (int i = 1; i < requiredOutputs; ++i)
|
||||
outputs[i] = MatType(CV_64S);
|
||||
}
|
||||
|
||||
void forward(InputArrayOfArrays inputs_arr,
|
||||
OutputArrayOfArrays outputs_arr,
|
||||
OutputArrayOfArrays /*internals_arr*/) CV_OVERRIDE
|
||||
{
|
||||
std::vector<Mat> inputs;
|
||||
inputs_arr.getMatVector(inputs);
|
||||
CV_Assert(inputs.size() == 1);
|
||||
|
||||
const Mat& Xin = inputs[0];
|
||||
CV_Assert(Xin.dims >= 1);
|
||||
|
||||
int ax = hasAxis_ ? normalize_axis(axis_, Xin.dims) : -1;
|
||||
|
||||
auto kind = outputs_arr.kind();
|
||||
if (kind == _InputArray::STD_VECTOR_MAT)
|
||||
{
|
||||
std::vector<Mat>& outs = outputs_arr.getMatVecRef();
|
||||
CV_Assert(!outs.empty());
|
||||
dispatchByDepth(Xin, outs, ax, sorted_);
|
||||
}
|
||||
else if (kind == _InputArray::STD_VECTOR_UMAT)
|
||||
{
|
||||
std::vector<UMat>& uouts = outputs_arr.getUMatVecRef();
|
||||
CV_Assert(!uouts.empty());
|
||||
std::vector<Mat> tmp(uouts.size());
|
||||
dispatchByDepth(Xin, tmp, ax, sorted_);
|
||||
for (size_t i = 0; i < uouts.size(); ++i)
|
||||
{
|
||||
if (tmp[i].empty())
|
||||
continue;
|
||||
MatShape sh = shape(tmp[i]);
|
||||
uouts[i].fit(sh, tmp[i].type());
|
||||
tmp[i].copyTo(uouts[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CV_Error(cv::Error::StsBadArg, cv::format("Unsupported output array kind: %d", kind));
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename Key, typename LessKey, typename EqualKey>
|
||||
void sortAndGroupKeys(std::vector<std::pair<int, Key>>& keyed,
|
||||
const LessKey& lessKey,
|
||||
const EqualKey& equalKey,
|
||||
std::vector<int>& groupRepIndex,
|
||||
std::vector<int>& firstJ,
|
||||
std::vector<int64>& counts,
|
||||
std::vector<int>& inv) const
|
||||
{
|
||||
std::sort(keyed.begin(), keyed.end(), [&](const std::pair<int, Key>& a, const std::pair<int, Key>& b){
|
||||
if (lessKey(a.second, b.second)) return true;
|
||||
if (lessKey(b.second, a.second)) return false;
|
||||
return a.first < b.first;
|
||||
});
|
||||
|
||||
int g = 0;
|
||||
const int A = (int)keyed.size();
|
||||
for (int pos = 0; pos < A; )
|
||||
{
|
||||
int start = pos;
|
||||
int minJ = keyed[pos].first;
|
||||
int64 cnt = 0;
|
||||
while (pos < A && equalKey(keyed[start].second, keyed[pos].second)) {
|
||||
inv[ keyed[pos].first ] = g;
|
||||
minJ = std::min(minJ, keyed[pos].first);
|
||||
++cnt; ++pos;
|
||||
}
|
||||
groupRepIndex.push_back(keyed[start].first);
|
||||
firstJ.push_back(minJ);
|
||||
counts.push_back(cnt);
|
||||
++g;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T, typename WT = T>
|
||||
void uniqueAxisImpl(const Mat& X, std::vector<Mat>& outs, int ax, bool sorted) const
|
||||
{
|
||||
const int r = X.dims;
|
||||
|
||||
MatShape inShape = shape(X);
|
||||
int dimAxis;
|
||||
size_t outer = 1, inner = 1;
|
||||
if (ax < 0) {
|
||||
dimAxis = (int)X.total();
|
||||
} else {
|
||||
dimAxis = inShape[ax];
|
||||
outer = std::accumulate(inShape.begin(), inShape.begin() + ax, (size_t)1, std::multiplies<int>());
|
||||
inner = std::accumulate(inShape.begin() + ax + 1, inShape.end(), (size_t)1, std::multiplies<int>());
|
||||
}
|
||||
|
||||
const int A = dimAxis;
|
||||
const int64 sliceElems = (ax < 0) ? 1 : (int64)outer * (int64)inner;
|
||||
|
||||
const T* inPtr = X.ptr<const T>();
|
||||
|
||||
std::vector<int> groupRepIndex; groupRepIndex.reserve(A);
|
||||
std::vector<int> firstJ; firstJ.reserve(A);
|
||||
std::vector<int64> counts; counts.reserve(A);
|
||||
std::vector<int> inv(A, -1);
|
||||
|
||||
if (ax < 0)
|
||||
{
|
||||
using Pair = std::pair<int, WT>; // (original index, value)
|
||||
std::vector<Pair> keyed(A);
|
||||
for (int u = 0; u < A; ++u)
|
||||
keyed[u] = {u, static_cast<WT>(inPtr[(size_t)u])};
|
||||
|
||||
auto lessPair = [](const WT& a, const WT& b){ return a < b; };
|
||||
auto equalPair = [](const WT& a, const WT& b){ return a == b; };
|
||||
sortAndGroupKeys(keyed, lessPair, equalPair, groupRepIndex, firstJ, counts, inv);
|
||||
}
|
||||
else
|
||||
{
|
||||
const size_t S = (size_t)sliceElems;
|
||||
std::vector<WT> flat((size_t)A * S);
|
||||
for (int u = 0; u < A; ++u)
|
||||
{
|
||||
size_t wrote = 0;
|
||||
for (size_t ob = 0; ob < outer; ++ob)
|
||||
{
|
||||
for (size_t ij = 0; ij < inner; ++ij)
|
||||
{
|
||||
size_t srcOff = ob * (size_t)dimAxis * inner + (size_t)u * inner + ij;
|
||||
flat[(size_t)u * S + wrote] = static_cast<WT>(inPtr[srcOff]);
|
||||
++wrote;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
using Pair = std::pair<int, size_t>; // (original index, offset into flat)
|
||||
std::vector<Pair> keyed(A);
|
||||
for (int u = 0; u < A; ++u)
|
||||
keyed[u] = {u, (size_t)u * S};
|
||||
|
||||
auto lessLex = [&](size_t aoff, size_t boff){
|
||||
const WT* pa = &flat[aoff];
|
||||
const WT* pb = &flat[boff];
|
||||
for (size_t t = 0; t < S; ++t) {
|
||||
if (pa[t] < pb[t]) return true;
|
||||
if (pa[t] > pb[t]) return false;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
auto equalLex = [&](size_t aoff, size_t boff){
|
||||
const WT* pa = &flat[aoff];
|
||||
const WT* pb = &flat[boff];
|
||||
for (size_t t = 0; t < S; ++t) {
|
||||
if (pa[t] != pb[t]) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
sortAndGroupKeys(keyed, lessLex, equalLex, groupRepIndex, firstJ, counts, inv);
|
||||
}
|
||||
|
||||
std::vector<int> order((int)groupRepIndex.size());
|
||||
std::iota(order.begin(), order.end(), 0);
|
||||
if (!sorted) {
|
||||
auto firstOccurLess = [&](int ga, int gb){ return firstJ[ga] < firstJ[gb]; };
|
||||
std::sort(order.begin(), order.end(), firstOccurLess);
|
||||
}
|
||||
|
||||
std::vector<int> remap(order.size());
|
||||
for (int newi = 0; newi < (int)order.size(); ++newi)
|
||||
remap[ order[newi] ] = newi;
|
||||
|
||||
if (ax < 0)
|
||||
{
|
||||
MatShape yshape(1); yshape[0] = (int)order.size();
|
||||
outs[0].fit(yshape, X.type());
|
||||
T* yp = outs[0].ptr<T>();
|
||||
for (int yi = 0; yi < (int)order.size(); ++yi)
|
||||
{
|
||||
int u = groupRepIndex[ order[yi] ];
|
||||
yp[yi] = inPtr[(size_t)u];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<int> ysz(r);
|
||||
for (int k = 0; k < r; ++k) ysz[k] = X.size[k];
|
||||
ysz[ax] = (int)order.size();
|
||||
MatShape yshape(ysz);
|
||||
outs[0].fit(yshape, X.type());
|
||||
|
||||
parallel_for_(Range(0, (int)order.size()), [&](const Range& rq){
|
||||
std::vector<int> yidx(r, 0);
|
||||
std::vector<size_t> ystepB(r);
|
||||
for (int k = 0; k < r; ++k) ystepB[k] = outs[0].step[k];
|
||||
|
||||
for (int q = rq.start; q < rq.end; ++q) {
|
||||
int oldq = order[q];
|
||||
int oldu = groupRepIndex[oldq];
|
||||
|
||||
std::fill(yidx.begin(), yidx.end(), 0);
|
||||
int64 wrote = 0;
|
||||
for (int64 t = 0; t < sliceElems; ++t)
|
||||
{
|
||||
yidx[ax] = q;
|
||||
size_t yoff = 0;
|
||||
for (int k = 0; k < r; ++k) yoff += (size_t)yidx[k] * ystepB[k];
|
||||
T* yptrT = reinterpret_cast<T*>(outs[0].ptr() + yoff);
|
||||
|
||||
size_t ob = (size_t)(wrote / (int64)inner);
|
||||
size_t ij = (size_t)(wrote % (int64)inner);
|
||||
size_t srcOff = ob * (size_t)dimAxis * inner + (size_t)oldu * inner + ij;
|
||||
const T vs = inPtr[srcOff];
|
||||
*yptrT = vs;
|
||||
wrote++;
|
||||
|
||||
for (int k = r - 1; k >= 0; --k) {
|
||||
if (k == ax) continue;
|
||||
if (++yidx[k] < X.size[k]) break;
|
||||
yidx[k] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (outs.size() > 1) {
|
||||
MatShape ishape(1); ishape[0] = (int)order.size();
|
||||
outs[1].fit(ishape, CV_64S);
|
||||
auto ip = outs[1].ptr<int64_t>();
|
||||
for (int yi = 0; yi < (int)order.size(); ++yi)
|
||||
ip[yi] = firstJ[ order[yi] ];
|
||||
}
|
||||
|
||||
if (outs.size() > 2) {
|
||||
MatShape invshape(1); invshape[0] = A;
|
||||
outs[2].fit(invshape, CV_64S);
|
||||
auto invp = outs[2].ptr<int64_t>();
|
||||
for (int j = 0; j < A; ++j)
|
||||
invp[j] = remap[ inv[j] ];
|
||||
}
|
||||
|
||||
if (outs.size() > 3) {
|
||||
MatShape cshape(1); cshape[0] = (int)order.size();
|
||||
outs[3].fit(cshape, CV_64S);
|
||||
auto cp = outs[3].ptr<int64_t>();
|
||||
for (int yi = 0; yi < (int)order.size(); ++yi)
|
||||
cp[yi] = counts[ order[yi] ];
|
||||
}
|
||||
}
|
||||
|
||||
void dispatchByDepth(const Mat& X, std::vector<Mat>& outs, int ax, bool sorted) const
|
||||
{
|
||||
switch (X.depth())
|
||||
{
|
||||
case CV_8U: uniqueAxisImpl<uint8_t >(X, outs, ax, sorted); break;
|
||||
case CV_8S: uniqueAxisImpl<int8_t >(X, outs, ax, sorted); break;
|
||||
case CV_16U: uniqueAxisImpl<uint16_t >(X, outs, ax, sorted); break;
|
||||
case CV_16S: uniqueAxisImpl<int16_t >(X, outs, ax, sorted); break;
|
||||
case CV_16F: uniqueAxisImpl<hfloat, float >(X, outs, ax, sorted); break;
|
||||
case CV_16BF: uniqueAxisImpl<bfloat, float >(X, outs, ax, sorted); break;
|
||||
case CV_32U: uniqueAxisImpl<uint32_t >(X, outs, ax, sorted); break;
|
||||
case CV_32S: uniqueAxisImpl<int32_t >(X, outs, ax, sorted); break;
|
||||
case CV_32F: uniqueAxisImpl<float >(X, outs, ax, sorted); break;
|
||||
case CV_64U: uniqueAxisImpl<uint64_t >(X, outs, ax, sorted); break;
|
||||
case CV_64S: uniqueAxisImpl<int64_t >(X, outs, ax, sorted); break;
|
||||
case CV_64F: uniqueAxisImpl<double >(X, outs, ax, sorted); break;
|
||||
default:
|
||||
CV_Error(Error::StsUnsupportedFormat, "Unsupported data type for Unique");
|
||||
}
|
||||
}
|
||||
|
||||
bool sorted_;
|
||||
bool hasAxis_;
|
||||
int axis_;
|
||||
};
|
||||
|
||||
Ptr<UniqueLayer> UniqueLayer::create(const LayerParams& params)
|
||||
{
|
||||
return Ptr<UniqueLayer>(new UniqueLayerImpl(params));
|
||||
}
|
||||
|
||||
}} // namespace cv::dnn
|
||||
@@ -216,7 +216,8 @@ protected:
|
||||
void parseDet (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseGridSample (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseResize (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseSize (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseSize (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseUnique (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseReshape (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseScatter (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
void parseShape (LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto);
|
||||
@@ -1602,6 +1603,12 @@ void ONNXImporter2::parseResize(LayerParams& layerParams, const opencv_onnx::Nod
|
||||
addLayer(layerParams, node_proto, ninputs);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseUnique(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
layerParams.type = "Unique";
|
||||
addLayer(layerParams, node_proto);
|
||||
}
|
||||
|
||||
void ONNXImporter2::parseSize(LayerParams& layerParams, const opencv_onnx::NodeProto& node_proto)
|
||||
{
|
||||
layerParams.type = "Size";
|
||||
@@ -2496,6 +2503,7 @@ void ONNXImporter2::buildDispatchMap_ONNX_AI(int opset_version)
|
||||
dispatch["If"] = &ONNXImporter2::parseIf;
|
||||
dispatch["Resize"] = &ONNXImporter2::parseResize;
|
||||
dispatch["Size"] = &ONNXImporter2::parseSize;
|
||||
dispatch["Unique"] = &ONNXImporter2::parseUnique;
|
||||
dispatch["Trilu"] = &ONNXImporter2::parseTrilu;
|
||||
dispatch["IsNaN"] = &ONNXImporter2::parseIsNaN;
|
||||
dispatch["IsInf"] = &ONNXImporter2::parseIsInf;
|
||||
|
||||
@@ -2094,15 +2094,15 @@ CASE(test_triu_square_neg)
|
||||
CASE(test_triu_zero)
|
||||
// no filter
|
||||
CASE(test_unique_not_sorted_without_axis)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_unique_sorted_with_axis)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_unique_sorted_with_axis_3d)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_unique_sorted_with_negative_axis)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_unique_sorted_without_axis)
|
||||
// no filter
|
||||
SKIP;
|
||||
CASE(test_unsqueeze_axis_0)
|
||||
SKIP;
|
||||
CASE(test_unsqueeze_axis_1)
|
||||
|
||||
@@ -140,3 +140,8 @@
|
||||
"test_constantofshape_float_ones",
|
||||
"test_constantofshape_int_zeros",
|
||||
"test_nonzero_example",
|
||||
"test_unique_not_sorted_without_axis",
|
||||
"test_unique_sorted_with_axis",
|
||||
"test_unique_sorted_with_axis_3d",
|
||||
"test_unique_sorted_with_negative_axis",
|
||||
"test_unique_sorted_without_axis",
|
||||
|
||||
@@ -287,8 +287,3 @@
|
||||
"test_training_dropout_zero_ratio_mask", // ---- same as above ---
|
||||
"test_tril_zero", // ---- same as above ---
|
||||
"test_triu_zero", // ---- same as above ---
|
||||
"test_unique_not_sorted_without_axis", // Issue:: Parser: Can't create layer "onnx_node_output_0!Y" of type "Unique" in function 'getLayerInstance'
|
||||
"test_unique_sorted_with_axis", // ---- same as above ---
|
||||
"test_unique_sorted_with_axis_3d", // ---- same as above ---
|
||||
"test_unique_sorted_with_negative_axis", // ---- same as above ---
|
||||
"test_unique_sorted_without_axis", // ---- same as above ---
|
||||
|
||||
Reference in New Issue
Block a user