Skip to content

Commit c4e2916

Browse files
authored
Merge pull request #1735 from alicevision/dev/applyExternalCalibration
ApplyCalibration can use a json file
2 parents b9e582b + 8c0863b commit c4e2916

File tree

2 files changed

+244
-59
lines changed

2 files changed

+244
-59
lines changed

src/software/utils/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,8 +311,10 @@ if(ALICEVISION_BUILD_SFM)
311311
aliceVision_cmdline
312312
aliceVision_sfmData
313313
aliceVision_sfmDataIO
314+
aliceVision_dataio
314315
aliceVision_camera
315316
Boost::program_options
317+
Boost::json
316318
)
317319
endif()
318320

src/software/utils/main_applyCalibration.cpp

Lines changed: 242 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <aliceVision/sfmData/SfMData.hpp>
1212
#include <aliceVision/sfmDataIO/sfmDataIO.hpp>
1313
#include <aliceVision/camera/IntrinsicInitMode.hpp>
14+
#include <aliceVision/dataio/json.hpp>
1415

1516
#include <boost/program_options.hpp>
1617

@@ -26,62 +27,8 @@
2627
namespace po = boost::program_options;
2728
using namespace aliceVision;
2829

29-
int aliceVision_main(int argc, char** argv)
30+
bool applySfmData(sfmData::SfMData & sfmData, const sfmData::SfMData & sfmDataCalibrated)
3031
{
31-
// command-line parameters
32-
std::string sfmDataFilename;
33-
std::string outSfMDataFilename;
34-
std::string sfmDataCalibratedFilename;
35-
36-
// clang-format off
37-
po::options_description requiredParams("Required parameters");
38-
requiredParams.add_options()
39-
("input,i", po::value<std::string>(&sfmDataFilename)->required(),
40-
"SfMData scene to apply calibration to.")
41-
("output,o", po::value<std::string>(&outSfMDataFilename)->required(),
42-
"Output SfMData scene.")
43-
("calibration,c", po::value<std::string>(&sfmDataCalibratedFilename)->required(),
44-
"Calibrated SfMData scene.");
45-
// clang-format on
46-
47-
CmdLine cmdline("AliceVision applyCalibration");
48-
cmdline.add(requiredParams);
49-
if (!cmdline.execute(argc, argv))
50-
{
51-
return EXIT_FAILURE;
52-
}
53-
54-
// Load input scene
55-
sfmData::SfMData sfmData;
56-
if (!sfmDataIO::load(sfmData, sfmDataFilename, sfmDataIO::ESfMData::ALL))
57-
{
58-
ALICEVISION_LOG_ERROR("The input SfMData file '" << sfmDataFilename << "' cannot be read");
59-
return EXIT_FAILURE;
60-
}
61-
62-
// Special case to handle:
63-
// If calibrated SfMData filename is an empty string
64-
// simply copy the input SfMData
65-
if (sfmDataCalibratedFilename.empty())
66-
{
67-
// Save sfmData to disk
68-
if (!sfmDataIO::save(sfmData, outSfMDataFilename, sfmDataIO::ESfMData::ALL))
69-
{
70-
ALICEVISION_LOG_ERROR("The output SfMData file '" << outSfMDataFilename << "' cannot be written.");
71-
return EXIT_FAILURE;
72-
}
73-
74-
return EXIT_SUCCESS;
75-
}
76-
77-
// Load calibrated scene
78-
sfmData::SfMData sfmDataCalibrated;
79-
if (!sfmDataIO::load(sfmDataCalibrated, sfmDataCalibratedFilename, sfmDataIO::ESfMData::ALL))
80-
{
81-
ALICEVISION_LOG_ERROR("The calibrated SfMData file '" << sfmDataCalibratedFilename << "' cannot be read");
82-
return EXIT_FAILURE;
83-
}
84-
8532
// Detect calibration setup
8633
const auto& calibratedIntrinsics = sfmDataCalibrated.getIntrinsics();
8734

@@ -91,7 +38,7 @@ int aliceVision_main(int argc, char** argv)
9138
if (!isMonoCam && !isMultiCam)
9239
{
9340
ALICEVISION_LOG_ERROR("Unknown calibration setup");
94-
return EXIT_FAILURE;
41+
return false;
9542
}
9643

9744
// Apply rig calibration
@@ -100,7 +47,7 @@ int aliceVision_main(int argc, char** argv)
10047
if (sfmData.getRigs().size() != 1)
10148
{
10249
ALICEVISION_LOG_ERROR("There must be exactly 1 rig to apply rig calibration");
103-
return EXIT_FAILURE;
50+
return false;
10451
}
10552

10653
// Retrieve calibrated sub-poses
@@ -114,7 +61,7 @@ int aliceVision_main(int argc, char** argv)
11461
if (subPoses.size() != calibratedSubPoses.size())
11562
{
11663
ALICEVISION_LOG_ERROR("Incoherent number of sub-poses");
117-
return EXIT_FAILURE;
64+
return false;
11865
}
11966

12067
// Copy calibrated sub-poses
@@ -192,7 +139,9 @@ int aliceVision_main(int argc, char** argv)
192139
const bool isDistortionCalibrated = calibratedIntrinsic->getDistortionInitializationMode() == camera::EInitMode::CALIBRATED;
193140

194141
if (!isIntrinsicCalibrated && !isDistortionCalibrated)
142+
{
195143
continue;
144+
}
196145

197146
// Aspect ratio of input intrinsic
198147
const unsigned int width = intrinsic->w();
@@ -230,7 +179,7 @@ int aliceVision_main(int argc, char** argv)
230179
if (distortionOnly == false || smaller == false)
231180
{
232181
ALICEVISION_LOG_ERROR("Intrinsic from input SfMData and calibrated SfMData are incompatible.");
233-
return EXIT_FAILURE;
182+
return false;
234183
}
235184
}
236185

