DGtalTools  1.5.beta
heightfield2shading.cpp
1 
30 #include <iostream>
31 #include <fstream>
32 #include "DGtal/base/Common.h"
33 #include "DGtal/helpers/StdDefs.h"
34 #include "DGtal/images/ImageContainerBySTLVector.h"
35 #include "DGtal/io/readers/GenericReader.h"
36 #include "DGtal/io/writers/GenericWriter.h"
37 #include "DGtal/io/writers/PPMWriter.h"
38 #include "DGtal/images/ImageSelector.h"
39 #include "DGtal/io/readers/PointListReader.h"
40 #include "DGtal/images/ConstImageAdapter.h"
41 #include "DGtal/kernel/BasicPointFunctors.h"
42 
43 #include "DGtal/io/colormaps/GrayscaleColorMap.h"
44 
45 #include "CLI11.hpp"
46 
47 
48 using namespace std;
49 using namespace DGtal;
50 
119 template<typename TImage, typename TImageVector>
120 void
121 computerBasicNormalsFromHeightField(const TImage &anHeightMap, TImageVector &vectorField,
122  bool invertN = false)
123 {
124  for(typename TImage::Domain::ConstIterator it = anHeightMap.domain().begin();
125  it != anHeightMap.domain().end(); it++){
126  if(anHeightMap.domain().isInside(*it+Z2i::Point::diagonal(1))&&
127  anHeightMap.domain().isInside(*it-Z2i::Point::diagonal(1))){
128  double dx = (anHeightMap(*it-Z2i::Point(1,0))-anHeightMap(*it+Z2i::Point(1,0)))/2.0;
129  double dy = (anHeightMap(*it-Z2i::Point(0,1))-anHeightMap(*it+Z2i::Point(0,1)))/2.0;
130  Z3i::RealPoint n (dx, dy, 1);
131  n /= n.norm();
132  vectorField.setValue(*it,invertN? -n : n);
133  }
134  }
135 }
136 
137 
138 
139 template<typename TImageVector>
140 void
141 importNormals(std::string file, TImageVector &vectorField,
142  bool invertN = false)
143 {
144  std::vector<Z3i::RealPoint> vp = PointListReader<Z3i::RealPoint>::getPointsFromFile(file);
145  trace.info() << "import done: " << vp.size() << std::endl;
146  for(unsigned int i = 0; i< vp.size()-1; i=i+2){
147  Z3i::RealPoint p = vp.at(i);
148  Z3i::RealPoint q = vp.at(i+1);
149  Z3i::RealPoint n = (q-p)/(p-q).norm();
150  vectorField.setValue(Z2i::Point(p[0], p[1]),invertN? -n : n);
151  }
152  trace.info() <<endl;
153 }
154 
155 
156 template<typename TImageVector>
157 void
158 importNormalsOrdDir(std::string file, TImageVector &vectorField,
159  unsigned int width, unsigned int height, bool invertN = false)
160 {
161  std::vector<Z3i::RealPoint> vp = PointListReader<Z3i::RealPoint>::getPointsFromFile(file);
162 
163  for(unsigned int i = 0; i< vp.size()-1; i++){
164  Z2i::Point p ( i-(width*floor((i/width))), i/width);
165  Z3i::RealPoint n = vp[i];
166  vectorField.setValue(p,invertN? -n : n);
167  }
168  trace.info() <<endl;
169 }
170 
171 
172 // Basic Lambertian reflectance model only based on light source direction.
173 template<typename TImage2D, typename TPoint3D >
174 struct LambertianShadindFunctor{
175  LambertianShadindFunctor(const TPoint3D &aLightSourceDir ):
176  myLightSourceDirection(aLightSourceDir/aLightSourceDir.norm()){}
177 
178  inline
179  unsigned int operator()(const TPoint3D &aNormal) const
180  {
181  int intensity = aNormal.dot(myLightSourceDirection)*std::numeric_limits<typename TImage2D::Value>::max();
182  return intensity>0? intensity:0;
183  }
184  TPoint3D myLightSourceDirection;
185 };
186 
187 
188 // Basic Lambertian reflectance model from one light source positiion emeting in all direction.
189 template<typename TImage2D, typename TPoint3D >
190 struct LambertianShadindFunctorAllDirections{
191  LambertianShadindFunctorAllDirections(const TPoint3D &aLightSourcePosition ):
192  myLightSourcePosition(aLightSourcePosition){}
193 
194  inline
195  unsigned int operator()(const TPoint3D &aNormal, const Z2i::Point &aPoint, const double h) const
196  {
197  TPoint3D l;
198 
199  Z3i::RealPoint posL (aPoint[0], aPoint[1], h);
200  l = -posL+myLightSourcePosition;
201  l /= l.norm();
202  int intensity = aNormal.dot(l)*std::numeric_limits<typename TImage2D::Value>::max();
203  return intensity>0? intensity:0;
204  }
205  TPoint3D myLightSourcePosition;
206 };
207 
208 
209 
210 
211 
212 // Specular reflectance from Nayar model.
213 template<typename TImage2D, typename TPoint3D >
214 struct SpecularNayarShadindFunctor{
215  SpecularNayarShadindFunctor(const TPoint3D &lightSourceDirection, const double kld,
216  const double kls, const double sigma ):
217  myLightSourceDirection(lightSourceDirection/lightSourceDirection.norm()),
218  myKld(kld), myKls(kls), mySigma(sigma){}
219 
220  inline
221  unsigned int operator()(const TPoint3D &aNormal) const {
222  double lambertianIntensity = std::max(aNormal.dot(myLightSourceDirection), 0.0);
223  double alpha = acos(((myLightSourceDirection+Z3i::RealPoint(0,0,1.0))/2.0).dot(aNormal/aNormal.norm()));
224  double specularIntensity = exp(-alpha*alpha/(2.0*mySigma));
225  double resu = myKld*lambertianIntensity+myKls*specularIntensity;
226 
227  resu = std::max(resu, 0.0);
228  resu = std::min(resu, 1.0);
229  return resu*std::numeric_limits<typename TImage2D::Value>::max();
230  }
231 
232  TPoint3D myLightSourceDirection;
233  double myKld, myKls, mySigma;
234 };
235 
236 
237 
238 // Specular reflectance from Nayar model.
239 template<typename TImage2D, typename TPoint3D >
240 struct SpecularNayarShadindFunctorAllDirections{
241  SpecularNayarShadindFunctorAllDirections(const TPoint3D &lightSourcePosition, const double kld,
242  const double kls, const double sigma ):
243  myLightSourcePosition(lightSourcePosition),
244  myKld(kld), myKls(kls), mySigma(sigma){}
245 
246  inline
247  unsigned int operator()(const TPoint3D &aNormal, const Z2i::Point &aPoint, const double h) const {
248  TPoint3D l;
249  Z3i::RealPoint posL (aPoint[0], aPoint[1], h);
250  l = -posL+myLightSourcePosition;
251  l /= l.norm();
252 
253  double lambertianIntensity = std::max(aNormal.dot(l), 0.0);
254  double alpha = acos(((l+Z3i::RealPoint(0,0,1.0))/2.0).dot(aNormal/aNormal.norm()));
255  double specularIntensity = exp(-alpha*alpha/(2.0*mySigma));
256  double resu = myKld*lambertianIntensity+myKls*specularIntensity;
257 
258  resu = std::max(resu, 0.0);
259  resu = std::min(resu, 1.0);
260  return resu*std::numeric_limits<typename TImage2D::Value>::max();
261  }
262 
263  TPoint3D myLightSourcePosition;
264  double myKld, myKls, mySigma;
265 };
266 
267 
268 
269 
270 // Basic Lambertian reflectance model from one light source positiion emeting in all direction.
271 template<typename TImage2D, typename TPoint3D >
272 struct ImageMapReflectance{
273  ImageMapReflectance(const std::string &filename): myImageMap (PPMReader<TImage2D>::importPPM(filename))
274  {
275 
276  myCenterPoint = (myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())/2;
277  myImageRadius = min((myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())[1], (myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())[0])/2;
278  }
279 
280  inline
281  unsigned int operator()(const TPoint3D &aNormal) const
282  {
283  Z2i::Point p(aNormal[0]*myImageRadius,aNormal[1]*myImageRadius ) ;
284  p += myCenterPoint;
285  if(myImageMap.domain().isInside(p)){
286  return myImageMap(p);
287  }else{
288  return myImageMap(Z2i::Point(0,0,0));
289  }
290  }
291  TImage2D myImageMap;
292  Z2i::Point myCenterPoint;
293  unsigned int myImageRadius;
294 };
295 
296 
297 
298 struct IdColor{
299  Color operator()( const unsigned int & aValue ) const{
300  return DGtal::Color(aValue);
301  }
302 };
303 
304 
305 
307 colorFromHSB(double h, double saturation, double value){
308  double r, g, b;
309  DGtal::Color::HSVtoRGB(r, g, b, h,saturation, value);
310  return DGtal::Color(r*255.0,g*255.0,b*255.0);
311 
312 }
313 
314 
315 int main( int argc, char** argv )
316 {
320 
321 
322 
323  // parse command line using CLI ----------------------------------------------
324  CLI::App app;
325  std::string inputFileName;
326  std::string outputFileName {"result.pgm"};
327  std::string normalFileName {""};
328  double lx, ly, lz, px, py, pz;
329  bool usingAllDirectionLightSource = false;
330  bool useOrderedImportNormal = false;
331  bool hsvShading = false;
332  bool normalMap = false;
333  bool invertNormals = false;
334  std::vector<double> specularModel;
335  std::string reflectanceMap;
336  std::vector<double> lDir = {0, 0, 1};
337  std::vector<double> lPos;
338  std::vector<unsigned int> domain;
339 
340  app.description("Render a 2D heightfield image into a shading image. You can choose between lambertian model (diffuse reflectance) and specular model (Nayar reflectance model). You can also choose between a single directional light source (using --lightDir option) or use light source which emits in all direction (by specifying the light source position with --lightPos} option). Another rendering mode is given from a bitmap reflectance map which represents the rendering for a normal vector value (mapped according the x/y coordinates).\nExample:\n heightfield2shading ${DGtal}/examples/samples/bunnyHeightField.pgm shading.pgm --lPos 10.0 -120.0 550.0 --importNormal ${DGtal}/examples/samples/bunnyHeightField_normals.sdp -s 1.0 0.2 0.8 \n"
341  "Other example: heightfield2shading ${DGtal}/examples/samples/bunnyHeightField.pgm shading.ppm --importNormal ${DGtal}/examples/samples/bunnyHeightField_normals.sdp --hsvShading\n");
342  auto opt1 = app.add_option("-i,--input,1", inputFileName, "input heightfield file (2D image).")
343  ->check(CLI::ExistingFile);
344  auto domOpt = app.add_option("--domain,-d", domain , "specify the domain (required when normal are imported and if --inout is not given).")
345  -> expected(2);
346  app.add_option("-o,--output,2", outputFileName,"output image.");
347  auto impNOpt = app.add_option("--importNormal", normalFileName, "import normals from file.");
348  app.add_flag("--orderedNormalsImport",useOrderedImportNormal, "Use ordered normals." );
349  app.add_option("--lightDir,--lDir,--ld", lDir, "light source direction: lx ly lz.")
350  ->expected(3);
351  app.add_option("--lightPos,--lPos,--lp", lPos, "light source position: px py pz.")
352  ->expected(3);
353  app.add_option("-s,--specularModel", specularModel, "use specular Nayar model with 3 param Kdiff, Kspec, sigma." )
354  ->expected(3);
355  app.add_option("-r,--reflectanceMap",reflectanceMap, "specify a image as reflectance map.")
356  ->check(CLI::ExistingFile);
357  app.add_flag("--hsvShading", hsvShading, "use shading with HSV shading (given from the normal vector)");
358  app.add_flag("--normalMap", normalMap, "generates normal map.");
359  app.add_flag("--invertNormals,-v", invertNormals, "invert normal orientations.");
360  app.get_formatter()->column_width(40);
361  CLI11_PARSE(app, argc, argv);
362  // END parse command line using CLI ----------------------------------------------
363 
364  if(! *opt1 && !(*domOpt && *impNOpt ) ){
365  trace.error() << "You need either set input file (--input) or use a domain (--domain) with the --importNormal option." << std::endl;
366  exit(0);
367  }
368 
369 
370  if(lDir.size() == 3)
371  {
372  lx = lDir[0];
373  ly = lDir[1];
374  lz = lDir[2];
375  }
376  else if(lPos.size() == 3)
377  {
378  px = lPos[0];
379  py = lPos[1];
380  pz = lPos[2];
381  usingAllDirectionLightSource = true;
382  }
383  else if (reflectanceMap == "" && ! hsvShading && !normalMap)
384  {
385  trace.error() << "You need to specify either the light source direction or position (if you use a all directions model)." << std::endl;
386  exit(0);
387  }
388 
389  LambertianShadindFunctor<Image2D, Z3i::RealPoint> lShade (Z3i::RealPoint(lx,ly,lz));
390  LambertianShadindFunctorAllDirections<Image2D, Z3i::RealPoint> lShadePosD (Z3i::RealPoint(px ,py, pz));
391  SpecularNayarShadindFunctor<Image2D, Z3i::RealPoint> lSpecular (Z3i::RealPoint(lx,ly,lz), 0, 0, 0);
392  SpecularNayarShadindFunctorAllDirections<Image2D, Z3i::RealPoint> lSpecularPosD (Z3i::RealPoint(px,py,pz), 0, 0, 0);
393 
394 
395  bool useSpecular = false;
396  if(specularModel.size() == 3){
397  useSpecular = true;
398  lSpecular.myKld = specularModel[0];
399  lSpecular.myKls = specularModel[1];
400  lSpecular.mySigma = specularModel[2];
401  lSpecularPosD.myKld = specularModel[0];
402  lSpecularPosD.myKls = specularModel[1];
403  lSpecularPosD.mySigma = specularModel[2];
404  if(specularModel[2]==0.0)
405  {
406  trace.error()<< "a 0 value for sigma is not possible in the Nayar model, please change it. "<< std::endl;
407  exit(1);
408  }
409 
410  }
411 
412 
413  Image2D inputImage(Z2i::Domain(Z2i::Point(0,0), Z2i::Point(0,0) ));
414  if (inputFileName != "") {
415  trace.info() << "Reading input file " << inputFileName ;
416  inputImage = DGtal::GenericReader<Image2D>::import(inputFileName);
417  trace.info() << "[done]" << std::endl;
418  }
419  else {
420  inputImage = Image2D(Z2i::Domain(Z2i::Domain(Z2i::Point(0,0),
421  Z2i::Point(domain[0],domain[1]) )));
422  }
423 
424  Image2DNormals vectNormals (inputImage.domain());
425  Image2D result (inputImage.domain());
426  Image2DC resultC (inputImage.domain());
427 
428  if(normalFileName != ""){
429  trace.info() << "Import normal file " << inputFileName << vectNormals.domain();
430  if (useOrderedImportNormal)
431  {
432  importNormalsOrdDir(normalFileName, vectNormals,
433  inputImage.domain().upperBound()[0]+1,
434  inputImage.domain().upperBound()[1]+1, invertNormals);
435  }
436  else
437  {
438  importNormals(normalFileName, vectNormals, invertNormals);
439  }
440  trace.info() << "[done]" << std::endl;
441  }
442  else
443  {
444  computerBasicNormalsFromHeightField(inputImage, vectNormals, invertNormals);
445  }
446  if (hsvShading)
447  {
448  for(typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
449  it != inputImage.domain().end(); it++){
450  auto n = vectNormals(*it);
451  double sat = 1.0*( sin(acos(Z3i::RealPoint(0.0,0.0,1.0).dot(n))));
452  double value = 1.0;
453  double hue = ((int)(((2.0*M_PI+atan2(Z3i::RealPoint(0.0,1.0,0.0).dot(n),
454  Z3i::RealPoint(1.0,0.0,0.0).dot(n)))/(2.0*M_PI))*360.0+100))%360;
455  DGtal::uint32_t colCode = colorFromHSB(hue, sat, value).getRGB();
456  resultC.setValue(*it, colCode);
457  }
458  IdColor id;
459  PPMWriter<Image2DC, IdColor >::exportPPM(outputFileName, resultC, id);
460  }
461  else if (normalMap)
462  {
463  DGtal::functors::Rescaling<double, unsigned int> rgRescale (-1.0, 1.0, 0, 255);
464  DGtal::functors::Rescaling<double, unsigned int> bRescale (0.0, -1.0, 128, 255);
465  for(typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
466  it != inputImage.domain().end(); it++){
467  auto n = vectNormals(*it);
468  DGtal::Color c (rgRescale(n[0]), rgRescale(n[1]), bRescale(n[2]) );
469  resultC.setValue(*it, c.getRGB());
470  }
471  IdColor id;
472  PPMWriter<Image2DC, IdColor >::exportPPM(outputFileName, resultC, id);
473  }
474  else if(reflectanceMap != "")
475  {
476  ImageMapReflectance<Image2D, Z3i::RealPoint> lMap(reflectanceMap);
477  for(typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
478  it != inputImage.domain().end(); it++){
479  result.setValue(*it, lMap(vectNormals(*it)));
480  }
481  IdColor id;
482  PPMWriter<Image2D, IdColor >::exportPPM(outputFileName, result, id);
483  }
484 
485  else
486  {
487  for(typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
488  it != inputImage.domain().end(); it++){
489  if(usingAllDirectionLightSource)
490  {
491  result.setValue(*it, useSpecular? lSpecularPosD(vectNormals(*it), *it, inputImage(*it)):
492  lShadePosD(vectNormals(*it), *it, inputImage(*it)));
493  }
494  else
495  {
496  result.setValue(*it, useSpecular? lSpecular(vectNormals(*it)):lShade(vectNormals(*it)));
497  }
498 
499  }
500  result >> outputFileName;
501  }
502 
503  return 0;
504 }
int main(int argc, char **argv)
DGtal::uint32_t getRGB() const
std::vector< Value >::const_iterator ConstIterator
double norm(const NormType type=L_2) const
std::ostream & error()
std::ostream & info()
boost::uint32_t uint32_t
Trace trace(traceWriterTerm)
static TContainer import(const std::string &filename, std::vector< unsigned int > dimSpace=std::vector< unsigned int >())