opencv/modules/dnn/test/test_layers_1d.cpp

311 lines
9.6 KiB
C++
Raw Normal View History

// 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) 2024, OpenCV Team, all rights reserved.
// Third party copyrights are property of their respective owners.
#include "test_precomp.hpp"
#include <opencv2/dnn/shape_utils.hpp>
#include <opencv2/dnn/all_layers.hpp>
#include <opencv2/dnn/layer.details.hpp> // CV_DNN_REGISTER_LAYER_CLASS
namespace opencv_test { namespace {
typedef testing::TestWithParam<tuple<int>> Layer_1d_Test;
TEST_P(Layer_1d_Test, Scale)
{
int batch_size = get<0>(GetParam());
LayerParams lp;
lp.type = "Scale";
lp.name = "scaleLayer";
lp.set("axis", 0);
lp.set("mode", "scale");
lp.set("bias_term", false);
Ptr<ScaleLayer> layer = ScaleLayer::create(lp);
std::vector<int> input_shape = {batch_size, 3};
std::vector<int> output_shape = {batch_size, 3};
if (batch_size == 0){
input_shape.erase(input_shape.begin());
output_shape.erase(output_shape.begin());
}
cv::Mat input = cv::Mat(input_shape, CV_32F, 1.0);
cv::randn(input, 0.0, 1.0);
cv::Mat weight = cv::Mat(output_shape, CV_32F, 2.0);
std::vector<Mat> inputs{input, weight};
std::vector<Mat> outputs;
cv::Mat output_ref = input.mul(weight);
runLayer(layer, inputs, outputs);
ASSERT_EQ(shape(output_ref), shape(outputs[0]));
normAssert(output_ref, outputs[0]);
}
typedef testing::TestWithParam<tuple<int, int>> Layer_Gather_1d_Test;
TEST_P(Layer_Gather_1d_Test, Accuracy) {
int batch_size = get<0>(GetParam());
int axis = get<1>(GetParam());
LayerParams lp;
lp.type = "Gather";
lp.name = "gatherLayer";
lp.set("axis", axis);
lp.set("real_ndims", 1);
Ptr<GatherLayer> layer = GatherLayer::create(lp);
std::vector<int> input_shape = {batch_size, 1};
std::vector<int> indices_shape = {1, 1};
std::vector<int> output_shape = {batch_size, 1};
if (batch_size == 0){
input_shape.erase(input_shape.begin());
indices_shape.erase(indices_shape.begin());
output_shape.erase(output_shape.begin());
} else if (axis == 0) {
output_shape[0] = 1;
}
cv::Mat input = cv::Mat(input_shape, CV_32F, 1.0);
cv::randu(input, 0.0, 1.0);
Merge pull request #24411 from alexlyulkov:al/dnn-type-inference Added int32, int64 support and type inference to dnn #24411 **Added a type inference to dnn similar to the shape inference, added int32 and int64 support.** - Added getTypes method for layers that calculates layer outputs types and internals types from inputs types (Similar to getMemoryShapes). By default outputs and internals types = input[0] type - Added type inference pipeline similar to shape inference pipeline. LayersShapes struct (that is used in shape inference pipeline) now contains both shapes and types - All layers output blobs are now allocated using the calculated types from the type inference. - Inputs and constants with int32 and int64 types are not automatically converted into float32 now. - Added int32 and int64 support for all the layers with indexing and for all the layers required in tests. Added int32 and int64 support for CUDA: - Added host<->device data moving for int32 and int64 - Added int32 and int64 support for several layers (just slightly modified CUDA C++ templates) Passed all the accuracy tests on CPU, OCL, OCL_FP16, CUDA, CUDA_FP16. (except RAFT model) **CURRENT PROBLEMS**: - ONNX parser always converts int64 constants and layers attributes to int32, so some models with int64 constants doesn't work (e.g. RAFT). The solution is to disable int64->int32 conversion and fix attributes reading in a lot of ONNX layers parsers (https://github.com/opencv/opencv/issues/25102) - I didn't add type inference and int support to VULCAN, so it doesn't work at all now. - Some layers don't support int yet, so some unknown models may not work. **CURRENT WORKAROUNDS**: - CPU arg_layer indides are implemented in int32 followed by a int32->int64 conversion (the master branch has the same workaround with int32->float conversion) - CPU and OCL pooling_layer indices are implemented in float followed by a float->int64 conversion - CPU gather_layer indices are implemented in int32, so int64 indices are converted to int32 (the master branch has the same workaround with float->int32 conversion) **DISABLED TESTS**: - RAFT model **REMOVED TESTS**: - Greater_input_dtype_int64 (because it doesn't fit ONNX rules, the whole test is just comparing float tensor with int constant) **TODO IN NEXT PULL REQUESTS**: - Add int64 support for ONNX parser - Add int support for more layers - Add int support for OCL (currently int layers just run on CPU) - Add int tests - Add int support for other backends
2024-03-01 22:07:38 +08:00
cv::Mat indices = cv::Mat(indices_shape, CV_32S, 0.0);
cv::Mat output_ref = cv::Mat(output_shape, CV_32F, input(cv::Range::all(), cv::Range(0, 1)).data);
std::vector<Mat> inputs{input, indices};
std::vector<Mat> outputs;
runLayer(layer, inputs, outputs);
ASSERT_EQ(shape(output_ref), shape(outputs[0]));
normAssert(output_ref, outputs[0]);
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Layer_Gather_1d_Test, Combine(
/*input blob shape*/ Values(0, 1, 2, 3),
/*operation*/ Values(0, 1)
));
typedef testing::TestWithParam<tuple<int, int, std::string>> Layer_Arg_1d_Test;
TEST_P(Layer_Arg_1d_Test, Accuracy) {
int batch_size = get<0>(GetParam());
int axis = get<1>(GetParam());
std::string operation = get<2>(GetParam());
LayerParams lp;
lp.type = "Arg";
lp.name = "arg" + operation + "_Layer";
lp.set("op", operation);
lp.set("axis", axis);
lp.set("keepdims", 1);
lp.set("select_last_index", 0);
Ptr<ArgLayer> layer = ArgLayer::create(lp);
std::vector<int> input_shape = {batch_size, 1};
std::vector<int> output_shape = {1, 1};
if (batch_size == 0){
input_shape.erase(input_shape.begin());
output_shape.erase(output_shape.begin());
}
if (axis != 0 && batch_size != 0){
output_shape[0] = batch_size;
}
cv::Mat input = cv::Mat(input_shape, CV_32F, 1);
cv::Mat output_ref = cv::Mat(output_shape, CV_32F, 0);
for (int i = 0; i < batch_size; ++i)
input.at<float>(i, 0) = static_cast<float>(i + 1);
std::vector<Mat> inputs{input};
std::vector<Mat> outputs;
runLayer(layer, inputs, outputs);
ASSERT_EQ(shape(output_ref), shape(outputs[0]));
normAssert(output_ref, outputs[0]);
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Layer_Arg_1d_Test, Combine(
/*input blob shape*/ Values(0, 1, 2, 3),
/*operation*/ Values(0, 1),
/*operation*/ Values( "max", "min")
));
typedef testing::TestWithParam<tuple<int, std::string>> Layer_NaryElemwise_1d_Test;
TEST_P(Layer_NaryElemwise_1d_Test, Accuracy) {
int batch_size = get<0>(GetParam());
std::string operation = get<1>(GetParam());
LayerParams lp;
lp.type = "Eltwise";
lp.name = operation + "_Layer";
lp.set("operation", operation);
Ptr<NaryEltwiseLayer> layer = NaryEltwiseLayer::create(lp);
std::vector<int> input_shape = {batch_size, 1};
if (batch_size == 0)
input_shape.erase(input_shape.begin());
cv::Mat input1 = cv::Mat(input_shape, CV_32F, 0.0);
cv::Mat input2 = cv::Mat(input_shape, CV_32F, 0.0);
cv::randu(input1, 0.0, 1.0);
cv::randu(input2, 0.0, 1.0);
cv::Mat output_ref;
if (operation == "sum") {
output_ref = input1 + input2;
} else if (operation == "mul") {
output_ref = input1.mul(input2);
} else if (operation == "div") {
output_ref = input1 / input2;
} else if (operation == "sub") {
output_ref = input1 - input2;
} else {
output_ref = cv::Mat();
}
std::vector<Mat> inputs{input1, input2};
std::vector<Mat> outputs;
runLayer(layer, inputs, outputs);
if (!output_ref.empty()) {
ASSERT_EQ(shape(output_ref), shape(outputs[0]));
normAssert(output_ref, outputs[0]);
} else {
CV_Error(Error::StsAssert, "Provided operation: " + operation + " is not supported. Please check the test instantiation.");
}
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Layer_NaryElemwise_1d_Test, Combine(
/*input blob shape*/ Values(0, 1),
/*operation*/ Values("div", "mul", "sum", "sub")
));
typedef testing::TestWithParam<tuple<int, std::string>> Layer_Elemwise_1d_Test;
TEST_P(Layer_Elemwise_1d_Test, Accuracy) {
int batch_size = get<0>(GetParam());
std::string operation = get<1>(GetParam());
LayerParams lp;
lp.type = "Eltwise";
lp.name = operation + "_Layer";
lp.set("operation", operation);
Ptr<EltwiseLayer> layer = EltwiseLayer::create(lp);
std::vector<int> input_shape = {batch_size, 1};
if (batch_size == 0)
input_shape.erase(input_shape.begin());
cv::Mat input1 = cv::Mat(input_shape, CV_32F, 1.0);
cv::Mat input2 = cv::Mat(input_shape, CV_32F, 1.0);
cv::randu(input1, 0.0, 1.0);
cv::randu(input2, 0.0, 1.0);
// Dynamically select the operation
cv::Mat output_ref;
if (operation == "sum") {
output_ref = input1 + input2;
} else if (operation == "max") {
output_ref = cv::max(input1, input2);
} else if (operation == "min") {
output_ref = cv::min(input1, input2);
} else if (operation == "prod") {
output_ref = input1.mul(input2);
} else if (operation == "div") {
output_ref = input1 / input2;
} else {
output_ref = cv::Mat();
}
std::vector<Mat> inputs{input1, input2};
std::vector<Mat> outputs;
runLayer(layer, inputs, outputs);
if (!output_ref.empty()) {
ASSERT_EQ(shape(output_ref), shape(outputs[0]));
normAssert(output_ref, outputs[0]);
} else {
CV_Error(Error::StsAssert, "Provided operation: " + operation + " is not supported. Please check the test instantiation.");
}
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, Layer_Elemwise_1d_Test, Combine(
/*input blob shape*/ Values(0, 1, 2, 3),
/*operation*/ Values("div", "prod", "max", "min", "sum")
));
TEST(Layer_Reshape_Test, Accuracy)
{
LayerParams lp;
lp.type = "Reshape";
lp.name = "ReshapeLayer";
lp.set("axis", 0); // Set axis to 0 to start reshaping from the first dimension
lp.set("num_axes", -1); // Set num_axes to -1 to indicate all following axes are included in the reshape
int newShape[] = {1};
lp.set("dim", DictValue::arrayInt(newShape, 1));
Ptr<ReshapeLayer> layer = ReshapeLayer::create(lp);
std::vector<int> input_shape = {0};
Mat input(0, input_shape.data(), CV_32F);
randn(input, 0.0, 1.0);
Mat output_ref(1, newShape, CV_32F, input.data);
std::vector<Mat> inputs{input};
std::vector<Mat> outputs;
runLayer(layer, inputs, outputs);
ASSERT_EQ(shape(output_ref), shape(outputs[0]));
normAssert(output_ref, outputs[0]);
}
typedef testing::TestWithParam<tuple<std::vector<int>>> Layer_Split_Test;
TEST_P(Layer_Split_Test, Accuracy_01D)
{
LayerParams lp;
lp.type = "Split";
lp.name = "SplitLayer";
int top_count = 2; // 2 is for simplicity
lp.set("top_count", top_count);
Ptr<SplitLayer> layer = SplitLayer::create(lp);
std::vector<int> input_shape = std::get<0>(GetParam());
Mat input(input_shape.size(), input_shape.data(), CV_32F);
cv::randn(input, 0.0, 1.0);
Mat output_ref = Mat(input_shape.size(), input_shape.data(), CV_32F, input.data);
std::vector<Mat> inputs{input};
std::vector<Mat> outputs;
runLayer(layer, inputs, outputs);
for (int i = 0; i < top_count; i++)
{
ASSERT_EQ(shape(output_ref), shape(outputs[i]));
normAssert(output_ref, outputs[i]);
}
}
INSTANTIATE_TEST_CASE_P(/*nothting*/, Layer_Split_Test,
testing::Values(
std::vector<int>({}),
std::vector<int>({1}),
std::vector<int>({1, 4}),
std::vector<int>({1, 5}),
std::vector<int>({4, 1}),
std::vector<int>({4, 5})
));
}}