Blender V4.3
kernel_camera_projection_test.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include "testing/testing.h"
6
7#include "util/math.h"
8#include "util/types.h"
9
12
13#include "kernel/types.h"
14
17
19
26TEST(KernelCamera, FisheyeLensPolynomialRoundtrip)
27{
28 const float fov = 150.0f * (M_PI_F / 180.0f);
29 const float width = 36.0f;
30 const float height = 41.142857142857144f;
31
32 /* Trivial case: The coefficients create a perfect equidistant fisheye */
33 const float4 k_equidistant = make_float4(-5.79e-02f, 0.0f, 0.0f, 0.0f);
34
35 /* The coefficients mimic a stereographic fisheye model */
36 const float4 k_stereographic = make_float4(-5.79e-02f, 0.0f, 9.48e-05f, -7.67e-06f);
37
38 /* The coefficients mimic a rectilinear camera (badly, but the point is to have a wide range of
39 * tests). */
40 const float4 k_rectilinear = make_float4(-6.50e-02f, 0.0f, 8.32e-05f, -1.80e-06f);
41
42 const float4 parameters[]{k_equidistant, k_stereographic, k_rectilinear};
43
44 const std::pair<float, float> points[]{
45 {0.1f, 0.4f},
46 {0.1f, 0.5f},
47 {0.1f, 0.7f},
48 {0.5f, 0.5f},
49 {0.5f, 0.9f},
50 {0.6f, 0.9f},
51 };
52
53 /* In the test cases k0 = k2 = 0, because for non-zero values the model is not smooth at the
54 * center, but real lenses are really smooth near the center. In order to test the method
55 * thoroughly, nonzero values are tested for both parameters. */
56 for (const float k0 : {0.0f, -1e-2f, -2e-2f, -5e-2f, -1e-1f}) {
57 for (const float k2 : {0.0f, -1e-4f, 1e-4f, -2e-4f, 2e-4f}) {
58 for (float4 k : parameters) {
59 k.y = k2;
60 for (std::pair<float, float> const &pt : points) {
61 const float x = pt.first;
62 const float y = pt.second;
64 pt.first, pt.second, k0, k, fov, width, height);
65
66 EXPECT_NEAR(len(direction), 1, 1e-6) << "x: " << x << std::endl
67 << "y: " << y << std::endl
68 << "k0: " << k0 << std::endl
69 << "k2: " << k2;
70
72 direction, k0, k, width, height);
73
74 EXPECT_NEAR(reprojection.x, x, 1e-6) << "k0: " << k0 << std::endl
75 << "k1: " << k.x << std::endl
76 << "k2: " << k.y << std::endl
77 << "k3: " << k.z << std::endl
78 << "k4: " << k.w << std::endl;
79 EXPECT_NEAR(reprojection.y, y, 3e-6) << "k0: " << k0 << std::endl
80 << "k1: " << k.x << std::endl
81 << "k2: " << k.y << std::endl
82 << "k3: " << k.z << std::endl
83 << "k4: " << k.w << std::endl;
84 }
85 }
86 }
87 }
88}
89
93TEST(KernelCamera, FisheyeLensPolynomialToDirectionSymmetry)
94{
95 const float fov = M_PI_F;
96 const float width = 1.0f;
97 const float height = 1.0f;
98
99 /* Trivial case: The coefficients create a perfect equidistant fisheye */
100 const float4 k_equidistant = make_float4(-1.0f, 0.0f, 0.0f, 0.0f);
101 const float k0 = 0.0f;
102
103 /* Symmetry tests */
104 const float2 center{0.5f, 0.5f};
105 const float2 offsets[]{
106 {0.00f, 0.00f},
107 {0.25f, 0.00f},
108 {0.00f, 0.25f},
109 {0.25f, 0.25f},
110
111 {0.5f, 0.0f},
112 {0.0f, 0.5f},
113 {0.5f, 0.5f},
114
115 {0.75f, 0.00f},
116 {0.00f, 0.75f},
117 {0.75f, 0.75f},
118 };
119
120 for (float2 const &offset : offsets) {
121 const float2 point = center + offset;
123 point.x, point.y, k0, k_equidistant, fov, width, height);
124 EXPECT_NEAR(len(direction), 1.0, 1e-6);
125
126 const float2 point_mirror = center - offset;
127 const float3 direction_mirror = fisheye_lens_polynomial_to_direction(
128 point_mirror.x, point_mirror.y, k0, k_equidistant, fov, width, height);
129 EXPECT_NEAR(len(direction_mirror), 1.0, 1e-6);
130
131 EXPECT_NEAR(direction.x, +direction_mirror.x, 1e-6)
132 << "offset: (" << offset.x << ", " << offset.y << ")";
133 EXPECT_NEAR(direction.y, -direction_mirror.y, 1e-6)
134 << "offset: (" << offset.x << ", " << offset.y << ")";
135 ;
136 EXPECT_NEAR(direction.z, -direction_mirror.z, 1e-6)
137 << "offset: (" << offset.x << ", " << offset.y << ")";
138 ;
139 }
140}
141
146TEST(KernelCamera, FisheyeLensPolynomialToDirection)
147{
148 const float fov = M_PI_F;
149 const float k0 = 0.0f;
150
151 const float rad60 = M_PI_F / 3.0f;
152 const float cos60 = 0.5f;
153 const float sin60 = M_SQRT3_F / 2.0f;
154
155 const float rad30 = M_PI_F / 6.0f;
156 const float cos30 = M_SQRT3_F / 2.0f;
157 const float sin30 = 0.5f;
158
159 const float rad45 = M_PI_4F;
160 const float cos45 = M_SQRT1_2F;
161 const float sin45 = M_SQRT1_2F;
162
163 const std::pair<float2, float3> tests[]{
164 /* Center (0°) */
165 {make_float2(0.0f, 0.0f), make_float3(1.0f, 0.0f, 0.0f)},
166
167 /* 60° */
168 {make_float2(0.0f, +rad60), make_float3(cos60, 0.0f, +sin60)},
169 {make_float2(0.0f, -rad60), make_float3(cos60, 0.0f, -sin60)},
170 {make_float2(+rad60, 0.0f), make_float3(cos60, -sin60, 0.0f)},
171 {make_float2(-rad60, 0.0f), make_float3(cos60, +sin60, 0.0f)},
172
173 /* 45° */
174 {make_float2(0.0f, +rad45), make_float3(cos45, 0.0f, +sin45)},
175 {make_float2(0.0f, -rad45), make_float3(cos45, 0.0f, -sin45)},
176 {make_float2(+rad45, 0.0f), make_float3(cos45, -sin45, 0.0f)},
177 {make_float2(-rad45, 0.0f), make_float3(cos45, +sin45, 0.0f)},
178
179 {make_float2(+rad45 * M_SQRT1_2F, +rad45 * M_SQRT1_2F), make_float3(cos45, -0.5f, +0.5f)},
180 {make_float2(-rad45 * M_SQRT1_2F, +rad45 * M_SQRT1_2F), make_float3(cos45, +0.5f, +0.5f)},
181 {make_float2(+rad45 * M_SQRT1_2F, -rad45 * M_SQRT1_2F), make_float3(cos45, -0.5f, -0.5f)},
182 {make_float2(-rad45 * M_SQRT1_2F, -rad45 * M_SQRT1_2F), make_float3(cos45, +0.5f, -0.5f)},
183
184 /* 30° */
185 {make_float2(0.0f, +rad30), make_float3(cos30, 0.0f, +sin30)},
186 {make_float2(0.0f, -rad30), make_float3(cos30, 0.0f, -sin30)},
187 {make_float2(+rad30, 0.0f), make_float3(cos30, -sin30, 0.0f)},
188 {make_float2(-rad30, 0.0f), make_float3(cos30, +sin30, 0.0f)},
189 };
190
191 for (auto [offset, direction] : tests) {
192 const float2 sensor = offset + make_float2(0.5f, 0.5f);
193 for (float const scale : {1.0f, 0.5f, 2.0f, 0.25f, 4.0f, 0.125f, 8.0f, 0.0625f, 16.0f}) {
194 const float width = 1.0f / scale;
195 const float height = 1.0f / scale;
196 /* Trivial case: The coefficients create a perfect equidistant fisheye */
197 const float4 k_equidistant = make_float4(-scale, 0.0f, 0.0f, 0.0f);
198
200 sensor.x, sensor.y, k0, k_equidistant, fov, width, height);
201
202 EXPECT_NEAR(direction.x, computed.x, 1e-6)
203 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl
204 << "scale: " << scale;
205 EXPECT_NEAR(direction.y, computed.y, 1e-6)
206 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl
207 << "scale: " << scale;
208 EXPECT_NEAR(direction.z, computed.z, 1e-6)
209 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl
210 << "scale: " << scale;
211
213 direction, k0, k_equidistant, width, height);
214
215 EXPECT_NEAR(sensor.x, reprojected.x, 1e-6) << "scale: " << scale;
216 EXPECT_NEAR(sensor.y, reprojected.y, 1e-6) << "scale: " << scale;
217 }
218 }
219}
220
232 virtual double threshold() const
233 {
234 return 2e-6;
235 }
236
241 virtual bool skip_invalid() const
242 {
243 return false;
244 }
245};
246
247struct Spherical : public CommonValues {
249 float const fov,
250 float const width,
251 float const height)
252 {
253 return direction_to_spherical(dir);
254 }
255 static float3 sensor_to_direction(float2 const &sensor,
256 float const fov,
257 float const width,
258 float const height)
259 {
260 return spherical_to_direction(sensor.x, sensor.y);
261 }
262};
263
266 float const fov,
267 float const width,
268 float const height)
269 {
271 }
272 static float3 sensor_to_direction(float2 const &sensor,
273 float const fov,
274 float const width,
275 float const height)
276 {
277 return equirectangular_to_direction(sensor.x, sensor.y);
278 }
279};
280
283 float const fov,
284 float const width,
285 float const height)
286 {
287 return direction_to_fisheye(dir, fov);
288 }
289 static float3 sensor_to_direction(float2 const &sensor,
290 float const fov,
291 float const width,
292 float const height)
293 {
294 return fisheye_to_direction(sensor.x, sensor.y, fov);
295 }
296};
297
299 bool skip_invalid() const
300 {
301 return true;
302 }
303
304 static constexpr float lens = 15.0f;
305
307 float const fov,
308 float const width,
309 float const height)
310 {
311 return direction_to_fisheye_equisolid(dir, lens, width, height);
312 }
313 static float3 sensor_to_direction(float2 const &sensor,
314 float const fov,
315 float const width,
316 float const height)
317 {
318 return fisheye_equisolid_to_direction(sensor.x, sensor.y, lens, fov, width, height);
319 }
320};
321
322struct MirrorBall : public CommonValues {
324 float const fov,
325 float const width,
326 float const height)
327 {
328 return direction_to_mirrorball(dir);
329 }
330 static float3 sensor_to_direction(float2 const &sensor,
331 float const fov,
332 float const width,
333 float const height)
334 {
335 return mirrorball_to_direction(sensor.x, sensor.y);
336 }
337};
338
341 float const fov,
342 float const width,
343 float const height)
344 {
346 }
347 static float3 sensor_to_direction(float2 const &sensor,
348 float const fov,
349 float const width,
350 float const height)
351 {
352 return equiangular_cubemap_face_to_direction(sensor.x, sensor.y);
353 }
354};
355
356template<typename T> class PanoramaProjection : public testing::Test {};
357using MyTypes = ::testing::Types<Spherical,
364
372{
373
374 TypeParam test;
375
376 const float2 sensors[]{{0.5f, 0.5f},
377 {0.4f, 0.4f},
378 {0.3f, 0.3f},
379 {0.4f, 0.6f},
380 {0.3f, 0.7f},
381 {0.2f, 0.8f},
382 {0.5f, 0.9f},
383 {0.5f, 0.1f},
384 {0.1f, 0.5f},
385 {0.9f, 0.5f}};
386
387 for (float const size : {36.0f, 24.0f, 6.0f * M_PI_F}) {
388 float const width = size;
389 float const height = size;
390 for (const float fov : {2.0f * M_PI_F, M_PI_F, M_PI_2_F, M_PI_4_F, 1.0f, 2.0f}) {
391 size_t test_count = 0;
392 for (const float2 &sensor : sensors) {
393 const float3 direction = TypeParam::sensor_to_direction(sensor, fov, width, height);
394 if (test.skip_invalid() && len(direction) < 0.9f) {
395 continue;
396 }
397 test_count++;
398 EXPECT_NEAR(len(direction), 1.0, 1e-6)
399 << "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
400 << std::endl
401 << "fov: " << fov << std::endl
402 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
403 const float2 projection = TypeParam::direction_to_sensor(direction, fov, width, height);
404 EXPECT_NEAR(sensor.x, projection.x, test.threshold())
405 << "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
406 << std::endl
407 << "fov: " << fov << std::endl
408 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
409 EXPECT_NEAR(sensor.y, projection.y, test.threshold())
410 << "dir: (" << direction.x << ", " << direction.y << ", " << direction.z << ")"
411 << std::endl
412 << "fov: " << fov << std::endl
413 << "sensor: (" << sensor.x << ", " << sensor.y << ")" << std::endl;
414 }
415 EXPECT_GE(test_count, 2) << "fov: " << fov << std::endl << "size: " << size << std::endl;
416 }
417 }
418}
419
ATTR_WARN_UNUSED_RESULT const BMVert const BMEdge * e
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
ccl_device float3 equiangular_cubemap_face_to_direction(float u, float v)
ccl_device float2 direction_to_fisheye(float3 dir, float fov)
ccl_device float3 fisheye_to_direction(float u, float v, float fov)
ccl_device float2 direction_to_mirrorball(float3 dir)
ccl_device float2 direction_to_equiangular_cubemap_face(float3 dir)
ccl_device float2 direction_to_fisheye_lens_polynomial(float3 dir, float coeff0, float4 coeffs, float width, float height)
CCL_NAMESPACE_BEGIN ccl_device float2 direction_to_spherical(float3 dir)
ccl_device float3 equirectangular_to_direction(float u, float v)
ccl_device float2 direction_to_equirectangular(float3 dir)
ccl_device float2 direction_to_fisheye_equisolid(float3 dir, float lens, float width, float height)
ccl_device_inline float3 fisheye_equisolid_to_direction(float u, float v, float lens, float fov, float width, float height)
ccl_device float3 spherical_to_direction(float theta, float phi)
ccl_device float3 mirrorball_to_direction(float u, float v)
ccl_device_inline float3 fisheye_lens_polynomial_to_direction(float u, float v, float coeff0, float4 coeffs, float fov, float width, float height)
#define CCL_NAMESPACE_END
ccl_device_forceinline float4 make_float4(const float x, const float y, const float z, const float w)
ccl_device_forceinline float3 make_float3(const float x, const float y, const float z)
ccl_device_forceinline float2 make_float2(const float x, const float y)
int len
CCL_NAMESPACE_BEGIN TEST(KernelCamera, FisheyeLensPolynomialRoundtrip)
Test fisheye_lens_polynomial_to_direction and its inverse direction_to_fisheye_lens_polynomial by che...
TYPED_TEST_SUITE(PanoramaProjection, MyTypes)
TYPED_TEST(PanoramaProjection, round_trip)
Test <projection>to_direction and its inverse direction_to<projection> by checking if sensor position...
::testing::Types< Spherical, Equirectangular, FisheyeEquidistant, FisheyeEquisolid, MirrorBall, EquiangularCubemapFace > MyTypes
double parameters[NUM_PARAMETERS]
#define M_PI_F
Definition mikk_util.hh:15
#define M_PI_2_F
Definition sky_float3.h:20
The CommonValues struct contains information about the tests which is common across the different tes...
virtual bool skip_invalid() const
If skip_invalid returns true, invalid unprojections are ignored in the test.
virtual double threshold() const
Threshold for the reprojection error.
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
bool skip_invalid() const
If skip_invalid returns true, invalid unprojections are ignored in the test.
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
static float2 direction_to_sensor(float3 const &dir, float const fov, float const width, float const height)
static float3 sensor_to_direction(float2 const &sensor, float const fov, float const width, float const height)
float x
float y
float z
Definition sky_float3.h:27
float y
Definition sky_float3.h:27
float x
Definition sky_float3.h:27