1
0
mirror of https://github.com/opencv/opencv.git synced 2026-07-31 00:03:03 +04:00

Merge branch 4.x

This commit is contained in:
Alexander Alekhin
2021-04-09 10:30:38 +00:00
1114 changed files with 64039 additions and 14611 deletions
+121
View File
@@ -120,6 +120,11 @@ TEST(OpenCL, support_SPIR_programs)
cv::ocl::ProgramSource src = cv::ocl::ProgramSource::fromSPIR(module_name, "simple_spir", (uchar*)&program_binary_code[0], program_binary_code.size(), "");
cv::String errmsg;
cv::ocl::Program program(src, "", errmsg);
if (program.ptr() == NULL && device.isAMD())
{
// https://community.amd.com/t5/opencl/spir-support-in-new-drivers-lost/td-p/170165
throw cvtest::SkipTestException("Bypass AMD OpenCL runtime bug: 'cl_khr_spir' extension is declared, but it doesn't really work");
}
ASSERT_TRUE(program.ptr() != NULL);
k.create("test_kernel", program);
}
@@ -127,4 +132,120 @@ TEST(OpenCL, support_SPIR_programs)
testOpenCLKernel(k);
}
TEST(OpenCL, move_construct_assign)
{
cv::ocl::Context ctx1 = cv::ocl::Context::getDefault();
if (!ctx1.ptr())
{
throw cvtest::SkipTestException("OpenCL is not available");
}
void* const ctx_ptr = ctx1.ptr();
cv::ocl::Context ctx2(std::move(ctx1));
ASSERT_EQ(ctx1.ptr(), nullptr);
ASSERT_EQ(ctx2.ptr(), ctx_ptr);
cv::ocl::Context ctx3 = std::move(ctx2);
ASSERT_EQ(ctx2.ptr(), nullptr);
ASSERT_EQ(ctx3.ptr(), ctx_ptr);
cv::ocl::Platform pl1 = cv::ocl::Platform::getDefault();
void* const pl_ptr = pl1.ptr();
cv::ocl::Platform pl2(std::move(pl1));
ASSERT_EQ(pl1.ptr(), nullptr);
ASSERT_EQ(pl2.ptr(), pl_ptr);
cv::ocl::Platform pl3 = std::move(pl2);
ASSERT_EQ(pl2.ptr(), nullptr);
ASSERT_EQ(pl3.ptr(), pl_ptr);
std::vector<cv::ocl::PlatformInfo> platformInfos;
cv::ocl::getPlatfomsInfo(platformInfos);
const cv::String pi_name = platformInfos[0].name();
cv::ocl::PlatformInfo pinfo2(std::move(platformInfos[0]));
ASSERT_EQ(platformInfos[0].name(), cv::String());
ASSERT_EQ(pinfo2.name(), pi_name);
cv::ocl::PlatformInfo pinfo3 = std::move(pinfo2);
ASSERT_EQ(pinfo2.name(), cv::String());
ASSERT_EQ(pinfo3.name(), pi_name);
cv::ocl::Queue q1 = cv::ocl::Queue::getDefault();
void* const q_ptr = q1.ptr();
cv::ocl::Queue q2(std::move(q1));
ASSERT_EQ(q1.ptr(), nullptr);
ASSERT_EQ(q2.ptr(), q_ptr);
cv::ocl::Queue q3 = std::move(q2);
ASSERT_EQ(q2.ptr(), nullptr);
ASSERT_EQ(q3.ptr(), q_ptr);
cv::ocl::Device d1 = cv::ocl::Device::getDefault();
if (!d1.compilerAvailable())
{
throw cvtest::SkipTestException("OpenCL compiler is not available");
}
void* const d_ptr = d1.ptr();
cv::ocl::Device d2(std::move(d1));
ASSERT_EQ(d1.ptr(), nullptr);
ASSERT_EQ(d2.ptr(), d_ptr);
cv::ocl::Device d3 = std::move(d2);
ASSERT_EQ(d2.ptr(), nullptr);
ASSERT_EQ(d3.ptr(), d_ptr);
if (d3.imageSupport()) {
cv::UMat umat1 = cv::UMat::ones(640, 480, CV_32FC1);
cv::ocl::Image2D img1(umat1);
void *const img_ptr = img1.ptr();
cv::ocl::Image2D img2(std::move(img1));
ASSERT_EQ(img1.ptr(), nullptr);
ASSERT_EQ(img2.ptr(), img_ptr);
cv::ocl::Image2D img3 = std::move(img2);
ASSERT_EQ(img2.ptr(), nullptr);
ASSERT_EQ(img3.ptr(), img_ptr);
}
static const char* opencl_kernel_src =
"__kernel void test_kernel(__global const uchar* src, int src_step, int src_offset,\n"
" __global uchar* dst, int dst_step, int dst_offset, int dst_rows, int dst_cols,\n"
" int c)\n"
"{\n"
" int x = get_global_id(0);\n"
" int y = get_global_id(1);\n"
" if (x < dst_cols && y < dst_rows)\n"
" {\n"
" int src_idx = y * src_step + x + src_offset;\n"
" int dst_idx = y * dst_step + x + dst_offset;\n"
" dst[dst_idx] = src[src_idx] + c;\n"
" }\n"
"}\n";
cv::String module_name; // empty to disable OpenCL cache
cv::ocl::ProgramSource ps1(module_name, "move_construct_assign", opencl_kernel_src, "");
cv::ocl::ProgramSource::Impl* const ps_ptr = ps1.getImpl();
cv::ocl::ProgramSource ps2(std::move(ps1));
ASSERT_EQ(ps1.getImpl(), nullptr);
ASSERT_EQ(ps2.getImpl(), ps_ptr);
cv::ocl::ProgramSource ps3 = std::move(ps2);
ASSERT_EQ(ps2.getImpl(), nullptr);
ASSERT_EQ(ps3.getImpl(), ps_ptr);
cv::String errmsg;
cv::ocl::Program prog1(ps3, "", errmsg);
void* const prog_ptr = prog1.ptr();
ASSERT_NE(prog_ptr, nullptr);
cv::ocl::Program prog2(std::move(prog1));
ASSERT_EQ(prog1.ptr(), nullptr);
ASSERT_EQ(prog2.ptr(), prog_ptr);
cv::ocl::Program prog3 = std::move(prog2);
ASSERT_EQ(prog2.ptr(), nullptr);
ASSERT_EQ(prog3.ptr(), prog_ptr);
cv::ocl::Kernel k1("test_kernel", prog3);
void* const k_ptr = k1.ptr();
ASSERT_NE(k_ptr, nullptr);
cv::ocl::Kernel k2(std::move(k1));
ASSERT_EQ(k1.ptr(), nullptr);
ASSERT_EQ(k2.ptr(), k_ptr);
cv::ocl::Kernel k3 = std::move(k2);
ASSERT_EQ(k2.ptr(), nullptr);
ASSERT_EQ(k3.ptr(), k_ptr);
testOpenCLKernel(k3);
}
}} // namespace
+12
View File
@@ -2456,4 +2456,16 @@ TEST(Core_MinMaxIdx, rows_overflow)
}
TEST(Core_Magnitude, regression_19506)
{
for (int N = 1; N <= 64; ++N)
{
Mat a(1, N, CV_32FC1, Scalar::all(1e-20));
Mat res;
magnitude(a, a, res);
EXPECT_LE(cvtest::norm(res, NORM_L1), 1e-15) << N;
}
}
}} // namespace
+21
View File
@@ -0,0 +1,21 @@
// 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.
#if defined(HAVE_CUDA)
#include "test_precomp.hpp"
#include <cuda_runtime.h>
#include "opencv2/core/cuda.hpp"
namespace opencv_test { namespace {
TEST(CUDA_Stream, construct_cudaFlags)
{
cv::cuda::Stream stream(cudaStreamNonBlocking);
EXPECT_NE(stream.cudaPtr(), nullptr);
}
}} // namespace
#endif
+1 -1
View File
@@ -1466,7 +1466,7 @@ template<typename R> struct TheTest
R r1 = vx_load_expand((const cv::float16_t*)data.a.d);
R r2(r1);
EXPECT_EQ(1.0f, r1.get0());
vx_store(data_f32.a.d, r2);
v_store(data_f32.a.d, r2);
EXPECT_EQ(-2.0f, data_f32.a.d[R::nlanes - 1]);
out.a.clear();
-1
View File
@@ -1988,7 +1988,6 @@ class TestInputArrayRangeChecking {
C(EXPR);
C(MATX);
C(STD_VECTOR);
C(STD_ARRAY);
C(NONE);
C(STD_VECTOR_VECTOR);
C(STD_BOOL_VECTOR);
+15 -1
View File
@@ -2,6 +2,7 @@
// 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.
#include "test_precomp.hpp"
#include <cmath>
namespace opencv_test { namespace {
@@ -189,7 +190,7 @@ TEST(Core_OutputArrayCreate, _13772)
TEST(Core_String, find_last_of__with__empty_string)
{
cv::String s;
size_t p = s.find_last_of("q", 0);
size_t p = s.find_last_of('q', 0);
// npos is not exported: EXPECT_EQ(cv::String::npos, p);
EXPECT_EQ(std::string::npos, p);
}
@@ -783,5 +784,18 @@ TEST(Core_Check, testSize_1)
}
}
TEST(Core_Allocation, alignedAllocation)
{
// iterate from size=1 to approximate byte size of 8K 32bpp image buffer
for (int i = 0; i < 200; i++) {
const size_t size = static_cast<size_t>(std::pow(1.091, (double)i));
void * const buf = cv::fastMalloc(size);
ASSERT_NE((uintptr_t)0, (uintptr_t)buf)
<< "failed to allocate memory";
ASSERT_EQ((uintptr_t)0, (uintptr_t)buf % CV_MALLOC_ALIGN)
<< "memory not aligned to " << CV_MALLOC_ALIGN;
cv::fastFree(buf);
}
}
}} // namespace
+36 -12
View File
@@ -8,6 +8,23 @@
namespace opencv_test {
namespace ocl {
static
testing::internal::ParamGenerator<std::string> getOpenCLTestConfigurations()
{
if (!cv::ocl::useOpenCL())
{
return testing::ValuesIn(std::vector<std::string>());
}
std::vector<std::string> configurations = {
":GPU:0",
":GPU:1",
":CPU:0",
};
return testing::ValuesIn(configurations);
}
static void executeUMatCall(bool requireOpenCL = true)
{
UMat a(100, 100, CV_8UC1, Scalar::all(0));
@@ -45,7 +62,7 @@ TEST(OCL_Context, createFromDevice)
EXPECT_TRUE(context.getImpl() == context2.getImpl()) << "Broken cache for OpenCL context (device)";
}
TEST(OCL_OpenCLExecutionContext, basic)
TEST(OCL_OpenCLExecutionContextDefault, basic)
{
bool useOCL = cv::ocl::useOpenCL();
@@ -72,7 +89,7 @@ TEST(OCL_OpenCLExecutionContext, basic)
EXPECT_TRUE(queue.getImpl() == queue2.getImpl());
}
TEST(OCL_OpenCLExecutionContext, createAndBind)
TEST(OCL_OpenCLExecutionContextDefault, createAndBind)
{
bool useOCL = cv::ocl::useOpenCL();
@@ -106,7 +123,9 @@ TEST(OCL_OpenCLExecutionContext, createAndBind)
}
}
TEST(OCL_OpenCLExecutionContext, createGPU)
typedef testing::TestWithParam<std::string> OCL_OpenCLExecutionContext_P;
TEST_P(OCL_OpenCLExecutionContext_P, multipleBindAndExecute)
{
bool useOCL = cv::ocl::useOpenCL();
@@ -120,12 +139,11 @@ TEST(OCL_OpenCLExecutionContext, createGPU)
ASSERT_FALSE(ctx.empty());
ocl::Context context = ocl::Context::create(":GPU:1");
std::string opencl_device = GetParam();
ocl::Context context = ocl::Context::create(opencl_device);
if (context.empty())
{
context = ocl::Context::create(":CPU:");
if (context.empty())
throw SkipTestException("OpenCL GPU1/CPU devices are not available");
throw SkipTestException(std::string("OpenCL device is not available: '") + opencl_device + "'");
}
ocl::Device device = context.device(0);
@@ -135,8 +153,10 @@ TEST(OCL_OpenCLExecutionContext, createGPU)
try
{
std::cout << "ctx2..." << std::endl;
ctx2.bind();
executeUMatCall();
std::cout << "ctx..." << std::endl;
ctx.bind();
executeUMatCall();
}
@@ -147,7 +167,7 @@ TEST(OCL_OpenCLExecutionContext, createGPU)
}
}
TEST(OCL_OpenCLExecutionContext, ScopeTest)
TEST_P(OCL_OpenCLExecutionContext_P, ScopeTest)
{
bool useOCL = cv::ocl::useOpenCL();
@@ -161,12 +181,11 @@ TEST(OCL_OpenCLExecutionContext, ScopeTest)
ASSERT_FALSE(ctx.empty());
ocl::Context context = ocl::Context::create(":GPU:1");
std::string opencl_device = GetParam();
ocl::Context context = ocl::Context::create(opencl_device);
if (context.empty())
{
context = ocl::Context::create(":CPU:");
if (context.empty())
context = ctx.getContext();
throw SkipTestException(std::string("OpenCL device is not available: '") + opencl_device + "'");
}
ocl::Device device = context.device(0);
@@ -188,4 +207,9 @@ TEST(OCL_OpenCLExecutionContext, ScopeTest)
executeUMatCall();
}
INSTANTIATE_TEST_CASE_P(/*nothing*/, OCL_OpenCLExecutionContext_P, getOpenCLTestConfigurations());
} } // namespace opencv_test::ocl
+10
View File
@@ -1551,4 +1551,14 @@ TEST(Core_MatExpr, empty_check_15760)
EXPECT_THROW(Mat c = Mat().cross(Mat()), cv::Exception);
}
TEST(Core_Arithm, scalar_handling_19599) // https://github.com/opencv/opencv/issues/19599 (OpenCV 4.x+ only)
{
Mat a(1, 1, CV_32F, Scalar::all(1));
Mat b(4, 1, CV_64F, Scalar::all(1)); // MatExpr may convert Scalar to Mat
Mat c;
EXPECT_NO_THROW(cv::multiply(a, b, c));
EXPECT_EQ(1, c.cols);
EXPECT_EQ(1, c.rows);
}
}} // namespace
+249 -12
View File
@@ -3,11 +3,15 @@
// of this distribution and at http://opencv.org/license.html.
#include "test_precomp.hpp"
#include <opencv2/ts/cuda_test.hpp> // EXPECT_MAT_NEAR
#include <opencv2/core/quaternion.hpp>
#include <opencv2/ts/cuda_test.hpp>
using namespace cv;
#include <opencv2/core/dualquaternion.hpp>
namespace opencv_test{ namespace {
class QuatTest: public ::testing::Test {
class QuatTest: public ::testing::Test
{
protected:
void SetUp() override
{
@@ -18,7 +22,7 @@ protected:
}
double scalar = 2.5;
double angle = CV_PI;
int qNorm2 = 2;
double qNorm2 = 2;
Vec<double, 3> axis{1, 1, 1};
Vec<double, 3> unAxis{0, 0, 0};
Vec<double, 3> unitAxis{1.0 / sqrt(3), 1.0 / sqrt(3), 1.0 / sqrt(3)};
@@ -37,7 +41,8 @@ protected:
};
TEST_F(QuatTest, constructor){
TEST_F(QuatTest, constructor)
{
Vec<double, 4> coeff{1, 2, 3, 4};
EXPECT_EQ(Quat<double> (coeff), q1);
EXPECT_EQ(q3, q3UnitAxis);
@@ -78,7 +83,8 @@ TEST_F(QuatTest, constructor){
EXPECT_EQ(Quatd::createFromRvec(Vec3d(0, 0, 0)), qIdentity);
}
TEST_F(QuatTest, basicfuns){
TEST_F(QuatTest, basicfuns)
{
Quat<double> q1Conj{1, -2, -3, -4};
EXPECT_EQ(q3Norm2.normalize(), q3);
EXPECT_EQ(q1.norm(), sqrt(30));
@@ -124,7 +130,7 @@ TEST_F(QuatTest, basicfuns){
EXPECT_EQ(exp(qNull), qIdentity);
EXPECT_EQ(exp(Quatd(0, angle * unitAxis[0] / 2, angle * unitAxis[1] / 2, angle * unitAxis[2] / 2)), q3);
EXPECT_EQ(power(q3, 2), Quatd::createFromAngleAxis(2*angle, axis));
EXPECT_EQ(power(q3, 2.0), Quatd::createFromAngleAxis(2*angle, axis));
EXPECT_EQ(power(Quatd(0.5, 0.5, 0.5, 0.5), 2.0, assumeUnit), Quatd(-0.5,0.5,0.5,0.5));
EXPECT_EQ(power(Quatd(0.5, 0.5, 0.5, 0.5), -2.0), Quatd(-0.5,-0.5,-0.5,-0.5));
EXPECT_EQ(sqrt(q1), power(q1, 0.5));
@@ -160,7 +166,8 @@ TEST_F(QuatTest, basicfuns){
EXPECT_EQ(tan(atan(q1)), q1);
}
TEST_F(QuatTest, opeartor){
TEST_F(QuatTest, test_operator)
{
Quatd minusQ{-1, -2, -3, -4};
Quatd qAdd{3.5, 0, 6.5, 8};
Quatd qMinus{-1.5, 4, -0.5, 0};
@@ -171,7 +178,15 @@ TEST_F(QuatTest, opeartor){
EXPECT_EQ(-q1, minusQ);
EXPECT_EQ(q1 + q2, qAdd);
EXPECT_EQ(q1 + scalar, Quatd(3.5, 2, 3, 4));
EXPECT_EQ(scalar + q1, Quatd(3.5, 2, 3, 4));
EXPECT_EQ(q1 + 2.0, Quatd(3, 2, 3, 4));
EXPECT_EQ(2.0 + q1, Quatd(3, 2, 3, 4));
EXPECT_EQ(q1 - q2, qMinus);
EXPECT_EQ(q1 - scalar, Quatd(-1.5, 2, 3, 4));
EXPECT_EQ(scalar - q1, Quatd(1.5, -2, -3, -4));
EXPECT_EQ(q1 - 2.0, Quatd(-1, 2, 3, 4));
EXPECT_EQ(2.0 - q1, Quatd(1, -2, -3, -4));
EXPECT_EQ(q1 * q2, qMultq);
EXPECT_EQ(q1 * scalar, qMults);
EXPECT_EQ(scalar * q1, qMults);
@@ -195,7 +210,8 @@ TEST_F(QuatTest, opeartor){
EXPECT_ANY_THROW(q1.at(4));
}
TEST_F(QuatTest, quatAttrs){
TEST_F(QuatTest, quatAttrs)
{
double angleQ1 = 2 * acos(1.0 / sqrt(30));
Vec3d axis1{0.3713906763541037, 0.557086014, 0.742781352};
Vec<double, 3> q1axis1 = q1.getAxis();
@@ -215,7 +231,8 @@ TEST_F(QuatTest, quatAttrs){
EXPECT_NEAR(axis1[2], axis1[2], 1e-6);
}
TEST_F(QuatTest, interpolation){
TEST_F(QuatTest, interpolation)
{
Quatd qNoRot = Quatd::createFromAngleAxis(0, axis);
Quatd qLerpInter(1.0 / 2, sqrt(3) / 6, sqrt(3) / 6, sqrt(3) / 6);
EXPECT_EQ(Quatd::lerp(qNoRot, q3, 0), qNoRot);
@@ -250,6 +267,226 @@ TEST_F(QuatTest, interpolation){
EXPECT_EQ(Quatd::spline(tr1, tr2, tr3, tr3, 0.5), Quatd(0.336889853392, 0.543600719487, 0.543600719487, 0.543600719487));
}
} // namespace
static const Quatd qEuler[24] = {
Quatd(0.7233214, 0.3919013, 0.2005605, 0.5319728), //INT_XYZ
Quatd(0.8223654, 0.0222635, 0.3604221, 0.4396766), //INT_XZY
Quatd(0.822365, 0.439677, 0.0222635, 0.360422), //INT_YXZ
Quatd(0.723321, 0.531973, 0.391901, 0.20056), //INT_YZX
Quatd(0.723321, 0.20056, 0.531973, 0.391901), //INT_ZXY
Quatd(0.822365, 0.360422, 0.439677, 0.0222635), //INT_ZYX
Quatd(0.653285, 0.65328, 0.369641, -0.0990435), //INT_XYX
Quatd(0.653285, 0.65328, 0.0990435, 0.369641), //INT_XZX
Quatd(0.653285, 0.369641, 0.65328, 0.0990435), //INT_YXY
Quatd(0.653285, -0.0990435, 0.65328, 0.369641), //INT_YZY
Quatd(0.653285, 0.369641, -0.0990435, 0.65328), //INT_ZXZ
Quatd(0.653285, 0.0990435, 0.369641, 0.65328), //INT_ZYZ
}// opencv_test
Quatd(0.822365, 0.0222635, 0.439677, 0.360422), //EXT_XYZ
Quatd(0.723321, 0.391901, 0.531973, 0.20056), //EXT_XZY
Quatd(0.723321, 0.20056, 0.391901, 0.531973), //EXT_YXZ
Quatd(0.822365, 0.360422, 0.0222635, 0.439677), //EXT_YZX
Quatd(0.822365, 0.439677, 0.360422, 0.0222635), //EXT_ZXY
Quatd(0.723321, 0.531973, 0.20056, 0.391901), //EXT_ZYX
Quatd(0.653285, 0.65328, 0.369641, 0.0990435), //EXT_XYX
Quatd(0.653285, 0.65328, -0.0990435, 0.369641), //EXT_XZX
Quatd(0.653285, 0.369641, 0.65328, -0.0990435), //EXT_YXY
Quatd(0.653285, 0.0990435, 0.65328, 0.369641), //EXT_YZY
Quatd(0.653285, 0.369641, 0.0990435, 0.65328), //EXT_ZXZ
Quatd(0.653285, -0.0990435, 0.369641, 0.65328) //EXT_ZYZ
};
TEST_F(QuatTest, EulerAngles)
{
Vec3d test_angle = {0.523598, 0.78539, 1.04719};
for (QuatEnum::EulerAnglesType i = QuatEnum::EulerAnglesType::INT_XYZ; i <= QuatEnum::EulerAnglesType::EXT_ZYZ; i = (QuatEnum::EulerAnglesType)(i + 1))
{
SCOPED_TRACE(cv::format("EulerAnglesType=%d", i));
Quatd q = Quatd::createFromEulerAngles(test_angle, i);
EXPECT_EQ(q, qEuler[i]);
Vec3d Euler_Angles = q.toEulerAngles(i);
EXPECT_NEAR(Euler_Angles[0], test_angle[0], 1e-6);
EXPECT_NEAR(Euler_Angles[1], test_angle[1], 1e-6);
EXPECT_NEAR(Euler_Angles[2], test_angle[2], 1e-6);
}
Quatd qEuler0 = {0, 0, 0, 0};
EXPECT_ANY_THROW(qEuler0.toEulerAngles(QuatEnum::INT_XYZ));
Quatd qEulerLock1 = {0.5612665, 0.43042, 0.5607083, 0.4304935};
Vec3d test_angle_lock1 = {1.3089878, CV_PI * 0.5, 0};
Vec3d Euler_Angles_solute_1 = qEulerLock1.toEulerAngles(QuatEnum::INT_XYZ);
EXPECT_NEAR(Euler_Angles_solute_1[0], test_angle_lock1[0], 1e-6);
EXPECT_NEAR(Euler_Angles_solute_1[1], test_angle_lock1[1], 1e-6);
EXPECT_NEAR(Euler_Angles_solute_1[2], test_angle_lock1[2], 1e-6);
Quatd qEulerLock2 = {0.7010574, 0.0922963, 0.7010573, -0.0922961};
Vec3d test_angle_lock2 = {-0.2618, CV_PI * 0.5, 0};
Vec3d Euler_Angles_solute_2 = qEulerLock2.toEulerAngles(QuatEnum::INT_ZYX);
EXPECT_NEAR(Euler_Angles_solute_2[0], test_angle_lock2[0], 1e-6);
EXPECT_NEAR(Euler_Angles_solute_2[1], test_angle_lock2[1], 1e-6);
EXPECT_NEAR(Euler_Angles_solute_2[2], test_angle_lock2[2], 1e-6);
Vec3d test_angle6 = {CV_PI * 0.25, CV_PI * 0.5, CV_PI * 0.25};
Vec3d test_angle7 = {CV_PI * 0.5, CV_PI * 0.5, 0};
EXPECT_EQ(Quatd::createFromEulerAngles(test_angle6, QuatEnum::INT_ZXY), Quatd::createFromEulerAngles(test_angle7, QuatEnum::INT_ZXY));
}
class DualQuatTest: public ::testing::Test
{
protected:
double scalar = 2.5;
double angle = CV_PI;
Vec<double, 3> axis{1, 1, 1};
Vec<double, 3> unAxis{0, 0, 0};
Vec<double, 3> unitAxis{1.0 / sqrt(3), 1.0 / sqrt(3), 1.0 / sqrt(3)};
DualQuatd dq1{1, 2, 3, 4, 5, 6, 7, 8};
Vec3d trans{0, 0, 5};
double rotation_angle = 2.0 / 3 * CV_PI;
DualQuatd dq2 = DualQuatd::createFromAngleAxisTrans(rotation_angle, axis, trans);
DualQuatd dqAllOne{1, 1, 1, 1, 1, 1, 1, 1};
DualQuatd dqAllZero{0, 0, 0, 0, 0, 0, 0, 0};
DualQuatd dqIdentity{1, 0, 0, 0, 0, 0, 0, 0};
DualQuatd dqTrans{1, 0, 0, 0, 0, 2, 3, 4};
DualQuatd dqOnlyTrans{0, 0, 0, 0, 0, 2, 3, 4};
DualQuatd dualNumber1{-3,0,0,0,-31.1,0,0,0};
DualQuatd dualNumber2{4,0,0,0,5.1,0,0,0};
};
TEST_F(DualQuatTest, constructor)
{
EXPECT_EQ(dq1, DualQuatd::createFromQuat(Quatd(1, 2, 3, 4), Quatd(5, 6, 7, 8)));
EXPECT_EQ(dq2 * dq2.conjugate(), dqIdentity);
EXPECT_NEAR(dq2.getRotation(QUAT_ASSUME_UNIT).norm(), 1, 1e-6);
EXPECT_NEAR(dq2.getRealPart().dot(dq2.getDualPart()), 0, 1e-6);
EXPECT_MAT_NEAR(dq2.getTranslation(QUAT_ASSUME_UNIT), trans, 1e-6);
DualQuatd q_conj = DualQuatd::createFromQuat(dq2.getRealPart().conjugate(), -dq2.getDualPart().conjugate());
DualQuatd q{1,0,0,0,0,3,0,0};
EXPECT_EQ(dq2 * q * q_conj, DualQuatd(1,0,0,0,0,0,3,5));
Matx44d R1 = dq2.toMat();
DualQuatd dq3 = DualQuatd::createFromMat(R1);
EXPECT_EQ(dq3, dq2);
axis = axis / std::sqrt(axis.dot(axis));
Vec3d moment = 1.0 / 2 * (trans.cross(axis) + axis.cross(trans.cross(axis)) *
std::cos(rotation_angle / 2) / std::sin(rotation_angle / 2));
double d = trans.dot(axis);
DualQuatd dq4 = DualQuatd::createFromPitch(rotation_angle, d, axis, moment);
EXPECT_EQ(dq4, dq3);
EXPECT_EQ(dq2, DualQuatd::createFromAffine3(dq2.toAffine3()));
EXPECT_EQ(dq1.normalize(), DualQuatd::createFromAffine3(dq1.toAffine3()));
}
TEST_F(DualQuatTest, test_operator)
{
DualQuatd dq_origin{1, 2, 3, 4, 5, 6, 7, 8};
EXPECT_EQ(dq1 - dqAllOne, DualQuatd(0, 1, 2, 3, 4, 5, 6, 7));
EXPECT_EQ(-dq1, DualQuatd(-1, -2, -3, -4, -5, -6, -7, -8));
EXPECT_EQ(dq1 + dqAllOne, DualQuatd(2, 3, 4, 5, 6, 7, 8, 9));
EXPECT_EQ(dq1 / dq1, dqIdentity);
DualQuatd dq3{-4, 1, 3, 2, -15.5, 0, -3, 8.5};
EXPECT_EQ(dq1 * dq2, dq3);
EXPECT_EQ(dq3 / dq2, dq1);
DualQuatd dq12{2, 4, 6, 8, 10, 12, 14, 16};
EXPECT_EQ(dq1 * 2.0, dq12);
EXPECT_EQ(2.0 * dq1, dq12);
EXPECT_EQ(dq1 - 1.0, DualQuatd(0, 2, 3, 4, 5, 6, 7, 8));
EXPECT_EQ(1.0 - dq1, DualQuatd(0, -2, -3, -4, -5, -6, -7, -8));
EXPECT_EQ(dq1 + 1.0, DualQuatd(2, 2, 3, 4, 5, 6, 7, 8));
EXPECT_EQ(1.0 + dq1, DualQuatd(2, 2, 3, 4, 5, 6, 7, 8));
dq1 += dq2;
EXPECT_EQ(dq1, dq_origin + dq2);
dq1 -= dq2;
EXPECT_EQ(dq1, dq_origin);
dq1 *= dq2;
EXPECT_EQ(dq1, dq_origin * dq2);
dq1 /= dq2;
EXPECT_EQ(dq1, dq_origin);
}
TEST_F(DualQuatTest, basic_ops)
{
EXPECT_EQ(dq1.getRealPart(), Quatd(1, 2, 3, 4));
EXPECT_EQ(dq1.getDualPart(), Quatd(5, 6, 7, 8));
EXPECT_EQ((dq1 * dq2).conjugate(), conjugate(dq1 * dq2));
EXPECT_EQ(dq1.conjugate(), DualQuatd::createFromQuat(dq1.getRealPart().conjugate(), dq1.getDualPart().conjugate()));
EXPECT_EQ((dq2 * dq1).conjugate(), dq1.conjugate() * dq2.conjugate());
EXPECT_EQ(dq1.conjugate() * dq1, dq1.norm() * dq1.norm());
EXPECT_EQ(dq1.conjugate() * dq1, dq1.norm().power(2.0));
EXPECT_EQ(dualNumber2.power(2.0), DualQuatd(16, 0, 0, 0, 40.8, 0, 0, 0));
EXPECT_EQ(dq1.power(2.0), (2.0 * dq1.log()).exp());
EXPECT_EQ(power(dq1, 2.0), (exp(2.0 * log(dq1))));
EXPECT_EQ(dq2.power(3.0 / 2, QUAT_ASSUME_UNIT).power(4.0 / 3, QUAT_ASSUME_UNIT), dq2 * dq2);
EXPECT_EQ(dq2.power(-0.5).power(2.0), dq2.inv());
EXPECT_EQ(power(dq1, dq2), exp(dq2 * log(dq1)));
EXPECT_EQ(power(dq2, dq1, QUAT_ASSUME_UNIT), exp(dq1 * log(dq2)));
EXPECT_EQ((dq2.norm() * dq1).power(2.0), dq1.power(2.0) * dq2.norm().power(2.0));
DualQuatd q1norm = dq1.normalize();
EXPECT_EQ(dq2.norm(), dqIdentity);
EXPECT_NEAR(q1norm.getRealPart().norm(), 1, 1e-6);
EXPECT_NEAR(q1norm.getRealPart().dot(q1norm.getDualPart()), 0, 1e-6);
EXPECT_NEAR(dq1.getRotation().norm(), 1, 1e-6);
EXPECT_NEAR(dq2.getRotation(QUAT_ASSUME_UNIT).norm(), 1, 1e-6);
EXPECT_NEAR(dq2.getRotation(QUAT_ASSUME_UNIT).norm(), 1, 1e-6);
EXPECT_MAT_NEAR(Mat(dq2.getTranslation()), Mat(trans), 1e-6);
EXPECT_MAT_NEAR(Mat(q1norm.getTranslation(QUAT_ASSUME_UNIT)), Mat(dq1.getTranslation()), 1e-6);
EXPECT_EQ(dq2.getTranslation(), dq2.getTranslation(QUAT_ASSUME_UNIT));
EXPECT_EQ(dq1.inv() * dq1, dqIdentity);
EXPECT_EQ(inv(dq1) * dq1, dqIdentity);
EXPECT_EQ(dq2.inv(QUAT_ASSUME_UNIT) * dq2, dqIdentity);
EXPECT_EQ(inv(dq2, QUAT_ASSUME_UNIT) * dq2, dqIdentity);
EXPECT_EQ(dq2.inv(), dq2.conjugate());
EXPECT_EQ(dqIdentity.inv(), dqIdentity);
EXPECT_ANY_THROW(dqAllZero.inv());
EXPECT_EQ(dqAllZero.exp(), dqIdentity);
EXPECT_EQ(exp(dqAllZero), dqIdentity);
EXPECT_ANY_THROW(log(dqAllZero));
EXPECT_EQ(log(dqIdentity), dqAllZero);
EXPECT_EQ(dqIdentity.log(), dqAllZero);
EXPECT_EQ(dualNumber1 * dualNumber2, dualNumber2 * dualNumber1);
EXPECT_EQ(dualNumber2.exp().log(), dualNumber2);
EXPECT_EQ(dq2.log(QUAT_ASSUME_UNIT).exp(), dq2);
EXPECT_EQ(exp(log(dq2, QUAT_ASSUME_UNIT)), dq2);
EXPECT_EQ(dqIdentity.log(QUAT_ASSUME_UNIT).exp(), dqIdentity);
EXPECT_EQ(dq1.log().exp(), dq1);
EXPECT_EQ(dqTrans.log().exp(), dqTrans);
EXPECT_MAT_NEAR(q1norm.toMat(QUAT_ASSUME_UNIT), dq1.toMat(), 1e-6);
Matx44d R1 = dq2.toMat();
Mat point = (Mat_<double>(4, 1) << 3, 0, 0, 1);
Mat new_point = R1 * point;
Mat after = (Mat_<double>(4, 1) << 0, 3, 5 ,1);
EXPECT_MAT_NEAR(new_point, after, 1e-6);
Vec<double, 8> vec = dq1.toVec();
EXPECT_EQ(DualQuatd(vec), dq1);
Affine3d afd = q1norm.toAffine3(QUAT_ASSUME_UNIT);
EXPECT_MAT_NEAR(Mat(afd.translation()), Mat(q1norm.getTranslation(QUAT_ASSUME_UNIT)), 1e-6);
Affine3d dq1_afd = dq1.toAffine3();
EXPECT_MAT_NEAR(dq1_afd.matrix, afd.matrix, 1e-6);
EXPECT_ANY_THROW(dqAllZero.toAffine3());
}
TEST_F(DualQuatTest, interpolation)
{
DualQuatd dq = DualQuatd::createFromAngleAxisTrans(8 * CV_PI / 5, Vec3d{0, 0, 1}, Vec3d{0, 0, 10});
EXPECT_EQ(DualQuatd::sclerp(dqIdentity, dq, 0.5), DualQuatd::sclerp(-dqIdentity, dq, 0.5, false));
EXPECT_EQ(DualQuatd::sclerp(dqIdentity, dq, 0), -dqIdentity);
EXPECT_EQ(DualQuatd::sclerp(dqIdentity, dq2, 1), dq2);
EXPECT_EQ(DualQuatd::sclerp(dqIdentity, dq2, 0.4, false, QUAT_ASSUME_UNIT), DualQuatd(0.91354546, 0.23482951, 0.23482951, 0.23482951, -0.23482951, -0.47824988, 0.69589767, 0.69589767));
EXPECT_EQ(DualQuatd::dqblend(dqIdentity, dq1.normalize(), 0.2, QUAT_ASSUME_UNIT), DualQuatd::dqblend(dqIdentity, -dq1, 0.2));
EXPECT_EQ(DualQuatd::dqblend(dqIdentity, dq2, 0.4), DualQuatd(0.91766294, 0.22941573, 0.22941573, 0.22941573, -0.21130397, -0.48298049, 0.66409818, 0.66409818));
DualQuatd gdb = DualQuatd::gdqblend(Vec<DualQuatd, 3>{dqIdentity, dq, dq2}, Vec3d{0.4, 0, 0.6}, QUAT_ASSUME_UNIT);
EXPECT_EQ(gdb, DualQuatd::dqblend(dqIdentity, dq2, 0.6));
EXPECT_ANY_THROW(DualQuatd::gdqblend(Vec<DualQuatd, 1>{dq2}, Vec2d{0.5, 0.5}));
Mat gdqb_d(1, 2, CV_64FC(7));
gdqb_d.at<Vec<double, 7>>(0, 0) = Vec<double, 7>{1,2,3,4,5,6,7};
gdqb_d.at<Vec<double, 7>>(0, 1) = Vec<double, 7>{1,2,3,4,5,6,7};
EXPECT_ANY_THROW(DualQuatd::gdqblend(gdqb_d, Vec2d{0.5, 0.5}));
Mat gdqb_f(1, 2, CV_32FC(8));
gdqb_f.at<Vec<float, 8>>(0, 0) = Vec<float, 8>{1.f,2.f,3.f,4.f,5.f,6.f,7.f,8.f};
gdqb_f.at<Vec<float, 8>>(0, 1) = Vec<float, 8>{1.f,2.f,3.f,4.f,5.f,6.f,7.f,8.f};
EXPECT_ANY_THROW(DualQuatd::gdqblend(gdqb_f, Vec2d{0.5, 0.5}));
EXPECT_ANY_THROW(DualQuatd::gdqblend(Vec<DualQuatd, 3>{dqIdentity, dq, dq2}, Vec3f{0.4f, 0.f, 0.6f}, QUAT_ASSUME_UNIT));
EXPECT_EQ(gdb, DualQuatd::gdqblend(Vec<DualQuatd, 3>{dqIdentity, dq * dualNumber1, -dq2}, Vec3d{0.4, 0, 0.6}));
}
}} // namespace