Basic Image AlgorithmS Library  2.8.0
 All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Groups Pages
ImageBlender.cpp
1 #include <Image/ImageBlender.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 using namespace std;
10 using namespace BIAS;
11 
12 
13 // --- constructor ---
14 // -------------------
15 ImageBlender::ImageBlender() {
16  cylinderHeight_ = 1.0;
17  SetOuputImageSize((unsigned int)(1200));
18 
19  writeVrml_ = false;
20  drawImageBorders_ = false;
21  horizonAlignment_ = HORIZON_ALIGNMENT_X;
22 }
23 
24 
25 // -------------------------------------
26 // --- adds a camera to the database ---
27 // -------------------------------------
28 int ImageBlender::AddCamera(const BIAS::Camera<unsigned char>& camera,
29  unsigned int weightType) {
30 
31  if (!camera.IsProjValid()) {
32  BIASWARN("Projection is invalid! I'll ignore this image...");
33  return -1;
34  }
35 
36  BIAS::UUID uuid = camera.GetUID();
37  if (!uuid.IsValid()) {
38  BIASWARN("UUID is invalid! I'll ignore this image...");
39  return -1;
40  }
41 
42 
43  // convert to rgb
44  Camera<unsigned char> temp(camera);
45  ImageConvert::ToRGB(camera, temp);
46 
47  // compute alpha channel weight
48  Camera<float> temp2;
49  temp2.Init(temp.GetWidth(), temp.GetHeight(), 3);
50 
51  // TODO: use ImageConvert::ConvertST()
52  for (unsigned int y = 0; y < temp.GetHeight(); y++) {
53  for (unsigned int x = 0; x < temp.GetWidth(); x++) {
54  unsigned char *pD = &temp.GetImageDataArray()[y][3 * x];
55  float *pD2 = &temp2.GetImageDataArray()[y][3 * x];
56 
57  pD2[0] = pD[0];
58  pD2[1] = pD[1];
59  pD2[2] = pD[2];
60  }
61  }
62 
63  ComputeAlphaChannelWeight(temp2, weightType);
64 
65  // copy projection data
66  temp2.SetProj(camera.GetProj());
67 
68  // save image to database
69  inputImages_[uuid] = temp2;
70  imageIDs_.push_back(uuid);
71 
72  return 0;
73 }
74 
75 
76 // -------------------------------
77 // --- blends all added images ---
78 // -------------------------------
79 bool ImageBlender::BlendImages(BIAS::Camera<unsigned char> &destination,
80  const ProjectionParametersBase& ppc,
81  const Image<float>* depthmap,
82  double gaussSigma) {
83 
84  if (imageIDs_.size() < 1) {
85  BIASWARN("I need at least one image to blend!");
86  return false;
87  }
88 
89  Camera<float> lowPassCam;
90  Camera<float> highPassCam;
91 
92  Projection TargetProjection(ppc);
93 
95  // set sink cam and depth map pointer (or null to disable 3D)
96  pm.SetSinkCam(TargetProjection, depthmap);
97  InterpolationMethod mappingQuality = MapTrilinear;
98  double superSampling = 1.0; // set to 2.0 for double width supersampling
99 
100  // prepare visualization cyl cam in 3D
101  ThreeDOut scene3D;
102 
103  // init destination image:
104  // - set size
105  // - convert to RGBA
106  // - set alpha values to transparent
107  cout << "size of final image is "
108  << cylindricImageWidth_ << "x" << cylindricImageHeight_ << endl;
109  destination.Release();
110  destination.Init(cylindricImageWidth_, cylindricImageHeight_, 4);
111 
112  for (unsigned int y = 0; y < destination.GetHeight(); y++) {
113  for (unsigned int x = 0; x < destination.GetWidth(); x++) {
114  unsigned char *pDA = &destination.GetImageDataArray()[y][4 * x];
115  pDA[0] = 0;
116  pDA[1] = 0;
117  pDA[2] = 0;
118  pDA[3] = ALPHA_TRANSPARENT;
119  }
120  }
121 
122 
123  // ------------ FILTERING
124 
125  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_FILTERING),
126  "filtering images..." << endl << flush);
127 
128  gaussFilter_.SetSigma(gaussSigma);
129 
130  // low-pass and high-pass filtering for all images
131  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
132  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_FILTERING),
133  "processing image " << i << endl << flush);
134 
135  Projection proj(inputImages_[imageIDs_[i]].GetProj());
136  pm.SetSourceCam(proj);
137 
138  // map orig images to cyl
139  Camera<float> camCyl;
140  camCyl.Init(cylindricImageWidth_, cylindricImageHeight_, 4);
141  for (unsigned int y = 0; y < camCyl.GetHeight(); y++) {
142  for (unsigned int x = 0; x < camCyl.GetWidth(); x++) {
143  float *pD = &camCyl.GetImageDataArray()[y][4 * x];
144  pD[3] = ALPHA_TRANSPARENT;
145  }
146  }
147  pm.Map(inputImages_[imageIDs_[i]],
148  camCyl,
149  mappingQuality, false,
150  superSampling);
151 
152  // --- perform low-pass filtering ---
153  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_FILTERING),
154  "low-pass filtering with sigma=" << gaussSigma << "..."
155  << endl << flush);
156 
157  // low-pass filtering
158  gaussFilter_.Filter(camCyl, lowPassCam);
159 
160  // weight channel is now blurred, too -> we need to fix this
161  for (unsigned int y = 0; y < camCyl.GetHeight(); y++) {
162  for (unsigned int x = 0; x < camCyl.GetWidth(); x++) {
163  float *pC = &camCyl.GetImageDataArray()[y][4 * x];
164  float *pL = &lowPassCam.GetImageDataArray()[y][4 * x];
165  pL[3] = pC[3];
166  }
167  }
168 
169  // store low-pass filtered image to disk
170  BIASCDOUT(D_IMAGEBLENDER_FILTERING,
171  "writing temp low-pass image to disk... " << endl << flush);
172  //ImageIO::Save(string("tmp_pano_blender_low-pass_") + FileHandling::toString(i)
173  // + ".mip",
174  // lowPassCam);
175  ImageIO::Save(string("tmp_pano_blender_low-pass_")
176  + FileHandling::toString(i)
177  + ".mip",
178  lowPassCam);
179 
180  if (DebugLevelIsSet(D_IMAGEBLENDER_FILTERING)) {
181 #ifdef BLENDER_FIX_ME
183  temp.Init(lowPassCam.GetWidth(), lowPassCam.GetHeight(), 4);
184  ImageConvert::ConvertST(dynamic_cast<const BIAS::ImageBase>(lowPassCam),
185  dynamic_cast<const BIAS::ImageBase>(temp),
186  ImageBase::CM_RGBA);
187 
188  ostringstream s;
189  s << "100_low-pass_image_" << i;
190  cout << "writing " << s.str() << " to disk..." << endl;
191 
192  s << ".png";
193  ImageIO::Save(s.str(), temp);
194 #endif
195  }
196 
197  // --- perform high-pass filtering ---
198  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_FILTERING),
199  "high-pass filtering... "<< endl << flush);
200 
201  // high-pass filtering
202  highPassCam.Release();
203  highPassCam.Init(lowPassCam.GetWidth(), lowPassCam.GetHeight(), 4);
204 
205  // subtract low-pass filtered image from normal image to
206  // get high frequencies
207  for (unsigned int y = 0; y < highPassCam.GetHeight(); y++) {
208  for (unsigned int x = 0; x < highPassCam.GetWidth(); x++) {
209  float *pDC = &camCyl.GetImageDataArray()[y][4 * x];
210  float *pDL = &lowPassCam.GetImageDataArray()[y][4 * x];
211  float *pDH = &highPassCam.GetImageDataArray()[y][4 * x];
212 
213  pDH[0] = pDC[0] - pDL[0];
214  pDH[1] = pDC[1] - pDL[1];
215  pDH[2] = pDC[2] - pDL[2];
216  pDH[3] = pDC[3]; // take alpha value from weighted input image
217  }
218  }
219 
220  // store high-pass filtered image to disk
221  BIASCDOUT(D_IMAGEBLENDER_FILTERING,
222  "writing temp high-pass image to disk... "<< endl << flush);
223  //ImageIO::Save(string("tmp_pano_blender_high-pass_") + FileHandling::toString(i)
224  // + ".mip",
225  // highPassCam);
226  ImageIO::Save(string("tmp_pano_blender_high-pass_")
227  + FileHandling::toString(i)
228  + ".mip",
229  highPassCam);
230 
231  if (DebugLevelIsSet(D_IMAGEBLENDER_FILTERING)) {
232 #ifdef BLENDER_FIX_ME
234  temp.Init(highPassCam.GetWidth(), highPassCam.GetHeight(), 4);
235  ImageConvert::ConvertST(highPassCam, temp, ImageBase::CM_RGBA);
236 
237  ostringstream s;
238  s << "100_high-pass_image_" << i;
239  cout << "writing " << s.str() << " to disk..." << endl;
240 
241  s << ".png";
242  ImageIO::Save(s.str(), temp);
243 #endif
244  }
245  }
246 
247  // input images can be erased to save memory (only if drawImageBorders_ = false)
248  if (!drawImageBorders_) {
249  inputImages_.clear();
250  }
251 
252  // ----------- BLENDING
253  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_BLENDING),
254  endl << "blending images");
255 
256  float r, g, b, w, rLow, gLow, bLow, wLow;
257 
258  Camera<float> destLow;
259  Camera<float> destHigh;
260  destLow.Init(destination.GetWidth(), destination.GetHeight(),
261  destination.GetChannelCount());
262  destHigh.Init(destination.GetWidth(), destination.GetHeight(),
263  destination.GetChannelCount());
264  destination.Clear(0);
265  destLow.Clear(0.0);
266  destHigh.Clear(0.0);
267  unsigned char *pDest = destination.GetImageData();
268  float *pDLow = destLow.GetImageData();
269  float *pDHigh = destHigh.GetImageData();
270 
271  // run over all images
272  for (unsigned int imageCount = 0; imageCount < imageIDs_.size(); imageCount++) {
273 
274  // load current low/high pair
275  ImageIO::Load(string("tmp_pano_blender_low-pass_") + FileHandling::toString(imageCount)
276  + ".mip",
277  lowPassCam);
278  ImageIO::Load(string("tmp_pano_blender_high-pass_") + FileHandling::toString(imageCount)
279  + ".mip",
280  highPassCam);
281 
282  // run over all pixels
283  for (unsigned int i = 0;
284  i < destination.GetWidth() * destination.GetHeight() * 4;
285  i += 4) {
286 
287  // produce some output every some pixels
288  if ((i % 30000) == 0) {
289  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_BLENDING), ".");
290  }
291 
292  // draw low frequency with linear weight
293  float *pL = lowPassCam.GetImageData();
294 
295  if (pL[i + 3] > 0) {
296  r = pDLow[i + 0];
297  g = pDLow[i + 1];
298  b = pDLow[i + 2];
299  w = pDLow[i + 3];
300 
301  rLow = float(pL[i + 0]);
302  gLow = float(pL[i + 1]);
303  bLow = float(pL[i + 2]);
304  wLow = float(pL[i + 3]);
305 
306  r = (w * r + wLow * rLow) / (w + wLow);
307  g = (w * g + wLow * gLow) / (w + wLow);
308  b = (w * b + wLow * bLow) / (w + wLow);
309  w = w + wLow;
310 
311  pDLow[i + 0] = r;
312  pDLow[i + 1] = g;
313  pDLow[i + 2] = b;
314  pDLow[i + 3] = w;
315  }
316 
317  // add high frequency from image with highest weight
318  float *pH = highPassCam.GetImageData();
319 
320  if (pH[i + 3] > pDHigh[i + 3]) {
321  pDHigh[i + 0] = pH[i + 0];
322  pDHigh[i + 1] = pH[i + 1];
323  pDHigh[i + 2] = pH[i + 2];
324  pDHigh[i + 3] = pH[i + 3];
325  }
326  }
327  }
328 
329  // store result in destination image
330  for (unsigned int i = 0;
331  i < destination.GetWidth() * destination.GetHeight() * 4;
332  i += 4) {
333 
334  if (pDLow[i + 3] > 0.0) {
335  // avoid overflow and underflow
336  float sumR = pDLow[i + 0] + pDHigh[i + 0];
337  float sumG = pDLow[i + 1] + pDHigh[i + 1];
338  float sumB = pDLow[i + 2] + pDHigh[i + 2];
339  if (sumR > 255.0) sumR = 255.0;
340  if (sumG > 255.0) sumG = 255.0;
341  if (sumB > 255.0) sumB = 255.0;
342  if (sumR < 0.0) sumR = 0.0;
343  if (sumG < 0.0) sumG = 0.0;
344  if (sumB < 0.0) sumB = 0.0;
345 
346  // store result in image
347  pDest[i + 0] = (unsigned char)(sumR);
348  pDest[i + 1] = (unsigned char)(sumG);
349  pDest[i + 2] = (unsigned char)(sumB);
350  pDest[i + 3] = ALPHA_OPAQUE;
351  }
352  }
353 
354  BIASCDOUT((D_IMAGEBLENDER_MINIMAL | D_IMAGEBLENDER_BLENDING),
355  endl << flush);
356 
357  // draw border of images into cylinder
358  if (drawImageBorders_) {
359  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
360  cout << "drawing border of image " << i << endl;
361 
362  PixelIterator it;
363 
364  Projection projection;
365  projection = inputImages_[imageIDs_[i]].GetProj();
366 
368  ppp = (ProjectionParametersPerspective*)(projection.GetParameters());
369 
370  // run over all border pixels of image
371  ppp->GetFirstBorderPixel(it);
372  do {
373  HomgPoint2D p2DHom(it.x, it.y);
374 
375  // compute ray and project it to cylinder
376  Vector3<double> ray, p;
377  ppp->UnProjectToRay(p2DHom, p, ray);
378  HomgPoint3D p3DHom(ray); // interface conversion
379 
380  // if the cylinder geometry is correct, all borders should be in the
381  // cylinder image, but you never know:
382  if (!ppc.DoesPointProjectIntoImage(p3DHom, p2DHom)) continue;
383 
384  // paint border pixel in red
385  destination.SetPixel(255,
386  (unsigned int)(p2DHom[0]),
387  (unsigned int)(p2DHom[1]),
388  0);
389  destination.SetPixel(0,
390  (unsigned int)(p2DHom[0]),
391  (unsigned int)(p2DHom[1]),
392  1);
393  destination.SetPixel(0,
394  (unsigned int)(p2DHom[0]),
395  (unsigned int)(p2DHom[1]),
396  2);
397 
398  // ensure opaque alpha channel
399  destination.SetPixel(ALPHA_OPAQUE,
400  (unsigned int)(p2DHom[0]),
401  (unsigned int)(p2DHom[1]),
402  3);
403  } while (ppp->GetNextBorderPixel(it));
404  }
405  }
406 
407  // write cyl projection to output image
408  Projection proj(ppc);
409  destination.SetProj(proj);
410  destination.UpdateMetaData();
411 
412  // visualize cyl cam in 3D
413  // TODO: dafuer sorgen, dass TriangleMesh auch RGBA nimmt
414  if (writeVrml_) {
415  Image<unsigned char> rgbim;
416  ImageConvert::Convert(destination, rgbim, ImageBase::CM_RGB);
417 
418  TriangleMesh triangleMesh;
419  triangleMesh.GenerateTexturedCamera(&ppc,
420  rgbim,
421  1.0,
422  1.0,
423  1.0);
424  scene3D.AddTriangleMesh(triangleMesh);
425 
426  scene3D.VRMLOut("cylindricPanorama.wrl");
427  }
428 
429  return true;
430 }
431 
432 
433 void ImageBlender::
434 ComputeCylCamGeometry(ProjectionParametersCylindric &ppc) {
435 
436  if (imageIDs_.size() < 1) {
437  BIASWARN("I need at least one image to compute destination geometry!");
438  return;
439  }
440 
441  // init projection parameters
442  Vector3<double> C(inputImages_[imageIDs_[0]].GetProj().GetC());
443  RMatrix RTarget(inputImages_[imageIDs_[0]].GetProj().GetR());
444 
445  // compute orientation R:
446  // get axis of each image that is assumed to lie in a plane
447  // and save them in a matrix
448  Matrix<double> M(2 * imageIDs_.size(), 3, 0.0);
449 
450 
451 
452  unsigned int testAlignment = horizonAlignment_;
453  if (horizonAlignment_== HORIZON_ALIGNMENT_AUTO)
454  testAlignment = 0;
455 
456  double bestResiduum = HUGE_VAL;
457  int selectedmode = -1;
458  Vector3<double> besth(0,1,0), h(0,1,0);
459  do {
460  // regard alignment of horizon
461  if (testAlignment == HORIZON_ALIGNMENT_X) {
462  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
463  RMatrix R = inputImages_[imageIDs_[i]].GetProj().GetR();
464  for (unsigned int c=0;c<3;c++) M[i][c] = R[c][0];
465  }
466  } else if (testAlignment == HORIZON_ALIGNMENT_Y) {
467  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
468  RMatrix R = inputImages_[imageIDs_[i]].GetProj().GetR();
469  for (unsigned int c=0;c<3;c++) M[i][c] = R[c][1];
470  }
471  } else if (testAlignment == HORIZON_ALIGNMENT_UNKNOWN) {
472  // HORIZON_ALIGNMENT_UNKNOWN
473  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
474  RMatrix R = inputImages_[imageIDs_[i]].GetProj().GetR();
475  for (unsigned int c=0;c<3;c++) M[i][c] = R[c][2];
476  }
477  }
478 
479  // now we can solve the LES M*h=0, where h will become
480  // the y axis of the cylindrical camera
481  SVD svd(M, 0.1, false);
482  // the smallest eigenvalue may become quite large, so we grab
483  // the solution vector "by hand"
484  Matrix<double> vt;
485  vt = svd.GetVT();
486 
487  // now get the first, the second and the third best null vectors
488  Vector3<double> secondh(0,0,1), thirdh(0,0,1);
489  for (unsigned int i = 0; i < 3; i++) {
490  h[i] = vt[vt.num_rows() - 1][i];
491  secondh[i] =vt[vt.num_rows() - 2][i];
492  thirdh[i] =vt[0][i];
493  }
494  // normalize them for fair comparison
495  h.Normalize();
496  secondh.Normalize();
497  thirdh.Normalize();
498  // M*h is a vector containing the scalar products (cosines) of all camera
499  // vectors with the solution. Its norm is an "average" cosine and should be
500  // zero since we search a perpendicular vector cos(90)=0
501  double residuum = (M*h).NormL2()/double(h.Size());
502  double secondresiduum = (M*secondh).NormL2()/double(h.Size());
503  double thirdresiduum = (M*thirdh).NormL2()/double(h.Size());
504  // avoid pathological case and division by zero
505  if (residuum<1e-10) residuum = 1e-10;
506  if (secondresiduum<1e-10) secondresiduum=1e-10;
507  if (thirdresiduum<1e-10) thirdresiduum=1e-10;
508  //cout<<"r1="<<residuum<<" r2="<<secondresiduum<<" r3="<<thirdresiduum<<endl;
509  //cout<<"S is "<<svd.GetS()<<endl;
510  // we dont want to get the rotation axis, therefore if the null space is
511  // two-dimensional, reject this solution! Two dimensional means here that
512  // the residuum of the second solution is closer to the best than to the
513  // worst solution on an order of magnitude scale
514  bool nullspacedim2 = thirdresiduum / secondresiduum > 10.0;
515  // (log(thirdresiduum / residuum) - 2 * secondresiduum / residuum)>0;
516  //if (nullspacedim2) cout<<"2-dim. null space!"<<endl;
517  //else cout<<"1-dim. null space!"<<endl;
518  if (residuum<bestResiduum && !nullspacedim2) {
519  // found better axis!
520  besth = h;
521  bestResiduum = residuum;
522  selectedmode = testAlignment;
523  }
524 
525  if (horizonAlignment_== HORIZON_ALIGNMENT_AUTO) testAlignment++;
526  } while (testAlignment!=horizonAlignment_);
527  h = besth;
528  if (horizonAlignment_ == HORIZON_ALIGNMENT_AUTO) switch (selectedmode) {
529  case HORIZON_ALIGNMENT_X:cout<<"Applying horizontal alignment"<<endl;break;
530  case HORIZON_ALIGNMENT_Y:cout<<"Applying vertical alignment"<<endl;break;
531  default:cout<<"Applying optical axis alignment"<<endl;break;
532  }
533 
534  horizonAlignment_ = selectedmode;
535 
536 
537 
538 
539 
540 
541 
542 
543 
544 
545 
546  // project the choosen axes of all cams onto the plane defined
547  // by its normal vector h using parallel projection:
548  vector< Vector3<double> > projectedVectors;
549 
550  for (unsigned int imCount = 0; imCount < imageIDs_.size(); imCount++) {
551  Vector3<double> orig, proj, temp;
552  RMatrix Rot = inputImages_[imageIDs_[imCount]].GetProj().GetR();
553  //orig[0] = Rot[0][2];
554  //orig[1] = Rot[1][2];
555  //orig[2] = Rot[2][2];
556 
557  // regard alignment of horizon
558  if (horizonAlignment_ == HORIZON_ALIGNMENT_X) {
559  orig[0] = Rot[0][0];
560  orig[1] = Rot[1][0];
561  orig[2] = Rot[2][0];
562  }
563  else if (horizonAlignment_ == HORIZON_ALIGNMENT_Y) {
564  orig[0] = Rot[0][1];
565  orig[1] = Rot[1][1];
566  orig[2] = Rot[2][1];
567  }
568  else { // HORIZON_ALIGNMENT_UNKNOWN and illegal values
569  orig[0] = Rot[0][2];
570  orig[1] = Rot[1][2];
571  orig[2] = Rot[2][2];
572  }
573 
574  h.Mult(orig.ScalarProduct(h), temp);
575  orig.Sub(temp, proj);
576 
577  projectedVectors.push_back(proj);
578  }
579 
580  // choose vectors a and v that span the plane
581  // a is the first camera's projected optical axis
582  BIASASSERT(!projectedVectors.empty());
583  Vector3<double> a(projectedVectors[0]);
584  a.Normalize();
585 
586  // v must be orthogonal to h and a
587  Vector3<double> v;
588  a.CrossProduct(h, v);
589  v.Normalize();
590 
591  // now calc rotation so that pano is centered in the cyl
592  // for this we need the angles between successive optical axis to find the
593  // biggest "gap"
594 
595  // first transform the projected vectors into coordinate system of the plane.
596  // {a, v} is a base of the plane space, so multliply each vec with a
597  // 2x3-matrix that contains a and v als row vectors
598  vector< Vector2<double> > vectorsInPlaneSpace(projectedVectors.size());
599 
600  Matrix<double> planeTransform(2, 3, 0);
601  planeTransform[0][0] = a[0];
602  planeTransform[0][1] = a[1];
603  planeTransform[0][2] = a[2];
604  planeTransform[1][0] = v[0];
605  planeTransform[1][1] = v[1];
606  planeTransform[1][2] = v[2];
607 
608  for (unsigned int i = 0; i < projectedVectors.size(); i++) {
609  // interface conversion
610  Vector<double> v1(projectedVectors[i]);
611  Vector<double> v2;
612 
613  // transform each vec to plane's 2d coord system
614  planeTransform.Mult(v1, v2);
615  vectorsInPlaneSpace[i] = v2;
616 
617  // normalize vec
618  double norm = vectorsInPlaneSpace[i].NormL2();
619  vectorsInPlaneSpace[i] /= norm;
620 
621  cout << "vec " << i << " " << vectorsInPlaneSpace[i]
622  << " norm " << vectorsInPlaneSpace[i].NormL2()<< endl;
623  }
624 
625  // we choose vectorsInPlaneSpace[0] as reference.
626  // note: vectorsInPlaneSpace[0] points in direction of x axis
627 
628  // calc angles between ref vec and all other vecs
629  vector<double> angles(vectorsInPlaneSpace.size());
630 
631  for (unsigned int i = 0; i < vectorsInPlaneSpace.size(); i++) {
632  angles[i] = CalcAngleToXAxis(vectorsInPlaneSpace[i], true);
633  cout << i << ". angle relative to reference vector " << angles[i] << endl;
634  }
635 
636  // determine the order of the vecs and calc angles of successive vecs
637  vector<bool> visited(vectorsInPlaneSpace.size());
638  vector<double> successiveAngles;
639  vector<int> indices;
640  double sumAngles = 0.0;
641  // init vector
642  for (unsigned int i = 0; i < visited.size(); i++) {
643  visited[i] = false;
644  }
645 
646  // mark ref axis as visited, since angle between ref vec and ref vec is 0
647  visited[0] = true;
648 
649  for (unsigned int i = 1; i < vectorsInPlaneSpace.size(); i++) {
650  double smallestAngle = 361;
651  int index = -1;
652 
653  // look for next axis
654  for (unsigned int j = 0; j < vectorsInPlaneSpace.size(); j++) {
655  if (!visited[j] && (angles[j] < smallestAngle)) {
656  smallestAngle = angles[j];
657  index = j;
658  }
659  }
660  BIASASSERT(index != -1);
661 
662  visited[index] = true;
663  double angle;
664  if (successiveAngles.empty()) {
665  angle = smallestAngle;
666  }
667  else {
668  angle = smallestAngle - successiveAngles[successiveAngles.size() - 1];
669  }
670  successiveAngles.push_back(angle);
671  indices.push_back(index);
672 
673  sumAngles += angle;
674  }
675 
676  double sumAnglesRemainder = sumAngles - int(sumAngles);
677  sumAngles = int(sumAngles) % 360;
678  sumAngles += sumAnglesRemainder;
679 
680  cout << "sum of all angles is " << sumAngles << endl;
681 
682  BIASASSERT(sumAngles <= 360);
683 
684  // add angle between last axis und our reference axis, i.e. close the circle
685  successiveAngles.push_back(360 - sumAngles);
686  indices.push_back(0);
687 
688  for (unsigned int i = 0; i < indices.size(); i++) {
689  cout << "index " << indices[i]
690  << " angle " << successiveAngles[i] << endl;
691  }
692 
693  // search for vecs with greates "gap" between them
694  double biggestAngle = 0.0;
695  int indexL = -1, indexR = -1;
696 
697  for (unsigned int i = 0; i < indices.size(); i++) {
698  if (successiveAngles[i] > biggestAngle) {
699  biggestAngle = successiveAngles[i];
700  if (i == 0) {
701  indexL = indices[indices.size() - 1];
702  }
703  else {
704  indexL = indices[i - 1];
705  }
706  indexR = indices[i];
707  }
708  }
709 
710  cout << "biggest gap is " << biggestAngle << " degrees between axis "
711  << indexL << " and " << indexR << endl;
712 
713  // calc vector that lies in the middle of this two vecs
714  Vector2<double> midVec;
715  if (biggestAngle != 180.0) {
716  midVec = (vectorsInPlaneSpace[indexL] + vectorsInPlaneSpace[indexR]) / 2.0;
717  }
718  else { // biggest gap is exactly 180 degrees
719  // take vectorsInPlaneSpace[indexL] and rotate 90 degrees to get midVec
720  Matrix2x2<double> rotTmp;
721 
722  rotTmp[0][0] = 0;
723  rotTmp[0][1] = 1;
724  rotTmp[1][0] = -1;
725  rotTmp[1][1] = 0;
726 
727  cout << "found 180 deg gap - using rot " << rotTmp << endl;
728 
729  cout << "with vec " << vectorsInPlaneSpace[indexL] << endl;
730 
731  rotTmp.Mult(vectorsInPlaneSpace[indexL], midVec);
732 
733  cout << "mid vec is " << midVec << endl;
734  }
735  // normalize vec
736  midVec /= midVec.NormL2();
737  cout << "optical axis after norm " << midVec << endl;
738 
739  // transform midVec back to 3d space and use it as cylinder's optical axis
740  Matrix<double> planeTransformInv(3, 2, 0);
741  SVD svdInvert;
742  planeTransformInv = svdInvert.Invert(planeTransform);
743 
744  // interface conversion
745  Vector<double> v1(midVec);
746  Vector<double> v2;
747 
748  planeTransformInv.Mult(v1, v2);
749  a = v2;
750 
751  cout << "final optical axis is " << a << endl;
752 
753  // v must be orthogonal to h and a
754  a.CrossProduct(h, v);
755  v.Normalize();
756 
757  // modify RTarget
758  RTarget[0][0] = h[0];
759  RTarget[1][0] = h[1];
760  RTarget[2][0] = h[2];
761  RTarget[0][1] = v[0];
762  RTarget[1][1] = v[1];
763  RTarget[2][1] = v[2];
764  RTarget[0][2] = a[0];
765  RTarget[1][2] = a[1];
766  RTarget[2][2] = a[2];
767 
768  ppc.SetC(C);
769  ppc.SetR(RTarget);
770 
771  CheckFov(ppc);
772 
773  // CheckFov alters ppc so we need to set this again
774  // TODO: dies hier mit nach CheckFov()
775  ppc.SetC(C);
776  ppc.SetR(RTarget);
777 }
778 
779 
780 inline double ImageBlender::CalcAngleToXAxis(const Vector2<double> &v,
781  bool wantDegrees) {
782  Vector2<double> xAxis(1.0, 0.0);
783  double ret = 0.0;
784  double temp = 0.0;
785 
786  // the angle of two vecs x, y is computed as follows:
787  // angle(x, y) = arccos ( (x * y) / (|x| * |y|) )
788  temp = xAxis.ScalarProduct(v);
789  temp /= xAxis.NormL2() * v.NormL2();
790  ret = acos(temp);
791 
792  if (wantDegrees) { // want degrees, not rad
793  ret = ret * 180 / M_PI;
794  }
795  // if y component < 0 then this vec is "to the left", i.e. ccw
796  // since this equation only yields values in [0, ... , 180[, we need to
797  // consider the algebraic sign of the y component of the other vec,
798  // which determines the relative direction.
799  if (v[1] < 0) {
800  if (wantDegrees) {
801  ret = 360.0 - ret;
802  }
803  else {
804  ret = 2 * M_PI - ret;
805  }
806  }
807 
808  return ret;
809 }
810 
811 
812 inline double ImageBlender::CalcAngleToYAxis(const Vector2<double> &v,
813  bool wantDegrees) {
814  Vector2<double> yAxis(0.0, 1.0);
815  double ret = 0.0;
816  double temp = 0.0;
817 
818  // the angle of two vecs x, y is computed as follows:
819  // angle(x, y) = arccos ( (x * y) / (|x| * |y|) )
820  temp = yAxis.ScalarProduct(v);
821  temp /= yAxis.NormL2() * v.NormL2();
822  ret = acos(temp);
823 
824  if (wantDegrees) { // want degrees, not rad
825  ret = ret * 180 / M_PI;
826  }
827  // if x-component <= 0 then this vec is "to the left", i.e. ccw
828  // since this equation only yields values in [0, ... , 180[, we need to
829  // consider the algebraic sign of the x component of the other vec,
830  // which determines the relative direction.
831  if (v[0] <= 0) {
832  if (wantDegrees) {
833  ret = 360.0 - ret;
834  }
835  else {
836  ret = 2 * M_PI - ret;
837  }
838  }
839 
840  return ret;
841 }
842 
843 
844 inline void ImageBlender::CheckFov(ProjectionParametersCylindric &ppc) {
845 
846  cout << "checking fov of cameras..." << endl;
847 
848  double minX=1e100, minY=1e100,
849  maxX=-1e100, maxY=-1e100; // the borders of panorama in cylinder coords
850  Vector3<double> p;
851 
852  for (unsigned int i = 0; i < imageIDs_.size(); i++) {
853  double minZ = 1.0;
854  PixelIterator it;
855 
856  Projection proj;
857  proj = inputImages_[imageIDs_[i]].GetProj();
858 
860 
862 
863  // run over all border pixels of image
864  ppp->GetFirstBorderPixel(it);
865  do {
866 
867  HomgPoint2D x(it.x, it.y);
868 
869  // --- check FOV of input cam
870 
871  // compute ray in camera coordinate system
872  Vector3<double> ray;
873  ppp->UnProjectLocal(x, p, ray);
874 
875 #ifdef BIAS_DEBUG
876  double norm = 0;
877  norm = ray.NormL2();
878  if (!Equal(norm, 1.0)) {
879  BIASERR("ray not unit length: " << ray);
880  BIASABORT;
881  }
882 #endif
883  if (ray[2] < minZ) {
884  minZ = ray[2];
885  }
886 
887  // --- check FOV of cyl cam
888 
889  // compute ray and project it to cylinder
890  ppp->UnProjectToRay(x, p, ray);
891  HomgPoint3D p3DHom(ray); // interface conversion
892 
893  // if the cylinder geometry is correct, all borders should be in the
894  // cylinder image, but you never know:
895  if (!ppc.DoesPointProjectIntoImage(p3DHom, x)) continue;
896 
897  if (x[0] < minX) minX = x[0];
898  if (x[1] < minY) minY = x[1];
899  if (x[0] > maxX) maxX = x[0];
900  if (x[1] > maxY) maxY = x[1];
901 
902  } while (ppp->GetNextBorderPixel(it));
903 
904  //double angle = asin(minZ) * 360.0 / M_PI;
905 #ifdef BIAS_DEBUG
906  double angle = 0.0;
907  angle = asin(minZ);
908 
909  BIASCDOUT(D_IMAGEBLENDER_GEOMETRY,
910  "Max field of view of cam "
911  << i << " is " << angle << endl << flush);
912 #endif
913  // add some safety margin
914  minZ *= 0.8;
915 
916  // prevent strange cases
917  if (minZ < 0.01) {
918  minZ = 0.01;
919  }
920 
921  // clip everything with smaller z as behind camera
922  ppp->SetMinZLocal(minZ);
923  inputImages_[imageIDs_[i]].SetProj(Projection(*ppp));
924  }
925 
926  // calc FOV of cyl
927  double halfWidth = cylindricImageWidth_ / 2.0;
928  double halfHeight = cylindricImageHeight_ / 2.0;
929 
930  double xMin = -1.0 * ((halfWidth - minX) / halfWidth);
931  double xMax = (maxX - halfWidth) / halfWidth;
932 
933  double phiMin = -M_PI * ((halfHeight - minY) / halfHeight);
934  double phiMax = M_PI * ((maxY - halfHeight) / halfHeight);
935 
936  double factor = cylindricImageHeight_ / (maxY - minY);
937  cylindricImageWidth_ = (unsigned int)((maxX - minX) * factor);
938 
939  ProjectionParametersCylindric ppc2(xMin, xMax,
940  phiMin, phiMax,
941  cylindricImageWidth_,
942  cylindricImageHeight_);
943 
944  ppc = ppc2;
945 }
946 
947 
948 inline double CalculateWeightCircular(int dx,
949  int dy,
950  double distCenter) {
951  double ret = (1.0 - sqrt(double(dx * dx + dy * dy)) / distCenter) * 255.0;
952 
953  if (ret < 0.0)
954  return 0.0;
955  else
956  return ret;
957 }
958 
959 
960 inline double CalculateWeightRectangular(unsigned int w,
961  unsigned int h,
962  unsigned int x,
963  unsigned int y,
964  unsigned int mx,
965  unsigned int my) {
966  // if pixel is near border assign weight = 0
967  //if (x == 0 || y == 0 || x == (w - 1) || y == (h - 1)) return 0;
968  //if (x == 1 || y == 1 || x == (w - 2) || y == (h - 2)) return 0;
969 
970  double m, mp;
971  bool aboveAC, aboveBD;
972  double alphaValue = 0.0;
973 
974  // determine in wich of the four triangles 1, 2, 3, 4 the pixel lies:
975  // A-----------D
976  // | \ 1 / |
977  // | \ / |
978  // | 4 X 2 |
979  // | / \ |
980  // | / 3 \ |
981  // B-----------C
982 
983  m = double(h) / double(w);
984 
985  // determine if the pixel is above or below the line AC
986  mp = double(y) / double(x);
987  if (mp > m) {
988  aboveAC = false;
989  }
990  else {
991  aboveAC = true;
992  }
993 
994  // determine if the pixel is above or below the line BD
995  mp = double(y) / double(mx + (mx - x));
996  if (mp > m) {
997  aboveBD = false;
998  }
999  else {
1000  aboveBD = true;
1001  }
1002 
1003  // compute weight
1004  if (aboveAC && aboveBD) { // triangle 1
1005  alphaValue = double(y) / double(my);
1006  }
1007  else if (aboveAC && !aboveBD) { // triangle 2
1008  alphaValue = double(w - x) / double(mx);
1009  }
1010  else if (!aboveAC && !aboveBD) { // triangle 3
1011  alphaValue = double(h - y) / double(my);
1012  }
1013  else if (!aboveAC && aboveBD) { // triangle 4
1014  alphaValue = double(x) / double(mx);
1015  }
1016 
1017  return alphaValue * 255.0;
1018 }
1019 
1020 
1021 // -----------------------------------------
1022 // --- computes the weight of each pixel ---
1023 // -----------------------------------------
1024 void ImageBlender::ComputeAlphaChannelWeight(BIAS::Image<float> &image,
1025  unsigned int weightType) {
1026  // TODO: calculate weight only for one quarter of the image, rest is symmetric
1027 
1028  ConvertImageToRGBA(image);
1029 
1030  const unsigned int w = image.GetWidth();
1031  const unsigned int h = image.GetHeight();
1032 
1033  const unsigned int mx = w / 2;
1034  const unsigned int my = h / 2;
1035 
1036  // calc length of the line from (0, 0) to (mx, my)
1037  const double distCenter = sqrt(double(mx * mx + my * my));
1038 
1039 
1040  // if WEIGHT_TYPE_MIX is used, mixRatio defines the ratio between
1041  // rectangular and circular weight
1042  // e.g. if mixRation = 0.3 then use 30 % rect weight and 70 % circ weight
1043  // if mixRatio is less than 1.0, gain ensures that the highest weights in the
1044  // final image (which are at the center of the image) are 1.0
1045  const double mixRatio = 0.5; // must be in [0, 1]
1046  const double gain = 1.0 / (mixRatio * (1.0 - mixRatio));
1047 
1048  for (unsigned int x = 0; x < w; x++) {
1049  for (unsigned int y = 0; y < h; y++) {
1050 
1051  unsigned int dx = mx - x;
1052  unsigned int dy = my - y;
1053 
1054  double alphaValue = 0.0;
1055 
1056  double rectVal, circVal;
1057 
1058  switch (weightType) {
1059  case WEIGHT_TYPE_NONE:
1060  alphaValue = ALPHA_OPAQUE;
1061  break;
1062 
1063  case WEIGHT_TYPE_CIRCULAR:
1064  alphaValue = CalculateWeightCircular(dx, dy, distCenter);
1065  break;
1066 
1067  case WEIGHT_TYPE_CIRCULAR_FIT:
1068  if (mx < my) {
1069  alphaValue = CalculateWeightCircular(dx, dy, mx);
1070  }
1071  else {
1072  alphaValue = CalculateWeightCircular(dx, dy, my);
1073  }
1074  break;
1075 
1076  case WEIGHT_TYPE_RECTANGULAR:
1077  alphaValue = CalculateWeightRectangular(w, h, x, y, mx, my);
1078  break;
1079 
1080  case WEIGHT_TYPE_MIX:
1081  rectVal = CalculateWeightRectangular(w, h, x, y, mx, my) / 255.0;
1082  circVal = CalculateWeightCircular(dx, dy, distCenter) / 255.0;
1083  alphaValue = (mixRatio * rectVal)
1084  * ((1.0 - mixRatio) * circVal)
1085  * gain
1086  * 255.0;
1087  break;
1088 
1089  case WEIGHT_TYPE_RECT_NONLINEAR:
1090  rectVal = CalculateWeightRectangular(w, h, x, y, mx, my) / 255.0;
1091  alphaValue = rectVal * rectVal * 255.0;
1092  break;
1093 
1094  default:
1095  BIASERR("Unknown weight type!");
1096  }
1097 
1098  // avoid clipping
1099  if (alphaValue < 0.0) alphaValue = 0.0;
1100  if (alphaValue > 255.0) alphaValue = 255.0;
1101 
1102  image.GetImageDataArray()[y][4 * x + 3] = alphaValue;
1103  }
1104  }
1105 }
1106 
1107 
1108 // ------------------------------
1109 // --- converts image to RGBA ---
1110 // ------------------------------
1111 void ImageBlender::ConvertImageToRGBA(BIAS::Image<float> &image) {
1112 
1113  // convert image to RGB
1114  if (image.GetColorModel() != ImageBase::CM_RGB) {
1115  Image<float> tempImage;
1116  ImageConvert::Convert(image, tempImage, ImageBase::CM_RGB);
1117  image = tempImage;
1118  }
1119 
1120  // add alphachannel
1121  Image<float> alphaImage(image.GetWidth(),
1122  image.GetHeight(), 4);
1123 
1124  alphaImage.SetUID(image.GetUID());
1125 
1126  alphaImage.SetColorModel(ImageBase::CM_RGBA);
1127  for (unsigned int y = 0; y < alphaImage.GetHeight(); y++) {
1128  for (unsigned int x = 0; x < alphaImage.GetWidth(); x++) {
1129  float *pD = &image.GetImageDataArray()[y][3 * x];
1130  float *pDA = &alphaImage.GetImageDataArray()[y][4 * x];
1131  *pDA++ = *pD++;
1132  *pDA++ = *pD++;
1133  *pDA++ = *pD++;
1134  *pDA++ = ALPHA_OPAQUE;
1135  }
1136  }
1137 
1138  image = alphaImage;
1139 }
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.
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 SetSinkCam(const Projection &P, const Image< float > *sinkdepth=NULL)
Set your sink projection before calling Map(),.
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
void SetSourceCam(const Projection &P)
Set your source projection before calling Map()
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
int Map(const Image< InputStorageType > &src, Image< OutputStorageType > &sink, InterpolationMethod=MapTrilinear, bool newSink=false, double SuperSampling=1.0)
backward mapping with various interpolations
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.
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...
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
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