10#include "FgCommand.hpp"
11#include "FgFileSystem.hpp"
12#include "Fg3dMeshIo.hpp"
13#include "FgCamera.hpp"
14#include "FgRender.hpp"
17#include "Fg3Controls.hpp"
18#include "FgImageIo.hpp"
31 float lightDirectionRange {0.3f};
32 RgbaF backgroundColor {0,0,0,0};
33 Vec2UI imagePixelSize {512,512};
34 uint antiAliasBitDepth {3};
35 float cameraFovDegrees {17};
36 FG_SER5(lightDirectionRange,backgroundColor,imagePixelSize,antiAliasBitDepth,cameraFovDegrees)
43 Vec2D rollRangeDegrees {-5,5};
44 Vec2D tiltRangeDegrees {-10,10};
45 Vec2D panRangeDegrees {-45,45};
46 FG_SER5(toHcs,scale,rollRangeDegrees,tiltRangeDegrees,panRangeDegrees)
61 FG_SER3(fixed,shape,color)
67 float genderFixval {0};
71 FG_SER5(genderDistro,genderFixval,age,caricature,asymmetry)
79 bool useDetailTextures {
true};
82 string outputFormat {
".jpg"};
83 Attributes attributes;
84 FG_SER8(sams,optsSams,optsMorphs,useDetailTextures,pose,rend,outputFormat,attributes)
87void setup(CLArgs
const & 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.
96 <config>.txt A configuration file that can be modified and used to do a random run
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
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)
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"(
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)
122 <fixed> 0 - varies randomly, 1 - fixed to <val>
123 <val> exact age projection
125 <fixed> 0 - varies randomly, 1 - fixed to <shape> and <color>
126 <shape> exact shape caricature projection
127 <color> exact color caricature projection
129 <fixed> 0 - varies randomly, 1 - fixed to <val>
130 <val> exact asymmetry
135 string configFile = syn.next();
138 if (syn.peekNext()[0] ==
'-')
140 opt.sams.push_back(syn.next());
143 string optstr = syn.next();
144 if (optstr ==
"-s") {
146 while (syn.more() && (syn.peekNext()[0] !=
'-'))
147 optSams.push_back(syn.next());
148 opt.optsSams.push_back(optSams);
150 else if (optstr ==
"-m") {
152 while (syn.more() && (syn.peekNext()[0] !=
'-'))
153 optMorphs.push_back(syn.next());
154 opt.optsMorphs.push_back(optMorphs);
157 syn.error(
"Unrecognized option",syn.next());
159 saveRaw(srlzText(opt),configFile);
168 explicit Model(
string const & name) : sam(name)
170 if (!(name.empty()) && sam.mesh.verts.empty())
171 fgout << fgnl <<
"WARNING: SAM not found: " << name;
174 Mesh construct(Face3
const & face)
177 Mesh mesh = sam.mesh;
178 if (!mesh.surfaces.empty()) {
179 Surf & surf = mesh.surfaces[0];
180 if (!colors.empty()) {
181 uint sel = randUniformUint(uint(colors.size()));
182 surf.material.albedoMap = std::make_shared<ImgRgba8>(colors[sel]);
188typedef vector<Model> Models;
189typedef vector<Models> Modelss;
191double interpolate(Vec2D bounds,
double val) {
return bounds[0] * (1.0-val) + bounds[1] * val; }
193void rrun(CLArgs
const & args)
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.
200 For each sample, the following files with base name '<label>####' are generated:
202 .fg face coordinate and detail texture if selected
203 .txt face coordinate values in TXT format.)"
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>();
213 syn.error(
"No images generated");
220 for (
size_t ii=0; ii<opt.sams.size(); ++ii)
221 sams.push_back(Model(opt.sams[ii]));
223 for (Strings
const & optSamsBn : opt.optsSams) {
225 for (
string const & optName : optSamsBn) {
227 optSams.push_back(Model());
229 Model model(optName);
230 if (model.sam.scms.empty()) {
232 String8s clrFiles = globFiles(String8(optName)+
"_*.*");
233 for (String8
const & cf : clrFiles)
234 model.colors.push_back(loadImage(path.dir()+cf));
236 optSams.push_back(model);
239 optsSams.push_back(optSams);
241 Sam3Controls ctls(dataDir() +
"main/si.ctl");
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);
254 for (
size_t ii=0; ii<num; ++ii) {
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);
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;
273 syn.error(
"Gender value must be 0 (random), -1 (male), +1 (female), or 2 (fixed)");
275 if (opt.attributes.caricature.fixed) {
276 face.coord.ts(0,0)[0] = 1.0f;
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);
281 if (opt.attributes.asymmetry.fixed) {
282 face.coord.ts(0,1)[0] = 1.0f;
283 ctls.setAsymmetry_(face.coord,opt.attributes.asymmetry.val);
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);
290 if (opt.useDetailTextures) {
291 string genStr = (ctls.getGender(face.coord,
FANTYPE_GEO) > 0 ?
"Male " :
"Female ");
300 String8 sw = dataDir()+
"main/detail/"+genStr+ageStr;
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);
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));
317 for (
size_t jj=0; jj<opt.optsMorphs.size(); ++jj) {
318 string morph = opt.optsMorphs[jj][randUniformUint(uint(opt.optsMorphs[jj].size()))];
320 morphs.push_back(morph);
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;
330 mesh.morph(morphCoord,mesh.verts);
331 mesh.transform_(SimilarityD{opt.pose.toHcs});
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;
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");
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"},
@ FANTYPE_TEX
Texture (color)
@ FANTYPE_GEO
Geometry (shape)
void cmd3Random(CLArgs const &)