/** * @brief Create an HDR image or calibrate a response curve from a set * of differently exposed images supplied in PFS stream * * * This file is a part of PFS CALIBRATION package. * ---------------------------------------------------------------------- * Copyright (C) 2004 Grzegorz Krawczyk * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * ---------------------------------------------------------------------- * * @author Grzegorz Krawczyk, * * $Id: pfshdrcalibrate.cpp,v 1.6 2006/08/23 07:38:11 gkrawczyk Exp $ */ #include #include #include #include #include #include #include #include #include #include using namespace std; #define PROG_NAME "pfshdrcalibrate" inline float max3( float a, float b, float c ) { float max = (a>b) ? a : b; return (c>max) ? c : max; } inline float min3( float a, float b, float c ) { // ignore zero values if( int(a)==0 ) a=1e8; if( int(b)==0 ) b=1e8; if( int(c)==0 ) c=1e8; float min = (a] [--luminance]\n" "\t[--response ] [--response-file ] \n" "\t[--save-response ] \n" "\t[--multiplier ] \n" "\t[--bpp ] \n" "\t[--verbose] [--help]\n" "See man page for more information.\n" ); } void pfshdrcalibrate( int argc, char* argv[] ) { pfs::DOMIO pfsio; enum TCalibration { NONE, ROBERTSON, DEBEVEC } opt_calibration = ROBERTSON; enum TResponse { FROM_FILE, LINEAR, GAMMA, LOG10 } opt_response = LINEAR; float input_multiplier = 1.0f; FILE* responseFile = NULL; FILE* responseSaveFile = NULL; int opt_bpp = 8; bool opt_fillgaps = false; bool opt_luminance = false; float opt_gauss = 8.0f; int opt_maxresponse = -1; int opt_minresponse = -1; static struct option cmdLineOptions[] = { { "help", no_argument, NULL, 'h' }, { "verbose", no_argument, NULL, 'v' }, { "luminance", no_argument, NULL, 'Y' }, // { "fillin-response", no_argument, NULL, 'F' }, { "calibration", required_argument, NULL, 'c' }, { "gauss", required_argument, NULL, 'g' }, { "max-response", required_argument, NULL, 'A' }, { "min-response", required_argument, NULL, 'S' }, { "response", required_argument, NULL, 'r' }, { "response-file", required_argument, NULL, 'f' }, { "save-response", required_argument, NULL, 's' }, { "multiplier", required_argument, NULL, 'm' }, { "bpp", required_argument, NULL, 'b' }, { NULL, 0, NULL, 0 } }; int optionIndex = 0; while( 1 ) { int c = getopt_long (argc, argv, "hvYFc:g:r:f:s:m:b:S:A:", cmdLineOptions, &optionIndex); if( c == -1 ) break; switch( c ) { case 'h': printHelp(); throw QuietException(); case 'v': verbose = true; break; case 'Y': opt_luminance = true; break; case 'F': opt_fillgaps = true; break; case 'c': if( strcmp(optarg,"none")==0 ) opt_calibration = NONE; else if( strcmp(optarg,"robertson")==0 ) opt_calibration = ROBERTSON; else if( strcmp(optarg,"debevec")==0 ) opt_calibration = DEBEVEC; else throw pfs::Exception("unknown automatic self-calibration method"); break; case 'g': opt_gauss = atof(optarg); if( opt_gauss<=0.0f or opt_gauss>1024.0f) throw pfs::Exception("sigma value for Gaussian out of range. accepted range 0:1024"); break; case 'r': if( strcmp(optarg,"linear")==0 ) opt_response = LINEAR; else if( strcmp(optarg,"gamma")==0 ) opt_response = GAMMA; else if( strcmp(optarg,"log")==0 ) opt_response = LOG10; else throw pfs::Exception("unknown standard response (check the manpage or use default)"); break; case 'f': opt_response = FROM_FILE; opt_calibration = NONE; responseFile = fopen(optarg, "r"); if( !responseFile ) throw pfs::Exception("could not open file with response curve"); break; case 's': responseSaveFile = fopen(optarg,"w"); if( !responseSaveFile ) throw pfs::Exception("could not open file to save response curve"); break; case 'm': input_multiplier = atof(optarg); if( input_multiplier<=0.0f ) throw pfs::Exception("input multiplier value out of range. accepted range >0"); break; case 'b': opt_bpp = atoi(optarg); if( opt_bpp<8 || opt_bpp>32) throw pfs::Exception("bits per pixel value out of range. accepted range >=8"); break; case 'A': // max response opt_maxresponse = atoi(optarg); if( opt_maxresponse<=opt_minresponse ) throw pfs::Exception("max response should be higher than min response"); break; case 'S': // min response opt_minresponse = atoi(optarg); if( opt_minresponse<0 ) throw pfs::Exception("min response should be >0"); break; case '?': throw QuietException(); case ':': throw QuietException(); } } //!! FIX // in PFS streams, 8bit data are mapped to 0:1 range if( opt_bpp == 8 ) input_multiplier = 255.0f; //--- verbose information and load initialization data VERBOSE_STR << "calibrating channels: " << (opt_luminance ? "LUMINANCE" : "RGB") << endl; switch( opt_response ) { case FROM_FILE: VERBOSE_STR << "response curve from file" << endl; break; case LINEAR: VERBOSE_STR << "initial response: linear" << endl; break; case GAMMA: VERBOSE_STR << "initial response: gamma" << endl; break; case LOG10: VERBOSE_STR << "initial response: logarithmic" << endl; break; default: throw pfs::Exception("undefined standard response"); break; } switch( opt_calibration ) { case ROBERTSON: VERBOSE_STR << "automatic self-calibration method: robertson" << endl; VERBOSE_STR << "sigma for Gaussian: " << opt_gauss << endl; break; case DEBEVEC: VERBOSE_STR << "automatic self-calibration method: debevec" << endl; break; case NONE: VERBOSE_STR << "self-calibration disabled" << endl; break; default: throw pfs::Exception("calibration method not set or not supported"); } // VERBOSE_STR << "interpolate missing parts of response: " // << (opt_fillgaps ? "yes" : "no") << endl; if( responseSaveFile!=NULL ) VERBOSE_STR << "save response curve to a file (do not generate HDR image)" << endl; // number of input levels int M = (int) powf(2.0f,opt_bpp); VERBOSE_STR << "number of input levels: " << M << endl; VERBOSE_STR << "input multiplier: " << input_multiplier << endl; //--- read frames from pfs stream int frame_no = 1; int width=0, height=0, size; float minResponse = M; float maxResponse = 0.0f; // collected exposures ExposureList imgsY; ExposureList imgsR; ExposureList imgsG; ExposureList imgsB; while( true ) { pfs::Frame *frame = pfsio.readFrame( stdin ); if( frame == NULL ) break; // No more frames pfs::Channel *X, *Y, *Z; frame->getXYZChannels( X, Y, Z ); if( X==NULL || Y==NULL || Z==NULL ) throw pfs::Exception( "missing XYZ channels in the PFS stream (try to preview your files using pfsview)" ); const char* exp_str = frame->getTags()->getString("BV"); if( exp_str==NULL ) throw pfs::Exception( "missing exposure information in the PFS stream (use pfsinhdrgen to input files)" ); // relate APEX brightness value only as a function of exposure time // that is assume aperture=1 and sensitivity=1 float exp_time = 1.0f / powf(2.0f,atof( exp_str )); // absolute calibration: this magic multiplier is a result of my // research in internet plus a bit of tweaking with a luminance // meter. tested with canon 350d, so might be a bit off for other // cameras. to control absolute calibration modify iso values in // hdrgen script or use pfsabsolute program. exp_time /= 1.0592f * 11.4f / 3.125f; // frame size width = Y->getCols(); height = Y->getRows(); size = width*height; // new exposure image // in luminance only mode if( opt_luminance ) { Exposure eY; eY.ti = exp_time; eY.yi = new pfs::Array2DImpl(width,height); for( int i=0 ; i0.0f ) // discard zero values { maxResponse = (maxResponse>val) ? maxResponse : val; minResponse = (minResponsemaxval) ? maxResponse : maxval; minResponse = (minResponsecreateXYZChannels( Xj, Yj, Zj ); // !!! this currently does more bad than good, relevant command line // option is disabled if( opt_fillgaps ) { if( opt_luminance ) { int num = responseFillGaps(Iy,w,M); float perc = 100.0f*num/M; VERBOSE_STR << "interpolated " << perc << "% of the Y response curve..." << endl; } else { int numr = responseFillGaps(Ir,w,M); int numg = responseFillGaps(Ig,w,M); int numb = responseFillGaps(Ib,w,M); float perc = 100.0f*(numr+numb+numg)/(3*M); VERBOSE_STR << "interpolated " << perc << "% of the RGB response curve..." << endl; } } // calibration long sp = 0; // saturated pixels switch( opt_calibration ) { case NONE: if( opt_luminance ) { VERBOSE_STR << "applying response to Y channel..." << endl; sp = robertson02_applyResponse( Yj, imgsY, Iy, w, M); } else { VERBOSE_STR << "applying response to R channel..." << endl; sp = robertson02_applyResponse( Xj, imgsR, Ir, w, M); VERBOSE_STR << "applying response to G channel..." << endl; sp += robertson02_applyResponse( Yj, imgsG, Ig, w, M); VERBOSE_STR << "applying response to B channel..." << endl; sp += robertson02_applyResponse( Zj, imgsB, Ib, w, M); sp /= 3; } break; case ROBERTSON: if( opt_luminance ) { VERBOSE_STR << "recovering Y channel..." << endl; sp = robertson02_getResponse( Yj, imgsY, Iy, w, M); } else { VERBOSE_STR << "recovering R channel..." << endl; sp = robertson02_getResponse( Xj, imgsR, Ir, w, M); VERBOSE_STR << "recovering G channel..." << endl; sp += robertson02_getResponse( Yj, imgsG, Ig, w, M); VERBOSE_STR << "recovering B channel..." << endl; sp += robertson02_getResponse( Zj, imgsB, Ib, w, M); sp /= 3; } break; case DEBEVEC: break; } if( sp>0 ) { float perc = ceilf(100.0f*sp/size); VERBOSE_STR << "saturated pixels found in " << perc << "% of the image!" << endl; } // save response curve to a given file if( responseSaveFile!=NULL ) { if( opt_luminance ) { responseSave(responseSaveFile, Iy, M, "IY"); weightsSave(responseSaveFile, w, M, "W"); fclose(responseSaveFile); } else { responseSave(responseSaveFile, Ir, M, "IR"); responseSave(responseSaveFile, Ig, M, "IG"); responseSave(responseSaveFile, Ib, M, "IB"); weightsSave(responseSaveFile, w, M, "W"); fclose(responseSaveFile); } } // output PFS stream with calibrated response else { if( opt_luminance ) for( int i=0 ; i