diff --git a/modules/ANSCV/ANSOpenCV.cpp b/modules/ANSCV/ANSOpenCV.cpp index 1f26585..edd2eb1 100644 --- a/modules/ANSCV/ANSOpenCV.cpp +++ b/modules/ANSCV/ANSOpenCV.cpp @@ -2021,8 +2021,23 @@ namespace ANSCENTER return false; } - // Sort for consistent ordering - std::sort(imageFiles.begin(), imageFiles.end()); + // Sort by filename (lexicographic, case-sensitive) so playback order + // follows the on-disk name order. Sorting by std::filesystem::path:: + // filename() is explicit and robust even if cv::glob ever returns + // entries with different path prefixes or separators. + // Example input: + // 20260114_181439.703.jpg + // 20260114_181439.703_000.jpg + // 20260114_181439.703_001.jpg + // 20260114_181439.703_002.jpg + // 20260114_181439.803.jpg + // Byte-wise '.' (0x2E) < '_' (0x5F), so "...703.jpg" correctly + // precedes "...703_000.jpg". + std::sort(imageFiles.begin(), imageFiles.end(), + [](const cv::String& a, const cv::String& b) { + return std::filesystem::path(a).filename().string() < + std::filesystem::path(b).filename().string(); + }); // Cap at 5 minutes max duration const int maxFrames = fps * 300; @@ -2307,7 +2322,13 @@ namespace ANSCENTER std::cerr << "Error: No images found in folder: " << imageFolder << std::endl; return false; } - std::sort(imageFiles.begin(), imageFiles.end()); + // Sort by filename (lexicographic, case-sensitive). See matching + // comment in ImagesToMP4() for rationale and ordering example. + std::sort(imageFiles.begin(), imageFiles.end(), + [](const cv::String& a, const cv::String& b) { + return std::filesystem::path(a).filename().string() < + std::filesystem::path(b).filename().string(); + }); // Cap at 5 minutes max duration const int maxFrames = fps * 300; @@ -2712,7 +2733,13 @@ namespace ANSCENTER std::cerr << "Error: No images found in folder: " << imageFolder << std::endl; return false; } - std::sort(imageFiles.begin(), imageFiles.end()); + // Sort by filename (lexicographic, case-sensitive). See matching + // comment in ImagesToMP4() for rationale and ordering example. + std::sort(imageFiles.begin(), imageFiles.end(), + [](const cv::String& a, const cv::String& b) { + return std::filesystem::path(a).filename().string() < + std::filesystem::path(b).filename().string(); + }); const int maxFrames = fps * 300; if (static_cast(imageFiles.size()) > maxFrames) { @@ -5551,7 +5578,7 @@ extern "C" __declspec(dllexport) int ANSCV_ImagesToMP4_S( const char* imageFolder, const char* outputVideoPath, int maxWidth, int fps) { - + return ANSCV_ImagesToMP4FF_S(imageFolder,outputVideoPath,maxWidth,fps); try { if (!imageFolder || strlen(imageFolder) == 0) { std::cerr << "Error: Invalid image folder path!" << std::endl; diff --git a/tests/ANSCV-UnitTest/ANSCV-UnitTest.cpp b/tests/ANSCV-UnitTest/ANSCV-UnitTest.cpp index 5c5a4f3..2dd2eb0 100644 --- a/tests/ANSCV-UnitTest/ANSCV-UnitTest.cpp +++ b/tests/ANSCV-UnitTest/ANSCV-UnitTest.cpp @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include using namespace ANSCENTER; using namespace cv; @@ -1369,7 +1372,62 @@ int TestGetImage() { int GenerateVideo() { std::string imageFolder = "E:\\Programs\\DemoAssets\\ImageSeries\\20260415_142435.655"; std::string outputVideoPath = "E:\\Programs\\DemoAssets\\ImageSeries\\output7.mp4"; - int conversionResult = ANSCV_ImagesToMP4_S(imageFolder.c_str(), outputVideoPath.c_str(), 0,20); + + // Enumerate + sort by filename (same comparator the DLL uses) so we can + // preview the exact playback order. + std::vector images; + try { + for (const auto& entry : std::filesystem::directory_iterator(imageFolder)) { + if (!entry.is_regular_file()) continue; + std::string ext = entry.path().extension().string(); + std::transform(ext.begin(), ext.end(), ext.begin(), + [](unsigned char c) { return std::tolower(c); }); + if (ext == ".jpg" || ext == ".jpeg" || ext == ".png" || + ext == ".bmp" || ext == ".tif" || ext == ".tiff") { + images.push_back(entry.path().string()); + } + } + } catch (const std::exception& e) { + std::cerr << "Warning: could not enumerate image folder: " << e.what() << std::endl; + } + std::sort(images.begin(), images.end(), + [](const std::string& a, const std::string& b) { + return std::filesystem::path(a).filename().string() < + std::filesystem::path(b).filename().string(); + }); + size_t imageCount = images.size(); + + std::cout << "[Benchmark] ImagesToMP4_S" << std::endl; + std::cout << " source folder : " << imageFolder << std::endl; + std::cout << " output file : " << outputVideoPath << std::endl; + std::cout << " image count : " << imageCount << std::endl; + std::cout << " fps argument : 20" << std::endl; + + // Print the on-disk playback order (what the DLL will consume). + std::cout << " playback order:" << std::endl; + for (size_t i = 0; i < images.size(); ++i) { + std::cout << " [" << (i + 1) << "] " + << std::filesystem::path(images[i]).filename().string() << std::endl; + } + + auto t0 = std::chrono::high_resolution_clock::now(); + int conversionResult = ANSCV_ImagesToMP4_S(imageFolder.c_str(), outputVideoPath.c_str(), 0, 20); + auto t1 = std::chrono::high_resolution_clock::now(); + + auto elapsed_us = std::chrono::duration_cast(t1 - t0).count(); + double elapsed_ms = elapsed_us / 1000.0; + double elapsed_s = elapsed_us / 1000000.0; + + std::cout << "[Benchmark] ImagesToMP4_S finished." << std::endl; + std::cout << " result : " << conversionResult << std::endl; + std::cout << " elapsed : " << elapsed_ms << " ms (" << elapsed_s << " s)" << std::endl; + if (imageCount > 0) { + double per_image_ms = elapsed_ms / static_cast(imageCount); + double throughput = static_cast(imageCount) / elapsed_s; + std::cout << " per image : " << per_image_ms << " ms" << std::endl; + std::cout << " throughput : " << throughput << " images/s" << std::endl; + } + if (!conversionResult) { std::cerr << "Failed to convert images to MP4." << std::endl; return -1;