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