• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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