@@ -274,6 +223,240 @@ int aliceVision_main(int argc, char** argv)
274223
intrinsics.at(intrinsicId) = newIntrinsic;
275224
}
276225

226+
return true;
227+
}
228+
229+
bool applyJson(sfmData::SfMData & sfmData, boost::json::value & input)
230+
{
231+
if (input.kind() != boost::json::kind::object)
232+
{
233+
return false;
234+
}
235+
236+
if (sfmData.getIntrinsics().size() != 1)
237+
{
238+
ALICEVISION_LOG_ERROR("Number of intrinsics is not one");
239+
return false;
240+
}
241+
242+
auto intrinsic = std::dynamic_pointer_cast<camera::IntrinsicScaleOffsetDisto>(sfmData.getIntrinsics().begin()->second);
243+
if (!camera::isPinhole(intrinsic->getType()))
244+
{
245+
ALICEVISION_LOG_ERROR("Original intrinsic is not a pinhole");
246+
return false;
247+
}
248+
249+
auto const& obj = input.get_object();
250+
251+
const std::string & model = boost::json::value_to<std::string>(obj.at("distortionModel"));
252+
253+
const double & focalLength = boost::json::value_to<double>(obj.at("focalLength"));
254+
const double & focusDistance = boost::json::value_to<double>(obj.at("focusDistance"));
255+
const double & filmbackWidth = boost::json::value_to<double>(obj.at("filmbackWidth"));
256+
const double & filmbackHeight = boost::json::value_to<double>(obj.at("filmbackHeight"));
257+
const double & filmbackOffsetX = boost::json::value_to<double>(obj.at("filmbackOffsetX"));
258+
const double & filmbackOffsetY = boost::json::value_to<double>(obj.at("filmbackOffsetY"));
259+
const double & pixelAspect = boost::json::value_to<double>(obj.at("pixelAspect"));
260+
261+
if (std::abs(filmbackOffsetX) > 1e-12)
262+
{
263+
ALICEVISION_LOG_ERROR("Unsupported value for filmbackOffsetX");
264+
return false;
265+
}
266+
267+
if (std::abs(filmbackOffsetY) > 1e-12)
268+
{
269+
ALICEVISION_LOG_ERROR("Unsupported value for filmbackOffsetY");
270+
return false;
271+
}
272+
273+
double w = intrinsic->w();
274+
double h = intrinsic->h();
275+
double nh = h / pixelAspect;
276+
double ratio = w / nh;
277+
double ratioDesqueezed = w / nh;
278+
double iratio = filmbackWidth / filmbackHeight;
279+
280+
//Compare image size ratio and filmback size ratio
281+
bool isDesqueezed = false;
282+
if (std::abs(ratioDesqueezed - iratio) < 1e-2)
283+
{
284+
ALICEVISION_LOG_INFO("Input image look desqueezed");
285+
isDesqueezed = true;
286+
}
287+
else if (std::abs(ratio - iratio) > 1e-2)
288+
{
289+
ALICEVISION_LOG_ERROR("Incompatible image ratios");
290+
return false;
291+
}
292+
293+
intrinsic->setSensorWidth(filmbackWidth);
294+
intrinsic->setSensorHeight((isDesqueezed)?filmbackHeight:filmbackHeight*pixelAspect);
295+
intrinsic->setDistortionObject(nullptr);
296+
297+
if (model == "anamorphic4")
298+
{
299+
std::vector<double> params =
300+
{
301+
boost::json::value_to<double>(obj.at("Cx02Degree2")),
302+
boost::json::value_to<double>(obj.at("Cy02Degree2")),
303+
boost::json::value_to<double>(obj.at("Cx22Degree2")),
304+
boost::json::value_to<double>(obj.at("Cy22Degree2")),
305+
boost::json::value_to<double>(obj.at("Cx04Degree4")),
306+
boost::json::value_to<double>(obj.at("Cy04Degree4")),
307+
boost::json::value_to<double>(obj.at("Cx24Degree4")),
308+
boost::json::value_to<double>(obj.at("Cy24Degree4")),
309+
boost::json::value_to<double>(obj.at("Cx44Degree4")),
310+
boost::json::value_to<double>(obj.at("Cy44Degree4")),
311+
degreeToRadian(boost::json::value_to<double>(obj.at("lensRotation"))),
312+
boost::json::value_to<double>(obj.at("squeezeX")),
313+
boost::json::value_to<double>(obj.at("squeezeY")),
314+
};
315+
316+
auto undistortion = camera::createUndistortion(camera::EUNDISTORTION::UNDISTORTION_3DEANAMORPHIC4);
317+
undistortion->setSize(w, h);
318+
undistortion->setPixelAspectRatio(pixelAspect);
319+
undistortion->setDesqueezed(isDesqueezed);
320+
undistortion->setParameters(params);
321+
intrinsic->setUndistortionObject(undistortion);
322+
intrinsic->setDistortionInitializationMode(camera::EInitMode::CALIBRATED);
323+
}
324+
else if (model == "classicLD")
325+
{
326+
std::vector<double> params =
327+
{
328+
boost::json::value_to<double>(obj.at("distortion")),
329+
boost::json::value_to<double>(obj.at("anamorphicSqueeze")),
330+
boost::json::value_to<double>(obj.at("curvatureX")),
331+
boost::json::value_to<double>(obj.at("curvatureY")),
332+
boost::json::value_to<double>(obj.at("quarticDistortion"))
333+
};
334+
335+
auto undistortion = camera::createUndistortion(camera::EUNDISTORTION::UNDISTORTION_3DECLASSICLD);
336+
undistortion->setSize(w, h);
337+
undistortion->setPixelAspectRatio(pixelAspect);
338+
undistortion->setDesqueezed(isDesqueezed);
339+
undistortion->setParameters(params);
340+
intrinsic->setUndistortionObject(undistortion);
341+
intrinsic->setDistortionInitializationMode(camera::EInitMode::CALIBRATED);
342+
}
343+
else if (model == "radial4")
344+
{
345+
std::vector<double> params =
346+
{
347+
boost::json::value_to<double>(obj.at("distortionDegree2")),
348+
boost::json::value_to<double>(obj.at("uDegree2")),
349+
boost::json::value_to<double>(obj.at("vDegree2")),
350+
boost::json::value_to<double>(obj.at("quarticDistortionDegree4")),
351+
boost::json::value_to<double>(obj.at("uDegree4")),
352+
boost::json::value_to<double>(obj.at("vDegree4")),
353+
boost::json::value_to<double>(obj.at("phiCylindricDirection")),
354+
boost::json::value_to<double>(obj.at("bCylindricBending")),
355+
};
356+
357+
auto undistortion = camera::createUndistortion(camera::EUNDISTORTION::UNDISTORTION_3DERADIAL4);
358+
undistortion->setSize(w, h);
359+
undistortion->setPixelAspectRatio(pixelAspect);
360+
undistortion->setDesqueezed(isDesqueezed);
361+
undistortion->setParameters(params);
362+
intrinsic->setUndistortionObject(undistortion);
363+
intrinsic->setDistortionInitializationMode(camera::EInitMode::CALIBRATED);
364+
}
365+
else
366+
{
367+
ALICEVISION_LOG_ERROR("unknown distortion model");
368+
return false;
369+
}
370+
371+
return true;
372+
}
373+
374+
int aliceVision_main(int argc, char** argv)
375+
{
376+
// command-line parameters
377+
std::string sfmDataFilename;
378+
std::string outSfMDataFilename;
379+
std::string calibrationFilename;
380+
bool useJson = true;
381+
382+
// clang-format off
383+
po::options_description requiredParams("Required parameters");
384+
requiredParams.add_options()
385+
("input,i", po::value<std::string>(&sfmDataFilename)->required(),
386+
"SfMData scene to apply calibration to.")
387+
("output,o", po::value<std::string>(&outSfMDataFilename)->required(),
388+
"Output SfMData scene.")
389+
("useJson", po::value<bool>(&useJson)->default_value(useJson),
390+
"Calibration is a Lens calibration file generated using 3Dequalizer instead of an sfmData.")
391+
("calibration,c", po::value<std::string>(&calibrationFilename)->required(),
392+
"Calibration file (SfmData or Lens calibration file).");
393+
// clang-format on
394+
395+
CmdLine cmdline("AliceVision applyCalibration");
396+
cmdline.add(requiredParams);
397+
if (!cmdline.execute(argc, argv))
398+
{
399+
return EXIT_FAILURE;
400+
}
401+
402+
// Load input scene
403+
sfmData::SfMData sfmData;
404+
if (!sfmDataIO::load(sfmData, sfmDataFilename, sfmDataIO::ESfMData::ALL))
405+
{
406+
ALICEVISION_LOG_ERROR("The input SfMData file '" << sfmDataFilename << "' cannot be read");
407+
return EXIT_FAILURE;
408+
}
409+
410+
// Special case to handle:
411+
// If calibrated SfMData filename is an empty string
412+
// simply copy the input SfMData
413+
if (calibrationFilename.empty())
414+
{
415+
// Save sfmData to disk
416+
if (!sfmDataIO::save(sfmData, outSfMDataFilename, sfmDataIO::ESfMData::ALL))
417+
{
418+
ALICEVISION_LOG_ERROR("The output SfMData file '" << outSfMDataFilename << "' cannot be written.");
419+
return EXIT_FAILURE;
420+
}
421+
422+
return EXIT_SUCCESS;
423+
}
424+
425+
426+
if (useJson)
427+
{
428+
boost::json::error_code ec;
429+
std::ifstream inputfile(calibrationFilename);
430+
std::vector<boost::json::value> content = readJsons(inputfile, ec);
431+
if (content.size() != 1)
432+
{
433+
ALICEVISION_LOG_ERROR("Invalid calibration file");
434+
return EXIT_FAILURE;
435+
}
436+
437+
if (!applyJson(sfmData, content[0]))
438+
{
439+
ALICEVISION_LOG_ERROR("Error applying calibrated json");
440+
return EXIT_FAILURE;
441+
}
442+
}
443+
else
444+
{
445+
// Load calibrated scene
446+
sfmData::SfMData sfmDataCalibrated;
447+
if (!sfmDataIO::load(sfmDataCalibrated, calibrationFilename, sfmDataIO::ESfMData::ALL))
448+
{
449+
ALICEVISION_LOG_ERROR("The calibrated SfMData file '" << calibrationFilename << "' cannot be read");
450+
return EXIT_FAILURE;
451+
}
452+
453+
if (!applySfmData(sfmData, sfmDataCalibrated))
454+
{
455+
ALICEVISION_LOG_ERROR("Error applying calibrated sfmData");
456+
return EXIT_FAILURE;
457+
}
458+
}
459+
277460
// Save sfmData to disk
278461
if (!sfmDataIO::save(sfmData, outSfMDataFilename, sfmDataIO::ESfMData(sfmDataIO::ALL)))
279462
{

0 commit comments

Comments
 (0)