FaceGen 3 SDKs Reference
Loading...
Searching...
No Matches
Fg3CmdRandom.cpp
1//
2// Copyright (c) Singular Inversions Inc. 2013
3//
4// Authors: Andrew Beatty
5// Created: Oct 15, 2013
6//
7
8#include "stdafx.h"
9
10#include "FgCommand.hpp"
11#include "FgFileSystem.hpp"
12#include "Fg3dMeshIo.hpp"
13#include "FgCamera.hpp"
14#include "FgRender.hpp"
15#include "FgTime.hpp"
16#include "Fg3Sam.hpp"
17#include "Fg3Controls.hpp"
18#include "FgImageIo.hpp"
19
20using namespace std;
21
22namespace Fg {
23
24namespace {
25
26struct Render
27{
28 // 0 - light direction fixed behind camera.
29 // 0.5 - light randomly from camera hemisphere.
30 // 1 - light randomly from any direction:
31 float lightDirectionRange {0.3f};
32 RgbaF backgroundColor {0,0,0,0}; // [0,255] per channel
33 Vec2UI imagePixelSize {512,512};
34 uint antiAliasBitDepth {3};
35 float cameraFovDegrees {17}; // FOV of larger image dimension.
36 FG_SER5(lightDirectionRange,backgroundColor,imagePixelSize,antiAliasBitDepth,cameraFovDegrees)
37};
38
39struct Pose
40{
41 Rigid3D toHcs; // defaults to identity
42 double scale {1};
43 Vec2D rollRangeDegrees {-5,5};
44 Vec2D tiltRangeDegrees {-10,10};
45 Vec2D panRangeDegrees {-45,45};
46 FG_SER5(toHcs,scale,rollRangeDegrees,tiltRangeDegrees,panRangeDegrees)
47};
48
49struct LockVal
50{
51 bool fixed {false};
52 float val {0}; // Will be set to val if fixed, ignored otherwise
53 FG_SER2(fixed,val)
54};
55
56struct LockVal2
57{
58 bool fixed {false};
59 float shape {0}; // Will be set to val if fixed, ignored otherwise
60 float color {0}; // "
61 FG_SER3(fixed,shape,color)
62};
63
64struct Attributes
65{
66 int genderDistro {0}; // -1 - male, 0 - either, 1 - female, 2 - fixed
67 float genderFixval {0}; // Ignored if above value is not 2
68 LockVal age;
69 LockVal2 caricature;
70 LockVal asymmetry;
71 FG_SER5(genderDistro,genderFixval,age,caricature,asymmetry)
72};
73
74struct Options
75{
76 Strings sams;
77 Stringss optsSams; // Empty string is null choice.
78 Stringss optsMorphs; // "
79 bool useDetailTextures {true};
80 Pose pose;
81 Render rend;
82 string outputFormat {".jpg"};
83 Attributes attributes;
84 FG_SER8(sams,optsSams,optsMorphs,useDetailTextures,pose,rend,outputFormat,attributes)
85};
86
87void setup(CLArgs const & args)
88{
89 Syntax syn {args,
90 R"(<config>.txt (<sam>)+ [-s (<sam>)+]* [-m (<morph>)+]*
91 <sam> - A SAM name (file path without extension)
92 -s - A list of SAMs from which one will be chosen at random
93 -m - A list of morphs from which one will be chosen at random
94 - Use "" for the null choice in either list above.
95OUTPUT:
96 <config>.txt A configuration file that can be modified and used to do a random run
97NOTES:
98 Fields in the configuration file can be modified as long as the XML structure is not changed:
99 <sams> add or remove more SAM base names in the <item> elements
100 <optsSams> add / remove SAM base names one of which will be chosen at random (empty means none)
101 <optsMorphs> add / remove morph names, one of which will be chosen at random (empty means none)
102 <useDetailTextures> 0 - no, 1 - yes
103 <pose>
104 <toHcs> all meshes will be rigidly transformed to put them in the head coordinate
105 system (HCS) which is X - left, Y - up, Z - toward camera
106 <scale> scale relative to the scale that would result in a good portrait fill of the image
107 <rollRangeDegrees> limits of roll angle to be chosen randomly (uniform)
108 <tiltRangeDegrees> limits of tilt angle to be chosen randomly (uniform)
109 <panRangeDegrees> limits of pan angle to be chosen randomly (unform)
110 <rend>
111 <lightDirectionRange> [0,1] angle range of light source (at infinity) as a ratio of full circle
112 <backgroundColor> [0,255] per channel RGBA
113 <imagePixelSize> desired pixel width, height
114 <antiAliasBitDepth> 3 is high quality (equivalent to 8x FSAA) without running too slowly
115 <cameraFovDegrees> field of view or the larger image dimension in degrees. Default is 17
116 (ie. moderate zoom lens). Use a larger FOV to simulate a mobile device camera.
117 <outputFormat> )" + clOptionsStr(getImgExts()) + R"(
118 <attributes>
119 <genderDistro> 0 - choose at random, 1 - female, -1 - male, 2 - use the fixed value in <genderFixed>
120 <genderFixval> exact gender projection (genders normally fall into a range of values)
121 <age>
122 <fixed> 0 - varies randomly, 1 - fixed to <val>
123 <val> exact age projection
124 <caricature>
125 <fixed> 0 - varies randomly, 1 - fixed to <shape> and <color>
126 <shape> exact shape caricature projection
127 <color> exact color caricature projection
128 <asymmetry>
129 <fixed> 0 - varies randomly, 1 - fixed to <val>
130 <val> exact asymmetry
131)"
132 };
133 if (args.size() < 3)
134 syn.errorNumArgs();
135 string configFile = syn.next();
136 Options opt;
137 while (syn.more()) {
138 if (syn.peekNext()[0] == '-')
139 break;
140 opt.sams.push_back(syn.next());
141 }
142 while (syn.more()) {
143 string optstr = syn.next();
144 if (optstr == "-s") {
145 Strings optSams;
146 while (syn.more() && (syn.peekNext()[0] != '-'))
147 optSams.push_back(syn.next());
148 opt.optsSams.push_back(optSams);
149 }
150 else if (optstr == "-m") {
151 Strings optMorphs;
152 while (syn.more() && (syn.peekNext()[0] != '-'))
153 optMorphs.push_back(syn.next());
154 opt.optsMorphs.push_back(optMorphs);
155 }
156 else
157 syn.error("Unrecognized option",syn.next());
158 }
159 saveRaw(srlzText(opt),configFile);
160}
161
162struct Model
163{
164 Sam3 sam;
165 ImgRgba8s colors; // If sam has no SCM, pick one of these at random
166
167 Model() {}
168 explicit Model(string const & name) : sam(name)
169 {
170 if (!(name.empty()) && sam.mesh.verts.empty())
171 fgout << fgnl << "WARNING: SAM not found: " << name;
172 }
173
174 Mesh construct(Face3 const & face)
175 {
176 sam.applyFace(face);
177 Mesh mesh = sam.mesh;
178 if (!mesh.surfaces.empty()) {
179 Surf & surf = mesh.surfaces[0];
180 if (!colors.empty()) { // Surfaces for some reason constructed with size 0 abedo map so ignore that
181 uint sel = randUniformUint(uint(colors.size()));
182 surf.material.albedoMap = std::make_shared<ImgRgba8>(colors[sel]);
183 }
184 }
185 return mesh;
186 }
187};
188typedef vector<Model> Models;
189typedef vector<Models> Modelss;
190
191double interpolate(Vec2D bounds,double val) {return bounds[0] * (1.0-val) + bounds[1] * val; }
192
193void rrun(CLArgs const & args)
194{
195 Syntax syn {args,R"(<config>.txt <label> <number>
196 <config>.txt - Configuration file for the random run (create using 'fg3 random setup')
197 <label> - Base label for the output images.
198 <number> - How many random images to generate.
199OUTPUT:
200 For each sample, the following files with base name '<label>####' are generated:
201 .jpg rendered image
202 .fg face coordinate and detail texture if selected
203 .txt face coordinate values in TXT format.)"
204 };
205 Options opt = dsrlzText<Options>(loadRawString(syn.next()));
206 if (!opt.pose.toHcs.rot.normalize())
207 fgThrow("toHcs::rot: quaternion cannot be zero magnitude");
208 if (opt.pose.scale <= 0)
209 fgThrow("Scale must be greater than 0");
210 String label = syn.next();
211 size_t num = syn.nextAs<size_t>();
212 if (num < 1)
213 syn.error("No images generated");
214
216 randSeedTime();
217
219 Models sams;
220 for (size_t ii=0; ii<opt.sams.size(); ++ii)
221 sams.push_back(Model(opt.sams[ii]));
222 Modelss optsSams;
223 for (Strings const & optSamsBn : opt.optsSams) {
224 Models optSams;
225 for (string const & optName : optSamsBn) {
226 if (optName.empty())
227 optSams.push_back(Model());
228 else {
229 Model model(optName);
230 if (model.sam.scms.empty()) {
231 Path path(optName);
232 String8s clrFiles = globFiles(String8(optName)+"_*.*");
233 for (String8 const & cf : clrFiles)
234 model.colors.push_back(loadImage(path.dir()+cf));
235 }
236 optSams.push_back(model);
237 }
238 }
239 optsSams.push_back(optSams);
240 }
241 Sam3Controls ctls(dataDir() + "main/si.ctl");
242
244 Affine3F xf {opt.pose.toHcs.asAffine()};
245 Mat32F bounds = cBounds(mapMul(xf,sams[0].sam.ssm.meanVerts));
246 for (size_t ii=1; ii<sams.size(); ++ii)
247 bounds = cBoundsUnion(bounds,cBounds(mapMul(xf,sams[ii].sam.ssm.meanVerts)));
248 CameraParams cps {Mat32D{bounds}};
249 cps.logRelScale = std::log(opt.pose.scale);
250 cps.fovMaxDeg = opt.rend.cameraFovDegrees;
251 Camera baseCam = cps.camera(opt.rend.imagePixelSize);
252
254 for (size_t ii=0; ii<num; ++ii) {
255 Face3 face;
256 if (opt.attributes.age.fixed) {
257 ctls.setAge_(face.coord,FANTYPE_GEO,opt.attributes.age.val);
258 ctls.setAge_(face.coord,FANTYPE_TEX,opt.attributes.age.val);
259 }
260 FanGender gender = FANGENDER_SIZE;
261 if (opt.attributes.genderDistro != 0) {
262 if (opt.attributes.genderDistro == -1)
263 gender = FANGENDER_MALE;
264 else if (opt.attributes.genderDistro == 1)
265 gender = FANGENDER_FEMALE;
266 else if (opt.attributes.genderDistro == 2) {
267 if (opt.attributes.genderFixval > 0.0f)
268 gender = FANGENDER_FEMALE;
269 else if (opt.attributes.genderDistro < 0.0f)
270 gender = FANGENDER_MALE;
271 }
272 else
273 syn.error("Gender value must be 0 (random), -1 (male), +1 (female), or 2 (fixed)");
274 }
275 if (opt.attributes.caricature.fixed) {
276 face.coord.ts(0,0)[0] = 1.0f; // Must be non-zero to set caricature
277 face.coord.ts(1,0)[0] = 1.0f; // "
278 ctls.setCaricature_(face.coord,FANRACE_ALL,FANTYPE_GEO,opt.attributes.caricature.shape);
279 ctls.setCaricature_(face.coord,FANRACE_ALL,FANTYPE_TEX,opt.attributes.caricature.color);
280 }
281 if (opt.attributes.asymmetry.fixed) {
282 face.coord.ts(0,1)[0] = 1.0f;
283 ctls.setAsymmetry_(face.coord,opt.attributes.asymmetry.val);
284 }
285 ctls.setRandom_(face.coord,FANRACE_ALL,gender,opt.attributes.age.fixed,opt.attributes.caricature.fixed,opt.attributes.asymmetry.fixed);
286 if (opt.attributes.genderDistro == 2) {
287 ctls.setGender_(face.coord,FANTYPE_GEO,opt.attributes.genderFixval);
288 ctls.setGender_(face.coord,FANTYPE_TEX,opt.attributes.genderFixval);
289 }
290 if (opt.useDetailTextures) {
291 string genStr = (ctls.getGender(face.coord,FANTYPE_GEO) > 0 ? "Male " : "Female ");
292 float age = ctls.getAge(face.coord,FANTYPE_GEO);
293 string ageStr;
294 if (age < 40)
295 ageStr = "Young ";
296 else if (age < 60)
297 ageStr = "Middle ";
298 else
299 ageStr = "Older ";
300 String8 sw = dataDir()+"main/detail/"+genStr+ageStr;
301 Path swp(sw);
302 DirContents dc = globNodeStartsWith(swp);
303 FGASSERT(!dc.filenames.empty());
304 String8 detFile = swp.dir() + dc.filenames[randUniformUint(uint(dc.filenames.size()))];
305 face.detail = loadRaw(detFile);
306 }
307 Meshes meshes;
308 for (Model & sam : sams)
309 meshes.push_back(sam.construct(face));
310 for (Models & optSams : optsSams) {
311 size_t idx = randUniformUint(uint(optSams.size()));
312 Model & sam = optSams[idx];
313 if (!sam.sam.mesh.verts.empty())
314 meshes.push_back(sam.construct(face));
315 }
316 Strings morphs;
317 for (size_t jj=0; jj<opt.optsMorphs.size(); ++jj) {
318 string morph = opt.optsMorphs[jj][randUniformUint(uint(opt.optsMorphs[jj].size()))];
319 if (!morph.empty())
320 morphs.push_back(morph);
321 }
322 for (size_t jj=0; jj<meshes.size(); ++jj) {
323 Mesh & mesh = meshes[jj];
324 Floats morphCoord(mesh.numMorphs(),0);
325 for (size_t kk=0; kk<morphs.size(); ++kk) {
326 Valid<size_t> morphIdx = mesh.findMorph(morphs[kk]);
327 if (morphIdx.valid())
328 morphCoord[morphIdx.val()] = 1.0f;
329 }
330 mesh.morph(morphCoord,mesh.verts);
331 mesh.transform_(SimilarityD{opt.pose.toHcs});
332 }
333 QuaternionD pose =
334 cRotateY(degToRad(interpolate(opt.pose.panRangeDegrees,randUniform()))) *
335 cRotateX(degToRad(interpolate(opt.pose.tiltRangeDegrees,randUniform()))) *
336 cRotateZ(degToRad(interpolate(opt.pose.rollRangeDegrees,randUniform())));
337 Camera cam = baseCam;
338 cam.modelview.rot = baseCam.modelview.rot * pose;
339 RenderOptions ro;
340 float lr = opt.rend.lightDirectionRange;
341 Vec3F ld = ro.lighting.lights[0].direction;
342 ld = ld * (1-lr) + Vec3F::randNormal(lr);
343 ro.lighting.lights[0].direction = normalize(ld);
344 ro.backgroundColor = opt.rend.backgroundColor;
345 ro.antiAliasBitDepth = opt.rend.antiAliasBitDepth;
347 ImgRgba8 image = renderSoft(opt.rend.imagePixelSize,meshes,cam.modelview,cam.itcsToIucs,ro);
348 string base = label + toStrDigits(ii,4);
349 saveImage(image,base+opt.outputFormat);
350 face.save(base+".fg");
351 saveRaw(srlzText(face.coord),base+".txt");
352 }
353}
354
355}
356
361void cmd3Random(CLArgs const & args)
362{
363 Cmds cmds {
364 {setup,"setup","Setup an XML file describing a random image batch run"},
365 {rrun,"run","Run random image batch creation from an XML description"},
366 };
367 doMenu(args,cmds);
368}
369
370}
@ FANTYPE_TEX
Texture (color)
Definition Fg3Face.hpp:29
@ FANTYPE_GEO
Geometry (shape)
Definition Fg3Face.hpp:27
void cmd3Random(CLArgs const &)