1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define LOG_NDEBUG 0
18 #define LOG_TAG "DistortionMapperTest"
19
20 #include <random>
21
22 #include <gtest/gtest.h>
23 #include <android-base/stringprintf.h>
24 #include <android-base/chrono_utils.h>
25
26 #include "../device3/DistortionMapper.h"
27
28 using namespace android;
29 using namespace android::camera3;
30 using DistortionMapperInfo = android::camera3::DistortionMapper::DistortionMapperInfo;
31
32 int32_t testActiveArray[] = {100, 100, 1000, 750};
33 int32_t testPreCorrActiveArray[] = {90, 90, 1020, 770};
34
35 float testICal[] = { 1000.f, 1000.f, 500.f, 500.f, 0.f };
36
37 float identityDistortion[] = { 0.f, 0.f, 0.f, 0.f, 0.f};
38
39 std::array<int32_t, 12> basicCoords = {
40 0, 0,
41 testActiveArray[2] - 1, 0,
42 testActiveArray[2] - 1, testActiveArray[3] - 1,
43 0, testActiveArray[3] - 1,
44 testActiveArray[2] / 2, testActiveArray[3] / 2,
45 251, 403 // A particularly bad coordinate for current grid count/array size
46 };
47
48
setupTestMapper(DistortionMapper * m,float distortion[5],float intrinsics[5],int32_t activeArray[4],int32_t preCorrectionActiveArray[4])49 void setupTestMapper(DistortionMapper *m,
50 float distortion[5], float intrinsics[5],
51 int32_t activeArray[4], int32_t preCorrectionActiveArray[4]) {
52 CameraMetadata deviceInfo;
53
54 deviceInfo.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
55 preCorrectionActiveArray, 4);
56
57 deviceInfo.update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
58 activeArray, 4);
59
60 deviceInfo.update(ANDROID_LENS_INTRINSIC_CALIBRATION,
61 intrinsics, 5);
62
63 deviceInfo.update(ANDROID_LENS_DISTORTION,
64 distortion, 5);
65
66 m->setupStaticInfo(deviceInfo);
67 }
68
TEST(DistortionMapperTest,Initialization)69 TEST(DistortionMapperTest, Initialization) {
70 CameraMetadata deviceInfo;
71
72 ASSERT_FALSE(DistortionMapper::isDistortionSupported(deviceInfo));
73
74 uint8_t distortionModes[] =
75 {ANDROID_DISTORTION_CORRECTION_MODE_OFF,
76 ANDROID_DISTORTION_CORRECTION_MODE_FAST,
77 ANDROID_DISTORTION_CORRECTION_MODE_HIGH_QUALITY};
78
79 deviceInfo.update(ANDROID_DISTORTION_CORRECTION_AVAILABLE_MODES,
80 distortionModes, 1);
81
82 ASSERT_FALSE(DistortionMapper::isDistortionSupported(deviceInfo));
83
84 deviceInfo.update(ANDROID_DISTORTION_CORRECTION_AVAILABLE_MODES,
85 distortionModes, 3);
86
87 ASSERT_TRUE(DistortionMapper::isDistortionSupported(deviceInfo));
88
89 DistortionMapper m;
90
91 ASSERT_FALSE(m.calibrationValid());
92
93 ASSERT_NE(m.setupStaticInfo(deviceInfo), OK);
94
95 ASSERT_FALSE(m.calibrationValid());
96
97 deviceInfo.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
98 testPreCorrActiveArray, 4);
99
100 deviceInfo.update(ANDROID_SENSOR_INFO_ACTIVE_ARRAY_SIZE,
101 testActiveArray, 4);
102
103 deviceInfo.update(ANDROID_LENS_INTRINSIC_CALIBRATION,
104 testICal, 5);
105
106 deviceInfo.update(ANDROID_LENS_DISTORTION,
107 identityDistortion, 5);
108
109 ASSERT_EQ(m.setupStaticInfo(deviceInfo), OK);
110
111 ASSERT_TRUE(m.calibrationValid());
112
113 CameraMetadata captureResult;
114
115 ASSERT_NE(m.updateCalibration(captureResult), OK);
116
117 captureResult.update(ANDROID_LENS_INTRINSIC_CALIBRATION,
118 testICal, 5);
119 captureResult.update(ANDROID_LENS_DISTORTION,
120 identityDistortion, 5);
121
122 ASSERT_EQ(m.updateCalibration(captureResult), OK);
123
124 }
125
TEST(DistortionMapperTest,IdentityTransform)126 TEST(DistortionMapperTest, IdentityTransform) {
127 status_t res;
128
129 DistortionMapper m;
130 setupTestMapper(&m, identityDistortion, testICal,
131 /*activeArray*/ testActiveArray,
132 /*preCorrectionActiveArray*/ testActiveArray);
133
134 auto coords = basicCoords;
135 DistortionMapperInfo *mapperInfo = m.getMapperInfo();
136 res = m.mapCorrectedToRaw(coords.data(), 5, mapperInfo, /*clamp*/true);
137 ASSERT_EQ(res, OK);
138
139 for (size_t i = 0; i < coords.size(); i++) {
140 EXPECT_EQ(coords[i], basicCoords[i]);
141 }
142
143 res = m.mapRawToCorrected(coords.data(), 5, mapperInfo, /*clamp*/true);
144 ASSERT_EQ(res, OK);
145
146 for (size_t i = 0; i < coords.size(); i++) {
147 EXPECT_EQ(coords[i], basicCoords[i]);
148 }
149
150 std::array<int32_t, 8> rects = {
151 0, 0, 100, 100,
152 testActiveArray[2] - 101, testActiveArray[3] - 101, 100, 100
153 };
154
155 auto rectsOrig = rects;
156 res = m.mapCorrectedRectToRaw(rects.data(), 2, mapperInfo, /*clamp*/true);
157 ASSERT_EQ(res, OK);
158
159 for (size_t i = 0; i < rects.size(); i++) {
160 EXPECT_EQ(rects[i], rectsOrig[i]);
161 }
162
163 res = m.mapRawRectToCorrected(rects.data(), 2, mapperInfo, /*clamp*/true);
164 ASSERT_EQ(res, OK);
165
166 for (size_t i = 0; i < rects.size(); i++) {
167 EXPECT_EQ(rects[i], rectsOrig[i]);
168 }
169 }
170
TEST(DistortionMapperTest,ClampConsistency)171 TEST(DistortionMapperTest, ClampConsistency) {
172 status_t res;
173
174 std::array<int32_t, 4> activeArray = {0, 0, 4032, 3024};
175 DistortionMapper m;
176 setupTestMapper(&m, identityDistortion, testICal, /*activeArray*/ activeArray.data(),
177 /*preCorrectionActiveArray*/ activeArray.data());
178
179 auto rectsOrig = activeArray;
180 DistortionMapperInfo *mapperInfo = m.getMapperInfo();
181 res = m.mapCorrectedRectToRaw(activeArray.data(), 1, mapperInfo, /*clamp*/true,
182 /*simple*/ true);
183 ASSERT_EQ(res, OK);
184
185 for (size_t i = 0; i < activeArray.size(); i++) {
186 EXPECT_EQ(activeArray[i], rectsOrig[i]);
187 }
188
189 res = m.mapRawRectToCorrected(activeArray.data(), 1, mapperInfo, /*clamp*/true,
190 /*simple*/ true);
191 ASSERT_EQ(res, OK);
192
193 for (size_t i = 0; i < activeArray.size(); i++) {
194 EXPECT_EQ(activeArray[i], rectsOrig[i]);
195 }
196 }
197
TEST(DistortionMapperTest,SimpleTransform)198 TEST(DistortionMapperTest, SimpleTransform) {
199 status_t res;
200
201 DistortionMapper m;
202 setupTestMapper(&m, identityDistortion, testICal,
203 /*activeArray*/ testActiveArray,
204 /*preCorrectionActiveArray*/ testPreCorrActiveArray);
205
206 auto coords = basicCoords;
207 DistortionMapperInfo *mapperInfo = m.getMapperInfo();
208 res = m.mapCorrectedToRaw(coords.data(), 5, mapperInfo, /*clamp*/true, /*simple*/true);
209 ASSERT_EQ(res, OK);
210
211 ASSERT_EQ(coords[0], 0); ASSERT_EQ(coords[1], 0);
212 ASSERT_EQ(coords[2], testPreCorrActiveArray[2] - 1); ASSERT_EQ(coords[3], 0);
213 ASSERT_EQ(coords[4], testPreCorrActiveArray[2] - 1); ASSERT_EQ(coords[5], testPreCorrActiveArray[3] - 1);
214 ASSERT_EQ(coords[6], 0); ASSERT_EQ(coords[7], testPreCorrActiveArray[3] - 1);
215 ASSERT_EQ(coords[8], testPreCorrActiveArray[2] / 2); ASSERT_EQ(coords[9], testPreCorrActiveArray[3] / 2);
216 }
217
218
RandomTransformTest(::testing::Test * test,int32_t * activeArray,DistortionMapper & m,bool clamp,bool simple)219 void RandomTransformTest(::testing::Test *test,
220 int32_t* activeArray, DistortionMapper &m, bool clamp, bool simple) {
221 status_t res;
222 constexpr int maxAllowedPixelError = 2; // Maximum per-pixel error allowed
223 constexpr int bucketsPerPixel = 3; // Histogram granularity
224
225 unsigned int seed = 1234; // Ensure repeatability for debugging
226 const size_t coordCount = 1e5; // Number of random test points
227
228 std::default_random_engine gen(seed);
229
230 std::uniform_int_distribution<int> x_dist(0, activeArray[2] - 1);
231 std::uniform_int_distribution<int> y_dist(0, activeArray[3] - 1);
232
233 std::vector<int32_t> randCoords(coordCount * 2);
234
235 for (size_t i = 0; i < randCoords.size(); i += 2) {
236 randCoords[i] = x_dist(gen);
237 randCoords[i + 1] = y_dist(gen);
238 }
239
240 randCoords.insert(randCoords.end(), basicCoords.begin(), basicCoords.end());
241
242 auto origCoords = randCoords;
243
244 base::Timer correctedToRawTimer;
245 DistortionMapperInfo *mapperInfo = m.getMapperInfo();
246 res = m.mapCorrectedToRaw(randCoords.data(), randCoords.size() / 2, mapperInfo, clamp, simple);
247 auto correctedToRawDurationMs = correctedToRawTimer.duration();
248 EXPECT_EQ(res, OK);
249
250 base::Timer rawToCorrectedTimer;
251 res = m.mapRawToCorrected(randCoords.data(), randCoords.size() / 2, mapperInfo, clamp, simple);
252 auto rawToCorrectedDurationMs = rawToCorrectedTimer.duration();
253 EXPECT_EQ(res, OK);
254
255 float correctedToRawDurationPerCoordUs =
256 (std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(
257 correctedToRawDurationMs) / (randCoords.size() / 2) ).count();
258 float rawToCorrectedDurationPerCoordUs =
259 (std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(
260 rawToCorrectedDurationMs) / (randCoords.size() / 2) ).count();
261
262 test->RecordProperty("CorrectedToRawDurationPerCoordUs",
263 base::StringPrintf("%f", correctedToRawDurationPerCoordUs));
264 test->RecordProperty("RawToCorrectedDurationPerCoordUs",
265 base::StringPrintf("%f", rawToCorrectedDurationPerCoordUs));
266
267 // Calculate mapping errors after round trip
268 float totalErrorSq = 0;
269 // Basic histogram; buckets go from [N to N+1)
270 std::array<int, maxAllowedPixelError * bucketsPerPixel> histogram = {0};
271 int outOfHistogram = 0;
272
273 for (size_t i = 0; i < randCoords.size(); i += 2) {
274 int xOrig = origCoords[i];
275 int yOrig = origCoords[i + 1];
276 int xMapped = randCoords[i];
277 int yMapped = randCoords[i + 1];
278
279 float errorSq = (xMapped - xOrig) * (xMapped - xOrig) +
280 (yMapped - yOrig) * (yMapped - yOrig);
281
282 EXPECT_LE(errorSq, maxAllowedPixelError * maxAllowedPixelError) << "( " <<
283 xOrig << "," << yOrig << ") -> (" << xMapped << "," << yMapped << ")";
284
285 // Note: Integer coordinates, so histogram will be clumpy; error distances can only be of
286 // form sqrt(X^2+Y^2) where X, Y are integers, so:
287 // 0, 1, sqrt(2), 2, sqrt(5), sqrt(8), 3, sqrt(10), sqrt(13), 4 ...
288 totalErrorSq += errorSq;
289 float errorDist = std::sqrt(errorSq);
290 if (errorDist < maxAllowedPixelError) {
291 int histBucket = static_cast<int>(errorDist * bucketsPerPixel); // rounds down
292 histogram[histBucket]++;
293 } else {
294 outOfHistogram++;
295 }
296 }
297
298 float rmsError = std::sqrt(totalErrorSq / randCoords.size());
299 test->RecordProperty("RmsError", base::StringPrintf("%f", rmsError));
300 for (size_t i = 0; i < histogram.size(); i++) {
301 std::string label = base::StringPrintf("HistogramBin[%f,%f)",
302 (float)i/bucketsPerPixel, (float)(i + 1)/bucketsPerPixel);
303 test->RecordProperty(label, histogram[i]);
304 }
305 test->RecordProperty("HistogramOutOfRange", outOfHistogram);
306 }
307
308 // Test a realistic distortion function with matching calibration values, enforcing
309 // clamping.
TEST(DistortionMapperTest,DISABLED_SmallTransform)310 TEST(DistortionMapperTest, DISABLED_SmallTransform) {
311 int32_t activeArray[] = {0, 8, 3278, 2450};
312 int32_t preCorrectionActiveArray[] = {0, 0, 3280, 2464};
313
314 float distortion[] = {0.06875723, -0.13922249, 0.02818312, -0.00032781, -0.00025431};
315 float intrinsics[] = {1812.50000000, 1812.50000000, 1645.59533691, 1229.23229980, 0.00000000};
316
317 DistortionMapper m;
318 setupTestMapper(&m, distortion, intrinsics, activeArray, preCorrectionActiveArray);
319
320 RandomTransformTest(this, activeArray, m, /*clamp*/true, /*simple*/false);
321 }
322
323 // Test a realistic distortion function with matching calibration values, enforcing
324 // clamping, but using the simple linear transform
TEST(DistortionMapperTest,SmallSimpleTransform)325 TEST(DistortionMapperTest, SmallSimpleTransform) {
326 int32_t activeArray[] = {0, 8, 3278, 2450};
327 int32_t preCorrectionActiveArray[] = {0, 0, 3280, 2464};
328
329 float distortion[] = {0.06875723, -0.13922249, 0.02818312, -0.00032781, -0.00025431};
330 float intrinsics[] = {1812.50000000, 1812.50000000, 1645.59533691, 1229.23229980, 0.00000000};
331
332 DistortionMapper m;
333 setupTestMapper(&m, distortion, intrinsics, activeArray, preCorrectionActiveArray);
334
335 RandomTransformTest(this, activeArray, m, /*clamp*/true, /*simple*/true);
336 }
337
338 // Test a very large distortion function; the regions aren't valid for such a big transform,
339 // so disable clamping. This test is just to verify round-trip math accuracy for big transforms
TEST(DistortionMapperTest,LargeTransform)340 TEST(DistortionMapperTest, LargeTransform) {
341 float bigDistortion[] = {0.1, -0.003, 0.004, 0.02, 0.01};
342
343 DistortionMapper m;
344 setupTestMapper(&m, bigDistortion, testICal,
345 /*activeArray*/testActiveArray,
346 /*preCorrectionActiveArray*/testPreCorrActiveArray);
347
348 RandomTransformTest(this, testActiveArray, m, /*clamp*/false, /*simple*/false);
349 }
350
351 // Compare against values calculated by OpenCV
352 // undistortPoints() method, which is the same as mapRawToCorrected
353 // Ignore clamping
354 // See script DistortionMapperComp.py
355 #include "DistortionMapperTest_OpenCvData.h"
356
TEST(DistortionMapperTest,CompareToOpenCV)357 TEST(DistortionMapperTest, CompareToOpenCV) {
358 float bigDistortion[] = {0.1, -0.003, 0.004, 0.02, 0.01};
359
360 // Expect to match within sqrt(2) radius pixels
361 const int32_t maxSqError = 2;
362
363 DistortionMapper m;
364 setupTestMapper(&m, bigDistortion, testICal,
365 /*activeArray*/testActiveArray,
366 /*preCorrectionActiveArray*/testActiveArray);
367
368 using namespace openCvData;
369
370 DistortionMapperInfo *mapperInfo = m.getMapperInfo();
371 m.mapRawToCorrected(rawCoords.data(), rawCoords.size() / 2, mapperInfo, /*clamp*/false,
372 /*simple*/false);
373
374 for (size_t i = 0; i < rawCoords.size(); i+=2) {
375 int32_t dist = (rawCoords[i] - expCoords[i]) * (rawCoords[i] - expCoords[i]) +
376 (rawCoords[i + 1] - expCoords[i + 1]) * (rawCoords[i + 1] - expCoords[i + 1]);
377 EXPECT_LE(dist, maxSqError)
378 << "(" << rawCoords[i] << ", " << rawCoords[i + 1] << ") != ("
379 << expCoords[i] << ", " << expCoords[i + 1] << ")";
380 }
381 }
382