Hugin trunk 0.1
Loading...
Searching...
No Matches
PhotometricOptimizer.cpp
Go to the documentation of this file.
1// -*- c-basic-offset: 4 -*-
25
26#include <fstream>
27#include <foreign/levmar/levmar.h>
31
32#ifdef DEBUG
33#define DEBUG_LOG_VIG 1
34#endif
35
36
37namespace HuginBase {
38
40inline double weightHuber(double x, double sigma)
41{
42 if (x > sigma) {
43 x = sqrt(sigma* (2*x - sigma));
44 }
45 return x;
46}
47
48
49
51 const std::vector<vigra_ext::PointPairRGB> & data,
52 double mEstimatorSigma, bool symmetric,
54 : m_pano(pano), m_data(data), huberSigma(mEstimatorSigma), symmetricError(symmetric),
55 m_maxIter(maxIter), m_progress(progress)
56{
57 assert(pano.getNrOfImages() == optvars.size());
58
59 for (unsigned i=0; i < pano.getNrOfImages(); i++) {
60 m_imgs.push_back(pano.getSrcImage(i));
61 }
62
63 std::vector<std::set<std::string> > usedVars(pano.getNrOfImages());
64
65 // create variable map with param <-> var assignments
66 for (unsigned i=0; i < optvars.size(); i++)
67 {
68 const std::set<std::string> vars = optvars[i];
69 const SrcPanoImage & img_i = pano.getImage(i);
70 for (std::set<std::string>::const_iterator it = vars.begin();
71 it != vars.end(); ++it)
72 {
73 VarMapping var;
74 var.type = *it;
75 //check if variable is yet included
76 if(set_contains(usedVars[i],var.type))
77 continue;
78 var.imgs.insert(i);
79 usedVars[i].insert(var.type);
80 //now check all linked images and add image nr
81#define CheckLinked(name)\
82 if(img_i.name##isLinked())\
83 {\
84 for(unsigned j=i+1;j<pano.getNrOfImages();j++)\
85 if(img_i.name##isLinkedWith(pano.getImage(j)))\
86 {\
87 var.imgs.insert(j);\
88 usedVars[j].insert(var.type);\
89 };\
90 }
91
92 if(var.type=="Eev")
93 {
95 };
96 if(var.type=="Er")
97 {
99 };
100 if(var.type=="Eb")
101 {
103 };
104 if(var.type[0]=='R')
105 {
107 };
108 if(var.type=="Va" || var.type=="Vb" || var.type=="Vc" || var.type=="Vd")
109 {
111 }
112 if(var.type=="Vx" || var.type=="Vy")
113 {
115 };
116#undef CheckLinked
117 m_vars.push_back(var);
118 }
119 }
120}
121
123{
124 for (size_t i=0; i < m_vars.size(); i++)
125 {
126 assert(!m_vars[i].imgs.empty());
127 // get corresponding image number
128 unsigned j = *(m_vars[i].imgs.begin());
129 // get value
130 x[i] = m_imgs[j].getVar(m_vars[i].type);
131 // TODO: transform some variables, such as the vignetting center!
132 }
133}
134
135
137{
138 for (size_t i=0; i < m_vars.size(); i++)
139 {
140 // TODO: transform some variables, such as the vignetting center!
141 assert(!m_vars[i].imgs.empty());
142 // copy value int all images
143 for (std::set<unsigned>::const_iterator it = m_vars[i].imgs.begin();
144 it != m_vars[i].imgs.end(); ++it)
145 {
146 m_imgs[*it].setVar(m_vars[i].type, x[i]);
147 }
148 }
149}
150
151
152
153void PhotometricOptimizer::photometricError(double *p, double *x, int m, int n, void * data)
154{
155#ifdef DEBUG_LOG_VIG
156 static int iter = 0;
157#endif
160
161 int xi = 0 ;
162
163 OptimData * dat = static_cast<OptimData*>(data);
164 dat->FromX(p);
165#ifdef DEBUG_LOG_VIG
166 std::ostringstream oss;
167 oss << "vig_log_" << iter;
168 iter++;
169 std::ofstream log(oss.str().c_str());
170 log << "VIGparams = [";
171 for (int i = 0; i < m; i++) {
172 log << p[i] << " ";
173 }
174 log << " ]; " << std::endl;
175 // TODO: print parameters of images.
176 std::ofstream script("vig_test.pto");
177 OptimizeVector optvars(dat->m_pano.getNrOfImages());
178 UIntSet imgs = dat->m_pano.getActiveImages();
179 dat->m_pano.printPanoramaScript(script, optvars, dat->m_pano.getOptions(), imgs, false, "");
180#endif
181
182 size_t nImg = dat->m_imgs.size();
183 std::vector<RespFunc> resp(nImg);
184 std::vector<InvRespFunc> invResp(nImg);
185 for (size_t i=0; i < nImg; i++) {
186 resp[i] = RespFunc(dat->m_imgs[i]);
187 invResp[i] = InvRespFunc(dat->m_imgs[i]);
188 // calculate the monotonicity error
189 double monErr = 0;
190 if (dat->m_imgs[i].getResponseType() == SrcPanoImage::RESPONSE_EMOR) {
191 // calculate monotonicity error
192 int lutsize = resp[i].m_lutR.size();
193 for (int j=0; j < lutsize-1; j++)
194 {
195 double d = resp[i].m_lutR[j] - resp[i].m_lutR[j+1];
196 if (d > 0) {
197 monErr += d*d*lutsize;
198 }
199 }
200 }
201 x[xi++] = monErr;
202 // enforce a montonous response curves
203 resp[i].enforceMonotonicity();
204 invResp[i].enforceMonotonicity();
205 }
206
207#ifdef DEBUG
208 double sqerror=0;
209#endif
210 // loop over all points to calculate the error
211#ifdef DEBUG_LOG_VIG
212 log << "VIGval = [ ";
213#endif
214
215 for (std::vector<vigra_ext::PointPairRGB>::const_iterator it = dat->m_data.begin();
216 it != dat->m_data.end(); ++it)
217 {
218 vigra::RGBValue<double> l2 = invResp[it->imgNr2](it->i2, it->p2);
219 vigra::RGBValue<double> i2ini1 = resp[it->imgNr1](l2, it->p1);
220 vigra::RGBValue<double> error = it->i1 - i2ini1;
221
222
223 // if requested, calcuate the error in image 2 as well.
224 //TODO: weighting dependent on the pixel value? check if outside of i2 range?
225 vigra::RGBValue<double> l1 = invResp[it->imgNr1](it->i1, it->p1);
226 vigra::RGBValue<double> i1ini2 = resp[it->imgNr2](l1, it->p2);
227 vigra::RGBValue<double> error2 = it->i2 - i1ini2;
228
229#ifdef DEBUG
230 for (int i=0; i < 3; i++) {
231 sqerror += error[i]*error[i];
232 sqerror += error2[i]*error2[i];
233 }
234#endif
235
236 // use huber robust estimator
237 if (dat->huberSigma > 0) {
238 for (int i=0; i < 3; i++) {
239 x[xi++] = weightHuber(fabs(error[i]), dat->huberSigma);
240 x[xi++] = weightHuber(fabs(error2[i]), dat->huberSigma);
241 }
242 } else {
243 x[xi++] = error[0];
244 x[xi++] = error[1];
245 x[xi++] = error[2];
246 x[xi++] = error2[0];
247 x[xi++] = error2[1];
248 x[xi++] = error2[2];
249 }
250
251#ifdef DEBUG_LOG_VIG
252 log << it->i1.green() << " "<< l1.green() << " " << i1ini2.green() << " "
253 << it->i2.green() << " "<< l2.green() << " " << i2ini1.green() << "; " << std::endl;
254#endif
255
256 }
257#ifdef DEBUG_LOG_VIG
258 log << std::endl << "VIGerr = [";
259 for (int i = 0; i < n; i++) {
260 log << x[i] << std::endl;
261 }
262 log << " ]; " << std::endl;
263#endif
264#ifdef DEBUG
265 DEBUG_DEBUG("squared error: " << sqerror);
266#endif
267}
268
269int PhotometricOptimizer::photometricVis(double *p, double *x, int m, int n, int iter, double sqerror, void * data)
270{
271 OptimData * dat = static_cast<OptimData*>(data);
272 char tmp[200];
273 tmp[199] = 0;
274 double error = sqrt(sqerror/n)*255;
275 snprintf(tmp,199, "Iteration: %d, error: %f", iter, error);
276 return dat->m_progress->updateDisplay(std::string(tmp)) ? 1 : 0 ;
277}
278
280 const std::vector<vigra_ext::PointPairRGB> & correspondences,
281 const float imageStepSize,
283 double & error)
284{
285
287 // keep only the photometric variables
288 unsigned int optCount=0;
289 for (OptimizeVector::const_iterator it=vars.begin(); it != vars.end(); ++it)
290 {
291 std::set<std::string> cvars;
292 for (std::set<std::string>::const_iterator itv = (*it).begin();
293 itv != (*it).end(); ++itv)
294 {
295 if ((*itv)[0] == 'E' || (*itv)[0] == 'R' || (*itv)[0] == 'V') {
296 cvars.insert(*itv);
297 }
298 }
299 photometricVars.push_back(cvars);
300 optCount+=cvars.size();
301 }
302 //if no variables to optimize return
303 if(optCount==0)
304 {
305 return;
306 };
307
308 int nMaxIter = 250;
310
311 double info[LM_INFO_SZ];
312
313 // parameters
314 int m=data.m_vars.size();
315 vigra::ArrayVector<double> p(m, 0.0);
316
317 // vector for errors
318 int n=2*3*correspondences.size()+pano.getNrOfImages();
319 vigra::ArrayVector<double> x(n, 0.0);
320
321 data.ToX(p.begin());
322#ifdef DEBUG
323 printf("Parameters before optimisation: ");
324 for(int i=0; i<m; ++i)
325 printf("%.7g ", p[i]);
326 printf("\n");
327#endif
328
329 // TODO: setup optimisation options with some good defaults.
330 double optimOpts[5];
331
332 optimOpts[0] = LM_INIT_MU; // init mu
333 // stop thresholds
334 optimOpts[1] = LM_STOP_THRESH; // ||J^T e||_inf
335 optimOpts[2] = LM_STOP_THRESH; // ||Dp||_2
336 optimOpts[3] = std::pow(imageStepSize*0.1f, 2); // ||e||_2
337 // difference mode
339
340 dlevmar_dif(&photometricError, &photometricVis, &(p[0]), &(x[0]), m, n, nMaxIter, optimOpts, info, NULL,NULL, &data); // no jacobian
341
342 // copy to source images (data.m_imgs)
343 data.FromX(p.begin());
344 // calculate error at solution
345 data.huberSigma = 0;
346 photometricError(&(p[0]), &(x[0]), m, n, &data);
347 error = 0;
348 for (int i=0; i<n; i++) {
349 error += x[i]*x[i];
350 }
351 error = sqrt(error/n);
352
353#ifdef DEBUG
354 printf("Levenberg-Marquardt returned in %g iter, reason %g\nSolution: ", info[5], info[6]);
355 for(int i=0; i<m; ++i)
356 printf("%.7g ", p[i]);
357 printf("\n\nMinimization info:\n");
358 for(int i=0; i<LM_INFO_SZ; ++i)
359 printf("%g ", info[i]);
360 printf("\n");
361#endif
362
363 // copy settings to panorama
364 for (unsigned i=0; i<pano.getNrOfImages(); i++) {
365 pano.setSrcImage(i, data.m_imgs[i]);
366 }
367}
368
369bool IsHighVignetting(std::vector<double> vigCorr)
370{
371 SrcPanoImage srcImage;
372 srcImage.setRadialVigCorrCoeff(vigCorr);
373 srcImage.setSize(vigra::Size2D(500, 500));
374 Photometric::ResponseTransform<double> transform(srcImage);
375 for (size_t x = 0; x < 250; x += 10)
376 {
377 const double vigFactor = transform.calcVigFactor(hugin_utils::FDiff2D(x, x));
379 {
380 return true;
381 };
382 };
383 return false;
384};
385
387{
388 for(size_t i=0; i<pano.getNrOfImages(); i++)
389 {
390 if(pano.getImage(i).getWhiteBalanceBlue()>3)
391 {
392 return true;
393 };
394 if(pano.getImage(i).getWhiteBalanceRed()>3)
395 {
396 return true;
397 };
398 };
399 return false;
400};
401
403 const std::vector<vigra_ext::PointPairRGB> & correspondences,
404 const float imageStepSize,
406 double & error)
407{
408 PanoramaOptions opts = pano.getOptions();
409 UIntSet images;
410 fill_set(images, 0, pano.getNrOfImages()-1);
411 std::vector<UIntSet> stacks = getHDRStacks(pano, images, pano.getOptions());
412 bool singleStack = (stacks.size() == 1);
413 // check overlap explicit
414 if(singleStack)
415 {
417 overlap.calculate(10); // we are testing 10*10=100 points
418 double overlapValue = 0;
419 for (auto img = ++stacks[0].begin(); img != stacks[0].end(); ++img)
420 {
421 overlapValue = std::max(overlapValue, overlap.getOverlap(*stacks[0].begin(), *img));
422 };
423 if (overlapValue < 0.85)
424 {
425 // if overlap in the single stack is small enough, don't treat it as single stack
426 singleStack = false;
427 }
428 }
429
430 int vars = 0;
431 if (mode == OPT_PHOTOMETRIC_LDR || mode == OPT_PHOTOMETRIC_LDR_WB)
432 {
433 // optimize exposure
434 vars = OPT_EXP;
436 createOptVars(pano, vars, opts.colorReferenceImage),
438 };
439
440 if(!singleStack)
441 {
442 //optimize vignetting only if there are more than 1 image stack
443 // for a single stack vignetting can't be calculated by this optimization
444 vars |= OPT_VIG;
447 createOptVars(pano, vars, opts.colorReferenceImage),
449 // check if vignetting is plausible
450 if(IsHighVignetting(pano.getImage(0).getRadialVigCorrCoeff()))
451 {
452 vars &= ~OPT_VIG;
454 };
455 };
456
457 // now take response curve into account
458 vars |= OPT_RESP;
459 // also WB if desired
460 if (mode == OPT_PHOTOMETRIC_LDR_WB || mode == OPT_PHOTOMETRIC_HDR_WB)
461 {
462 vars |= OPT_WB;
463 };
466 createOptVars(pano, vars, opts.colorReferenceImage),
468 // now check the results
469 const bool hasHighVignetting = IsHighVignetting(pano.getImage(0).getRadialVigCorrCoeff());
470 // @TODO check also response curve, what parameters are considered invalid?
471 const bool hasHighWB = CheckStrangeWB(pano);
473 {
474 vars &= ~OPT_VIG;
475 };
476 if(hasHighWB)
477 {
478 vars &= ~OPT_WB;
479 };
481 {
482 // we got strange results, optimize again with less parameters
484 if(vars>0)
485 {
487 createOptVars(pano, vars, opts.colorReferenceImage),
489 };
490 };
491}
492
493
495{
499
500 // optimizePhotometric does not tell us if it's cancelled
502 {
504 }
505
506 return wasCancelled(); // let's hope so.
507}
508
510{
511 smartOptimizePhotometric(o_panorama,
512 o_optMode,
515
516 // smartOptimizePhotometric does not tell us if it's cancelled
518 {
520 };
521
522 return !wasCancelled(); // let's hope so.
523}
524
525} //namespace
definitions of classes to calculate overlap between different images
declaration of functions to handle stacks and layers
#define CheckLinked(name)
@ RESPONSE_EMOR
empirical model of response
class for calculating overlap of images
Model for a panorama.
virtual VariableMapVector getVariables() const =0
get variables of this panorama
virtual const PanoramaOptions & getOptions() const =0
returns the options for this panorama
virtual void setSrcImage(unsigned int nr, const SrcPanoImage &img)=0
set input image parameters TODO: Propagate changes to linked images.
virtual SrcPanoImage getSrcImage(unsigned imgNr) const =0
get a complete description of a source image
virtual const SrcPanoImage & getImage(std::size_t nr) const =0
get a panorama image, counting starts with 0
virtual void updateVariables(const VariableMapVector &vars)=0
Set the variables.
virtual std::size_t getNrOfImages() const =0
number of images.
Panorama image options.
virtual bool runAlgorithm()
implementation of the algorithm.
static void optimizePhotometric(PanoramaData &pano, const OptimizeVector &vars, const PointPairs &correspondences, const float imageStepSize, AppBase::ProgressDisplay *progress, double &error)
static void photometricError(double *p, double *x, int m, int n, void *data)
static int photometricVis(double *p, double *x, int m, int n, int iter, double sqerror, void *data)
radiometric transformation, includes exposure, vignetting and white balance
radiometric transformation, includes exposure, vignetting and white balance.
double calcVigFactor(hugin_utils::FDiff2D d) const
static void smartOptimizePhotometric(PanoramaData &pano, PhotometricOptimizeMode mode, const std::vector< vigra_ext::PointPairRGB > &correspondences, const float imageStepSize, AppBase::ProgressDisplay *progress, double &error)
use various heuristics to decide what to optimize.
PhotometricOptimizeMode
local optimize definition.
virtual bool runAlgorithm()
implementation of the algorithm.
All variables of a source image.
virtual void cancelAlgorithm()
Call this when the algorithm is cancelled.
virtual AppBase::ProgressDisplay * getProgressDisplay() const
static double sigma
#define DEBUG_DEBUG(msg)
Definition utils.h:68
std::vector< float > EMoRParams
double ExposureValue
double WhiteBalanceRed
hugin_utils::FDiff2D RadialVigCorrCenterShift
double WhiteBalanceBlue
std::vector< double > RadialVigCorrCoeff
mainly consists of wrapper around the pano tools library, to assist in ressource management and to pr...
Definition wxcms.cpp:39
std::vector< VariableMap > VariableMapVector
double weightHuber(double x, double sigma)
expects the abs(error) values
std::vector< std::set< std::string > > OptimizeVector
bool IsHighVignetting(std::vector< double > vigCorr)
std::set< unsigned int > UIntSet
std::vector< UIntSet > getHDRStacks(const PanoramaData &pano, UIntSet allImgs, PanoramaOptions opts)
returns vector of set of output stacks
bool CheckStrangeWB(PanoramaData &pano)
bool set_contains(const _Container &c, const typename _Container::key_type &key)
Definition stl_utils.h:74
void fill_set(_Container &c, typename _Container::key_type begin, typename _Container::key_type end)
Definition stl_utils.h:81
OptimData(const PanoramaData &pano, const OptimizeVector &optvars, const std::vector< vigra_ext::PointPairRGB > &data, double mEstimatorSigma, bool symmetric, int maxIter, AppBase::ProgressDisplay *progress)
void ToX(double *x)
copy optimisation variables into x
void FromX(double *x)
copy new values from x to into this->m_imgs
std::vector< deghosting::BImagePtr > threshold(const std::vector< deghosting::FImagePtr > &inputImages, const double threshold, const uint16_t flags)
Threshold function used for creating alpha masks for images.
Definition threshold.h:41