DGtalTools  1.3.beta
heightfield2shading.cpp
1 
29 #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 
97 template<typename TImage, typename TImageVector>
98 void
99 computerBasicNormalsFromHeightField(const TImage &anHeightMap, TImageVector &vectorField)
100 {
101  for(typename TImage::Domain::ConstIterator it = anHeightMap.domain().begin();
102  it != anHeightMap.domain().end(); it++){
103  if(anHeightMap.domain().isInside(*it+Z2i::Point::diagonal(1))&&
104  anHeightMap.domain().isInside(*it-Z2i::Point::diagonal(1))){
105  double dx = (anHeightMap(*it-Z2i::Point(1,0))-anHeightMap(*it+Z2i::Point(1,0)))/2.0;
106  double dy = (anHeightMap(*it-Z2i::Point(0,1))-anHeightMap(*it+Z2i::Point(0,1)))/2.0;
107  Z3i::RealPoint n (dx, dy, 1);
108  n /= n.norm();
109  vectorField.setValue(*it,n);
110  }
111  }
112 }
113 
114 
115 
116 template<typename TImageVector>
117 void
118 importNormals(std::string file, TImageVector &vectorField)
119 {
120  std::vector<Z3i::RealPoint> vp = PointListReader<Z3i::RealPoint>::getPointsFromFile(file);
121  for(unsigned int i = 0; i< vp.size(); i=i+2){
122  Z3i::RealPoint p = vp.at(i);
123  Z3i::RealPoint q = vp.at(i+1);
124  Z3i::RealPoint n = (q-p)/(p-q).norm();
125  vectorField.setValue(Z2i::Point(p[0], p[1]),n);
126  }
127 }
128 
129 // Basic Lambertian reflectance model only based on light source direction.
130 template<typename TImage2D, typename TPoint3D >
131 struct LambertianShadindFunctor{
132  LambertianShadindFunctor(const TPoint3D &aLightSourceDir ):
133  myLightSourceDirection(aLightSourceDir/aLightSourceDir.norm()){}
134 
135  inline
136  unsigned int operator()(const TPoint3D &aNormal) const
137  {
138  int intensity = aNormal.dot(myLightSourceDirection)*std::numeric_limits<typename TImage2D::Value>::max();
139  return intensity>0? intensity:0;
140  }
141  TPoint3D myLightSourceDirection;
142 };
143 
144 
145 // Basic Lambertian reflectance model from one light source positiion emeting in all direction.
146 template<typename TImage2D, typename TPoint3D >
147 struct LambertianShadindFunctorAllDirections{
148  LambertianShadindFunctorAllDirections(const TPoint3D &aLightSourcePosition ):
149  myLightSourcePosition(aLightSourcePosition){}
150 
151  inline
152  unsigned int operator()(const TPoint3D &aNormal, const Z2i::Point &aPoint, const double h) const
153  {
154  TPoint3D l;
155 
156  Z3i::RealPoint posL (aPoint[0], aPoint[1], h);
157  l = -posL+myLightSourcePosition;
158  l /= l.norm();
159  int intensity = aNormal.dot(l)*std::numeric_limits<typename TImage2D::Value>::max();
160  return intensity>0? intensity:0;
161  }
162  TPoint3D myLightSourcePosition;
163 };
164 
165 
166 
167 
168 
169 // Specular reflectance from Nayar model.
170 template<typename TImage2D, typename TPoint3D >
171 struct SpecularNayarShadindFunctor{
172  SpecularNayarShadindFunctor(const TPoint3D &lightSourceDirection, const double kld,
173  const double kls, const double sigma ):
174  myLightSourceDirection(lightSourceDirection/lightSourceDirection.norm()),
175  myKld(kld), myKls(kls), mySigma(sigma){}
176 
177  inline
178  unsigned int operator()(const TPoint3D &aNormal) const {
179  double lambertianIntensity = std::max(aNormal.dot(myLightSourceDirection), 0.0);
180  double alpha = acos(((myLightSourceDirection+Z3i::RealPoint(0,0,1.0))/2.0).dot(aNormal/aNormal.norm()));
181  double specularIntensity = exp(-alpha*alpha/(2.0*mySigma));
182  double resu = myKld*lambertianIntensity+myKls*specularIntensity;
183 
184  resu = std::max(resu, 0.0);
185  resu = std::min(resu, 1.0);
186  return resu*std::numeric_limits<typename TImage2D::Value>::max();
187  }
188 
189  TPoint3D myLightSourceDirection;
190  double myKld, myKls, mySigma;
191 };
192 
193 
194 
195 // Specular reflectance from Nayar model.
196 template<typename TImage2D, typename TPoint3D >
197 struct SpecularNayarShadindFunctorAllDirections{
198  SpecularNayarShadindFunctorAllDirections(const TPoint3D &lightSourcePosition, const double kld,
199  const double kls, const double sigma ):
200  myLightSourcePosition(lightSourcePosition),
201  myKld(kld), myKls(kls), mySigma(sigma){}
202 
203  inline
204  unsigned int operator()(const TPoint3D &aNormal, const Z2i::Point &aPoint, const double h) const {
205  TPoint3D l;
206  Z3i::RealPoint posL (aPoint[0], aPoint[1], h);
207  l = -posL+myLightSourcePosition;
208  l /= l.norm();
209 
210  double lambertianIntensity = std::max(aNormal.dot(l), 0.0);
211  double alpha = acos(((l+Z3i::RealPoint(0,0,1.0))/2.0).dot(aNormal/aNormal.norm()));
212  double specularIntensity = exp(-alpha*alpha/(2.0*mySigma));
213  double resu = myKld*lambertianIntensity+myKls*specularIntensity;
214 
215  resu = std::max(resu, 0.0);
216  resu = std::min(resu, 1.0);
217  return resu*std::numeric_limits<typename TImage2D::Value>::max();
218  }
219 
220  TPoint3D myLightSourcePosition;
221  double myKld, myKls, mySigma;
222 };
223 
224 
225 
226 
227 // Basic Lambertian reflectance model from one light source positiion emeting in all direction.
228 template<typename TImage2D, typename TPoint3D >
229 struct ImageMapReflectance{
230  ImageMapReflectance(const std::string &filename): myImageMap (PPMReader<TImage2D>::importPPM(filename))
231  {
232 
233  myCenterPoint = (myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())/2;
234  myImageRadius = min((myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())[1], (myImageMap.domain().upperBound()-myImageMap.domain().lowerBound())[0])/2;
235  }
236 
237  inline
238  unsigned int operator()(const TPoint3D &aNormal) const
239  {
240  Z2i::Point p(aNormal[0]*myImageRadius,aNormal[1]*myImageRadius ) ;
241  p += myCenterPoint;
242  if(myImageMap.domain().isInside(p)){
243  return myImageMap(p);
244  }else{
245  return myImageMap(Z2i::Point(0,0,0));
246  }
247  }
248  TImage2D myImageMap;
249  Z2i::Point myCenterPoint;
250  unsigned int myImageRadius;
251 };
252 
253 
254 struct IdColor{
255  Color operator()( const unsigned int & aValue ) const{
256  return DGtal::Color(aValue);
257  }
258 };
259 
260 
261 
262 int main( int argc, char** argv )
263 {
264  // typedef ImageContainerBySTLVector < Z2i::Domain, unsigned int> Image2D;
266  //typedef ImageContainerBySTLVector < Z2i::Domain, char> Image2DChar;
268 
269 
270 
271  // parse command line using CLI ----------------------------------------------
272  CLI::App app;
273  std::string inputFileName;
274  std::string outputFileName {"result.pgm"};
275  std::string normalFileName {""};
276  double lx, ly, lz, px, py, pz;
277  bool usingAllDirectionLightSource = false;
278  std::vector<double> specularModel;
279  std::string reflectanceMap;
280  std::vector<double> lDir;
281  std::vector<double> lPos;
282 
283 
284  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 ${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");
285  app.add_option("-i,--input,1", inputFileName, "input heightfield file (2D image).")
286  ->check(CLI::ExistingFile)
287  ->required();
288  app.add_option("-o,--output,2", outputFileName,"output image.");
289  app.add_option("--importNormal", normalFileName, "import normals from file.");
290 
291  app.add_option("--lightDir,--lDir,--ld", lDir, "light source direction: lx ly lz.")
292  ->expected(3);
293  app.add_option("--lightPos,--lPos,--lp", lPos, "light source position: px py pz.")
294  ->expected(3);
295  app.add_option("-s,--specularModel", specularModel, "use specular Nayar model with 3 param Kdiff, Kspec, sigma." )
296  ->expected(3);
297  app.add_option("-r,--reflectanceMap",reflectanceMap, "specify a image as reflectance map.")
298  ->check(CLI::ExistingFile);
299 
300 
301  app.get_formatter()->column_width(40);
302  CLI11_PARSE(app, argc, argv);
303  // END parse command line using CLI ----------------------------------------------
304 
305 
306 
307  if(lDir.size() == 3)
308  {
309  lx = lDir[0];
310  ly = lDir[1];
311  lz = lDir[2];
312  }
313  else if(lPos.size() == 3)
314  {
315  px = lPos[0];
316  py = lPos[1];
317  pz = lPos[2];
318  usingAllDirectionLightSource = true;
319  }
320  else if (reflectanceMap == "")
321  {
322  trace.error() << "You need to specify either the light source direction or position (if you use a all directions model)." << std::endl;
323  exit(0);
324  }
325 
326  LambertianShadindFunctor<Image2D, Z3i::RealPoint> lShade (Z3i::RealPoint(lx,ly,lz));
327  LambertianShadindFunctorAllDirections<Image2D, Z3i::RealPoint> lShadePosD (Z3i::RealPoint(px ,py, pz));
328  SpecularNayarShadindFunctor<Image2D, Z3i::RealPoint> lSpecular (Z3i::RealPoint(lx,ly,lz), 0, 0, 0);
329  SpecularNayarShadindFunctorAllDirections<Image2D, Z3i::RealPoint> lSpecularPosD (Z3i::RealPoint(px,py,pz), 0, 0, 0);
330 
331 
332  bool useSpecular = false;
333  if(specularModel.size() == 3){
334  useSpecular = true;
335  lSpecular.myKld = specularModel[0];
336  lSpecular.myKls = specularModel[1];
337  lSpecular.mySigma = specularModel[2];
338  lSpecularPosD.myKld = specularModel[0];
339  lSpecularPosD.myKls = specularModel[1];
340  lSpecularPosD.mySigma = specularModel[2];
341  if(specularModel[2]==0.0)
342  {
343  trace.error()<< "a 0 value for sigma is not possible in the Nayar model, please change it. "<< std::endl;
344  exit(1);
345  }
346 
347  }
348 
349 
350  trace.info() << "Reading input file " << inputFileName ;
351  Image2D inputImage = DGtal::GenericReader<Image2D>::import(inputFileName);
352  Image2DNormals vectNormals (inputImage.domain());
353  Image2D result (inputImage.domain());
354  if(normalFileName != ""){
355  importNormals(normalFileName, vectNormals);
356  }else{
357  computerBasicNormalsFromHeightField(inputImage, vectNormals);
358  }
359  if(reflectanceMap != "")
360  {
361  ImageMapReflectance<Image2D, Z3i::RealPoint> lMap(reflectanceMap);
362  for(typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
363  it != inputImage.domain().end(); it++){
364  if(reflectanceMap != "")
365  {
366  result.setValue(*it, lMap(vectNormals(*it)));
367  }
368  }
369  IdColor id;
370  PPMWriter<Image2D, IdColor >::exportPPM(outputFileName, result, id);
371  }
372 
373  else
374  {
375  for(typename Image2D::Domain::ConstIterator it = inputImage.domain().begin();
376  it != inputImage.domain().end(); it++){
377  if(usingAllDirectionLightSource)
378  {
379  result.setValue(*it, useSpecular? lSpecularPosD(vectNormals(*it), *it, inputImage(*it)):
380  lShadePosD(vectNormals(*it), *it, inputImage(*it)));
381  }
382  else
383  {
384  result.setValue(*it, useSpecular? lSpecular(vectNormals(*it)):lShade(vectNormals(*it)));
385  }
386 
387  }
388  result >> outputFileName;
389  }
390 
391  return 0;
392 }
STL namespace.
int main(int argc, char **argv)
static TContainer import(const std::string &filename, std::vector< unsigned int > dimSpace=std::vector< unsigned int >())
Trace trace(traceWriterTerm)
double norm(const NormType type=L_2) const
std::ostream & info()
const Domain & domain() const
std::vector< Value >::const_iterator ConstIterator
std::ostream & error()