Home | History | Annotate | Download | only in tests
      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