|
|
#include "ColorComparisonFace.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; |
|
|
} |
|
|
|
|
|
// 构造函数 |
|
|
ColorComparisonFace::ColorComparisonFace(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 ColorComparisonFace::addExactTriangleInfo(int faceId, |
|
|
MeshColor gaussianColor, |
|
|
MeshColor originalColor, |
|
|
const cv::Mat& triangleRegionWithAlpha, // 带透明通道 |
|
|
const cv::Mat& visualization, // 可视化图像 |
|
|
float colorDistance, |
|
|
float threshold, |
|
|
const std::string& filename) { |
|
|
// 存储信息 |
|
|
FaceColorInfo info; |
|
|
info.faceId = faceId; |
|
|
info.gaussianColor = gaussianColor; |
|
|
info.originalColor = originalColor; |
|
|
info.triangleRegion = triangleRegionWithAlpha.clone(); // 带透明通道 |
|
|
info.visualization = visualization.clone(); // 可视化图像 |
|
|
info.colorDistance = colorDistance; |
|
|
info.threshold = threshold; |
|
|
info.filename = filename; |
|
|
|
|
|
faceViewColorMap[faceId][filename].push_back(info); |
|
|
printf("addExactTriangleInfo faceId=%d\n", faceId); |
|
|
} |
|
|
|
|
|
// 获取总face数 |
|
|
int ColorComparisonFace::getTotalFaces() const { |
|
|
return faceViewColorMap.size(); |
|
|
} |
|
|
|
|
|
// 获取总记录数 |
|
|
int ColorComparisonFace::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> ColorComparisonFace::getFaceIds() const { |
|
|
std::vector<int> faceIds; |
|
|
for (const auto& faceEntry : faceViewColorMap) { |
|
|
faceIds.push_back(faceEntry.first); |
|
|
} |
|
|
return faceIds; |
|
|
} |
|
|
|
|
|
// 创建三角形区域对比图 |
|
|
void ColorComparisonFace::createBatchComparison(int maxBlocksPerRow, int maxFacesPerImage) { |
|
|
if (faceViewColorMap.empty()) { |
|
|
printf("⚠️ 没有颜色信息可生成\n"); |
|
|
return; |
|
|
} |
|
|
|
|
|
printf("正在创建三角形区域对比图...\n"); |
|
|
printf("总face数: %zu\n", faceViewColorMap.size()); |
|
|
printf("总记录数: %d\n", getTotalRecords()); |
|
|
|
|
|
// 块参数 |
|
|
int triangleSize = 200; // 三角形区域显示大小 |
|
|
int colorBlockSize = 100; // 颜色块大小 |
|
|
int blockMargin = 20; // 块之间的边距 |
|
|
int infoHeight = 100; // 信息区域高度 |
|
|
|
|
|
// 限制处理的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()); |
|
|
|
|
|
// 计算需要的行数和列数 |
|
|
int numBlocks = 0; |
|
|
for (const auto& viewEntry : viewMap) { |
|
|
numBlocks += viewEntry.second.size(); |
|
|
} |
|
|
|
|
|
int numCols = std::min(maxBlocksPerRow, numBlocks); |
|
|
int numRows = (numBlocks + numCols - 1) / numCols; |
|
|
|
|
|
// 计算每个块的宽度和高度 |
|
|
int blockWidth = colorBlockSize + triangleSize + blockMargin * 3; |
|
|
int blockHeight = triangleSize + blockMargin + infoHeight; |
|
|
|
|
|
// 图片总尺寸 |
|
|
int totalWidth = numCols * (blockWidth + blockMargin) + blockMargin; |
|
|
int totalHeight = 60 + (numRows * (blockHeight + blockMargin)) + blockMargin; |
|
|
|
|
|
// 创建大图 |
|
|
cv::Mat faceImage(totalHeight, totalWidth, CV_8UC3, cv::Scalar(245, 245, 245)); |
|
|
|
|
|
// 添加标题 |
|
|
std::string title = cv::format("Face %d - Triangle Region 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.colorDistance > 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 blockIndex = 0; |
|
|
int currentRow = 0; |
|
|
int currentCol = 0; |
|
|
|
|
|
// 遍历这个face的所有视图 |
|
|
for (const auto& viewEntry : viewMap) { |
|
|
const std::string& viewName = viewEntry.first; |
|
|
const std::vector<FaceColorInfo>& infos = viewEntry.second; |
|
|
|
|
|
for (const auto& info : infos) { |
|
|
// 计算当前块的位置 |
|
|
int blockX = blockMargin + currentCol * (blockWidth + blockMargin); |
|
|
int blockY = 60 + blockMargin + currentRow * (blockHeight + blockMargin); |
|
|
|
|
|
// 绘制块背景 |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(blockX, blockY, blockWidth, blockHeight), |
|
|
cv::Scalar(255, 255, 255), -1); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(blockX, blockY, blockWidth, blockHeight), |
|
|
cv::Scalar(200, 200, 200), 2); |
|
|
|
|
|
// 绘制高斯颜色块 |
|
|
int gaussianX = blockX + blockMargin; |
|
|
int gaussianY = blockY + blockMargin; |
|
|
|
|
|
cv::Scalar gaussianBGR(info.gaussianColor[2], info.gaussianColor[1], info.gaussianColor[0]); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(gaussianX, gaussianY, colorBlockSize, colorBlockSize), |
|
|
gaussianBGR, -1); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(gaussianX, gaussianY, colorBlockSize, colorBlockSize), |
|
|
cv::Scalar(0, 0, 0), 2); |
|
|
|
|
|
// 添加高斯标签 |
|
|
cv::putText(faceImage, "GAUSSIAN", |
|
|
cv::Point(gaussianX + 10, gaussianY - 5), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 绘制三角形区域 |
|
|
int triangleX = blockX + blockMargin + colorBlockSize + blockMargin; |
|
|
int triangleY = blockY + blockMargin; |
|
|
|
|
|
if (!info.visualization.empty()) { |
|
|
// 调整三角形区域大小 |
|
|
cv::Mat resizedTriangle; |
|
|
cv::resize(info.visualization, resizedTriangle, cv::Size(triangleSize, triangleSize)); |
|
|
|
|
|
// 将三角形区域绘制到指定位置(已经是RGB顺序) |
|
|
resizedTriangle.copyTo(faceImage(cv::Rect(triangleX, triangleY, triangleSize, triangleSize))); |
|
|
|
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(triangleX, triangleY, triangleSize, triangleSize), |
|
|
cv::Scalar(0, 0, 0), 2); |
|
|
|
|
|
// 添加三角形标签 |
|
|
cv::putText(faceImage, "TRIANGLE", |
|
|
cv::Point(triangleX + 10, triangleY - 5), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(0, 0, 0), 1); |
|
|
} else { |
|
|
// 如果没有三角形区域,绘制占位符 |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(triangleX, triangleY, triangleSize, triangleSize), |
|
|
cv::Scalar(200, 200, 200), -1); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(triangleX, triangleY, triangleSize, triangleSize), |
|
|
cv::Scalar(150, 150, 150), 2); |
|
|
|
|
|
cv::putText(faceImage, "NO TRIANGLE", |
|
|
cv::Point(triangleX + triangleSize/2 - 40, triangleY + triangleSize/2), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.5, cv::Scalar(100, 100, 100), 1); |
|
|
} |
|
|
|
|
|
// 绘制信息区域 |
|
|
int infoY = blockY + blockMargin + triangleSize; |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(blockX, infoY, blockWidth, infoHeight), |
|
|
cv::Scalar(240, 240, 240), -1); |
|
|
cv::rectangle(faceImage, |
|
|
cv::Rect(blockX, infoY, blockWidth, infoHeight), |
|
|
cv::Scalar(200, 200, 200), 1); |
|
|
|
|
|
// 添加视图信息 |
|
|
std::string displayName = viewName; |
|
|
if (displayName.length() > 20) { |
|
|
displayName = displayName.substr(0, 18) + "..."; |
|
|
} |
|
|
|
|
|
cv::putText(faceImage, cv::format("View: %s", displayName.c_str()), |
|
|
cv::Point(blockX + 10, infoY + 20), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
cv::putText(faceImage, cv::format("Face: %d", info.faceId), |
|
|
cv::Point(blockX + 10, infoY + 40), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加颜色值 |
|
|
cv::putText(faceImage, |
|
|
cv::format("Gauss: (%d,%d,%d)", |
|
|
info.gaussianColor[0], info.gaussianColor[1], info.gaussianColor[2]), |
|
|
cv::Point(blockX + 10, infoY + 60), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.35, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
cv::putText(faceImage, |
|
|
cv::format("Orgin: (%d,%d,%d)", |
|
|
info.originalColor[0], info.originalColor[1], info.originalColor[2]), |
|
|
cv::Point(blockX + 140, infoY + 60), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.35, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加距离和阈值 |
|
|
cv::putText(faceImage, |
|
|
cv::format("Distance: %.4f", info.colorDistance), |
|
|
cv::Point(blockX + 10, infoY + 80), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.35, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
cv::putText(faceImage, |
|
|
cv::format("Threshold: %.4f", info.threshold), |
|
|
cv::Point(blockX + 10, infoY + 95), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.35, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加判断结果 |
|
|
int resultX = blockX + blockWidth - 60; |
|
|
int resultY = infoY + 25; |
|
|
|
|
|
std::string resultText = (info.colorDistance > info.threshold) ? "FIX" : "OK"; |
|
|
cv::Scalar resultColor = (info.colorDistance > info.threshold) ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 180, 0); |
|
|
|
|
|
cv::putText(faceImage, resultText, |
|
|
cv::Point(resultX, resultY), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.5, resultColor, 1); |
|
|
|
|
|
// 更新行和列索引 |
|
|
blockIndex++; |
|
|
currentCol++; |
|
|
if (currentCol >= numCols) { |
|
|
currentCol = 0; |
|
|
currentRow++; |
|
|
} |
|
|
|
|
|
// 如果已经达到最大块数,跳出循环 |
|
|
if (blockIndex >= maxBlocksPerRow * numRows) { |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
// 如果已经达到最大块数,跳出循环 |
|
|
if (blockIndex >= maxBlocksPerRow * numRows) { |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
// 保存图片 |
|
|
std::string outputPath = outputDir + cv::format("face_%d_triangle_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 ColorComparisonFace::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_triangle_colors.csv", faceId); |
|
|
FILE* fp = fopen(filePath.c_str(), "w"); |
|
|
|
|
|
if (!fp) { |
|
|
printf("❌❌ 无法创建文件: %s\n", filePath.c_str()); |
|
|
continue; |
|
|
} |
|
|
|
|
|
// 写入CSV标题 |
|
|
fprintf(fp, "FaceID,View,Gaussian_R,Gaussian_G,Gaussian_B,Distance,Threshold,NeedsFix\n"); |
|
|
|
|
|
for (const auto& viewEntry : viewMap) { |
|
|
const std::string& viewName = viewEntry.first; |
|
|
const std::vector<FaceColorInfo>& infos = viewEntry.second; |
|
|
|
|
|
for (int i = 0; i < (int)infos.size(); i++) { |
|
|
const FaceColorInfo& info = infos[i]; |
|
|
bool needsFix = (info.colorDistance > info.threshold); |
|
|
fprintf(fp, "%d,%s,%d,%d,%d,%.4f,%.4f,%s\n", |
|
|
faceId, |
|
|
viewName.c_str(), |
|
|
info.gaussianColor[0], info.gaussianColor[1], info.gaussianColor[2], |
|
|
info.colorDistance, info.threshold, |
|
|
needsFix ? "YES" : "NO"); |
|
|
} |
|
|
} |
|
|
|
|
|
fclose(fp); |
|
|
printf("✅ face %d三角形颜色信息已保存到: %s\n", faceId, filePath.c_str()); |
|
|
} |
|
|
|
|
|
// 保存汇总统计信息 |
|
|
std::string summaryPath = outputDir + "triangle_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<FaceColorInfo>& infos = viewEntry.second; |
|
|
totalRecords += infos.size(); |
|
|
|
|
|
for (const auto& info : infos) { |
|
|
if (info.colorDistance > info.threshold) needFixCount++; |
|
|
minDist = std::min(minDist, info.colorDistance); |
|
|
maxDist = std::max(maxDist, info.colorDistance); |
|
|
sumDist += info.colorDistance; |
|
|
} |
|
|
} |
|
|
|
|
|
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()); |
|
|
} |
|
|
} |
|
|
|
|
|
// 在ColorComparisonFace.cpp中添加以下函数实现 |
|
|
|
|
|
// 添加连续区域信息 |
|
|
void ColorComparisonFace::addContinuousRegionInfo(int regionId, |
|
|
const std::set<unsigned int>& faceIds, // 修改为 unsigned int |
|
|
MeshColor regionGaussianColor) { |
|
|
if (continuousRegions.find(regionId) == continuousRegions.end()) { |
|
|
ContinuousRegionInfo regionInfo; |
|
|
regionInfo.regionId = regionId; |
|
|
regionInfo.faceIds = faceIds; // 这里类型匹配 |
|
|
regionInfo.regionGaussianColor = regionGaussianColor; |
|
|
regionInfo.totalPixels = 0; |
|
|
continuousRegions[regionId] = regionInfo; |
|
|
|
|
|
printf("添加连续区域 %d: 包含 %zu 个面\n", regionId, faceIds.size()); |
|
|
} |
|
|
} |
|
|
|
|
|
// 添加连续区域在特定视图中的信息 |
|
|
void ColorComparisonFace::addRegionViewInfo(int regionId, |
|
|
const std::string& filename, |
|
|
MeshColor viewColor, |
|
|
const cv::Mat& regionImage, |
|
|
const cv::Mat& visualization, |
|
|
float colorDistance) { |
|
|
if (continuousRegions.find(regionId) != continuousRegions.end()) { |
|
|
ContinuousRegionInfo& regionInfo = continuousRegions[regionId]; |
|
|
regionInfo.viewColors[filename] = viewColor; |
|
|
regionInfo.viewRegions[filename] = regionImage.clone(); |
|
|
regionInfo.viewVisualizations[filename] = visualization.clone(); |
|
|
regionInfo.viewDistances[filename] = colorDistance; |
|
|
|
|
|
printf(" 视图 %s: 颜色(R=%d,G=%d,B=%d), 距离=%.4f\n", |
|
|
filename.c_str(), viewColor.r, viewColor.g, viewColor.b, colorDistance); |
|
|
} |
|
|
} |
|
|
|
|
|
// 获取连续区域数 |
|
|
int ColorComparisonFace::getTotalRegions() const { |
|
|
return continuousRegions.size(); |
|
|
} |
|
|
|
|
|
// 创建连续区域跨视图比较图 |
|
|
void ColorComparisonFace::createContinuousRegionComparison(int maxBlocksPerRow, int maxRegionsPerImage) { |
|
|
if (continuousRegions.empty()) { |
|
|
printf("⚠️ 没有连续区域信息可生成\n"); |
|
|
return; |
|
|
} |
|
|
|
|
|
printf("正在创建连续区域跨视图比较图...\n"); |
|
|
printf("总连续区域数: %zu\n", continuousRegions.size()); |
|
|
|
|
|
// 限制处理的区域数 |
|
|
int regionsToProcess = std::min((int)continuousRegions.size(), maxRegionsPerImage); |
|
|
|
|
|
// 处理前N个区域 |
|
|
int regionCount = 0; |
|
|
for (const auto& regionEntry : continuousRegions) { |
|
|
if (regionCount >= regionsToProcess) break; |
|
|
|
|
|
int regionId = regionEntry.first; |
|
|
const ContinuousRegionInfo& regionInfo = regionEntry.second; |
|
|
|
|
|
printf("处理连续区域 %d: 在 %zu 个视图中可见\n", regionId, regionInfo.viewColors.size()); |
|
|
|
|
|
if (regionInfo.viewColors.size() < 2) { |
|
|
printf(" 区域 %d 在少于2个视图中可见,跳过跨视图比较\n", regionId); |
|
|
regionCount++; |
|
|
continue; |
|
|
} |
|
|
|
|
|
// 块参数 |
|
|
int regionSize = 200; // 区域显示大小 |
|
|
int colorBlockSize = 100; // 颜色块大小 |
|
|
int blockMargin = 20; // 块之间的边距 |
|
|
int infoHeight = 120; // 信息区域高度 |
|
|
|
|
|
// 计算需要的行数和列数 |
|
|
int numBlocks = regionInfo.viewColors.size(); |
|
|
int numCols = std::min(maxBlocksPerRow, numBlocks); |
|
|
int numRows = (numBlocks + numCols - 1) / numCols; |
|
|
|
|
|
// 计算每个块的宽度和高度 |
|
|
int blockWidth = colorBlockSize + regionSize + blockMargin * 3; |
|
|
int blockHeight = regionSize + blockMargin + infoHeight; |
|
|
|
|
|
// 图片总尺寸 |
|
|
int totalWidth = numCols * (blockWidth + blockMargin) + blockMargin; |
|
|
int totalHeight = 100 + (numRows * (blockHeight + blockMargin)) + blockMargin; |
|
|
|
|
|
// 创建大图 |
|
|
cv::Mat regionImage(totalHeight, totalWidth, CV_8UC3, cv::Scalar(240, 240, 240)); |
|
|
|
|
|
// 添加标题 |
|
|
std::string title = cv::format("Continuous Region %d - Cross-View Comparison", regionId); |
|
|
cv::putText(regionImage, title, |
|
|
cv::Point(20, 30), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 1.0, cv::Scalar(0, 0, 0), 2); |
|
|
|
|
|
// 添加区域统计信息 |
|
|
std::string regionStats = cv::format("Contains %zu faces, Visible in %zu views", |
|
|
regionInfo.faceIds.size(), regionInfo.viewColors.size()); |
|
|
cv::putText(regionImage, regionStats, |
|
|
cv::Point(20, 60), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
std::string gaussStats = cv::format("Region Gaussian Color: R=%d, G=%d, B=%d", |
|
|
regionInfo.regionGaussianColor.r, |
|
|
regionInfo.regionGaussianColor.g, |
|
|
regionInfo.regionGaussianColor.b); |
|
|
cv::putText(regionImage, gaussStats, |
|
|
cv::Point(20, 85), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(255, 0, 0), 1); |
|
|
|
|
|
int blockIndex = 0; |
|
|
int currentRow = 0; |
|
|
int currentCol = 0; |
|
|
|
|
|
// 计算统计数据 |
|
|
float sumDistances = 0.0f; |
|
|
float maxDistance = 0.0f; |
|
|
std::string worstView; |
|
|
|
|
|
// 遍历这个区域的所有视图 |
|
|
for (const auto& viewEntry : regionInfo.viewColors) { |
|
|
const std::string& viewName = viewEntry.first; |
|
|
const MeshColor& viewColor = viewEntry.second; |
|
|
float colorDistance = regionInfo.viewDistances.at(viewName); |
|
|
|
|
|
sumDistances += colorDistance; |
|
|
if (colorDistance > maxDistance) { |
|
|
maxDistance = colorDistance; |
|
|
worstView = viewName; |
|
|
} |
|
|
|
|
|
// 计算当前块的位置 |
|
|
int blockX = blockMargin + currentCol * (blockWidth + blockMargin); |
|
|
int blockY = 100 + blockMargin + currentRow * (blockHeight + blockMargin); |
|
|
|
|
|
// 绘制块背景 |
|
|
cv::rectangle(regionImage, |
|
|
cv::Rect(blockX, blockY, blockWidth, blockHeight), |
|
|
cv::Scalar(255, 255, 255), -1); |
|
|
cv::rectangle(regionImage, |
|
|
cv::Rect(blockX, blockY, blockWidth, blockHeight), |
|
|
cv::Scalar(200, 200, 200), 2); |
|
|
|
|
|
// 绘制区域高斯颜色块 |
|
|
int gaussianX = blockX + blockMargin; |
|
|
int gaussianY = blockY + blockMargin; |
|
|
|
|
|
cv::Scalar gaussianBGR(regionInfo.regionGaussianColor[2], |
|
|
regionInfo.regionGaussianColor[1], |
|
|
regionInfo.regionGaussianColor[0]); |
|
|
cv::rectangle(regionImage, |
|
|
cv::Rect(gaussianX, gaussianY, colorBlockSize, colorBlockSize), |
|
|
gaussianBGR, -1); |
|
|
cv::rectangle(regionImage, |
|
|
cv::Rect(gaussianX, gaussianY, colorBlockSize, colorBlockSize), |
|
|
cv::Scalar(0, 0, 0), 2); |
|
|
|
|
|
// 添加高斯标签 |
|
|
cv::putText(regionImage, "REGION GAUSS", |
|
|
cv::Point(gaussianX + 5, gaussianY - 5), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 绘制区域图像块 |
|
|
int regionX = blockX + blockMargin + colorBlockSize + blockMargin; |
|
|
int regionY = blockY + blockMargin; |
|
|
|
|
|
auto visIt = regionInfo.viewVisualizations.find(viewName); |
|
|
if (visIt != regionInfo.viewVisualizations.end() && !visIt->second.empty()) { |
|
|
cv::Mat resizedRegion; |
|
|
cv::resize(visIt->second, resizedRegion, cv::Size(regionSize, regionSize)); |
|
|
|
|
|
// 将区域图像绘制到指定位置 |
|
|
resizedRegion.copyTo(regionImage(cv::Rect(regionX, regionY, regionSize, regionSize))); |
|
|
|
|
|
cv::rectangle(regionImage, |
|
|
cv::Rect(regionX, regionY, regionSize, regionSize), |
|
|
cv::Scalar(0, 0, 0), 2); |
|
|
|
|
|
// 添加视图标签 |
|
|
cv::putText(regionImage, "VIEW REGION", |
|
|
cv::Point(regionX + 5, regionY - 5), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 0), 1); |
|
|
} |
|
|
|
|
|
// 绘制信息区域 |
|
|
int infoY = blockY + blockMargin + regionSize; |
|
|
cv::rectangle(regionImage, |
|
|
cv::Rect(blockX, infoY, blockWidth, infoHeight), |
|
|
cv::Scalar(240, 240, 240), -1); |
|
|
cv::rectangle(regionImage, |
|
|
cv::Rect(blockX, infoY, blockWidth, infoHeight), |
|
|
cv::Scalar(200, 200, 200), 1); |
|
|
|
|
|
// 添加视图信息 |
|
|
std::string displayName = viewName; |
|
|
if (displayName.length() > 20) { |
|
|
displayName = displayName.substr(0, 18) + "..."; |
|
|
} |
|
|
|
|
|
cv::putText(regionImage, cv::format("View: %s", displayName.c_str()), |
|
|
cv::Point(blockX + 10, infoY + 20), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
cv::putText(regionImage, cv::format("Region: %d", regionId), |
|
|
cv::Point(blockX + 10, infoY + 40), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.4, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加颜色值 |
|
|
cv::putText(regionImage, |
|
|
cv::format("Region Gauss: (%d,%d,%d)", |
|
|
regionInfo.regionGaussianColor[0], |
|
|
regionInfo.regionGaussianColor[1], |
|
|
regionInfo.regionGaussianColor[2]), |
|
|
cv::Point(blockX + 10, infoY + 60), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.35, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
cv::putText(regionImage, |
|
|
cv::format("View Color: (%d,%d,%d)", |
|
|
viewColor[0], viewColor[1], viewColor[2]), |
|
|
cv::Point(blockX + 10, infoY + 80), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.35, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加距离和阈值 |
|
|
cv::putText(regionImage, |
|
|
cv::format("Distance: %.4f", colorDistance), |
|
|
cv::Point(blockX + 10, infoY + 100), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.35, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 添加判断结果 |
|
|
int resultX = blockX + blockWidth - 50; |
|
|
int resultY = infoY + 30; |
|
|
|
|
|
std::string resultText = (colorDistance > 0.31f) ? "FIX" : "OK"; |
|
|
cv::Scalar resultColor = (colorDistance > 0.31f) ? cv::Scalar(0, 0, 255) : cv::Scalar(0, 180, 0); |
|
|
|
|
|
cv::putText(regionImage, resultText, |
|
|
cv::Point(resultX, resultY), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.6, resultColor, 1); |
|
|
|
|
|
// 更新行和列索引 |
|
|
blockIndex++; |
|
|
currentCol++; |
|
|
if (currentCol >= numCols) { |
|
|
currentCol = 0; |
|
|
currentRow++; |
|
|
} |
|
|
} |
|
|
|
|
|
// 在底部添加统计信息 |
|
|
float avgDistance = sumDistances / regionInfo.viewColors.size(); |
|
|
std::string stats = cv::format("Avg Distance: %.4f, Max Distance: %.4f (in %s)", |
|
|
avgDistance, maxDistance, worstView.c_str()); |
|
|
cv::putText(regionImage, stats, |
|
|
cv::Point(20, totalHeight - 20), |
|
|
cv::FONT_HERSHEY_SIMPLEX, 0.6, cv::Scalar(0, 0, 0), 1); |
|
|
|
|
|
// 保存图片 |
|
|
std::string outputPath = outputDir + cv::format("continuous_region_%d_comparison.png", regionId); |
|
|
|
|
|
if (!cv::imwrite(outputPath, regionImage)) { |
|
|
printf("❌❌ 无法保存连续区域 %d 的对比图: %s\n", regionId, outputPath.c_str()); |
|
|
} else { |
|
|
printf("✅ 连续区域 %d 对比图已保存: %s\n", regionId, outputPath.c_str()); |
|
|
} |
|
|
|
|
|
regionCount++; |
|
|
} |
|
|
|
|
|
// 保存连续区域信息到CSV文件 |
|
|
std::string regionSummaryPath = outputDir + "continuous_regions_summary.csv"; |
|
|
FILE* regionFp = fopen(regionSummaryPath.c_str(), "w"); |
|
|
|
|
|
if (regionFp) { |
|
|
fprintf(regionFp, "RegionID,FaceCount,Views,AvgDistance,MaxDistance,WorstView,NeedsFixCount,NeedsFix%%\n"); |
|
|
|
|
|
for (const auto& regionEntry : continuousRegions) { |
|
|
int regionId = regionEntry.first; |
|
|
const ContinuousRegionInfo& regionInfo = regionEntry.second; |
|
|
|
|
|
float sumDist = 0.0f; |
|
|
float maxDist = 0.0f; |
|
|
int needsFixCount = 0; |
|
|
|
|
|
for (const auto& distEntry : regionInfo.viewDistances) { |
|
|
float dist = distEntry.second; |
|
|
sumDist += dist; |
|
|
if (dist > maxDist) maxDist = dist; |
|
|
if (dist > 0.31f) needsFixCount++; |
|
|
} |
|
|
|
|
|
float avgDist = (regionInfo.viewDistances.size() > 0) ? |
|
|
(sumDist / regionInfo.viewDistances.size()) : 0.0f; |
|
|
float fixPercentage = (regionInfo.viewDistances.size() > 0) ? |
|
|
(needsFixCount * 100.0f / regionInfo.viewDistances.size()) : 0.0f; |
|
|
|
|
|
fprintf(regionFp, "%d,%zu,%zu,%.4f,%.4f,", |
|
|
regionId, regionInfo.faceIds.size(), regionInfo.viewDistances.size(), |
|
|
avgDist, maxDist); |
|
|
|
|
|
// 找到最差的视图 |
|
|
std::string worstView = ""; |
|
|
for (const auto& distEntry : regionInfo.viewDistances) { |
|
|
if (distEntry.second == maxDist) { |
|
|
worstView = distEntry.first; |
|
|
break; |
|
|
} |
|
|
} |
|
|
|
|
|
fprintf(regionFp, "%s,%d,%.2f%%\n", |
|
|
worstView.c_str(), needsFixCount, fixPercentage); |
|
|
} |
|
|
|
|
|
fclose(regionFp); |
|
|
printf("✅ 连续区域汇总统计信息已保存到: %s\n", regionSummaryPath.c_str()); |
|
|
} |
|
|
} |