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
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 res = m.mapCorrectedToRaw(coords.data(), 5, /*clamp*/true);
136 ASSERT_EQ(res, OK);
137
138 for (size_t i = 0; i < coords.size(); i++) {
139 EXPECT_EQ(coords[i], basicCoords[i]);
140 }
141
142 res = m.mapRawToCorrected(coords.data(), 5, /*clamp*/true);
143 ASSERT_EQ(res, OK);
144
145 for (size_t i = 0; i < coords.size(); i++) {
146 EXPECT_EQ(coords[i], basicCoords[i]);
147 }
148
149 std::array<int32_t, 8> rects = {
150 0, 0, 100, 100,
151 testActiveArray[2] - 101, testActiveArray[3] - 101, 100, 100
152 };
153
154 auto rectsOrig = rects;
155 res = m.mapCorrectedRectToRaw(rects.data(), 2, /*clamp*/true);
156 ASSERT_EQ(res, OK);
157
158 for (size_t i = 0; i < rects.size(); i++) {
159 EXPECT_EQ(rects[i], rectsOrig[i]);
160 }
161
162 res = m.mapRawRectToCorrected(rects.data(), 2, /*clamp*/true);
163 ASSERT_EQ(res, OK);
164
165 for (size_t i = 0; i < rects.size(); i++) {
166 EXPECT_EQ(rects[i], rectsOrig[i]);
167 }
168 }
169
TEST(DistortionMapperTest,ClampConsistency)170 TEST(DistortionMapperTest, ClampConsistency) {
171 status_t res;
172
173 std::array<int32_t, 4> activeArray = {0, 0, 4032, 3024};
174 DistortionMapper m;
175 setupTestMapper(&m, identityDistortion, testICal, /*activeArray*/ activeArray.data(),
176 /*preCorrectionActiveArray*/ activeArray.data());
177
178 auto rectsOrig = activeArray;
179 res = m.mapCorrectedRectToRaw(activeArray.data(), 1, /*clamp*/true, /*simple*/ true);
180 ASSERT_EQ(res, OK);
181
182 for (size_t i = 0; i < activeArray.size(); i++) {
183 EXPECT_EQ(activeArray[i], rectsOrig[i]);
184 }
185
186 res = m.mapRawRectToCorrected(activeArray.data(), 1, /*clamp*/true, /*simple*/ true);
187 ASSERT_EQ(res, OK);
188
189 for (size_t i = 0; i < activeArray.size(); i++) {
190 EXPECT_EQ(activeArray[i], rectsOrig[i]);
191 }
192 }
193
TEST(DistortionMapperTest,SimpleTransform)194 TEST(DistortionMapperTest, SimpleTransform) {
195 status_t res;
196
197 DistortionMapper m;
198 setupTestMapper(&m, identityDistortion, testICal,
199 /*activeArray*/ testActiveArray,
200 /*preCorrectionActiveArray*/ testPreCorrActiveArray);
201
202 auto coords = basicCoords;
203 res = m.mapCorrectedToRaw(coords.data(), 5, /*clamp*/true, /*simple*/true);
204 ASSERT_EQ(res, OK);
205
206 ASSERT_EQ(coords[0], 0); ASSERT_EQ(coords[1], 0);
207 ASSERT_EQ(coords[2], testPreCorrActiveArray[2] - 1); ASSERT_EQ(coords[3], 0);
208 ASSERT_EQ(coords[4], testPreCorrActiveArray[2] - 1); ASSERT_EQ(coords[5], testPreCorrActiveArray[3] - 1);
209 ASSERT_EQ(coords[6], 0); ASSERT_EQ(coords[7], testPreCorrActiveArray[3] - 1);
210 ASSERT_EQ(coords[8], testPreCorrActiveArray[2] / 2); ASSERT_EQ(coords[9], testPreCorrActiveArray[3] / 2);
211 }
212
213
RandomTransformTest(::testing::Test * test,int32_t * activeArray,DistortionMapper & m,bool clamp,bool simple)214 void RandomTransformTest(::testing::Test *test,
215 int32_t* activeArray, DistortionMapper &m, bool clamp, bool simple) {
216 status_t res;
217 constexpr int maxAllowedPixelError = 2; // Maximum per-pixel error allowed
218 constexpr int bucketsPerPixel = 3; // Histogram granularity
219
220 unsigned int seed = 1234; // Ensure repeatability for debugging
221 const size_t coordCount = 1e5; // Number of random test points
222
223 std::default_random_engine gen(seed);
224
225 std::uniform_int_distribution<int> x_dist(0, activeArray[2] - 1);
226 std::uniform_int_distribution<int> y_dist(0, activeArray[3] - 1);
227
228 std::vector<int32_t> randCoords(coordCount * 2);
229
230 for (size_t i = 0; i < randCoords.size(); i += 2) {
231 randCoords[i] = x_dist(gen);
232 randCoords[i + 1] = y_dist(gen);
233 }
234
235 randCoords.insert(randCoords.end(), basicCoords.begin(), basicCoords.end());
236
237 auto origCoords = randCoords;
238
239 base::Timer correctedToRawTimer;
240 res = m.mapCorrectedToRaw(randCoords.data(), randCoords.size() / 2, clamp, simple);
241 auto correctedToRawDurationMs = correctedToRawTimer.duration();
242 EXPECT_EQ(res, OK);
243
244 base::Timer rawToCorrectedTimer;
245 res = m.mapRawToCorrected(randCoords.data(), randCoords.size() / 2, clamp, simple);
246 auto rawToCorrectedDurationMs = rawToCorrectedTimer.duration();
247 EXPECT_EQ(res, OK);
248
249 float correctedToRawDurationPerCoordUs =
250 (std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(
251 correctedToRawDurationMs) / (randCoords.size() / 2) ).count();
252 float rawToCorrectedDurationPerCoordUs =
253 (std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(
254 rawToCorrectedDurationMs) / (randCoords.size() / 2) ).count();
255
256 test->RecordProperty("CorrectedToRawDurationPerCoordUs",
257 base::StringPrintf("%f", correctedToRawDurationPerCoordUs));
258 test->RecordProperty("RawToCorrectedDurationPerCoordUs",
259 base::StringPrintf("%f", rawToCorrectedDurationPerCoordUs));
260
261 // Calculate mapping errors after round trip
262 float totalErrorSq = 0;
263 // Basic histogram; buckets go from [N to N+1)
264 std::array<int, maxAllowedPixelError * bucketsPerPixel> histogram = {0};
265 int outOfHistogram = 0;
266
267 for (size_t i = 0; i < randCoords.size(); i += 2) {
268 int xOrig = origCoords[i];
269 int yOrig = origCoords[i + 1];
270 int xMapped = randCoords[i];
271 int yMapped = randCoords[i + 1];
272
273 float errorSq = (xMapped - xOrig) * (xMapped - xOrig) +
274 (yMapped - yOrig) * (yMapped - yOrig);
275
276 EXPECT_LE(errorSq, maxAllowedPixelError * maxAllowedPixelError) << "( " <<
277 xOrig << "," << yOrig << ") -> (" << xMapped << "," << yMapped << ")";
278
279 // Note: Integer coordinates, so histogram will be clumpy; error distances can only be of
280 // form sqrt(X^2+Y^2) where X, Y are integers, so:
281 // 0, 1, sqrt(2), 2, sqrt(5), sqrt(8), 3, sqrt(10), sqrt(13), 4 ...
282 totalErrorSq += errorSq;
283 float errorDist = std::sqrt(errorSq);
284 if (errorDist < maxAllowedPixelError) {
285 int histBucket = static_cast<int>(errorDist * bucketsPerPixel); // rounds down
286 histogram[histBucket]++;
287 } else {
288 outOfHistogram++;
289 }
290 }
291
292 float rmsError = std::sqrt(totalErrorSq / randCoords.size());
293 test->RecordProperty("RmsError", base::StringPrintf("%f", rmsError));
294 for (size_t i = 0; i < histogram.size(); i++) {
295 std::string label = base::StringPrintf("HistogramBin[%f,%f)",
296 (float)i/bucketsPerPixel, (float)(i + 1)/bucketsPerPixel);
297 test->RecordProperty(label, histogram[i]);
298 }
299 test->RecordProperty("HistogramOutOfRange", outOfHistogram);
300 }
301
302 // Test a realistic distortion function with matching calibration values, enforcing
303 // clamping.
TEST(DistortionMapperTest,DISABLED_SmallTransform)304 TEST(DistortionMapperTest, DISABLED_SmallTransform) {
305 int32_t activeArray[] = {0, 8, 3278, 2450};
306 int32_t preCorrectionActiveArray[] = {0, 0, 3280, 2464};
307
308 float distortion[] = {0.06875723, -0.13922249, 0.02818312, -0.00032781, -0.00025431};
309 float intrinsics[] = {1812.50000000, 1812.50000000, 1645.59533691, 1229.23229980, 0.00000000};
310
311 DistortionMapper m;
312 setupTestMapper(&m, distortion, intrinsics, activeArray, preCorrectionActiveArray);
313
314 RandomTransformTest(this, activeArray, m, /*clamp*/true, /*simple*/false);
315 }
316
317 // Test a realistic distortion function with matching calibration values, enforcing
318 // clamping, but using the simple linear transform
TEST(DistortionMapperTest,SmallSimpleTransform)319 TEST(DistortionMapperTest, SmallSimpleTransform) {
320 int32_t activeArray[] = {0, 8, 3278, 2450};
321 int32_t preCorrectionActiveArray[] = {0, 0, 3280, 2464};
322
323 float distortion[] = {0.06875723, -0.13922249, 0.02818312, -0.00032781, -0.00025431};
324 float intrinsics[] = {1812.50000000, 1812.50000000, 1645.59533691, 1229.23229980, 0.00000000};
325
326 DistortionMapper m;
327 setupTestMapper(&m, distortion, intrinsics, activeArray, preCorrectionActiveArray);
328
329 RandomTransformTest(this, activeArray, m, /*clamp*/true, /*simple*/true);
330 }
331
332 // Test a very large distortion function; the regions aren't valid for such a big transform,
333 // so disable clamping. This test is just to verify round-trip math accuracy for big transforms
TEST(DistortionMapperTest,LargeTransform)334 TEST(DistortionMapperTest, LargeTransform) {
335 float bigDistortion[] = {0.1, -0.003, 0.004, 0.02, 0.01};
336
337 DistortionMapper m;
338 setupTestMapper(&m, bigDistortion, testICal,
339 /*activeArray*/testActiveArray,
340 /*preCorrectionActiveArray*/testPreCorrActiveArray);
341
342 RandomTransformTest(this, testActiveArray, m, /*clamp*/false, /*simple*/false);
343 }
344
345 // Compare against values calculated by OpenCV
346 // undistortPoints() method, which is the same as mapRawToCorrected
347 // Ignore clamping
348 // See script DistortionMapperComp.py
349 #include "DistortionMapperTest_OpenCvData.h"
350
TEST(DistortionMapperTest,CompareToOpenCV)351 TEST(DistortionMapperTest, CompareToOpenCV) {
352 status_t res;
353
354 float bigDistortion[] = {0.1, -0.003, 0.004, 0.02, 0.01};
355
356 // Expect to match within sqrt(2) radius pixels
357 const int32_t maxSqError = 2;
358
359 DistortionMapper m;
360 setupTestMapper(&m, bigDistortion, testICal,
361 /*activeArray*/testActiveArray,
362 /*preCorrectionActiveArray*/testActiveArray);
363
364 using namespace openCvData;
365
366 res = m.mapRawToCorrected(rawCoords.data(), rawCoords.size() / 2, /*clamp*/false,
367 /*simple*/false);
368
369 for (size_t i = 0; i < rawCoords.size(); i+=2) {
370 int32_t dist = (rawCoords[i] - expCoords[i]) * (rawCoords[i] - expCoords[i]) +
371 (rawCoords[i + 1] - expCoords[i + 1]) * (rawCoords[i + 1] - expCoords[i + 1]);
372 EXPECT_LE(dist, maxSqError)
373 << "(" << rawCoords[i] << ", " << rawCoords[i + 1] << ") != ("
374 << expCoords[i] << ", " << expCoords[i + 1] << ")";
375 }
376 }
377