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 
34 float testICal[] = { 1000.f, 1000.f, 500.f, 500.f, 0.f };
35 
36 float identityDistortion[] = { 0.f, 0.f, 0.f, 0.f, 0.f};
37 
38 std::array<int32_t, 12> basicCoords = {
39     0, 0,
40     testActiveArray[2] - 1, 0,
41     testActiveArray[2] - 1,  testActiveArray[3] - 1,
42     0, testActiveArray[3] - 1,
43     testActiveArray[2] / 2, testActiveArray[3] / 2,
44     251, 403  // A particularly bad coordinate for current grid count/array size
45 };
46 
47 
setupTestMapper(DistortionMapper * m,float distortion[5])48 void setupTestMapper(DistortionMapper *m, float distortion[5]) {
49     CameraMetadata deviceInfo;
50 
51     deviceInfo.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
52             testActiveArray, 4);
53 
54     deviceInfo.update(ANDROID_LENS_INTRINSIC_CALIBRATION,
55             testICal, 5);
56 
57     deviceInfo.update(ANDROID_LENS_DISTORTION,
58             distortion, 5);
59 
60     m->setupStaticInfo(deviceInfo);
61 }
62 
TEST(DistortionMapperTest,Initialization)63 TEST(DistortionMapperTest, Initialization) {
64     CameraMetadata deviceInfo;
65 
66     ASSERT_FALSE(DistortionMapper::isDistortionSupported(deviceInfo));
67 
68     uint8_t distortionModes[] =
69             {ANDROID_DISTORTION_CORRECTION_MODE_OFF,
70              ANDROID_DISTORTION_CORRECTION_MODE_FAST,
71              ANDROID_DISTORTION_CORRECTION_MODE_HIGH_QUALITY};
72 
73     deviceInfo.update(ANDROID_DISTORTION_CORRECTION_AVAILABLE_MODES,
74             distortionModes, 1);
75 
76     ASSERT_FALSE(DistortionMapper::isDistortionSupported(deviceInfo));
77 
78     deviceInfo.update(ANDROID_DISTORTION_CORRECTION_AVAILABLE_MODES,
79             distortionModes, 3);
80 
81     ASSERT_TRUE(DistortionMapper::isDistortionSupported(deviceInfo));
82 
83     DistortionMapper m;
84 
85     ASSERT_FALSE(m.calibrationValid());
86 
87     ASSERT_NE(m.setupStaticInfo(deviceInfo), OK);
88 
89     ASSERT_FALSE(m.calibrationValid());
90 
91     deviceInfo.update(ANDROID_SENSOR_INFO_PRE_CORRECTION_ACTIVE_ARRAY_SIZE,
92             testActiveArray, 4);
93 
94     deviceInfo.update(ANDROID_LENS_INTRINSIC_CALIBRATION,
95             testICal, 5);
96 
97     deviceInfo.update(ANDROID_LENS_DISTORTION,
98             identityDistortion, 5);
99 
100     ASSERT_EQ(m.setupStaticInfo(deviceInfo), OK);
101 
102     ASSERT_TRUE(m.calibrationValid());
103 
104     CameraMetadata captureResult;
105 
106     ASSERT_NE(m.updateCalibration(captureResult), OK);
107 
108     captureResult.update(ANDROID_LENS_INTRINSIC_CALIBRATION,
109             testICal, 5);
110     captureResult.update(ANDROID_LENS_DISTORTION,
111             identityDistortion, 5);
112 
113     ASSERT_EQ(m.updateCalibration(captureResult), OK);
114 
115 }
116 
TEST(DistortionMapperTest,IdentityTransform)117 TEST(DistortionMapperTest, IdentityTransform) {
118     status_t res;
119 
120     DistortionMapper m;
121     setupTestMapper(&m, identityDistortion);
122 
123     auto coords = basicCoords;
124     res = m.mapCorrectedToRaw(coords.data(), 5);
125     ASSERT_EQ(res, OK);
126 
127     for (size_t i = 0; i < coords.size(); i++) {
128         EXPECT_EQ(coords[i], basicCoords[i]);
129     }
130 
131     res = m.mapRawToCorrected(coords.data(), 5);
132     ASSERT_EQ(res, OK);
133 
134     for (size_t i = 0; i < coords.size(); i++) {
135         EXPECT_EQ(coords[i], basicCoords[i]);
136     }
137 
138     std::array<int32_t, 8> rects = {
139         0, 0, 100, 100,
140         testActiveArray[2] - 100, testActiveArray[3]-100, 100, 100
141     };
142 
143     auto rectsOrig = rects;
144     res = m.mapCorrectedRectToRaw(rects.data(), 2);
145     ASSERT_EQ(res, OK);
146 
147     for (size_t i = 0; i < rects.size(); i++) {
148         EXPECT_EQ(rects[i], rectsOrig[i]);
149     }
150 
151     res = m.mapRawRectToCorrected(rects.data(), 2);
152     ASSERT_EQ(res, OK);
153 
154     for (size_t i = 0; i < rects.size(); i++) {
155         EXPECT_EQ(rects[i], rectsOrig[i]);
156     }
157 }
158 
TEST(DistortionMapperTest,LargeTransform)159 TEST(DistortionMapperTest, LargeTransform) {
160     status_t res;
161     constexpr int maxAllowedPixelError = 2; // Maximum per-pixel error allowed
162     constexpr int bucketsPerPixel = 3; // Histogram granularity
163 
164     unsigned int seed = 1234; // Ensure repeatability for debugging
165     const size_t coordCount = 1e6; // Number of random test points
166 
167     float bigDistortion[] = {0.1, -0.003, 0.004, 0.02, 0.01};
168 
169     DistortionMapper m;
170     setupTestMapper(&m, bigDistortion);
171 
172     std::default_random_engine gen(seed);
173 
174     std::uniform_int_distribution<int> x_dist(0, testActiveArray[2] - 1);
175     std::uniform_int_distribution<int> y_dist(0, testActiveArray[3] - 1);
176 
177     std::vector<int32_t> randCoords(coordCount * 2);
178 
179     for (size_t i = 0; i < randCoords.size(); i += 2) {
180         randCoords[i] = x_dist(gen);
181         randCoords[i + 1] = y_dist(gen);
182     }
183 
184     randCoords.insert(randCoords.end(), basicCoords.begin(), basicCoords.end());
185 
186     auto origCoords = randCoords;
187 
188     base::Timer correctedToRawTimer;
189     res = m.mapCorrectedToRaw(randCoords.data(), randCoords.size() / 2);
190     auto correctedToRawDurationMs = correctedToRawTimer.duration();
191     EXPECT_EQ(res, OK);
192 
193     base::Timer rawToCorrectedTimer;
194     res = m.mapRawToCorrected(randCoords.data(), randCoords.size() / 2);
195     auto rawToCorrectedDurationMs = rawToCorrectedTimer.duration();
196     EXPECT_EQ(res, OK);
197 
198     float correctedToRawDurationPerCoordUs =
199             (std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(
200                 correctedToRawDurationMs) / (randCoords.size() / 2) ).count();
201     float rawToCorrectedDurationPerCoordUs =
202             (std::chrono::duration_cast<std::chrono::duration<double, std::micro>>(
203                 rawToCorrectedDurationMs) / (randCoords.size() / 2) ).count();
204 
205     RecordProperty("CorrectedToRawDurationPerCoordUs",
206             base::StringPrintf("%f", correctedToRawDurationPerCoordUs));
207     RecordProperty("RawToCorrectedDurationPerCoordUs",
208             base::StringPrintf("%f", rawToCorrectedDurationPerCoordUs));
209 
210     // Calculate mapping errors after round trip
211     float totalErrorSq = 0;
212     // Basic histogram; buckets go from [N to N+1)
213     std::array<int, maxAllowedPixelError * bucketsPerPixel> histogram = {0};
214     int outOfHistogram = 0;
215 
216     for (size_t i = 0; i < randCoords.size(); i += 2) {
217         int xOrig = origCoords[i];
218         int yOrig = origCoords[i + 1];
219         int xMapped = randCoords[i];
220         int yMapped = randCoords[i + 1];
221 
222         float errorSq = (xMapped - xOrig) * (xMapped - xOrig) +
223                 (yMapped - yOrig) * (yMapped - yOrig);
224 
225         EXPECT_LE(errorSq, maxAllowedPixelError * maxAllowedPixelError) << "( " <<
226                 xOrig << "," << yOrig << ") -> (" << xMapped << "," << yMapped << ")";
227 
228         // Note: Integer coordinates, so histogram will be clumpy; error distances can only be of
229         // form sqrt(X^2+Y^2) where X, Y are integers, so:
230         //    0, 1, sqrt(2), 2, sqrt(5), sqrt(8), 3, sqrt(10), sqrt(13), 4 ...
231         totalErrorSq += errorSq;
232         float errorDist = std::sqrt(errorSq);
233         if (errorDist < maxAllowedPixelError) {
234             int histBucket = static_cast<int>(errorDist * bucketsPerPixel); // rounds down
235             histogram[histBucket]++;
236         } else {
237             outOfHistogram++;
238         }
239     }
240 
241     float rmsError = std::sqrt(totalErrorSq / randCoords.size());
242     RecordProperty("RmsError", base::StringPrintf("%f", rmsError));
243     for (size_t i = 0; i < histogram.size(); i++) {
244         std::string label = base::StringPrintf("HistogramBin[%f,%f)",
245                 (float)i/bucketsPerPixel, (float)(i + 1)/bucketsPerPixel);
246         RecordProperty(label, histogram[i]);
247     }
248     RecordProperty("HistogramOutOfRange", outOfHistogram);
249 }
250 
251 // Compare against values calculated by OpenCV
252 // undistortPoints() method, which is the same as mapRawToCorrected
253 // See script DistortionMapperComp.py
254 #include "DistortionMapperTest_OpenCvData.h"
255 
TEST(DistortionMapperTest,CompareToOpenCV)256 TEST(DistortionMapperTest, CompareToOpenCV) {
257     status_t res;
258 
259     float bigDistortion[] = {0.1, -0.003, 0.004, 0.02, 0.01};
260 
261     // Expect to match within sqrt(2) radius pixels
262     const int32_t maxSqError = 2;
263 
264     DistortionMapper m;
265     setupTestMapper(&m, bigDistortion);
266 
267     using namespace openCvData;
268 
269     res = m.mapRawToCorrected(rawCoords.data(), rawCoords.size() / 2);
270 
271     for (size_t i = 0; i < rawCoords.size(); i+=2) {
272         int32_t dist = (rawCoords[i] - expCoords[i]) * (rawCoords[i] - expCoords[i]) +
273                (rawCoords[i + 1] - expCoords[i + 1]) * (rawCoords[i + 1] - expCoords[i + 1]);
274         EXPECT_LE(dist, maxSqError)
275                 << "(" << rawCoords[i] << ", " << rawCoords[i + 1] << ") != ("
276                 << expCoords[i] << ", " << expCoords[i + 1] << ")";
277     }
278 }
279