diff --git a/src/software/export/main_exportDistortion.cpp b/src/software/export/main_exportDistortion.cpp index f99b780e5e..dddb2d4a0f 100644 --- a/src/software/export/main_exportDistortion.cpp +++ b/src/software/export/main_exportDistortion.cpp @@ -29,6 +29,234 @@ namespace po = boost::program_options; using namespace aliceVision; using namespace aliceVision::camera; +/** + * Build nuke animated distortion + * @param content the output string + * @param sfmData the input sfmData + * @param sequence list of views with SAME sensor/image size/pixelaspectratio/undistortiontype + * @return true if succeeded +*/ +bool sequenceToNuke(std::string & content, const sfmData::SfMData & sfmData, const std::vector & sequence) +{ + if (sequence.size() == 0) + { + return false; + } + + //Sort views by increasing frame ID + std::vector sorted = sequence; + std::sort(sorted.begin(), sorted.end(), [](const sfmData::View::sptr & v1, const sfmData::View::sptr & v2) + { + return v1->getFrameId() < v2->getFrameId(); + }); + + std::string vecFocal, vecOffsetx, vecOffsety; + std::vector vecParams; + bool first = true; + camera::EUNDISTORTION commonType; + double commonSensorWidth; + double commonSensorHeight; + double commonPixelAspect; + Vec2 commonSize; + + //Build strings + for (const auto & viewPtr: sorted) + { + IndexT idIntrinsic = viewPtr->getIntrinsicId(); + if (idIntrinsic == UndefinedIndexT) + { + continue; + } + + const auto intrinsic = sfmData.getIntrinsicSharedPtr(idIntrinsic); + const auto intrinsicDisto = std::dynamic_pointer_cast(intrinsic); + if (!intrinsicDisto) + { + continue; + } + + aliceVision::IndexT frameId = viewPtr->getFrameId(); + std::string sfid = "x" + std::to_string(frameId); + + const auto undistortion = intrinsicDisto->getUndistortion(); + + + const double focal = intrinsicDisto->getFocalLength(); + const double sensorWidth = intrinsicDisto->sensorWidth(); + const double sensorHeight = intrinsicDisto->sensorHeight(); + const auto& size = undistortion->getSize(); + const double pa = undistortion->getPixelAspectRatio(); + const double updatedSensorHeight = (undistortion->isDesqueezed())?sensorHeight:sensorHeight/pa; + const Vec2 offset = undistortion->getScaledOffset(); + + const std::vector& params = undistortion->getParameters(); + + if (first) + { + commonType = undistortion->getType(); + commonSensorWidth = sensorWidth; + commonSensorHeight = updatedSensorHeight; + commonSize = size; + commonPixelAspect = pa; + + vecParams.resize(params.size()); + } + else + { + bool same = (commonType == undistortion->getType()); + same &= (commonSensorWidth == sensorWidth); + same &= (commonSensorHeight == updatedSensorHeight); + same &= (commonSize == size); + same &= (commonPixelAspect == pa); + + if (!same) + { + ALICEVISION_LOG_ERROR("Unsupported change of image/sensor size in the sequence"); + return false; + } + } + + + vecFocal += sfid + " " + std::to_string(focal) + " "; + vecOffsetx += sfid + " " + std::to_string(offset.x()) + " "; + vecOffsety += sfid + " " + std::to_string(-offset.y()) + " "; + + for (int idParam = 0; idParam < params.size(); idParam++) + { + double param = params[idParam]; + + if (idParam == 10) + { + if (undistortion->getType() == EUNDISTORTION::UNDISTORTION_3DEANAMORPHIC4) + { + param = radianToDegree(params[10]); + } + } + + vecParams[idParam] += sfid + " " + std::to_string(param) + " "; + } + + first = false; + } + + for (int idParam = 0; idParam < vecParams.size(); idParam++) + { + vecParams[idParam] = "{{curve " + vecParams[idParam] + "}}"; + } + + vecFocal = "{{curve " + vecFocal + "}}"; + std::string vecOffset = "{{curve "+ vecOffsetx+ "} {curve " + vecOffsety + "}}"; + + std::stringstream ss; + + switch (commonType) + { + case EUNDISTORTION::UNDISTORTION_RADIALK3: + ss << "LensDistortion2 {" << "\n" + << "\n" + << " distortionModelPreset Custom" << "\n" + << " distortionModelPreset \"3DEqualizer/3DE4 Anamorphic - Standard, Degree 4\"\n" + << " distortionNumerator0 " << vecParams[0] << "\n" + << "\n" + << " distortionNumerator1 " << vecParams[1] << "\n" + << " lens Anamorphic\n" + << " distortionNumerator2 " << vecParams[2] << "\n" + << " centre " << vecOffset << "\n" + << " output Undistort" << "\n" + << " distortionScalingType Format" << "\n" + << " distortionScalingFormat \"" << commonSize(0) << " " << commonSize(1) << " 0 0 " << commonSize(0) << " " << commonSize(1) << " 1 AV_undist_fmt \"" + << "\n" + << "\n" + << " distortionOrder {3 0}" << "\n" + << " normalisationType Diagonal" << "\n" + << " distortInFisheyeSpace false" << "\n" + << "}" + << "\n"; + break; + case EUNDISTORTION::UNDISTORTION_3DEANAMORPHIC4: + ss << "LD_3DE4_Anamorphic_Standard_Degree_4 {\n" + << "direction undistort \n" + << "tde4_focal_length_cm " << vecFocal << " \n" + << "tde4_custom_focus_distance_cm 1.0 \n" + << "tde4_filmback_width_cm " << commonSensorWidth << " \n" + << "tde4_filmback_height_cm " << commonSensorWidth << " \n" + << "tde4_lens_center_offset_x_cm 0.0000000 \n" + << "tde4_lens_center_offset_y_cm 0.0000000 \n" + << "tde4_pixel_aspect " << commonPixelAspect << " \n" + << "field_of_view_xa_unit 0.0000000 \n" + << "field_of_view_xb_unit 1.0000000 \n" + << "field_of_view_ya_unit 0.0000000 \n" + << "field_of_view_yb_unit 1.0000000 \n" + << "Cx02_Degree_2 " << vecParams[0] << " \n" + << "Cy02_Degree_2 " << vecParams[1] << " \n" + << "Cx22_Degree_2 " << vecParams[2] << " \n" + << "Cy22_Degree_2 " << vecParams[3] << " \n" + << "Cx04_Degree_4 " << vecParams[4] << " \n" + << "Cy04_Degree_4 " << vecParams[5] << " \n" + << "Cx24_Degree_4 " << vecParams[6] << " \n" + << "Cy24_Degree_4 " << vecParams[7] << " \n" + << "Cx44_Degree_4 " << vecParams[8] << " \n" + << "Cy44_Degree_4 " << vecParams[9] << " \n" + << "Lens_Rotation " << vecParams[10] << " \n" + << "Squeeze_X " << vecParams[11] << "\n" + << "Squeeze_Y " << vecParams[12] << "\n" + << "}\n"; + break; + case EUNDISTORTION::UNDISTORTION_3DERADIAL4: + ss << "LD_3DE4_Radial_Standard_Degree_4 {\n" + << "direction undistort \n" + << "tde4_focal_length_cm " << vecFocal << " \n" + << "tde4_custom_focus_distance_cm 1.0 \n" + << "tde4_filmback_width_cm " << commonSensorWidth << " \n" + << "tde4_filmback_height_cm " << commonSensorWidth << " \n" + << "tde4_lens_center_offset_x_cm 0.0000000 \n" + << "tde4_lens_center_offset_y_cm 0.0000000 \n" + << "tde4_pixel_aspect " << commonPixelAspect << " \n" + << "field_of_view_xa_unit 0.0000000 \n" + << "field_of_view_xb_unit 1.0000000 \n" + << "field_of_view_ya_unit 0.0000000 \n" + << "field_of_view_yb_unit 1.0000000 \n" + << "Distortion_Degree_2 " << vecParams[0] << " \n" + << "U_Degree_2 " << vecParams[1] << " \n" + << "V_Degree_2 " << vecParams[2] << " \n" + << "Quartic_Distortion_Degree_4 " << vecParams[3] << " \n" + << "U_Degree_4 " << vecParams[4] << " \n" + << "V_Degree_4 " << vecParams[5] << " \n" + << "Phi_Cylindric_Direction " << vecParams[6] << " \n" + << "B_Cylindric_Bending " << vecParams[7] << " \n" + << "}\n"; + break; + case EUNDISTORTION::UNDISTORTION_3DECLASSICLD: + ss << "LD_3DE4_Radial_Standard_Degree_4 {\n" + << "direction undistort \n" + << "tde4_focal_length_cm " << vecFocal << " \n" + << "tde4_custom_focus_distance_cm 1.0 \n" + << "tde4_filmback_width_cm " << commonSensorWidth << " \n" + << "tde4_filmback_height_cm " << commonSensorHeight << " \n" + << "tde4_lens_center_offset_x_cm 0.0000000 \n" + << "tde4_lens_center_offset_y_cm 0.0000000 \n" + << "tde4_pixel_aspect " << commonPixelAspect << " \n" + << "field_of_view_xa_unit 0.0000000 \n" + << "field_of_view_xb_unit 1.0000000 \n" + << "field_of_view_ya_unit 0.0000000 \n" + << "field_of_view_yb_unit 1.0000000 \n" + << "Distortion " << vecParams[0] << " \n" + << "Anamorphic_Squeeze " << vecParams[1] << " \n" + << "Curvature_X " << vecParams[2] << " \n" + << "Curvature_Y " << vecParams[3] << " \n" + << "Quartic_Distortion " << vecParams[4] << " \n" + << "}\n"; + break; + default: + ALICEVISION_LOG_ERROR("Unsupported intrinsic type for conversion to Nuke LensDistortion node: " << EUNDISTORTION_enumToString(commonType)); + return false; + } + + content = ss.str(); + + return true; +} + std::string toNuke(std::shared_ptr intrinsic) { std::stringstream ss; @@ -50,11 +278,11 @@ std::string toNuke(std::shared_ptr intrinsic) ss << "LensDistortion2 {" << "\n" << "\n" << " distortionModelPreset Custom" << "\n" - << " distortionModelPreset \"3DEqualizer/3DE4 Anamorphic - Standard, Degree 4\"" + << " distortionModelPreset \"3DEqualizer/3DE4 Anamorphic - Standard, Degree 4\"\n" << " distortionNumerator0 " << params[0] << "\n" << "\n" << " distortionNumerator1 " << params[1] << "\n" - << " lens Anamorphic" + << " lens Anamorphic\n" << " distortionNumerator2 " << params[2] << "\n" << " centre {" << offset[0] << " " << -offset[1] << "}" << "\n" << " output Undistort" << "\n" @@ -193,6 +421,7 @@ int aliceVision_main(int argc, char* argv[]) bool exportLensGridsUndistorted = true; bool exportNukeNode = true; bool exportSTMaps = true; + bool exportAnimatedNukeNode = true; // Command line parameters // clang-format off @@ -205,9 +434,11 @@ int aliceVision_main(int argc, char* argv[]) po::options_description optionalParams("Optional parameters"); optionalParams.add_options() - ("exportNukeNode", po::value(&exportNukeNode)->default_value(exportLensGridsUndistorted), + ("exportNukeNode", po::value(&exportNukeNode)->default_value(exportNukeNode), + "Export Nuke node as FILE.nk.") + ("exportAnimatedNukeNode", po::value(&exportAnimatedNukeNode)->default_value(exportAnimatedNukeNode), "Export Nuke node as FILE.nk.") - ("exportSTMaps", po::value(&exportSTMaps)->default_value(exportLensGridsUndistorted), + ("exportSTMaps", po::value(&exportSTMaps)->default_value(exportSTMaps), "Export STMaps.") ("exportLensGridsUndistorted,e", po::value(&exportLensGridsUndistorted)->default_value(exportLensGridsUndistorted), "Export lens grids undistorted for validation."); @@ -229,6 +460,77 @@ int aliceVision_main(int argc, char* argv[]) return EXIT_FAILURE; } + if (exportAnimatedNukeNode) + { + //Map is indexed by a tuple of : + //Undistortion type + //sensor width + //sensor height + //image width + //image height + //Pixel aspect ratio + typedef std::tuple Descriptor; + + //Group views by unique descriptor + std::map> viewsPerUndistortionType; + for (const auto& [viewId, viewPtr] : sfmData.getViews()) + { + IndexT idIntrinsic = viewPtr->getIntrinsicId(); + if (idIntrinsic == UndefinedIndexT) + { + continue; + } + + const auto intrinsic = sfmData.getIntrinsicSharedPtr(idIntrinsic); + const auto intrinsicDisto = std::dynamic_pointer_cast(intrinsic); + if (!intrinsicDisto) + { + ALICEVISION_LOG_INFO("Intrinsic " << idIntrinsic << " has incorrect format"); + continue; + } + + const auto undistortion = intrinsicDisto->getUndistortion(); + + const double sensorWidth = intrinsicDisto->sensorWidth(); + const double sensorHeight = intrinsicDisto->sensorHeight(); + const auto& size = undistortion->getSize(); + const double pa = undistortion->getPixelAspectRatio(); + + Descriptor d = {undistortion->getType(), sensorWidth, sensorHeight, size.x(), size.y(), pa}; + viewsPerUndistortionType[d].push_back(viewPtr); + } + + for (const auto & [desc, vec]: viewsPerUndistortionType) + { + std::string nukeNodeStr; + + if (!sequenceToNuke(nukeNodeStr, sfmData, vec)) + { + continue; + } + + const std::string typeName = camera::EUNDISTORTION_enumToString(std::get<0>(desc)); + + ALICEVISION_LOG_INFO("Writing Nuke animated LensDistortion node for " << typeName); + + + //Create a unique filename + std::stringstream ss; + ss << outputFilePath << "/animated_"; + ss << typeName << "_"; + ss << std::to_string(std::get<1>(desc)) << "_"; + ss << std::to_string(std::get<2>(desc)) << "_"; + ss << std::to_string(std::get<3>(desc)) << "_"; + ss << std::to_string(std::get<4>(desc)) << "_"; + ss << std::to_string(int(std::get<5>(desc) * 100.0)) << ".nk"; + + //Store file + std::ofstream of(ss.str()); + of << nukeNodeStr; + of.close(); + } + } + for (const auto& [intrinsicId, intrinsicPtr] : sfmData.getIntrinsics()) { ALICEVISION_LOG_INFO("Exporting distortion for intrinsic " << intrinsicId);