|
|
#include "ColorComparisonPixel.h" |
|
|
#include "Mesh.h" // 在cpp中包含完整定义 |
|
|
#include <opencv2/opencv.hpp> |
|
|
|
|
|
#include <cstdlib> |
|
|
#include <cstdio> |
|
|
#include <algorithm> |
|
|
#include <sys/stat.h> |
|
|
#include <iomanip> |
|
|
#include <sstream> |
|
|
|
|
|
using namespace MVS; |
|
|
|
|
|
// 辅助函数:检查目录是否存在 |
|
|
static bool directoryExists(const std::string& path) { |
|
|
struct stat info; |
|
|
if (stat(path.c_str(), &info) != 0) { |
|
|
return false; |
|
|
} |
|
|
return (info.st_mode & S_IFDIR) != 0; |
|
|
} |
|
|
|
|
|
// 辅助函数:创建目录 |
|
|
static bool createDirectory(const std::string& path) { |
|
|
#ifdef _WIN32 |
|
|
return _mkdir(path.c_str()) == 0; |
|
|
#else |
|
|
return mkdir(path.c_str(), 0755) == 0; |
|
|
#endif |
|
|
} |
|
|
|
|
|
// 辅助函数:递归创建目录 |
|
|
static bool createDirectories(const std::string& path) { |
|
|
size_t pos = 0; |
|
|
std::string dir; |
|
|
|
|
|
while ((pos = path.find_first_of("/\\", pos + 1)) != std::string::npos) { |
|
|
dir = path.substr(0, pos); |
|
|
if (!directoryExists(dir)) { |
|
|
if (!createDirectory(dir)) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// 创建最终目录 |
|
|
if (!directoryExists(path)) { |
|
|
return createDirectory(path); |
|
|
} |
|
|
|
|
|
return true; |
|
|
} |
|
|
|
|
|
// 构造函数 |
|
|
ColorComparisonPixel::ColorComparisonPixel(const std::string& dir) : outputDir(dir) { |
|
|
// 确保输出目录以斜杠结尾 |
|
|
if (!outputDir.empty() && outputDir.back() != '/' && outputDir.back() != '\\') { |
|
|
outputDir += '/'; |
|
|
} |
|
|
|
|
|
// 创建输出目录 |
|
|
if (!createDirectories(outputDir)) { |
|
|
printf("⚠️ 无法创建目录: %s\n", outputDir.c_str()); |
|
|
} else { |
|
|
printf("✅ 输出目录: %s\n", outputDir.c_str()); |
|
|
} |
|
|
} |
|
|
|
|
|
// 添加颜色信息 |
|
|
void ColorComparisonPixel::addColorInfo(int faceId, |
|
|
const MeshColor& gaussianColor, |
|
|
const MeshColor& originalColor, |
|
|
float distance, float threshold, |
|
|
const std::string& filename) { |
|
|
ColorInfo info = {faceId, gaussianColor, originalColor, distance, threshold, filename}; |
|
|
faceViewColorMap[faceId][filename].push_back(info); |
|
|
} |
|
|
|
|
|
// 获取总face数 |
|
|
int ColorComparisonPixel::getTotalFaces() const { |
|
|
return faceViewColorMap.size(); |
|
|
} |
|
|
|
|
|
// 获取总记录数 |
|
|
int ColorComparisonPixel::getTotalRecords() const { |
|
|
int total = 0; |
|
|
for (const auto& faceEntry : faceViewColorMap) { |
|
|
for (const auto& viewEntry : faceEntry.second) { |
|
|
total += viewEntry.second.size(); |
|
|
} |
|
|
} |
|
|
return total; |
|
|
} |
|
|
|
|
|
// 获取faceid列表 |
|
|
std::vector<int> ColorComparisonPixel::getFaceIds() const { |
|
|
std::vector<int> faceIds; |
|
|
for (const auto& faceEntry : faceViewColorMap) { |
|
|
faceIds.push_back(faceEntry.first); |
|
|
} |
|
|
return faceIds; |
|
|
} |
|
|
|
|
|
// 创建按faceid分组的对比图 |
|
|
void ColorComparisonPixel::createBatchComparison(int maxCellsPerRow, int maxFacesPerImage) { |
|
|
if (faceViewColorMap.empty()) { |
|
|
printf("⚠️ 没有颜色信息可生成\n"); |
|
|
return; |
|
|
} |
|
|
|
|
|
printf("正在创建按faceid分组的对比图...\n"); |
|
|
printf("总face数: %zu\n", faceViewColorMap.size()); |
|
|
printf("总记录数: %d\n", getTotalRecords()); |
|
|
|
|
|
// 单元格参数 |
|
|
int cellWidth = 220; // 每个单元格宽度 |
|
|
int cellHeight = 150; // 每个单元格高度 |
|
|
int cellMargin = 5; // 单元格边距 |
|
|
|
|
|
// 左侧视图名称区域宽度 |
|
|
int viewNameWidth = 200; |
|
|
|
|
|
// 限制处理的face数 |
|
|
int facesToProcess = std::min((int)faceViewColorMap.size(), maxFacesPerImage); |
|
|
|
|
|
// 处理前N个face |
|
|
int faceCount = 0; |
|
|
for (const auto& faceEntry : faceViewColorMap) { |
|
|
if (faceCount >= facesToProcess) break; |
|
|
|
|
|
int faceId = faceEntry.first; |
|
|
const auto& viewMap = faceEntry.second; |
|
|
|
|
|
printf("处理 face %d (%zu 个视图)...\n", faceId, viewMap.size()); |
|
|
|
|
|
// 计算这个face需要的总行数 |
|
|
int totalRows = 0; |
|
|
std::vector<std::pair<std::string, int>> viewRowsInfo; // 存储每个视图需要的行数 |
|
|
|
|
|
for (const auto& viewEntry : viewMap) { |
|
|
const std::string& viewName = viewEntry.first; |
|
|
const std::vector<ColorInfo>& infos = viewEntry.second; |
|
|
|
|
|
// 计算这个视图需要的行数 |
|
|
int rowsForThisView = (infos.size() + maxCellsPerRow - 1) / maxCellsPerRow; |
|
|
viewRowsInfo.emplace_back(viewName, rowsForThisView); |
|
|
totalRows += rowsForThisView; |
|
|
} |
|
|
|
|
|
if (totalRows == 0) continue; |
|
|
|
|
|
// 图片总尺寸 |
|
|
int totalWidth = viewNameWidth + (maxCellsPerRow * cellWidth); |
|
|
int totalHeight = 60 + (totalRows * cellHeight); // 60像素用于标题 |
|
|
|
|
|
// 创建大图 |
|
|
cv::Mat faceImage(totalHeight, totalWidth, CV_8UC3, cv::Scalar(245, 245, 245)); |
|
|
|
|
|
// 添加标题 |
|
|
std::string title = cv::format("Face %d - Color Comparison", faceId); |
|
|
cv::putText(faceImage, title, |
|
|
cv::Point(20, 30), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.8, cv::Scalar(0, 0, 0), 2); |
|
|
|
|
|
// 添加统计信息 |
|
|
int totalRecords = 0; |
|
|
int needFixCount = 0; |
|
|
for (const auto& viewEntry : viewMap) { |
|
|
totalRecords += viewEntry.second.size(); |
|
|
for (const auto& info : viewEntry.second) { |
|
|
if (info.distance > info.threshold) needFixCount++; |
|
|
} |
|
|
} |
|
|
|
|
|
std::string stats = cv::format("Views: %zu, Records: %d, Need Fix: %d", |
|
|
viewMap.size(), totalRecords, needFixCount); |
|
|
cv::putText(faceImage, stats, |
|
|
cv::Point(20, 55), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
int currentGlobalRow = 0; // 全局行索引 |
|
|
|
|
|
// 遍历这个face的所有视图 |
|
|
for (const auto& viewRowPair : viewRowsInfo) { |
|
|
const std::string& viewName = viewRowPair.first; |
|
|
int rowsForThisView = viewRowPair.second; |
|
|
const std::vector<ColorInfo>& infos = viewMap.at(viewName); |
|
|
|
|
|
// 为这个视图的每一行绘制 |
|
|
for (int viewRow = 0; viewRow < rowsForThisView; viewRow++) { |
|
|
int globalRowY = 60 + (currentGlobalRow * cellHeight); |
|
|
|
|
|
// 绘制视图名称区域(只在第一行显示) |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(0, globalRowY, viewNameWidth, cellHeight), |
|
|
cv::Scalar(255, 255, 255), -1); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(0, globalRowY, viewNameWidth, cellHeight), |
|
|
cv::Scalar(200, 200, 200), 1); |
|
|
|
|
|
if (viewRow == 0) { |
|
|
// 第一行:显示完整视图信息 |
|
|
cv::putText(faceImage, viewName, |
|
|
cv::Point(10, globalRowY + 25), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
std::string countText = cv::format("Records: %zu", infos.size()); |
|
|
cv::putText(faceImage, countText, |
|
|
cv::Point(10, globalRowY + 50), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(100, 100, 100), 1); |
|
|
|
|
|
// 计算这个视图中需要修复的记录数 |
|
|
int viewNeedFixCount = 0; |
|
|
for (const auto& info : infos) { |
|
|
if (info.distance > info.threshold) viewNeedFixCount++; |
|
|
} |
|
|
|
|
|
if (viewNeedFixCount > 0) { |
|
|
std::string fixText = cv::format("Fix: %d", viewNeedFixCount); |
|
|
cv::Scalar fixColor = cv::Scalar(0, 0, 255); |
|
|
cv::putText(faceImage, fixText, |
|
|
cv::Point(10, globalRowY + 70), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, fixColor, 1); |
|
|
} |
|
|
} else { |
|
|
// 后续行:显示视图名称缩写和行号 |
|
|
std::string continuationText = cv::format("%s (cont.)", viewName.substr(0, 8).c_str()); |
|
|
cv::putText(faceImage, continuationText, |
|
|
cv::Point(10, globalRowY + 25), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(100, 100, 100), 1); |
|
|
|
|
|
std::string rowText = cv::format("Row %d/%d", viewRow + 1, rowsForThisView); |
|
|
cv::putText(faceImage, rowText, |
|
|
cv::Point(10, globalRowY + 50), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(100, 100, 100), 1); |
|
|
} |
|
|
|
|
|
// 计算这一行要显示的记录 |
|
|
int startIndex = viewRow * maxCellsPerRow; |
|
|
int endIndex = std::min(startIndex + maxCellsPerRow, (int)infos.size()); |
|
|
|
|
|
// 绘制这一行的单元格 |
|
|
for (int col = 0; col < (endIndex - startIndex); col++) { |
|
|
int infoIndex = startIndex + col; |
|
|
const ColorInfo& info = infos[infoIndex]; |
|
|
|
|
|
// 计算单元格位置 |
|
|
int cellX = viewNameWidth + (col * cellWidth); |
|
|
int cellY = globalRowY; |
|
|
|
|
|
// 绘制单个单元格 |
|
|
int innerX = cellX + cellMargin; |
|
|
int innerY = cellY + cellMargin; |
|
|
int innerWidth = cellWidth - 2 * cellMargin; |
|
|
int innerHeight = cellHeight - 2 * cellMargin; |
|
|
|
|
|
// 创建单元格背景 |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(cellX, cellY, cellWidth, cellHeight), |
|
|
cv::Scalar(255, 255, 255), -1); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(cellX, cellY, cellWidth, cellHeight), |
|
|
cv::Scalar(200, 200, 200), 1); |
|
|
|
|
|
// 颜色块参数 |
|
|
int colorBlockSize = 35; |
|
|
int colorBlockY = innerY + 10; |
|
|
|
|
|
// 绘制高斯颜色块 |
|
|
cv::Scalar gaussianBGR(info.gaussianColor[2], info.gaussianColor[1], info.gaussianColor[0]); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Point(innerX + 5, colorBlockY), |
|
|
cv::Point(innerX + 5 + colorBlockSize, colorBlockY + colorBlockSize), |
|
|
gaussianBGR, -1); |
|
|
|
|
|
// 绘制原始颜色块 |
|
|
cv::Scalar originalBGR(info.originalColor[2], info.originalColor[1], info.originalColor[0]); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Point(innerX + 5 + colorBlockSize + 35, colorBlockY), |
|
|
cv::Point(innerX + 5 + colorBlockSize + 35 + colorBlockSize, colorBlockY + colorBlockSize), |
|
|
originalBGR, -1); |
|
|
|
|
|
// 添加边框 |
|
|
cv::rectangle(faceImage, |
|
|
cv::Point(innerX + 5, colorBlockY), |
|
|
cv::Point(innerX + 5 + colorBlockSize, colorBlockY + colorBlockSize), |
|
|
cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
cv::rectangle(faceImage, |
|
|
cv::Point(innerX + 5 + colorBlockSize + 35, colorBlockY), |
|
|
cv::Point(innerX + 5 + colorBlockSize + 35 + colorBlockSize, colorBlockY + colorBlockSize), |
|
|
cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加标签 |
|
|
int labelY = colorBlockY - 5; |
|
|
cv::putText(faceImage, "G", |
|
|
cv::Point(innerX + 5 + 10, labelY), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
cv::putText(faceImage, "O", |
|
|
cv::Point(innerX + 5 + colorBlockSize + 35 + 10, labelY), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加颜色值(简化显示) |
|
|
std::string gValue = cv::format("%d,%d,%d", |
|
|
info.gaussianColor[0], info.gaussianColor[1], info.gaussianColor[2]); |
|
|
cv::putText(faceImage, gValue, |
|
|
cv::Point(innerX + 5, innerY + 60), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
std::string oValue = cv::format("%d,%d,%d", |
|
|
info.originalColor[0], info.originalColor[1], info.originalColor[2]); |
|
|
cv::putText(faceImage, oValue, |
|
|
cv::Point(innerX + 5 + colorBlockSize + 35, innerY + 60), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加距离 |
|
|
std::string distText = cv::format("D:%.3f", info.distance); |
|
|
cv::putText(faceImage, distText, |
|
|
cv::Point(innerX + 5, innerY + 75), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.35, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加阈值 |
|
|
std::string threshText = cv::format("T:%.3f", info.threshold); |
|
|
cv::putText(faceImage, threshText, |
|
|
cv::Point(innerX + 5 + colorBlockSize + 35, innerY + 75), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.35, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加判断结果 |
|
|
std::string resultText = (info.distance > info.threshold) ? "FIX" : "OK"; |
|
|
cv::Scalar resultColor = (info.distance > info.threshold) ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 180, 0); |
|
|
|
|
|
cv::putText(faceImage, resultText, |
|
|
cv::Point(innerX + 5, innerY + 90), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, resultColor, 1); |
|
|
|
|
|
// 添加记录序号 |
|
|
std::string seqText = cv::format("#%d", infoIndex + 1); |
|
|
cv::putText(faceImage, seqText, |
|
|
cv::Point(innerX + 5, innerY + 110), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.3, cv::Scalar(100, 100, 100), 1); |
|
|
|
|
|
// 添加判断标记 |
|
|
if (info.distance > info.threshold) { |
|
|
cv::circle(faceImage, |
|
|
cv::Point(innerX + innerWidth - 10, innerY + 10), |
|
|
5, cv::Scalar(0, 0, 255), -1); |
|
|
} else { |
|
|
cv::circle(faceImage, |
|
|
cv::Point(innerX + innerWidth - 10, innerY + 10), |
|
|
5, cv::Scalar(0, 180, 0), -1); |
|
|
} |
|
|
} |
|
|
|
|
|
// 如果这一行没有填满,留空 |
|
|
for (int col = (endIndex - startIndex); col < maxCellsPerRow; col++) { |
|
|
int cellX = viewNameWidth + (col * cellWidth); |
|
|
int cellY = globalRowY; |
|
|
|
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(cellX, cellY, cellWidth, cellHeight), |
|
|
cv::Scalar(245, 245, 245), -1); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(cellX, cellY, cellWidth, cellHeight), |
|
|
cv::Scalar(220, 220, 220), 1); |
|
|
} |
|
|
|
|
|
currentGlobalRow++; |
|
|
} |
|
|
|
|
|
// 在每个视图之后添加一个浅色分隔线 |
|
|
if (currentGlobalRow < totalRows) { |
|
|
int separatorY = 60 + (currentGlobalRow * cellHeight); |
|
|
cv::line(faceImage, |
|
|
cv::Point(0, separatorY - 1), |
|
|
cv::Point(totalWidth, separatorY - 1), |
|
|
cv::Scalar(220, 220, 220), 1); |
|
|
} |
|
|
} |
|
|
|
|
|
// 添加网格线 |
|
|
for (int row = 0; row <= totalRows; row++) { |
|
|
int y = 60 + (row * cellHeight); |
|
|
cv::line(faceImage, |
|
|
cv::Point(0, y), |
|
|
cv::Point(totalWidth, y), |
|
|
cv::Scalar(200, 200, 200), 1); |
|
|
} |
|
|
|
|
|
// 添加视图名称区域分隔线 |
|
|
cv::line(faceImage, |
|
|
cv::Point(viewNameWidth, 60), |
|
|
cv::Point(viewNameWidth, totalHeight), |
|
|
cv::Scalar(150, 150, 150), 2); |
|
|
|
|
|
// 保存图片 |
|
|
std::string outputPath = outputDir + cv::format("face_%d_comparison.png", faceId); |
|
|
|
|
|
if (!cv::imwrite(outputPath, faceImage)) { |
|
|
printf("❌ 无法保存face %d的对比图: %s\n", faceId, outputPath.c_str()); |
|
|
} else { |
|
|
printf("✅ face %d对比图已保存: %s\n", faceId, outputPath.c_str()); |
|
|
printf(" 尺寸: %d x %d 像素, 视图数: %zu, 记录数: %d\n", |
|
|
totalWidth, totalHeight, viewMap.size(), totalRecords); |
|
|
} |
|
|
|
|
|
faceCount++; |
|
|
} |
|
|
|
|
|
// 保存颜色信息到CSV文件 |
|
|
saveColorInfoToFile(); |
|
|
} |
|
|
|
|
|
// 保存颜色信息到CSV文件 |
|
|
void ColorComparisonPixel::saveColorInfoToFile() { |
|
|
// 为每个face保存单独的CSV |
|
|
for (const auto& faceEntry : faceViewColorMap) { |
|
|
int faceId = faceEntry.first; |
|
|
const auto& viewMap = faceEntry.second; |
|
|
|
|
|
if (viewMap.empty()) { |
|
|
continue; |
|
|
} |
|
|
|
|
|
std::string filePath = outputDir + cv::format("face_%d_colors.csv", faceId); |
|
|
FILE* fp = fopen(filePath.c_str(), "w"); |
|
|
|
|
|
if (!fp) { |
|
|
printf("❌ 无法创建文件: %s\n", filePath.c_str()); |
|
|
continue; |
|
|
} |
|
|
|
|
|
// 写入CSV标题 |
|
|
fprintf(fp, "FaceID,View,RecordNo,Gaussian_R,Gaussian_G,Gaussian_B,Original_R,Original_G,Original_B,Distance,Threshold,NeedsFix\n"); |
|
|
|
|
|
int recordNo = 1; |
|
|
for (const auto& viewEntry : viewMap) { |
|
|
const std::string& viewName = viewEntry.first; |
|
|
const std::vector<ColorInfo>& infos = viewEntry.second; |
|
|
|
|
|
for (int i = 0; i < (int)infos.size(); i++) { |
|
|
const ColorInfo& info = infos[i]; |
|
|
bool needsFix = (info.distance > info.threshold); |
|
|
fprintf(fp, "%d,%s,%d,%d,%d,%d,%d,%d,%d,%.4f,%.4f,%s\n", |
|
|
faceId, |
|
|
viewName.c_str(), |
|
|
i + 1, |
|
|
info.gaussianColor[0], info.gaussianColor[1], info.gaussianColor[2], |
|
|
info.originalColor[0], info.originalColor[1], info.originalColor[2], |
|
|
info.distance, info.threshold, |
|
|
needsFix ? "YES" : "NO"); |
|
|
} |
|
|
} |
|
|
|
|
|
fclose(fp); |
|
|
printf("✅ face %d颜色信息已保存到: %s\n", faceId, filePath.c_str()); |
|
|
} |
|
|
|
|
|
// 保存汇总统计信息 |
|
|
std::string summaryPath = outputDir + "face_comparison_summary.csv"; |
|
|
FILE* summaryFp = fopen(summaryPath.c_str(), "w"); |
|
|
|
|
|
if (summaryFp) { |
|
|
fprintf(summaryFp, "FaceID,Views,TotalRecords,NeedFixRecords,NeedFix%%,MinDistance,MaxDistance,AvgDistance\n"); |
|
|
|
|
|
for (const auto& faceEntry : faceViewColorMap) { |
|
|
int faceId = faceEntry.first; |
|
|
const auto& viewMap = faceEntry.second; |
|
|
|
|
|
int totalRecords = 0; |
|
|
int needFixCount = 0; |
|
|
float minDist = std::numeric_limits<float>::max(); |
|
|
float maxDist = 0.0f; |
|
|
float sumDist = 0.0f; |
|
|
|
|
|
for (const auto& viewEntry : viewMap) { |
|
|
const std::vector<ColorInfo>& infos = viewEntry.second; |
|
|
totalRecords += infos.size(); |
|
|
|
|
|
for (const auto& info : infos) { |
|
|
if (info.distance > info.threshold) needFixCount++; |
|
|
minDist = std::min(minDist, info.distance); |
|
|
maxDist = std::max(maxDist, info.distance); |
|
|
sumDist += info.distance; |
|
|
} |
|
|
} |
|
|
|
|
|
float avgDist = (totalRecords > 0) ? (sumDist / totalRecords) : 0.0f; |
|
|
float fixPercentage = (totalRecords > 0) ? (needFixCount * 100.0f / totalRecords) : 0.0f; |
|
|
|
|
|
fprintf(summaryFp, "%d,%zu,%d,%d,%.2f%%,%.4f,%.4f,%.4f\n", |
|
|
faceId, |
|
|
viewMap.size(), |
|
|
totalRecords, |
|
|
needFixCount, |
|
|
fixPercentage, |
|
|
minDist, |
|
|
maxDist, |
|
|
avgDist); |
|
|
} |
|
|
|
|
|
fclose(summaryFp); |
|
|
printf("✅ 汇总统计信息已保存到: %s\n", summaryPath.c_str()); |
|
|
} |
|
|
} |