Basic Image AlgorithmS Library  2.8.0
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
ImageBlenderIncremental.cpp
1 #include <Image/ImageBlenderIncremental.hh>
2 #include <math.h>
3 #include <MathAlgo/SVD.hh>
4 #include <Base/Common/FileHandling.hh>
5 #include <Base/Image/ImageIO.hh>
6 #include <Utils/ThreeDOut.hh>
7 #include <Base/Image/ImageConvert.hh>
8 
9 #ifndef WIN32
10 #include <sys/sysinfo.h> // determine available memeory
11 #endif
12 
13 using namespace std;
14 using namespace BIAS;
15 
16 
17 // --- constructor ---
18 // -------------------
19 ImageBlenderIncremental::ImageBlenderIncremental(bool incremental) {
20  cylinderHeight_ = 1.0;
21  SetOuputImageSize((unsigned int)(1200));
22  blendIncremental_ = incremental;
23  writeVrml_ = false;
24  drawImageBorders_ = false;
25  horizonAlignment_ = HORIZON_ALIGNMENT_X;
26  ppOutput_ = NULL;
27  gaussFilter_.SetSigma(1.5);
28  plane_.SetZero();
29 }
30 
31 
32 void ImageBlenderIncremental::
33 SetOutputParameters(const ProjectionParametersBase& imagegeometry,
34  RGBAuc bgcolor) {
35  if (ppOutput_!=NULL) {
36  delete ppOutput_;
37  }
38  ppOutput_ = imagegeometry.Clone();
39  ppOutput_->GetImageSize(outputImageWidth_, outputImageHeight_);
40 
41 #ifndef WIN32
42  struct sysinfo mysysinfo;
43  sysinfo(&mysysinfo);
44 
45  cout<<"output image size requested: "<<outputImageWidth_
46  <<"X"<< outputImageHeight_<<endl;
47 
48  cout<<"This will require more than "
49  << outputImageWidth_ * outputImageHeight_ * 64 / 1000000
50  <<"MB of RAM, you have "<<mysysinfo.freeram / 1000000
51  <<"MB available..."<<endl;
52 
53  if (outputImageWidth_ * outputImageHeight_ * 70 > mysysinfo.freeram) {
54  // later, we should tile the output image in this situation
55  BIASERR(endl<<endl<<endl<<"MEMORY SHORTAGE !!! ABORTING..."<<endl<<endl<<endl);
56  BIASABORT;
57  }
58 #endif
59 
60  BIASASSERT(outputImageHeight_>0);
61  BIASASSERT(outputImageWidth_>0);
62  // set sink cam and depth map pointer (or null to disable 3D)
63  Projection P(*ppOutput_);
64  pm_.SetSinkCam(P, Depth_.IsEmpty()?NULL:&Depth_);
65  if (plane_.NormL2()>0.1) {
66  cout<<"mapping across plane "<<plane_<<endl;
67  pm_.SetPlane(plane_);
68  }
69  if (!latestMosaicHigh_.IsEmpty()) latestMosaicHigh_.Release();
70  if (!latestMosaicLow_.IsEmpty()) latestMosaicLow_.Release();
71  latestMosaicHigh_.Init(outputImageWidth_, outputImageHeight_, 4u, ImageBase::ST_float);
72  latestMosaicHigh_.SetColorModel(ImageBase::CM_RGBA);
73  latestMosaicLow_.Init(outputImageWidth_, outputImageHeight_, 4u, ImageBase::ST_float);
74  latestMosaicLow_.SetColorModel(ImageBase::CM_RGBA);
75 
76  // normally, one wants to use zero weight here for bg color
77  bgcolor[3] = 0;
78  FillBGImage_(latestMosaicHigh_,bgcolor);
79  FillBGImage_(latestMosaicLow_,bgcolor);
80 
81 
82 
83 
84 }
85 
86 void ImageBlenderIncremental::
87 FillBGImage_(Image<float>& BGImage, const RGBAuc& bgcolor) {
88  const unsigned int w = BGImage.GetWidth();
89  const unsigned int h = BGImage.GetHeight();
90 
91  const float R=bgcolor[0];
92  const float G=bgcolor[1];
93  const float B=bgcolor[2];
94  const float A=bgcolor[3];
95 
96  // initialize with background color
97  float *pImage = BGImage.GetImageData();
98  for (unsigned int x = 0; x < w; x++) {
99  for (unsigned int y = 0; y < h; y++) {
100  *pImage++ = R;
101  *pImage++ = G;
102  *pImage++ = B;
103  *pImage++ = A;
104  }
105  }
106 }
107 
108 
109 
110 
111 void ImageBlenderIncremental::
112 SetUserWeightImage(const Image<float>& weights) {
113  weightimage_ = weights;
114 }
115 
116 
117 
118 // -------------------------------------
119 // --- adds a camera to the database ---
120 // -------------------------------------
121 int ImageBlenderIncremental::
122 AddCamera(const BIAS::Camera<unsigned char>& camera,
123  unsigned int weightType) {
124 
125  if (!camera.IsProjValid()) {
126  BIASWARN("Projection is invalid! I'll ignore this image...");
127  return -1;
128  }
129 
130  // convert to rgb
131  Camera<unsigned char> temp(camera);
132  ImageConvert::ToRGB(camera, temp);
133 
134  // compute alpha channel weight
135  Camera<float> temp2;
136  temp2.Init(temp.GetWidth(), temp.GetHeight(), 3);
137 
138  // TODO: use ImageConvert::ConvertST()
139  for (unsigned int y = 0; y < temp.GetHeight(); y++) {
140  for (unsigned int x = 0; x < temp.GetWidth(); x++) {
141  unsigned char *pD = &temp.GetImageDataArray()[y][3 * x];
142  float *pD2 = &temp2.GetImageDataArray()[y][3 * x];
143 
144  pD2[0] = pD[0];
145  pD2[1] = pD[1];
146  pD2[2] = pD[2];
147  }
148  }
149  ComputeAlphaChannelWeight_(temp2, weightType);
150  // copy projection data
151  temp2.SetProj(camera.GetProj());
152 
153  if (!blendIncremental_) {
154  // batch mode. wait until all data is there
155  // no work now, just store in vector ...
156  BIAS::UUID uuid = camera.GetUID();
157  BIASASSERT(uuid.IsValid());
158 
159  // save image to database
160  inputImages_[uuid] = temp2;
161  imageIDs_.push_back(uuid);
162  } else {
163  // ok, the real work has to be done now
164  pm_.SetSourceCam(camera.GetProj());
165  if (plane_.NormL2()>0.1) {
166  //cout<<"mapping across plane "<<plane_<<endl;
167  pm_.SetPlane(plane_);
168  } else {
169  //cout<<"not using a plane "<<plane_<<endl;
170  }
171 
172  // map orig images to cyl
173  Camera<float> camCyl;
174  camCyl.Init(outputImageWidth_, outputImageHeight_, 4);
175 
176  FillBGImage_(camCyl, RGBAuc(0,0,0,0));
177  //cout<<"output image size used in PM: "<<outputImageWidth_
178  // <<"X"<< outputImageHeight_<<endl;
179 
180  pm_.Map(temp2, camCyl);// mappingQuality, false, superSampling);
181 
182 //#define BIAS_DEBUG_IBI
183 #ifdef BIAS_DEBUG_IBI
184  ImageIO::Save("IBI_lastin.mip", temp2);
185  ImageIO::Save("IBI_lastout.mip", camCyl);
186 #endif
187 
188  Camera<float> lowPassCam, highPassCam;
189  GetLowPassAndHighPassImage_(camCyl, lowPassCam, highPassCam);
190 #ifdef BIAS_DEBUG_IBI
191  ImageIO::Save("IBI_lastlowpass.mip", lowPassCam);
192  ImageIO::Save("IBI_lasthighpass.mip", highPassCam);
193 #endif
194  LowPassFusiondAndHighPassMax_(latestMosaicLow_, latestMosaicHigh_,
195  lowPassCam, highPassCam);
196 #ifdef BIAS_DEBUG_IBI
197  ImageIO::Save("IBI_lasthighpass_fused.mip", latestMosaicHigh_);
198  ImageIO::Save("IBI_lastlowpass_fused.mip", latestMosaicLow_);
199 #endif
200 
201  }
202 
203  return 0;
204 }
205 
206 void ImageBlenderIncremental::
207 GetMosaicRGBA(Image<unsigned char>& mosaic){
208  // check if image already has correct dimensions
209  if (mosaic.GetWidth()!=latestMosaicHigh_.GetWidth() ||
210  mosaic.GetHeight()!=latestMosaicHigh_.GetHeight() ||
211  mosaic.GetChannelCount()!=4)
212  mosaic.Release();
213 
214  // clear output image
215  if (mosaic.IsEmpty()) {
216  mosaic.Init(latestMosaicHigh_.GetWidth(), latestMosaicHigh_.GetHeight(), 4);
217  }
218 
219  // fuse low pass and high pass parts so far
220  AddLowPassAndHighPassImage_(latestMosaicLow_, latestMosaicHigh_,
221  mosaic);
222 }
223 
224 void ImageBlenderIncremental::
225 GetMosaicRGB(Image<unsigned char>& mosaic){
226  // check if image already has correct dimensions
227  if (mosaic.GetWidth()!=latestMosaicHigh_.GetWidth() ||
228  mosaic.GetHeight()!=latestMosaicHigh_.GetHeight() ||
229  mosaic.GetChannelCount()!=3)
230  mosaic.Release();
231 
232  // clear output image
233  if (mosaic.IsEmpty()) {
234  mosaic.Init(latestMosaicHigh_.GetWidth(), latestMosaicHigh_.GetHeight(), 3);
235  }
236 
237  // fuse low pass and high pass parts so far
238  AddLowPassAndHighPassImage_(latestMosaicLow_, latestMosaicHigh_,
239  mosaic);
240 }
241 
242 void ImageBlenderIncremental::
243 GetMosaicRGBA(Image<float>& mosaic){
244  // check if image already has correct dimensions
245  if (mosaic.GetWidth()!=latestMosaicHigh_.GetWidth() ||
246  mosaic.GetHeight()!=latestMosaicHigh_.GetHeight() ||
247  mosaic.GetChannelCount()!=4)
248  mosaic.Release();
249 
250  // clear output image
251  if (mosaic.IsEmpty()) {
252  mosaic.Init(latestMosaicHigh_.GetWidth(), latestMosaicHigh_.GetHeight(), 4);
253  }
254 
255  // fuse low pass and high pass parts so far
256  AddLowPassAndHighPassImage_(latestMosaicLow_, latestMosaicHigh_,
257  mosaic);
258 }
259 
260 void ImageBlenderIncremental::
261 GetMosaicRGB(Image<float>& mosaic){
262  // check if image already has correct dimensions
263  if (mosaic.GetWidth()!=latestMosaicHigh_.GetWidth() ||
264  mosaic.GetHeight()!=latestMosaicHigh_.GetHeight() ||
265  mosaic.GetChannelCount()!=3)
266  mosaic.Release();
267 
268  // clear output image
269  if (mosaic.IsEmpty()) {
270  mosaic.Init(latestMosaicHigh_.GetWidth(), latestMosaicHigh_.GetHeight(), 3);
271  }
272 
273  // fuse low pass and high pass parts so far
274  AddLowPassAndHighPassImage_(latestMosaicLow_, latestMosaicHigh_,
275  mosaic);
276 }
277 
278 
279 
280 void ImageBlenderIncremental::
281 CopyAlphaChannel_(const Image<float>& camCyl,
282  Image<float>& lowPassCam) {
283  const float *pC = camCyl.GetImageData()+3;
284  float *pL = lowPassCam.GetImageData()+3;
285  for (unsigned int y = 0; y < camCyl.GetHeight(); y++) {
286  for (unsigned int x = 0; x < camCyl.GetWidth(); x++) {
287  *pL = *pC;
288  pL += 4;
289  pC += 4;
290  }
291  }
292 }
293 
294 void ImageBlenderIncremental::
295 GetLowPassAndHighPassImage_(const Image<float>& camCyl,
296  Image<float>& lowPassCam,
297  Image<float>& highPassCam) {
298 
299  // low-pass filtering
300  gaussFilter_.Filter7x7RGBA(camCyl, lowPassCam);
301 
302  // weight channel is now blurred, too, undo:
303  CopyAlphaChannel_(camCyl,lowPassCam);
304 
305  if (DebugLevelIsSet(D_IMAGEBLENDER_FILTERING)) {
306 #ifdef BLENDER_FIX_ME
308  temp.Init(lowPassCam.GetWidth(), lowPassCam.GetHeight(), 4);
309  ImageConvert::ConvertST(dynamic_cast<const BIAS::ImageBase>(lowPassCam),
310  dynamic_cast<const BIAS::ImageBase>(temp),
311  ImageBase::CM_RGBA);
312 
313  ostringstream s;
314  s << "100_low-pass_image_" << i;
315  cout << "writing " << s.str() << " to disk..." << endl;
316 
317  s << ".png";
318  ImageIO::Save(s.str(), temp);
319 #endif
320  }
321 
322  // --- perform high-pass filtering ---
323  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_FILTERING),
324  "high-pass filtering... "<< endl << flush);
325 
326  // high-pass filtering
327  highPassCam.Release();
328  highPassCam.Init(lowPassCam.GetWidth(), lowPassCam.GetHeight(), 4);
329 
330  // subtract low-pass filtered image from normal image to
331  // get high frequencies
332  const float *pDC = camCyl.GetImageData();
333  float *pDL = lowPassCam.GetImageData();
334  float *pDH = highPassCam.GetImageData();
335  for (unsigned int y = 0; y < highPassCam.GetHeight(); y++) {
336  for (unsigned int x = 0; x < highPassCam.GetWidth(); x++) {
337  *pDH++ = *pDC++ - *pDL++;
338  *pDH++ = *pDC++ - *pDL++;
339  *pDH++ = *pDC++ - *pDL++;
340  *pDH++ = *pDC++; // take alpha value from weighted input image
341  pDL++;
342  }
343  }
344 }
345 
346 
347 void ImageBlenderIncremental::
348 LowPassFusiondAndHighPassMax_(Image<float>& destLow, Image<float>& destHigh,
349  Image<float>& lowPassCam, Image<float>& highPassCam) {
350  BIASASSERT(destLow.GetWidth() == destHigh.GetWidth());
351  BIASASSERT(destLow.GetWidth() == lowPassCam.GetWidth());
352  BIASASSERT(destLow.GetWidth() == highPassCam.GetWidth());
353  BIASASSERT(destLow.GetChannelCount() == destHigh.GetChannelCount());
354  BIASASSERT(destLow.GetChannelCount() == lowPassCam.GetChannelCount());
355  BIASASSERT(destLow.GetChannelCount() == highPassCam.GetChannelCount());
356 
357  float w, wLow, weightsum;
358  float *pDLow = destLow.GetImageData();
359  float *pDHigh = destHigh.GetImageData();
360  // draw low frequency with linear weight
361  float *pL = lowPassCam.GetImageData();
362  // add high frequency from image with highest weight
363  float *pH = highPassCam.GetImageData();
364 
365  // run over all pixels
366  for (unsigned int i = 0;
367  i < destLow.GetWidth() * destLow.GetHeight();
368  i ++) {
369 
370  // produce some output every some pixels
371  if ((i % 3000) == 0) {
372  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_BLENDING), ".");
373  }
374  if (pL[3] > 0.0f) {
375  w = pDLow[3];
376  wLow = pL[3];
377  weightsum = w + wLow;
378 
379  *pDLow = (w * *pDLow + wLow * *pL++) / weightsum;
380  pDLow++;
381  *pDLow = (w * *pDLow + wLow * *pL++) / weightsum;
382  pDLow++;
383  *pDLow = (w * *pDLow + wLow * *pL++) / weightsum;
384  pDLow++;
385  *pDLow++ = weightsum;
386  pL++;
387 
388  // update only if pixel-alpha is better:
389  if (pH[3] > pDHigh[3]) {
390  *pDHigh++ = *pH++;
391  *pDHigh++ = *pH++;
392  *pDHigh++ = *pH++;
393  *pDHigh++ = *pH++;
394  } else {
395  pDHigh += 4;
396  pH += 4;
397  }
398  } else {
399  pDLow += 4;
400  pL += 4;
401  pDHigh += 4;
402  pH += 4;
403  }
404  }
405 }
406 
407 void ImageBlenderIncremental::
408 AddLowPassAndHighPassImage_(Image<float>& destLow, Image<float>& destHigh,
409  Image<unsigned char>& destination) {
410  int channelcount = destination.GetChannelCount();
411  //cout<<"have "<<channelcount<<" channels"<<endl;
412  unsigned char *pDest = destination.GetImageData();
413  float *pDLow = destLow.GetImageData();
414  float *pDHigh = destHigh.GetImageData();
415  if (channelcount==4) {
416  // store result in destination image
417  for (unsigned int i = 0;
418  i < destination.GetWidth() * destination.GetHeight() * 4;
419  i += 4) {
420  // if (pDLow[i + 3] > 0.0) {
421  // avoid overflow and underflow
422  float sumR = pDLow[i + 0] + pDHigh[i + 0];
423  float sumG = pDLow[i + 1] + pDHigh[i + 1];
424  float sumB = pDLow[i + 2] + pDHigh[i + 2];
425  if (sumR > 255.0) sumR = 255.0;
426  if (sumG > 255.0) sumG = 255.0;
427  if (sumB > 255.0) sumB = 255.0;
428  if (sumR < 0.0) sumR = 0.0;
429  if (sumG < 0.0) sumG = 0.0;
430  if (sumB < 0.0) sumB = 0.0;
431 
432  // store result in image
433  pDest[i + 0] = (unsigned char)(sumR);
434  pDest[i + 1] = (unsigned char)(sumG);
435  pDest[i + 2] = (unsigned char)(sumB);
436  pDest[i + 3] = ALPHA_OPAQUE;
437  // }
438  }
439 #ifdef BIAS_DEBUG_IBI
440  ImageIO::Save("IBI_last4channellow.mip", destLow);
441  ImageIO::Save("IBI_last4channelhigh.mip", destHigh);
442  ImageIO::Save("IBI_last4channelsum.mip", destination);
443 #endif
444  } else {
445  BIASASSERT(channelcount==3);
446  // store result in destination image
447  for (unsigned int i = 0;
448  i < destination.GetWidth() * destination.GetHeight();
449  i ++) {
450  // if (pDLow[3] > 0.0) {
451  // avoid overflow and underflow
452  float sumR = *pDLow++ + *pDHigh++;
453  float sumG = *pDLow++ + *pDHigh++;
454  float sumB = *pDLow++ + *pDHigh++;
455  pDLow++; pDHigh++; // alpha channel
456 
457  if (sumR > 255.0) sumR = 255.0;
458  if (sumR < 0.0) sumR = 0.0;
459 
460  if (sumG > 255.0) sumG = 255.0;
461  if (sumG < 0.0) sumG = 0.0;
462 
463  if (sumB > 255.0) sumB = 255.0;
464  if (sumB < 0.0) sumB = 0.0;
465 
466  // store result in image
467  *pDest++ = (unsigned char)(rint(sumR));
468  *pDest++ = (unsigned char)(rint(sumG));
469  *pDest++ = (unsigned char)(rint(sumB));
470 
471  // } else {
472  // pDest +=3;
473  // pDLow +=4;
474  // pDHigh +=4;
475  // }
476  }
477 #ifdef BIAS_DEBUG_IBI
478  ImageIO::Save("IBI_last3channellow.mip", destLow);
479  ImageIO::Save("IBI_last3channelhigh.mip", destHigh);
480  ImageIO::Save("IBI_last3channelsum.mip", destination);
481 #endif
482  }
483 }
484 
485 void ImageBlenderIncremental::
486 AddLowPassAndHighPassImage_(Image<float>& destLow, Image<float>& destHigh,
487  Image<float>& destination) {
488  int channelcount = destination.GetChannelCount();
489  //cout<<"have "<<channelcount<<" channels"<<endl;
490  float *pDest = destination.GetImageData();
491  float *pDLow = destLow.GetImageData();
492  float *pDHigh = destHigh.GetImageData();
493  if (channelcount==4) {
494  // store result in destination image
495  for (unsigned int i = 0;
496  i < destination.GetWidth() * destination.GetHeight();
497  i ++) {
498  *pDest++ = *pDLow++ + *pDHigh++;
499  *pDest++ = *pDLow++ + *pDHigh++;
500  *pDest++ = *pDLow++ + *pDHigh++;
501  *pDest++ = ALPHA_OPAQUE;
502  pDLow++; pDHigh++;
503 
504  }
505 #ifdef BIAS_DEBUG_IBI
506  ImageIO::Save("IBI_last4channellow.mip", destLow);
507  ImageIO::Save("IBI_last4channelhigh.mip", destHigh);
508  ImageIO::Save("IBI_last4channelsum.mip", destination);
509 #endif
510  } else {
511  BIASASSERT(channelcount==3);
512  // store result in destination image
513  for (unsigned int i = 0;
514  i < destination.GetWidth() * destination.GetHeight();
515  i ++) {
516  *pDest++ = *pDLow++ + *pDHigh++;
517  *pDest++ = *pDLow++ + *pDHigh++;
518  *pDest++ = *pDLow++ + *pDHigh++;
519  pDLow++; pDHigh++; // alpha channel
520  }
521 #ifdef BIAS_DEBUG_IBI
522  ImageIO::Save("IBI_last3channellow.mip", destLow);
523  ImageIO::Save("IBI_last3channelhigh.mip", destHigh);
524  ImageIO::Save("IBI_last3channelsum.mip", destination);
525 #endif
526  }
527 }
528 
529 
530 
531 // ----------------------------------------------
532 // --- blends all added images in batch mode ---
533 // ----------------------------------------------
534 bool ImageBlenderIncremental::
535 BlendImages(BIAS::Camera<unsigned char> &destination,
536  const ProjectionParametersBase& ppc,
537  const Image<float>* depthmap,
538  double gaussSigma) {
539  if (this->blendIncremental_) {
540  BIASERR("Call GetMosaic() to get the result image.");
541  BIASABORT;
542  }
543  if (imageIDs_.size() < 1) {
544  BIASWARN("I need at least one image to blend!");
545  return false;
546  }
547 
548  Camera<float> lowPassCam;
549  Camera<float> highPassCam;
550 
551  Projection TargetProjection(ppc);
552  pm_.SetSinkCam(TargetProjection, Depth_.IsEmpty()? NULL : &Depth_);
553 
554 
555 
556  InterpolationMethod mappingQuality = MapTrilinear;
557  double superSampling = 1.0; // set to 2.0 for double width supersampling
558 
559  // prepare visualization cyl cam in 3D
560  ThreeDOut scene3D;
561 
562  // init destination image:
563  // - set size
564  // - convert to RGBA
565  // - set alpha values to transparent
566  cout << "size of final image is "
567  << outputImageWidth_ << "x" << outputImageHeight_ << endl;
568  destination.Release();
569  destination.Init(outputImageWidth_, outputImageHeight_, 4);
570 
571  destination.SetZero();
572 
573 
574  // ------------ FILTERING
575 
576  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_FILTERING),
577  "filtering images..." << endl << flush);
578 
579  gaussFilter_.SetSigma(gaussSigma);
580 
581  // low-pass and high-pass filtering for all images
582  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
583  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_FILTERING),
584  "processing image " << i << endl << flush);
585 
586  Projection proj(inputImages_[imageIDs_[i]].GetProj());
587 
588 
589  pm_.SetSourceCam(proj);
590 
591  // map orig images to cyl
592  Camera<float> camCyl;
593  camCyl.Init(outputImageWidth_, outputImageHeight_, 4);
594  FillBGImage_(camCyl, RGBAuc(0,0,0,0));
595 
596 
597  pm_.Map(inputImages_[imageIDs_[i]], camCyl,
598  mappingQuality, false, superSampling);
599 
600  // --- perform low-pass filtering ---
601  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_FILTERING),
602  "low-pass filtering with sigma=" << gaussSigma << "..."
603  << endl << flush);
604  GetLowPassAndHighPassImage_(camCyl, lowPassCam, highPassCam);
605 
606  // store low-pass filtered image to disk
607  BIASCDOUT(D_IMAGEBLENDER_FILTERING,
608  "writing temp low-pass image to disk... " << endl << flush);
609  //ImageIO::Save(string("tmp_pano_blender_low-pass_") + FileHandling::toString(i)
610  // + ".mip",
611  // lowPassCam);
612  ImageIO::Save(string("tmp_pano_blender_low-pass_")
613  + FileHandling::toString(i)
614  + ".mip",
615  lowPassCam);
616 
617  // store high-pass filtered image to disk
618  BIASCDOUT(D_IMAGEBLENDER_FILTERING,
619  "writing temp high-pass image to disk... "<< endl << flush);
620  //ImageIO::Save(string("tmp_pano_blender_high-pass_") + FileHandling::toString(i)
621  // + ".mip",
622  // highPassCam);
623  ImageIO::Save(string("tmp_pano_blender_high-pass_")
624  + FileHandling::toString(i)
625  + ".mip",
626  highPassCam);
627 
628  if (DebugLevelIsSet(D_IMAGEBLENDER_FILTERING)) {
629 #ifdef BLENDER_FIX_ME
631  temp.Init(highPassCam.GetWidth(), highPassCam.GetHeight(), 4);
632  ImageConvert::ConvertST(highPassCam, temp, ImageBase::CM_RGBA);
633 
634  ostringstream s;
635  s << "100_high-pass_image_" << i;
636  cout << "writing " << s.str() << " to disk..." << endl;
637 
638  s << ".png";
639  ImageIO::Save(s.str(), temp);
640 #endif
641  }
642  }
643 
644  // input images can be erased to save memory (only if drawImageBorders_ = false)
645  if (!drawImageBorders_) {
646  inputImages_.clear();
647  }
648 
649  // ----------- BLENDING
650  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_BLENDING),
651  endl << "blending images");
652 
653 
654 
655  Camera<float> destLow;
656  Camera<float> destHigh;
657  destLow.Init(destination.GetWidth(), destination.GetHeight(),
658  destination.GetChannelCount());
659  destHigh.Init(destination.GetWidth(), destination.GetHeight(),
660  destination.GetChannelCount());
661 
662  destLow.Clear(0.0);
663  destHigh.Clear(0.0);
664 
665 
666  // run over all images
667  for (unsigned int imageCount = 0; imageCount < imageIDs_.size(); imageCount++) {
668 
669  // load current low/high pair
670  ImageIO::Load(string("tmp_pano_blender_low-pass_") + FileHandling::toString(imageCount)
671  + ".mip", lowPassCam);
672  ImageIO::Load(string("tmp_pano_blender_high-pass_") + FileHandling::toString(imageCount)
673  + ".mip", highPassCam);
674 
675  LowPassFusiondAndHighPassMax_(destLow, destHigh, lowPassCam, highPassCam);
676 
677  }
678 
679 
680  destination.Clear(0);
681  AddLowPassAndHighPassImage_(destLow, destHigh, destination);
682 
683 
684  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_BLENDING),
685  endl << flush);
686 
687  // draw border of images into cylinder
688  if (drawImageBorders_) {
689  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
690  cout << "drawing border of image " << i << endl;
691 
692  PixelIterator it;
693 
694  Projection projection;
695  projection = inputImages_[imageIDs_[i]].GetProj();
696 
698  ppp = (ProjectionParametersPerspective*)(projection.GetParameters());
699 
700  // run over all border pixels of image
701  ppp->GetFirstBorderPixel(it);
702  do {
703  HomgPoint2D p2DHom(it.x, it.y);
704 
705  // compute ray and project it to cylinder
706  Vector3<double> ray, p;
707  ppp->UnProjectToRay(p2DHom, p, ray);
708  HomgPoint3D p3DHom(ray); // interface conversion
709 
710  // if the cylinder geometry is correct, all borders should be in the
711  // cylinder image, but you never know:
712  if (!ppc.DoesPointProjectIntoImage(p3DHom, p2DHom)) continue;
713 
714  // paint border pixel in red
715  destination.SetPixel(255,
716  (unsigned int)(p2DHom[0]),
717  (unsigned int)(p2DHom[1]),
718  0);
719  destination.SetPixel(0,
720  (unsigned int)(p2DHom[0]),
721  (unsigned int)(p2DHom[1]),
722  1);
723  destination.SetPixel(0,
724  (unsigned int)(p2DHom[0]),
725  (unsigned int)(p2DHom[1]),
726  2);
727 
728  // ensure opaque alpha channel
729  destination.SetPixel(ALPHA_OPAQUE,
730  (unsigned int)(p2DHom[0]),
731  (unsigned int)(p2DHom[1]),
732  3);
733  } while (ppp->GetNextBorderPixel(it));
734  }
735  }
736 
737  // write cyl projection to output image
738  Projection proj(ppc);
739  destination.SetProj(proj);
740  destination.UpdateMetaData();
741 
742  // visualize cyl cam in 3D
743  // TODO: dafuer sorgen, dass TriangleMesh auch RGBA nimmt
744  if (writeVrml_) {
745  Image<unsigned char> rgbim;
746  ImageConvert::Convert(destination, rgbim, ImageBase::CM_RGB);
747 
748  TriangleMesh triangleMesh;
749  triangleMesh.GenerateTexturedCamera(&ppc,
750  rgbim,
751  1.0,
752  1.0,
753  1.0);
754  scene3D.AddTriangleMesh(triangleMesh);
755 
756  scene3D.VRMLOut("cylindricPanorama.wrl");
757  }
758 
759  return true;
760 }
761 
762 
763 void ImageBlenderIncremental::
764 ComputeCylCamGeometry_(ProjectionParametersCylindric &ppc) {
765 
766  if (imageIDs_.size() < 1) {
767  BIASWARN("I need at least one image to compute destination geometry!");
768  return;
769  }
770 
771  // init projection parameters
772  Vector3<double> C(inputImages_[imageIDs_[0]].GetProj().GetC());
773  RMatrix RTarget(inputImages_[imageIDs_[0]].GetProj().GetR());
774 
775  // compute orientation R:
776  // get axis of each image that is assumed to lie in a plane
777  // and save them in a matrix
778  Matrix<double> M(2 * imageIDs_.size(), 3, 0.0);
779 
780 
781 
782  unsigned int testAlignment = horizonAlignment_;
783  if (horizonAlignment_== HORIZON_ALIGNMENT_AUTO)
784  testAlignment = 0;
785 
786  double bestResiduum = HUGE_VAL;
787  int selectedmode = -1;
788  Vector3<double> besth(0,1,0), h(0,1,0);
789  do {
790  // regard alignment of horizon
791  if (testAlignment == HORIZON_ALIGNMENT_X) {
792  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
793  RMatrix R = inputImages_[imageIDs_[i]].GetProj().GetR();
794  for (unsigned int c=0;c<3;c++) M[i][c] = R[c][0];
795  }
796  } else if (testAlignment == HORIZON_ALIGNMENT_Y) {
797  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
798  RMatrix R = inputImages_[imageIDs_[i]].GetProj().GetR();
799  for (unsigned int c=0;c<3;c++) M[i][c] = R[c][1];
800  }
801  } else if (testAlignment == HORIZON_ALIGNMENT_UNKNOWN) {
802  // HORIZON_ALIGNMENT_UNKNOWN
803  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
804  RMatrix R = inputImages_[imageIDs_[i]].GetProj().GetR();
805  for (unsigned int c=0;c<3;c++) M[i][c] = R[c][2];
806  }
807  }
808 
809  // now we can solve the LES M*h=0, where h will become
810  // the y axis of the cylindrical camera
811  SVD svd(M, 0.1, false);
812  // the smallest eigenvalue may become quite large, so we grab
813  // the solution vector "by hand"
814  Matrix<double> vt;
815  vt = svd.GetVT();
816 
817  // now get the first, the second and the third best null vectors
818  Vector3<double> secondh(0,0,1), thirdh(0,0,1);
819  for (unsigned int i = 0; i < 3; i++) {
820  h[i] = vt[vt.num_rows() - 1][i];
821  secondh[i] =vt[vt.num_rows() - 2][i];
822  thirdh[i] =vt[0][i];
823  }
824  // normalize them for fair comparison
825  h.Normalize();
826  secondh.Normalize();
827  thirdh.Normalize();
828  // M*h is a vector containing the scalar products (cosines) of all camera
829  // vectors with the solution. Its norm is an "average" cosine and should be
830  // zero since we search a perpendicular vector cos(90)=0
831  double residuum = (M*h).NormL2()/double(h.Size());
832  double secondresiduum = (M*secondh).NormL2()/double(h.Size());
833  double thirdresiduum = (M*thirdh).NormL2()/double(h.Size());
834  // avoid pathological case and division by zero
835  if (residuum<1e-10) residuum = 1e-10;
836  if (secondresiduum<1e-10) secondresiduum=1e-10;
837  if (thirdresiduum<1e-10) thirdresiduum=1e-10;
838  //cout<<"r1="<<residuum<<" r2="<<secondresiduum<<" r3="<<thirdresiduum<<endl;
839  //cout<<"S is "<<svd.GetS()<<endl;
840  // we dont want to get the rotation axis, therefore if the null space is
841  // two-dimensional, reject this solution! Two dimensional means here that
842  // the residuum of the second solution is closer to the best than to the
843  // worst solution on an order of magnitude scale
844  bool nullspacedim2 = thirdresiduum / secondresiduum > 10.0;
845  // (log(thirdresiduum / residuum) - 2 * secondresiduum / residuum)>0;
846  //if (nullspacedim2) cout<<"2-dim. null space!"<<endl;
847  //else cout<<"1-dim. null space!"<<endl;
848  if (residuum<bestResiduum && !nullspacedim2) {
849  // found better axis!
850  besth = h;
851  bestResiduum = residuum;
852  selectedmode = testAlignment;
853  }
854 
855  if (horizonAlignment_== HORIZON_ALIGNMENT_AUTO) testAlignment++;
856  } while (testAlignment!=horizonAlignment_);
857  h = besth;
858  if (horizonAlignment_ == HORIZON_ALIGNMENT_AUTO) switch (selectedmode) {
859  case HORIZON_ALIGNMENT_X:cout<<"Applying horizontal alignment"<<endl;break;
860  case HORIZON_ALIGNMENT_Y:cout<<"Applying vertical alignment"<<endl;break;
861  default:cout<<"Applying optical axis alignment"<<endl;break;
862  }
863 
864  horizonAlignment_ = selectedmode;
865 
866 
867  // project the choosen axes of all cams onto the plane defined
868  // by its normal vector h using parallel projection:
869  vector< Vector3<double> > projectedVectors;
870 
871  for (unsigned int imCount = 0; imCount < imageIDs_.size(); imCount++) {
872  Vector3<double> orig, proj, temp;
873  RMatrix Rot = inputImages_[imageIDs_[imCount]].GetProj().GetR();
874  //orig[0] = Rot[0][2];
875  //orig[1] = Rot[1][2];
876  //orig[2] = Rot[2][2];
877 
878  // regard alignment of horizon
879  if (horizonAlignment_ == HORIZON_ALIGNMENT_X) {
880  orig[0] = Rot[0][0];
881  orig[1] = Rot[1][0];
882  orig[2] = Rot[2][0];
883  }
884  else if (horizonAlignment_ == HORIZON_ALIGNMENT_Y) {
885  orig[0] = Rot[0][1];
886  orig[1] = Rot[1][1];
887  orig[2] = Rot[2][1];
888  }
889  else { // HORIZON_ALIGNMENT_UNKNOWN and illegal values
890  orig[0] = Rot[0][2];
891  orig[1] = Rot[1][2];
892  orig[2] = Rot[2][2];
893  }
894 
895  h.Mult(orig.ScalarProduct(h), temp);
896  orig.Sub(temp, proj);
897 
898  projectedVectors.push_back(proj);
899  }
900 
901  // choose vectors a and v that span the plane
902  // a is the first camera's projected optical axis
903  BIASASSERT(!projectedVectors.empty());
904  Vector3<double> a(projectedVectors[0]);
905  a.Normalize();
906 
907  // v must be orthogonal to h and a
908  Vector3<double> v;
909  a.CrossProduct(h, v);
910  v.Normalize();
911 
912  // now calc rotation so that pano is centered in the cyl
913  // for this we need the angles between successive optical axis to find the
914  // biggest "gap"
915 
916  // first transform the projected vectors into coordinate system of the plane.
917  // {a, v} is a base of the plane space, so multliply each vec with a
918  // 2x3-matrix that contains a and v als row vectors
919  vector< Vector2<double> > vectorsInPlaneSpace(projectedVectors.size());
920 
921  Matrix<double> planeTransform(2, 3, 0);
922  planeTransform[0][0] = a[0];
923  planeTransform[0][1] = a[1];
924  planeTransform[0][2] = a[2];
925  planeTransform[1][0] = v[0];
926  planeTransform[1][1] = v[1];
927  planeTransform[1][2] = v[2];
928 
929  for (unsigned int i = 0; i < projectedVectors.size(); i++) {
930  // interface conversion
931  Vector<double> v1(projectedVectors[i]);
932  Vector<double> v2;
933 
934  // transform each vec to plane's 2d coord system
935  planeTransform.Mult(v1, v2);
936  vectorsInPlaneSpace[i] = v2;
937 
938  // normalize vec
939  double norm = vectorsInPlaneSpace[i].NormL2();
940  vectorsInPlaneSpace[i] /= norm;
941 
942  cout << "vec " << i << " " << vectorsInPlaneSpace[i]
943  << " norm " << vectorsInPlaneSpace[i].NormL2()<< endl;
944  }
945 
946  // we choose vectorsInPlaneSpace[0] as reference.
947  // note: vectorsInPlaneSpace[0] points in direction of x axis
948 
949  // calc angles between ref vec and all other vecs
950  vector<double> angles(vectorsInPlaneSpace.size());
951 
952  for (unsigned int i = 0; i < vectorsInPlaneSpace.size(); i++) {
953  angles[i] = CalcAngleToXAxis_(vectorsInPlaneSpace[i], true);
954  cout << i << ". angle relative to reference vector " << angles[i] << endl;
955  }
956 
957  // determine the order of the vecs and calc angles of successive vecs
958  vector<bool> visited(vectorsInPlaneSpace.size());
959  vector<double> successiveAngles;
960  vector<int> indices;
961  double sumAngles = 0.0;
962  // init vector
963  for (unsigned int i = 0; i < visited.size(); i++) {
964  visited[i] = false;
965  }
966 
967  // mark ref axis as visited, since angle between ref vec and ref vec is 0
968  visited[0] = true;
969 
970  for (unsigned int i = 1; i < vectorsInPlaneSpace.size(); i++) {
971  double smallestAngle = 361;
972  int index = -1;
973 
974  // look for next axis
975  for (unsigned int j = 0; j < vectorsInPlaneSpace.size(); j++) {
976  if (!visited[j] && (angles[j] < smallestAngle)) {
977  smallestAngle = angles[j];
978  index = j;
979  }
980  }
981  BIASASSERT(index != -1);
982 
983  visited[index] = true;
984  double angle;
985  if (successiveAngles.empty()) {
986  angle = smallestAngle;
987  }
988  else {
989  angle = smallestAngle - successiveAngles[successiveAngles.size() - 1];
990  }
991  successiveAngles.push_back(angle);
992  indices.push_back(index);
993 
994  sumAngles += angle;
995  }
996 
997  double sumAnglesRemainder = sumAngles - int(sumAngles);
998  sumAngles = int(sumAngles) % 360;
999  sumAngles += sumAnglesRemainder;
1000 
1001  cout << "sum of all angles is " << sumAngles << endl;
1002 
1003  BIASASSERT(sumAngles <= 360);
1004 
1005  // add angle between last axis und our reference axis, i.e. close the circle
1006  successiveAngles.push_back(360 - sumAngles);
1007  indices.push_back(0);
1008 
1009  for (unsigned int i = 0; i < indices.size(); i++) {
1010  cout << "index " << indices[i]
1011  << " angle " << successiveAngles[i] << endl;
1012  }
1013 
1014  // search for vecs with greates "gap" between them
1015  double biggestAngle = 0.0;
1016  int indexL = -1, indexR = -1;
1017 
1018  for (unsigned int i = 0; i < indices.size(); i++) {
1019  if (successiveAngles[i] > biggestAngle) {
1020  biggestAngle = successiveAngles[i];
1021  if (i == 0) {
1022  indexL = indices[indices.size() - 1];
1023  }
1024  else {
1025  indexL = indices[i - 1];
1026  }
1027  indexR = indices[i];
1028  }
1029  }
1030 
1031  cout << "biggest gap is " << biggestAngle << " degrees between axis "
1032  << indexL << " and " << indexR << endl;
1033 
1034  // calc vector that lies in the middle of this two vecs
1035  Vector2<double> midVec;
1036  if (biggestAngle != 180.0) {
1037  midVec = (vectorsInPlaneSpace[indexL] + vectorsInPlaneSpace[indexR]) / 2.0;
1038  }
1039  else { // biggest gap is exactly 180 degrees
1040  // take vectorsInPlaneSpace[indexL] and rotate 90 degrees to get midVec
1041  Matrix2x2<double> rotTmp;
1042 
1043  rotTmp[0][0] = 0;
1044  rotTmp[0][1] = 1;
1045  rotTmp[1][0] = -1;
1046  rotTmp[1][1] = 0;
1047 
1048  cout << "found 180 deg gap - using rot " << rotTmp << endl;
1049 
1050  cout << "with vec " << vectorsInPlaneSpace[indexL] << endl;
1051 
1052  rotTmp.Mult(vectorsInPlaneSpace[indexL], midVec);
1053 
1054  cout << "mid vec is " << midVec << endl;
1055  }
1056  // normalize vec
1057  midVec /= midVec.NormL2();
1058  cout << "optical axis after norm " << midVec << endl;
1059 
1060  // transform midVec back to 3d space and use it as cylinder's optical axis
1061  Matrix<double> planeTransformInv(3, 2, 0);
1062  SVD svdInvert;
1063  planeTransformInv = svdInvert.Invert(planeTransform);
1064 
1065  // interface conversion
1066  Vector<double> v1(midVec);
1067  Vector<double> v2;
1068 
1069  planeTransformInv.Mult(v1, v2);
1070  a = v2;
1071 
1072  cout << "final optical axis is " << a << endl;
1073 
1074  // v must be orthogonal to h and a
1075  a.CrossProduct(h, v);
1076  v.Normalize();
1077 
1078  // modify RTarget
1079  RTarget[0][0] = h[0];
1080  RTarget[1][0] = h[1];
1081  RTarget[2][0] = h[2];
1082  RTarget[0][1] = v[0];
1083  RTarget[1][1] = v[1];
1084  RTarget[2][1] = v[2];
1085  RTarget[0][2] = a[0];
1086  RTarget[1][2] = a[1];
1087  RTarget[2][2] = a[2];
1088 
1089  ppc.SetC(C);
1090  ppc.SetR(RTarget);
1091 
1092  CheckFov_(ppc);
1093 
1094  // CheckFov alters ppc so we need to set this again
1095  // TODO: dies hier mit nach CheckFov()
1096  ppc.SetC(C);
1097  ppc.SetR(RTarget);
1098 }
1099 
1100 
1101 inline double ImageBlenderIncremental::
1102 CalcAngleToXAxis_(const Vector2<double> &v,
1103  bool wantDegrees) {
1104  Vector2<double> xAxis(1.0, 0.0);
1105  double ret = 0.0;
1106  double temp = 0.0;
1107 
1108  // the angle of two vecs x, y is computed as follows:
1109  // angle(x, y) = arccos ( (x * y) / (|x| * |y|) )
1110  temp = xAxis.ScalarProduct(v);
1111  temp /= xAxis.NormL2() * v.NormL2();
1112  ret = acos(temp);
1113 
1114  if (wantDegrees) { // want degrees, not rad
1115  ret = ret * 180 / M_PI;
1116  }
1117  // if y component < 0 then this vec is "to the left", i.e. ccw
1118  // since this equation only yields values in [0, ... , 180[, we need to
1119  // consider the algebraic sign of the y component of the other vec,
1120  // which determines the relative direction.
1121  if (v[1] < 0) {
1122  if (wantDegrees) {
1123  ret = 360.0 - ret;
1124  }
1125  else {
1126  ret = 2 * M_PI - ret;
1127  }
1128  }
1129 
1130  return ret;
1131 }
1132 
1133 
1134 inline double ImageBlenderIncremental::
1135 CalcAngleToYAxis_(const Vector2<double> &v,
1136  bool wantDegrees) {
1137  Vector2<double> yAxis(0.0, 1.0);
1138  double ret = 0.0;
1139  double temp = 0.0;
1140 
1141  // the angle of two vecs x, y is computed as follows:
1142  // angle(x, y) = arccos ( (x * y) / (|x| * |y|) )
1143  temp = yAxis.ScalarProduct(v);
1144  temp /= yAxis.NormL2() * v.NormL2();
1145  ret = acos(temp);
1146 
1147  if (wantDegrees) { // want degrees, not rad
1148  ret = ret * 180 / M_PI;
1149  }
1150  // if x-component <= 0 then this vec is "to the left", i.e. ccw
1151  // since this equation only yields values in [0, ... , 180[, we need to
1152  // consider the algebraic sign of the x component of the other vec,
1153  // which determines the relative direction.
1154  if (v[0] <= 0) {
1155  if (wantDegrees) {
1156  ret = 360.0 - ret;
1157  }
1158  else {
1159  ret = 2 * M_PI - ret;
1160  }
1161  }
1162 
1163  return ret;
1164 }
1165 
1166 
1167 inline void ImageBlenderIncremental::
1169 
1170  cout << "checking fov of cameras..." << endl;
1171 
1172  double minX=1e100, minY=1e100,
1173  maxX=-1e100, maxY=-1e100; // the borders of panorama in cylinder coords
1174  Vector3<double> p;
1175 
1176  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
1177  double minZ = 1.0;
1178  PixelIterator it;
1179 
1180  Projection proj;
1181  proj = inputImages_[imageIDs_[i]].GetProj();
1182 
1184 
1186 
1187  // run over all border pixels of image
1188  ppp->GetFirstBorderPixel(it);
1189  do {
1190 
1191  HomgPoint2D x(it.x, it.y);
1192 
1193  // --- check FOV of input cam
1194 
1195  // compute ray in camera coordinate system
1196  Vector3<double> ray;
1197  ppp->UnProjectLocal(x, p, ray);
1198 
1199 #ifdef BIAS_DEBUG
1200  double norm = 0;
1201  norm = ray.NormL2();
1202  if (!Equal(norm, 1.0)) {
1203  BIASERR("ray not unit length: " << ray);
1204  BIASABORT;
1205  }
1206 #endif
1207  if (ray[2] < minZ) {
1208  minZ = ray[2];
1209  }
1210 
1211  // --- check FOV of cyl cam
1212 
1213  // compute ray and project it to cylinder
1214  ppp->UnProjectToRay(x, p, ray);
1215  HomgPoint3D p3DHom(ray); // interface conversion
1216 
1217  // if the cylinder geometry is correct, all borders should be in the
1218  // cylinder image, but you never know:
1219  if (!ppc.DoesPointProjectIntoImage(p3DHom, x)) continue;
1220 
1221  if (x[0] < minX) minX = x[0];
1222  if (x[1] < minY) minY = x[1];
1223  if (x[0] > maxX) maxX = x[0];
1224  if (x[1] > maxY) maxY = x[1];
1225 
1226  } while (ppp->GetNextBorderPixel(it));
1227 
1228  //double angle = asin(minZ) * 360.0 / M_PI;
1229 #ifdef BIAS_DEBUG
1230  double angle = 0.0;
1231  angle = asin(minZ);
1232 
1233  BIASCDOUT(D_IMAGEBLENDER_GEOMETRY,
1234  "Max field of view of cam "
1235  << i << " is " << angle << endl << flush);
1236 #endif
1237  // add some safety margin
1238  minZ *= 0.8;
1239 
1240  // prevent strange cases
1241  if (minZ < 0.01) {
1242  minZ = 0.01;
1243  }
1244 
1245  // clip everything with smaller z as behind camera
1246  ppp->SetMinZLocal(minZ);
1247  inputImages_[imageIDs_[i]].SetProj(Projection(*ppp));
1248  }
1249 
1250  // calc FOV of cyl
1251  double halfWidth = outputImageWidth_ / 2.0;
1252  double halfHeight = outputImageHeight_ / 2.0;
1253 
1254  double xMin = -1.0 * ((halfWidth - minX) / halfWidth);
1255  double xMax = (maxX - halfWidth) / halfWidth;
1256 
1257  double phiMin = -M_PI * ((halfHeight - minY) / halfHeight);
1258  double phiMax = M_PI * ((maxY - halfHeight) / halfHeight);
1259 
1260  double factor = outputImageHeight_ / (maxY - minY);
1261  outputImageWidth_ = (unsigned int)((maxX - minX) * factor);
1262 
1263  ProjectionParametersCylindric ppc2(xMin, xMax,
1264  phiMin, phiMax,
1265  outputImageWidth_,
1266  outputImageHeight_);
1267 
1268  ppc = ppc2;
1269 }
1270 
1271 
1272 inline double CalculateWeightCircular(int dx,
1273  int dy,
1274  double distCenter) {
1275  double ret = (1.0 - sqrt(double(dx * dx + dy * dy)) / distCenter) * 255.0;
1276 
1277  if (ret < 0.0)
1278  return 0.0;
1279  else
1280  return ret;
1281 }
1282 
1283 
1284 inline double CalculateWeightRectangular(unsigned int w,
1285  unsigned int h,
1286  unsigned int x,
1287  unsigned int y,
1288  unsigned int mx,
1289  unsigned int my) {
1290  // if pixel is near border assign weight = 0
1291  //if (x == 0 || y == 0 || x == (w - 1) || y == (h - 1)) return 0;
1292  //if (x == 1 || y == 1 || x == (w - 2) || y == (h - 2)) return 0;
1293 
1294  double m, mp;
1295  bool aboveAC, aboveBD;
1296  double alphaValue = 0.0;
1297 
1298  // determine in wich of the four triangles 1, 2, 3, 4 the pixel lies:
1299  // A-----------D
1300  // | \ 1 / |
1301  // | \ / |
1302  // | 4 X 2 |
1303  // | / \ |
1304  // | / 3 \ |
1305  // B-----------C
1306 
1307  m = double(h) / double(w);
1308 
1309  // determine if the pixel is above or below the line AC
1310  mp = double(y) / double(x);
1311  if (mp > m) {
1312  aboveAC = false;
1313  }
1314  else {
1315  aboveAC = true;
1316  }
1317 
1318  // determine if the pixel is above or below the line BD
1319  mp = double(y) / double(mx + (mx - x));
1320  if (mp > m) {
1321  aboveBD = false;
1322  }
1323  else {
1324  aboveBD = true;
1325  }
1326 
1327  // compute weight
1328  if (aboveAC && aboveBD) { // triangle 1
1329  alphaValue = double(y) / double(my);
1330  }
1331  else if (aboveAC && !aboveBD) { // triangle 2
1332  alphaValue = double(w - x) / double(mx);
1333  }
1334  else if (!aboveAC && !aboveBD) { // triangle 3
1335  alphaValue = double(h - y) / double(my);
1336  }
1337  else if (!aboveAC && aboveBD) { // triangle 4
1338  alphaValue = double(x) / double(mx);
1339  }
1340 
1341  return alphaValue * 255.0;
1342 }
1343 
1344 
1345 // -----------------------------------------
1346 // --- computes the weight of each pixel ---
1347 // -----------------------------------------
1348 void ImageBlenderIncremental::
1349 ComputeAlphaChannelWeight_(BIAS::Image<float> &image,
1350  unsigned int weightType) {
1351  // TODO: calculate weight only for one quarter of the image, rest is symmetric
1352 
1353  ConvertImageToRGBA_(image);
1354 
1355  const unsigned int w = image.GetWidth();
1356  const unsigned int h = image.GetHeight();
1357 
1358  const unsigned int mx = w / 2;
1359  const unsigned int my = h / 2;
1360 
1361  // calc length of the line from (0, 0) to (mx, my)
1362  const double distCenter = sqrt(double(mx * mx + my * my));
1363 
1364  if (weightType==WEIGHT_TYPE_USERIMAGE) {
1365  // in case the user specified a weight image, copy the weights from this
1366  // image into the alpha channel of the source
1367  // the weight image has to be of exactly the same size as the curent
1368  // input image
1369  float *pWeight = weightimage_.GetImageData();
1370  float *pImage = image.GetImageData()+3;
1371  BIASASSERT(image.GetWidth()==weightimage_.GetWidth());
1372  BIASASSERT(image.GetHeight()==weightimage_.GetHeight());
1373  for (unsigned int x = 0; x < w; x++) {
1374  for (unsigned int y = 0; y < h; y++) {
1375  *pImage = *pWeight++;
1376  pImage += 4; // jump to next alpha
1377  }
1378  }
1379  } else {
1380 
1381  // if WEIGHT_TYPE_MIX is used, mixRatio defines the ratio between
1382  // rectangular and circular weight
1383  // e.g. if mixRation = 0.3 then use 30 % rect weight and 70 % circ weight
1384  // if mixRatio is less than 1.0, gain ensures that the highest weights in the
1385  // final image (which are at the center of the image) are 1.0
1386  const double mixRatio = 0.5; // must be in [0, 1]
1387  const double gain = 1.0 / (mixRatio * (1.0 - mixRatio));
1388 
1389  for (unsigned int x = 0; x < w; x++) {
1390  for (unsigned int y = 0; y < h; y++) {
1391 
1392  unsigned int dx = mx - x;
1393  unsigned int dy = my - y;
1394 
1395  double alphaValue = 0.0;
1396 
1397  double rectVal, circVal;
1398 
1399  switch (weightType) {
1400  case WEIGHT_TYPE_NONE:
1401  alphaValue = ALPHA_OPAQUE;
1402  break;
1403 
1404  case WEIGHT_TYPE_CIRCULAR:
1405  alphaValue = CalculateWeightCircular(dx, dy, distCenter);
1406  break;
1407 
1408  case WEIGHT_TYPE_CIRCULAR_FIT:
1409  if (mx < my) {
1410  alphaValue = CalculateWeightCircular(dx, dy, mx);
1411  }
1412  else {
1413  alphaValue = CalculateWeightCircular(dx, dy, my);
1414  }
1415  break;
1416 
1417  case WEIGHT_TYPE_RECTANGULAR:
1418  alphaValue = CalculateWeightRectangular(w, h, x, y, mx, my);
1419  break;
1420 
1421  case WEIGHT_TYPE_MIX:
1422  rectVal = CalculateWeightRectangular(w, h, x, y, mx, my) / 255.0;
1423  circVal = CalculateWeightCircular(dx, dy, distCenter) / 255.0;
1424  alphaValue = (mixRatio * rectVal)
1425  * ((1.0 - mixRatio) * circVal)
1426  * gain
1427  * 255.0;
1428  break;
1429 
1430  case WEIGHT_TYPE_RECT_NONLINEAR:
1431  rectVal = CalculateWeightRectangular(w, h, x, y, mx, my) / 255.0;
1432  alphaValue = rectVal * rectVal * 255.0;
1433  break;
1434 
1435  default:
1436  BIASERR("Unknown weight type!");
1437  }
1438 
1439  // avoid clipping
1440  if (alphaValue < 0.0) alphaValue = 0.0;
1441  if (alphaValue > 255.0) alphaValue = 255.0;
1442 
1443  image.GetImageDataArray()[y][4 * x + 3] = alphaValue;
1444  }
1445  }
1446  }
1447 }
1448 
1449 
1450 // ------------------------------
1451 // --- converts image to RGBA ---
1452 // ------------------------------
1453 void ImageBlenderIncremental::
1454 ConvertImageToRGBA_(BIAS::Image<float> &image) {
1455 
1456  // convert image to RGB
1457  if (image.GetColorModel() != ImageBase::CM_RGB) {
1458  Image<float> tempImage;
1459  ImageConvert::Convert(image, tempImage, ImageBase::CM_RGB);
1460  image = tempImage;
1461  }
1462 
1463  // add alphachannel
1464  Image<float> alphaImage(image.GetWidth(),
1465  image.GetHeight(), 4);
1466 
1467  alphaImage.SetUID(image.GetUID());
1468 
1469  alphaImage.SetColorModel(ImageBase::CM_RGBA);
1470  for (unsigned int y = 0; y < alphaImage.GetHeight(); y++) {
1471  for (unsigned int x = 0; x < alphaImage.GetWidth(); x++) {
1472  float *pD = &image.GetImageDataArray()[y][3 * x];
1473  float *pDA = &alphaImage.GetImageDataArray()[y][4 * x];
1474  *pDA++ = *pD++;
1475  *pDA++ = *pD++;
1476  *pDA++ = *pD++;
1477  *pDA++ = ALPHA_OPAQUE;
1478  }
1479  }
1480 
1481  image = alphaImage;
1482 }
void Release()
reimplemented from ImageBase
Definition: Image.cpp:1579
InterpolationMethod
accuracy for resampling
unsigned int AddTriangleMesh(const TriangleMesh &mesh, const std::string &name="", const std::string &textureOutputName="", bool writeOutTexture=true, bool calcNormals=false)
Adds triangle mesh as IndexedFaceSet to ThreeDOut mem.
Definition: ThreeDOut.cpp:1104
class HomgPoint2D describes a point with 2 degrees of freedom in projective coordinates.
Definition: HomgPoint2D.hh:67
computes and holds the singular value decomposition of a rectangular (not necessarily quadratic) Matr...
Definition: SVD.hh:92
int VRMLOut(const std::string &sFilename)
flush all 3d objects to a vrml file with name sFilename, this is the function most users would call ...
Definition: ThreeDOut.cpp:3670
void GenerateTexturedCamera(const ProjectionParametersBase *PPB, Image< unsigned char > &rgbtexture, const double &scale=1.0, const double &opacity=1.0, const double &resolution=1.0)
generates the sensor plane / cylinder / sphere in 3D space
void ScalarProduct(const Vector2< T > &argvec, T &result) const
scalar product of two vectors, storing the result in result
Definition: Vector2.hh:356
virtual void SetR(const BIAS::RMatrix &R)
Set orientation from rotation matrix R.
bool IsEmpty() const
check if ImageData_ points to allocated image buffer or not
Definition: ImageBase.hh:245
double NormL2() const
Return the L2 norm: sqrt(a^1 + a^2 + ...)
Definition: Vector.hh:416
camera parameters which define the mapping between rays in the camera coordinate system and pixels in...
void ScalarProduct(const Vector3< T > &argvec, T &result) const
scalar product (=inner product) of two vectors, storing the result in result
Definition: Vector3.hh:603
virtual ProjectionParametersBase * Clone() const =0
Covariant virtual copy constructor used in BIAS::Projection.
Unified output of 3D entities via OpenGL or VRML.
Definition: ThreeDOut.hh:349
void SetMinZLocal(const double &minz)
sets minimum z value of unit-ray in local CCS to be accepted as in front of camera (e...
virtual bool DoesPointProjectIntoImage(const HomgPoint3D &X, HomgPoint2D &x, bool IgnoreDistortion=false) const
Checks if 3D point projects into specified image and returns belonging 2D image point.
void Mult(const Vector2< T > &argvec, Vector2< T > &destvec) const
matrix - vector multiplicate this matrix with Vector2, storing the result in destvec calculates: dest...
Definition: Matrix2x2.hh:228
unsigned int GetWidth() const
Definition: ImageBase.hh:312
virtual void UnProjectToRay(const HomgPoint2D &pos, Vector3< double > &origin, Vector3< double > &direction, bool ignoreDistortion=false) const
Calculates the view ray, which belongs to the given position on the image plane, in global coordinate...
const BIAS::UUID & GetUID() const
returns the UUID of the image
Definition: ImageBase.hh:449
3D rotation matrix
Definition: RMatrix.hh:49
virtual void GetFirstBorderPixel(PixelIterator &it)
call this to start a run at the outer boundary of an image.
void CrossProduct(const Vector3< T > &argvec, Vector3< T > &destvec) const
cross product of two vectors destvec = this x argvec
Definition: Vector3.hh:594
void Clear(const StorageType value=0)
sets all pixels to zero/value
Definition: Image.hh:289
const ProjectionParametersBase * GetParameters(unsigned int cam=0) const
const parameter access function
Definition: Projection.hh:194
const unsigned int Size() const
Definition: Vector3.hh:183
This class hides the underlying projection model, like projection matrix, spherical camera...
Definition: Projection.hh:70
unsigned int GetChannelCount() const
returns the number of Color channels, e.g.
Definition: ImageBase.hh:382
Create and represent a 3D triangle mesh.
Definition: TriangleMesh.hh:84
const Matrix< double > & GetVT() const
return VT (=transposed(V))
Definition: SVD.hh:177
int SetProj(const Projection &Proj)
Definition: Camera.hh:106
void Mult(const T &scalar, Vector3< T > &dest) const
Definition: Vector3.hh:332
unsigned int GetHeight() const
Definition: ImageBase.hh:319
class HomgPoint3D describes a point with 3 degrees of freedom in projective coordinates.
Definition: HomgPoint3D.hh:61
void SetPixel(const StorageType &value, const unsigned int &x, const unsigned int &y, const unsigned short int channel=0)
Set the value of a given pixel (x,y) in channel to value.
Definition: Image.hh:171
Can be used to run along the image border.
virtual int GetImageSize(unsigned int &Width, unsigned int &Height) const
Obtain image dimensions.
void Init(unsigned int Width, unsigned int Height, unsigned int channels=1, enum EStorageType storageType=ST_unsignedchar, const bool interleaved=true)
calls Init from ImageBase storageType is ignored, just dummy argument
Definition: Image.cpp:421
bool Equal(const T left, const T right, const T eps)
comparison function for floating point values See http://www.boost.org/libs/test/doc/components/test_...
const StorageType * GetImageData() const
overloaded GetImageData() from ImageBase
Definition: Image.hh:137
void SetUID(const BIAS::UUID &id)
Definition: ImageBase.hh:589
void Sub(const T &scalar, Vector3< T > &dest) const
Substraction with a scalar, storing results in destination vector.
Definition: Vector3.hh:705
void Mult(const Matrix< T > &arg, Matrix< T > &result) const
matrix multiplication, result is not allocated
Definition: Matrix.hh:913
enum EColorModel GetColorModel() const
Definition: ImageBase.hh:407
double x
If using BorderPixel methods these are the coordinates of the pixel.
int UpdateMetaData()
copy P_ and co.
Definition: Camera.cpp:446
Camera parameters which define the mapping between rays in the camera coordinate system and pixels in...
bool IsEmpty() const
Determine whether the Projection is Empty.
Definition: Projection.hh:174
double NormL2() const
Return the L2 norm: sqrt(a^2 + b^2)
Definition: Vector2.hh:430
Matrix< double > Invert()
returns pseudoinverse of A = U * S * V^T A^+ = V * S^+ * U^T
Definition: SVD.cpp:214
bool IsProjValid() const
Definition: Camera.hh:221
Subscript num_rows() const
Definition: cmat.h:319
virtual void UnProjectLocal(const HomgPoint2D &pos, Vector3< double > &pointOnRay, Vector3< double > &direction, bool IgnoreDistortion=false) const
calculates the viewing ray from the camera center (in the camera coordinate system) which belongs to ...
interface class for producing/storing Universally Unique IDentifiers
Definition: UUID.hh:98
Camera parameters which define the mapping between rays in the camera coordinate system and pixels in...
const BIAS::Projection & GetProj() const
Definition: Camera.hh:109
virtual void SetC(const BIAS::Vector3< double > &C)
Set projection center.
Vector3< T > & Normalize()
normalize this vector to length 1
Definition: Vector3.hh:663
void SetZero()
zeroes the image
Definition: ImageBase.hh:83
double NormL2() const
the L2 norm sqrt(a^2 + b^2 + c^2)
Definition: Vector3.hh:633
bool IsValid() const
checks whether this uuid is valid(true) or unitialized(false)
Definition: UUID.hh:210
virtual bool GetNextBorderPixel(PixelIterator &it)
call this iteratively to run at the outer boundary of an image.
const StorageType ** GetImageDataArray() const
overloaded GetImageDataArray() from ImageBase
Definition: Image.hh:153