00001
00002
00003
00004
00005 #if !defined(FCAM_VERSION)
00006 #define FCAM_VERSION "x.x.x"
00007 #endif
00008
00009 #include <FCam/FCam.h>
00010 #include "LinearAlgebra.h"
00011 #include <stdio.h>
00012 #include <unistd.h>
00013 #include <cmath>
00014
00015 void usage() {
00016 printf("fcamVignette\n"
00017 " (FCam version " FCAM_VERSION ")\n"
00018 " A simply utility to characterize camera vignetting for\n"
00019 " the FCam API.\n\n"
00020
00021 "Usage: fcamVignette [-o OUTPUT [-i INPUT]] FLAT1.dng FLAT2.dng ... FLATN.dng \n"
00022 " Takes in raw image(s) FLATX.dng and outputs polynomial coefficients for\n"
00023 " its vignetting function.\n"
00024 " The input image(s) need to be of a single color, uniformly illuminated\n"
00025 " object that fills the whole field of view. Examples are:\n"
00026 " 1. A white sheet of paper uniformly lit photographed straight-on.\n"
00027 " 2. Cloudless sky straight above (the horizon might have some color shifts).\n"
00028 "\n"
00029 " The image(s) should have no overexposed sections and the histogram.\n"
00030 " should be centered at about 1/2-2/3 max luminance.\n"
00031 " Multiple flat field images will be averaged together before estimation to reduce noise\n"
00032 "\n"
00033 " -o: Write out an adjusted .dng of the last flat field image\n"
00034 " -i: Use the specified DNG as the image to adjust instead of the last flat field\n"
00035 "\n");
00036 exit(1);
00037 }
00038
00039 int main(int argc, char **argv) {
00040 if (argc < 2) {
00041 usage();
00042 }
00043
00044 bool saveCorrected = false;
00045 bool useSourceFile = false;
00046 std::string outputFile;
00047 std::string sourceFile;
00048
00049 const char *options="o:i:";
00050
00051 int argChar;
00052 while ((argChar = getopt(argc,argv,options)) != -1) {
00053 switch (argChar) {
00054 case 'o':
00055 outputFile = std::string(optarg);
00056 saveCorrected = true;
00057 break;
00058 case 'i':
00059 sourceFile = std::string(optarg);
00060 useSourceFile = true;
00061 break;
00062 case '?':
00063 usage();
00064 break;
00065 }
00066 }
00067
00068 if (optind == argc) {
00069 printf("!! Error: No input file given!\n\n");
00070 usage();
00071 }
00072
00073
00074
00075 FCam::Image flatImg;
00076 FCam::Frame flatField;
00077
00078 int xOffset = 0;
00079 int yOffset = 0;
00080
00081 int inputs = 0;
00082 for (int i = optind; i < argc; i++) {
00083 inputs++;
00084
00085 std::string inputFile(argv[i]);
00086
00087 printf("Reading %s...\n", inputFile.c_str());
00088 flatField = FCam::loadDNG(inputFile);
00089
00090 FCam::Event e;
00091 if (getNextEvent(&e)) {
00092 printf(" Events reported while loading %s:\n", inputFile.c_str());
00093 do {
00094 switch (e.type) {
00095 case FCam::Event::Error:
00096 printf(" Error code: %d. Description: %s\n", e.data, e.description.c_str());
00097 break;
00098 case FCam::Event::Warning:
00099 printf(" Warning code: %d. Description: %s\n", e.data, e.description.c_str());
00100 break;
00101 default:
00102 printf(" Event type %d, code: %d. Description: %s\n", e.type, e.data, e.description.c_str());
00103 break;
00104 }
00105 } while (getNextEvent(&e));
00106 }
00107
00108 if (!flatField.valid()) {
00109 printf("!! Error: Unable to read input file %s\n", inputFile.c_str());
00110 exit(1);
00111 }
00112
00113
00114
00115 int w = flatField.image().width();
00116 int h = flatField.image().height();
00117
00118 FCam::Image curFlatImg;
00119 switch (flatField.platform().bayerPattern()) {
00120 case FCam::RGGB:
00121 curFlatImg = flatField.image().subImage(1, 0, FCam::Size(w, h));
00122 xOffset = 1;
00123 break;
00124 case FCam::BGGR:
00125 curFlatImg = flatField.image().subImage(0, 1, FCam::Size(w, h));
00126 yOffset = 1;
00127 break;
00128 case FCam::GRBG:
00129 curFlatImg = flatField.image();
00130 break;
00131 case FCam::GBRG:
00132 curFlatImg = flatField.image().subImage(1, 1, FCam::Size(w, h));
00133 xOffset = yOffset = 1;
00134 break;
00135 default:
00136 printf("Can't handle unknown or non-Bayer raw files. Sorry!\n");
00137 exit(1);
00138 break;
00139 }
00140
00141 if (!flatImg.valid()) {
00142 flatImg = curFlatImg;
00143 } else {
00144 if (flatImg.size() != curFlatImg.size()) {
00145 printf("!! Input images are of different sizes\n");
00146 exit(1);
00147 }
00148 for (unsigned int y=0; y < flatImg.height(); y++) {
00149 for (unsigned int x=0; x < flatImg.width(); x++) {
00150 *(unsigned short *)flatImg(x,y) += *(unsigned short *)curFlatImg(x,y);
00151 }
00152 }
00153 }
00154 }
00155
00156 int w = flatImg.width();
00157 int h = flatImg.height();
00158
00159 if (inputs > 1) {
00160 for (int y=0; y < h; y++) {
00161 for (int x=0; x < w; x++) {
00162 *(unsigned short *)flatImg(x,y) /= inputs;
00163 }
00164 }
00165 }
00166
00167
00168
00169 float saturationFraction = 0.9f;
00170 float darknessFraction = 0.1f;
00171
00172 int maxRaw = flatField.platform().maxRawValue();
00173 int minRaw = flatField.platform().minRawValue();
00174 int saturationCutoff = saturationFraction*(maxRaw-minRaw)+minRaw;
00175 int darknessCutoff = darknessFraction*(maxRaw-minRaw)+minRaw;
00176 int saturationCount = 0;
00177 int darknessCount = 0;
00178 int colAvgSum = 0;
00179 float colAvgBgRatioSum = 0;
00180 float colAvgBgRatioSqSum = 0;
00181 float colAvgRgRatioSum = 0;
00182 float colAvgRgRatioSqSum = 0;
00183
00184 for (int y =0; y < h; ++y) {
00185 int rowSum = 0;
00186 float bgRatioSum = 0;
00187 float bgRatioSqSum = 0;
00188 float rgRatioSum = 0;
00189 float rgRatioSqSum = 0;
00190
00191 int xStart = y % 2;
00192 for (int x =xStart; x < w; x=x+2) {
00193 rowSum += *(unsigned short *)flatImg(x,y) - minRaw;
00194 float green = *(unsigned short *)flatImg(x,y) - minRaw;
00195 if (green < 1) { green = 1; }
00196 if (y % 2) {
00197
00198 float blue = *(unsigned short *)flatImg(x-1,y) - minRaw;
00199 float bgRatio = blue/green;
00200 bgRatioSum += bgRatio;
00201 bgRatioSqSum += bgRatio*bgRatio;
00202 } else {
00203
00204 float red = *(unsigned short *)flatImg(x+1,y) - minRaw;
00205 float rgRatio = red/green;
00206 rgRatioSum += rgRatio;
00207 rgRatioSqSum += rgRatio*rgRatio;
00208 }
00209
00210 if (*(unsigned short *)flatImg(x,y) > saturationCutoff) { ++saturationCount; }
00211 if (*(unsigned short *)flatImg(x,y) < darknessCutoff) { ++darknessCount; }
00212 }
00213 colAvgSum += rowSum / (w/2);
00214 if (y % 2) {
00215
00216 colAvgBgRatioSum += bgRatioSum / (w/2);
00217 colAvgBgRatioSqSum += bgRatioSqSum / (w/2);
00218 } else {
00219
00220 colAvgRgRatioSum += rgRatioSum / (w/2);
00221 colAvgRgRatioSqSum += rgRatioSqSum / (w/2);
00222 }
00223 }
00224 int roughAvg = colAvgSum / h;
00225 float bgRatioAvg = colAvgBgRatioSum / (h/2);
00226 float bgVariance = colAvgBgRatioSqSum / (h/2) - bgRatioAvg*bgRatioAvg;
00227 float bgStdDev = std::sqrt(bgVariance);
00228 float rgRatioAvg = colAvgRgRatioSum / (h/2);
00229 float rgVariance = colAvgRgRatioSqSum / (h/2) - rgRatioAvg*rgRatioAvg;
00230 float rgStdDev = std::sqrt(rgVariance);
00231
00232 printf("Some statistics for the average input image:\n");
00233 printf(" %% Pixels more than %.0f%% of max raw value: %.2f\n", saturationFraction*100, 100*(float)saturationCount / (w*h));
00234 printf(" %% Pixels less than %.0f%% of max raw value: %.2f\n", darknessFraction*100, 100*(float)darknessCount / (w*h));
00235 printf(" Rough average value/max raw value: %d/%d (%f)\n", roughAvg, maxRaw-minRaw, (float)roughAvg/(maxRaw-minRaw));
00236 printf(" Blue-green ratio average: %.2f, standard deviation: %.2f\n", bgRatioAvg, bgStdDev);
00237 printf(" Red-green ratio average: %.2f, standard deviation: %.2f\n", rgRatioAvg, rgStdDev);
00238 printf("\n");
00239
00240 if (saturationCount * 20 > w*h) {
00241 printf("!! Warning: At least 5%% of the image is close to saturation - vignette fit may be poor\n");
00242 }
00243 if (saturationCount * 20 > w*h) {
00244 printf("!! Warning: At least 5%% of the image is close to black - vignette fit may be poor\n");
00245 }
00246 if (roughAvg < (maxRaw - minRaw)/4) {
00247 printf("!! Warning: Average green value is less than quarter of maximum - vignette fit may be poor\n");
00248 }
00249 if (bgStdDev > 0.2 || rgStdDev > 0.2) {
00250 printf("!! Warning: The image seems to have quite a bit of color tone variation - is it really an image of nothing but a solid color?\n");
00251 }
00252
00253
00254
00255
00256
00257
00258 printf("\nFinding vignetting parameters...\n");
00259
00260 ImageStack::LeastSquaresSolver<4, 1> centerFit;
00261 double coordNormalizer = 2.f/std::sqrt(w*w+h*h);
00262 double centerXn = w/2*coordNormalizer;
00263 double centerYn = h/2*coordNormalizer;
00264 for (int y =0; y < h; ++y) {
00265 for (int x = y % 2; x < w; x=x+2) {
00266 double coord[4];
00267 coord[0] = 1;
00268 coord[1] = x * coordNormalizer;
00269 coord[2] = y * coordNormalizer;
00270 coord[3] = coord[1]*coord[1]+coord[2]*coord[2];
00271
00272 double xn, yn, weight;
00273 xn = coord[1]-centerXn;
00274 yn = coord[2]-centerYn;
00275 weight = std::sqrt(xn*xn+yn*yn);
00276
00277 double p;
00278 p = *(unsigned short *)flatImg(x,y);
00279
00280 centerFit.addCorrespondence(coord, &p, weight);
00281 }
00282 }
00283
00284 double kC[4];
00285 if (!centerFit.solve(kC)) {
00286 printf("Error: Can't perform a least-squares fit on the image, for some odd reason.\n");
00287 exit(1);
00288 }
00289
00290 float cx = -kC[1]/(2*kC[3]*coordNormalizer);
00291 float cy = -kC[2]/(2*kC[3]*coordNormalizer);
00292
00293 printf("\nOptical center:\n (%.2f, %.2f)\n", cx + xOffset, cy + yOffset);
00294
00295
00296
00297
00298
00299
00300
00301 ImageStack::LeastSquaresSolver<6, 1> fullFit;
00302 double m_x = std::max(std::fabs(0-cx), std::fabs(w-1-cx));
00303 double m_y = std::max(std::fabs(0-cy), std::fabs(h-1-cy));
00304 double invMsq = 1/(m_x*m_x + m_y*m_y);
00305
00306 for (int y =0; y < h; ++y) {
00307 for (int x = y % 2; x < w; x=x+2) {
00308 double radPowers[6];
00309 radPowers[0] = 1;
00310 radPowers[1] = invMsq * ((x - cx)*(x - cx) + (y - cy)*(y - cy));
00311 radPowers[2] = radPowers[1]*radPowers[1];
00312 radPowers[3] = radPowers[2]*radPowers[1];
00313 radPowers[4] = radPowers[2]*radPowers[2];
00314 radPowers[5] = radPowers[3]*radPowers[2];
00315
00316 double weight;
00317 weight = radPowers[1];
00318
00319 double p;
00320 p = *(unsigned short *)flatImg(x,y);
00321
00322 fullFit.addCorrespondence(radPowers, &p, weight);
00323 }
00324 }
00325
00326 double k[6];
00327 if (!fullFit.solve(k)) {
00328 printf("Error: Can't perform a least-squares fit on the image, for some odd reason.\n");
00329 exit(1);
00330 }
00331
00332
00333
00334 k[1] = k[1]/k[0];
00335 k[2] = k[2]/k[0];
00336 k[3] = k[3]/k[0];
00337 k[4] = k[4]/k[0];
00338 k[5] = k[5]/k[0];
00339
00340 cx += xOffset;
00341 cy += yOffset;
00342
00343 printf("\nPolynomial fit:\n");
00344 printf(" 1 + %.4f r^2 + %.4f r^4 + %.4f r^6 + %.4f r^8 + %.4f r^10\n",
00345 k[1], k[2], k[3], k[4], k[5]);
00346
00347 printf("\nCoefficients (DNG spec notation):\n");
00348 printf(" cx_hat = %f\n", cx/(w-1));
00349 printf(" cy_hat = %f\n", cy/(h-1));
00350 printf(" k0 = %f\n", k[1]);
00351 printf(" k1 = %f\n", k[2]);
00352 printf(" k2 = %f\n", k[3]);
00353 printf(" k3 = %f\n", k[4]);
00354 printf(" k4 = %f\n", k[5]);
00355
00356 if (saveCorrected) {
00357 if (!useSourceFile) {
00358 sourceFile = std::string(argv[argc-1]);
00359 }
00360 printf("\nCorrecting input image %s, writing to %s\n", sourceFile.c_str(), outputFile.c_str());
00361 flatField = FCam::loadDNG(sourceFile);
00362
00363 FCam::Event e;
00364 if (getNextEvent(&e)) {
00365 printf(" Events reported while loading %s:\n", sourceFile.c_str());
00366 do {
00367 switch (e.type) {
00368 case FCam::Event::Error:
00369 printf(" Error code: %d. Description: %s\n", e.data, e.description.c_str());
00370 break;
00371 case FCam::Event::Warning:
00372 printf(" Warning code: %d. Description: %s\n", e.data, e.description.c_str());
00373 break;
00374 default:
00375 printf(" Event type %d, code: %d. Description: %s\n", e.type, e.data, e.description.c_str());
00376 break;
00377 }
00378 } while (getNextEvent(&e));
00379 }
00380
00381 if (!flatField.valid()) {
00382 printf("!! Error: Unable to read input file %s for correction\n", sourceFile.c_str());
00383 exit(1);
00384 }
00385
00386 for (int y=0; y < h; y++) {
00387 for (int x=0; x < w; x++) {
00388 float radPowers[6];
00389
00390 radPowers[1] = invMsq * ((x - cx)*(x - cx) + (y - cy)*(y - cy));
00391 radPowers[2] = radPowers[1]*radPowers[1];
00392 radPowers[3] = radPowers[2]*radPowers[1];
00393 radPowers[4] = radPowers[2]*radPowers[2];
00394 radPowers[5] = radPowers[3]*radPowers[2];
00395
00396 *(unsigned short *)flatField.image()(x,y) =
00397 (*(unsigned short *)flatField.image()(x,y) - flatField.platform().minRawValue())
00398 / (1 + k[1]*radPowers[1] + k[2]*radPowers[2]
00399 + k[3]*radPowers[3] + k[4]*radPowers[4]
00400 + k[5]*radPowers[5])
00401 + flatField.platform().minRawValue();
00402 }
00403 }
00404
00405 FCam::saveDNG(flatField, outputFile);
00406 }
00407
00408 printf("\n\nDone.\n");
00409 }