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 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 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 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 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 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