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