From 765a0e394ae086cddbb71c8bb0bb0661f9125dea Mon Sep 17 00:00:00 2001 From: castano Date: Sun, 6 Mar 2011 22:23:24 +0000 Subject: [PATCH] Add more error estimation methods. --- src/nvimage/ErrorMetric.cpp | 167 +++++++++++++++++++++++++++++++---- src/nvimage/ErrorMetric.h | 6 ++ src/nvtt/TexImage.cpp | 16 +++- src/nvtt/nvtt.h | 5 +- src/nvtt/tests/testsuite.cpp | 121 +++++++++++++++++-------- 5 files changed, 258 insertions(+), 57 deletions(-) diff --git a/src/nvimage/ErrorMetric.cpp b/src/nvimage/ErrorMetric.cpp index c4db41d..2abd20e 100644 --- a/src/nvimage/ErrorMetric.cpp +++ b/src/nvimage/ErrorMetric.cpp @@ -11,14 +11,14 @@ using namespace nv; float nv::rmsColorError(const FloatImage * img, const FloatImage * ref, bool alphaWeight) { - double mse = 0; - if (img == NULL || ref == NULL || img->width() != ref->width() || img->height() != ref->height()) { return FLT_MAX; } nvDebugCheck(img->componentNum() == 4); nvDebugCheck(ref->componentNum() == 4); + double mse = 0; + const uint count = img->width() * img->height(); for (uint i = 0; i < count; i++) { @@ -34,20 +34,13 @@ float nv::rmsColorError(const FloatImage * img, const FloatImage * ref, bool alp float r = r0 - r1; float g = g0 - g1; float b = b0 - b1; - //float a = a0 - a1; - - if (alphaWeight) - { - mse += r * r * a1; - mse += g * g * a1; - mse += b * b * a1; - } - else - { - mse += r * r; - mse += g * g; - mse += b * b; - } + + float a = 1; + if (alphaWeight) a = a1; + + mse += r * r * a; + mse += g * g * a; + mse += b * b * a; } return float(sqrt(mse / count)); @@ -55,13 +48,13 @@ float nv::rmsColorError(const FloatImage * img, const FloatImage * ref, bool alp float nv::rmsAlphaError(const FloatImage * img, const FloatImage * ref) { - double mse = 0; - if (img == NULL || ref == NULL || img->width() != ref->width() || img->height() != ref->height()) { return FLT_MAX; } nvDebugCheck(img->componentNum() == 4 && ref->componentNum() == 4); + double mse = 0; + const uint count = img->width() * img->height(); for (uint i = 0; i < count; i++) { @@ -76,6 +69,68 @@ float nv::rmsAlphaError(const FloatImage * img, const FloatImage * ref) return float(sqrt(mse / count)); } + +float nv::averageColorError(const FloatImage * img, const FloatImage * ref, bool alphaWeight) +{ + if (img == NULL || ref == NULL || img->width() != ref->width() || img->height() != ref->height()) { + return FLT_MAX; + } + nvDebugCheck(img->componentNum() == 4); + nvDebugCheck(ref->componentNum() == 4); + + double mae = 0; + + const uint count = img->width() * img->height(); + for (uint i = 0; i < count; i++) + { + float r0 = img->pixel(i + count * 0); + float g0 = img->pixel(i + count * 1); + float b0 = img->pixel(i + count * 2); + //float a0 = img->pixel(i + count * 3); + float r1 = ref->pixel(i + count * 0); + float g1 = ref->pixel(i + count * 1); + float b1 = ref->pixel(i + count * 2); + float a1 = ref->pixel(i + count * 3); + + float r = fabs(r0 - r1); + float g = fabs(g0 - g1); + float b = fabs(b0 - b1); + + float a = 1; + if (alphaWeight) a = a1; + + mae += r * a; + mae += g * a; + mae += b * a; + } + + return float(mae / count); +} + +float nv::averageAlphaError(const FloatImage * img, const FloatImage * ref) +{ + if (img == NULL || ref == NULL || img->width() != ref->width() || img->height() != ref->height()) { + return FLT_MAX; + } + nvDebugCheck(img->componentNum() == 4 && ref->componentNum() == 4); + + double mae = 0; + + const uint count = img->width() * img->height(); + for (uint i = 0; i < count; i++) + { + float a0 = img->pixel(i + count * 3); + float a1 = ref->pixel(i + count * 3); + + float a = a0 - a1; + + mae += fabs(a); + } + + return float(mae / count); +} + + // Assumes input is in *linear* sRGB color space. static Vector3 rgbToXyz(Vector3::Arg c) { @@ -269,5 +324,81 @@ float nv::spatialCieLabError(const FloatImage * img0, const FloatImage * img1) } +// Assumes input images are normal maps. +float nv::averageAngularError(const FloatImage * img0, const FloatImage * img1) +{ + if (img0 == NULL || img1 == NULL || img0->width() != img1->width() || img0->height() != img1->height()) { + return FLT_MAX; + } + nvDebugCheck(img0->componentNum() == 4 && img0->componentNum() == 4); + + uint w = img0->width(); + uint h = img0->height(); + + const float * x0 = img0->channel(0); + const float * y0 = img0->channel(1); + const float * z0 = img0->channel(2); + + const float * x1 = img1->channel(0); + const float * y1 = img1->channel(1); + const float * z1 = img1->channel(2); + + double error = 0.0f; + + const uint count = w*h; + for (uint i = 0; i < count; i++) + { + Vector3 n0 = Vector3(x0[i], y0[i], z0[i]); + Vector3 n1 = Vector3(x1[i], y1[i], z1[i]); + + n0 = 2 * n0 - Vector3(1); + n1 = 2 * n1 - Vector3(1); + + n0 = normalizeSafe(n0, Vector3(0), 0.0f); + n1 = normalizeSafe(n1, Vector3(0), 0.0f); + + error += acos(clamp(dot(n0, n1), -1.0f, 1.0f)); + } + return float(error / count); +} + +float nv::rmsAngularError(const FloatImage * img0, const FloatImage * img1) +{ + if (img0 == NULL || img1 == NULL || img0->width() != img1->width() || img0->height() != img1->height()) { + return FLT_MAX; + } + nvDebugCheck(img0->componentNum() == 4 && img0->componentNum() == 4); + + uint w = img0->width(); + uint h = img0->height(); + + const float * x0 = img0->channel(0); + const float * y0 = img0->channel(1); + const float * z0 = img0->channel(2); + + const float * x1 = img1->channel(0); + const float * y1 = img1->channel(1); + const float * z1 = img1->channel(2); + + double error = 0.0f; + + const uint count = w*h; + for (uint i = 0; i < count; i++) + { + Vector3 n0 = Vector3(x0[i], y0[i], z0[i]); + Vector3 n1 = Vector3(x1[i], y1[i], z1[i]); + + n0 = 2 * n0 - Vector3(1); + n1 = 2 * n1 - Vector3(1); + + n0 = normalizeSafe(n0, Vector3(0), 0.0f); + n1 = normalizeSafe(n1, Vector3(0), 0.0f); + + float angle = acos(clamp(dot(n0, n1), -1.0f, 1.0f)); + error += angle * angle; + } + + return float(sqrt(error / count)); +} diff --git a/src/nvimage/ErrorMetric.h b/src/nvimage/ErrorMetric.h index 9aa155c..af5c845 100644 --- a/src/nvimage/ErrorMetric.h +++ b/src/nvimage/ErrorMetric.h @@ -12,4 +12,10 @@ namespace nv float cieLabError(const FloatImage * img, const FloatImage * ref); float spatialCieLabError(const FloatImage * img, const FloatImage * ref); + float averageColorError(const FloatImage * img, const FloatImage * ref, bool alphaWeight); + float averageAlphaError(const FloatImage * img, const FloatImage * ref); + + float averageAngularError(const FloatImage * img0, const FloatImage * img1); + float rmsAngularError(const FloatImage * img0, const FloatImage * img1); + } // nv namespace diff --git a/src/nvtt/TexImage.cpp b/src/nvtt/TexImage.cpp index 39a1625..6344c53 100644 --- a/src/nvtt/TexImage.cpp +++ b/src/nvtt/TexImage.cpp @@ -1593,7 +1593,7 @@ void TexImage::transformNormals(NormalTransform xform) n.y = n.y * t; n.z = 0.0f; } - else if (xform == NormalTransform_DualParaboloid) { + else if (xform == NormalTransform_Quartic) { // Use Newton's method to solve equation: // f(t) = 1 - zt - (x^2+y^2)t^2 + x^2y^2t^4 = 0 // f'(t) = - z - 2(x^2+y^2)t + 4x^2y^2t^3 @@ -1618,6 +1618,9 @@ void TexImage::transformNormals(NormalTransform xform) n.y = n.y * t; n.z = 0.0f; } + /*else if (xform == NormalTransform_DualParaboloid) { + + }*/ x = n.x; y = n.y; @@ -1657,12 +1660,15 @@ void TexImage::reconstructNormals(NormalTransform xform) n.z = 1.0f - nv::clamp(n.x * n.x + n.y * n.y, 0.0f, 1.0f); n = normalizeSafe(n, Vector3(0.0f), 0.0f); } - else if (xform == NormalTransform_DualParaboloid) { + else if (xform == NormalTransform_Quartic) { n.x = n.x; n.y = n.y; n.z = nv::clamp((1 - n.x * n.x) * (1 - n.y * n.y), 0.0f, 1.0f); n = normalizeSafe(n, Vector3(0.0f), 0.0f); } + /*else if (xform == NormalTransform_DualParaboloid) { + + }*/ x = n.x; y = n.y; @@ -1753,6 +1759,12 @@ float nvtt::cieLabError(const TexImage & reference, const TexImage & image) return nv::cieLabError(reference.m->image, image.m->image); } +float nvtt::angularError(const TexImage & reference, const TexImage & image) +{ + //return nv::averageAngularError(reference.m->image, image.m->image); + return nv::rmsAngularError(reference.m->image, image.m->image); +} + TexImage nvtt::diff(const TexImage & reference, const TexImage & image, float scale) { diff --git a/src/nvtt/nvtt.h b/src/nvtt/nvtt.h index 743a02c..d01610b 100644 --- a/src/nvtt/nvtt.h +++ b/src/nvtt/nvtt.h @@ -391,7 +391,8 @@ namespace nvtt NormalTransform_Orthographic, NormalTransform_Stereographic, NormalTransform_Paraboloid, - NormalTransform_DualParaboloid, + NormalTransform_Quartic, + //NormalTransform_DualParaboloid, }; /// A texture mipmap. @@ -487,6 +488,7 @@ namespace nvtt NVTT_API friend float rmsError(const TexImage & reference, const TexImage & img); NVTT_API friend float rmsAlphaError(const TexImage & reference, const TexImage & img); NVTT_API friend float cieLabError(const TexImage & reference, const TexImage & img); + NVTT_API friend float angularError(const TexImage & reference, const TexImage & img); NVTT_API friend TexImage diff(const TexImage & reference, const TexImage & img, float scale); private: @@ -506,6 +508,7 @@ namespace nvtt NVTT_API float rmsError(const TexImage & reference, const TexImage & img); NVTT_API float rmsAlphaError(const TexImage & reference, const TexImage & img); NVTT_API float cieLabError(const TexImage & reference, const TexImage & img); + NVTT_API float angularError(const TexImage & reference, const TexImage & img); NVTT_API TexImage diff(const TexImage & reference, const TexImage & img, float scale); diff --git a/src/nvtt/tests/testsuite.cpp b/src/nvtt/tests/testsuite.cpp index adfa7cb..73c272c 100644 --- a/src/nvtt/tests/testsuite.cpp +++ b/src/nvtt/tests/testsuite.cpp @@ -185,7 +185,8 @@ enum Mode { Mode_BC5_Normal, Mode_BC5_Normal_Stereographic, Mode_BC5_Normal_Paraboloid, - Mode_BC5_Normal_DualParaboloid, + Mode_BC5_Normal_Quartic, + //Mode_BC5_Normal_DualParaboloid, Mode_Count }; static const char * s_modeNames[] = { @@ -202,7 +203,8 @@ static const char * s_modeNames[] = { "BC5-Normal", // Mode_BC5_Normal, "BC5-Normal-Stereographic", // Mode_BC5_Normal_Stereographic, "BC5-Normal-Paraboloid", // Mode_BC5_Normal_Paraboloid, - "BC5-Normal-DualParaboloid", // Mode_BC5_Normal_DualParaboloid, + "BC5-Normal-Quartic", // Mode_BC5_Normal_Quartic, + //"BC5-Normal-DualParaboloid", // Mode_BC5_Normal_DualParaboloid, }; nvStaticCheck(NV_ARRAY_SIZE(s_modeNames) == Mode_Count); @@ -215,7 +217,7 @@ static Test s_imageTests[] = { {"Color", 3, {Mode_BC1, Mode_BC3_YCoCg, Mode_BC3_RGBM, Mode_BC3_LUVW}}, {"Alpha", 3, {Mode_BC1_Alpha, Mode_BC2_Alpha, Mode_BC3_Alpha}}, //{"Normal", 3, {Mode_BC1_Normal, Mode_BC3_Normal, Mode_BC5_Normal}}, - {"Normal", 4, {Mode_BC5_Normal_DualParaboloid, Mode_BC5_Normal, Mode_BC5_Normal_Stereographic, Mode_BC5_Normal_Paraboloid}}, + {"Normal", 4, {Mode_BC5_Normal, Mode_BC5_Normal_Stereographic, Mode_BC5_Normal_Paraboloid, Mode_BC5_Normal_Quartic}}, {"Lightmap", 4, {Mode_BC1, Mode_BC3_YCoCg, Mode_BC3_RGBM, Mode_BC3_RGBS}}, }; const int s_imageTestCount = ARRAY_SIZE(s_imageTests); @@ -288,6 +290,12 @@ struct MyOutputHandler : public nvtt::OutputHandler unsigned char * m_ptr; }; +enum ErrorMode { + ErrorMode_RMSE, + ErrorMode_CieLab, + ErrorMode_AngularRMSE +}; + int main(int argc, char *argv[]) { @@ -303,6 +311,7 @@ int main(int argc, char *argv[]) int setIndex = 0; int testIndex = 0; + int errorMode = 0; bool fast = false; bool nocuda = false; bool showHelp = false; @@ -343,6 +352,13 @@ int main(int argc, char *argv[]) i++; } } + else if (strcmp("-err", argv[i]) == 0) + { + if (i+1 < argc && argv[i+1][0] != '-') { + errorMode = atoi(argv[i+1]); + i++; + } + } else if (strcmp("-fast", argv[i]) == 0) { fast = true; @@ -403,9 +419,9 @@ int main(int argc, char *argv[]) for (int i = 0; i < s_imageTestCount; i++) { printf(" %i: \t%s.\n", i, s_imageTests[i].name); } - printf(" -dec x \tDecompressor.\n"); - printf(" 0: \tReference (D3D10).\n"); - printf(" 1: \tReference (D3D9).\n"); + printf(" -dec [0:2] \tDecompressor.\n"); + printf(" 0: \tReference D3D10 (default).\n"); + printf(" 1: \tReference D3D9.\n"); printf(" 2: \tNVIDIA.\n"); printf("Compression options:\n"); @@ -414,6 +430,10 @@ int main(int argc, char *argv[]) printf("Output options:\n"); printf(" -out \tOutput directory.\n"); + printf(" -err [0:2] \tError mode.\n"); + printf(" 0: \tRMSE (default).\n"); + printf(" 1: \tCieLab.\n"); + printf(" 2: \tAngular RMSE.\n"); return 1; } @@ -477,10 +497,19 @@ int main(int argc, char *argv[]) // Grid lines. graphWriter << "&chxt=x,y&chxtc=0,-1000|1,-1000"; - // Labels. - graphWriter << "&chxr=0,1," << set.fileCount << ",1|1,0,0.05,0.01"; // rmse - //graphWriter << "&chxr=0,1," << set.fileCount << ",1|1,4,22,1"; // cielab - graphWriter << "&chdlp=b"; // Labels at the bottom. + // Labels on the left side. + if (errorMode == ErrorMode_RMSE) { + graphWriter << "&chxr=0,1," << set.fileCount << ",1|1,0,0.05,0.01"; + } + else if (errorMode == ErrorMode_CieLab) { + graphWriter << "&chxr=0,1," << set.fileCount << ",1|1,4,22,1"; + } + else if (errorMode == ErrorMode_AngularRMSE) { + graphWriter << "&chxr=0,1," << set.fileCount << ",1|1,0,0.05,0.01"; + } + + // Labels at the bottom. + graphWriter << "&chdlp=b"; // Line colors. graphWriter << "&chco="; @@ -506,8 +535,15 @@ int main(int argc, char *argv[]) graphWriter << "&chds="; for (int t = 0; t < test.count; t++) { - graphWriter << "0,0.05"; // rmse - //graphWriter << "4,22"; // cielab + if (errorMode == ErrorMode_RMSE) { + graphWriter << "0,0.05"; + } + else if (errorMode == ErrorMode_CieLab) { + graphWriter << "4,22"; + } + else if (errorMode == ErrorMode_AngularRMSE) { + graphWriter << "0,0.05"; + } if (t != test.count-1) graphWriter << ","; } @@ -520,8 +556,15 @@ int main(int argc, char *argv[]) } // Title - graphWriter << "&chtt=" << set.name << "%20-%20" << test.name << "%20-%20RMSE"; - //graphWriter << "&chtt=" << set.name << "%20-%20" << test.name << "%20-%20CIE-Lab"; + if (errorMode == ErrorMode_RMSE) { + graphWriter << "&chtt=" << set.name << "%20-%20" << test.name << "%20-%20RMSE"; + } + else if (errorMode == ErrorMode_CieLab) { + graphWriter << "&chtt=" << set.name << "%20-%20" << test.name << "%20-%20CIE-Lab"; + } + else if (errorMode == ErrorMode_AngularRMSE) { + graphWriter << "&chtt=" << set.name << "%20-%20" << test.name << "%20-%20Angular RMSE"; + } @@ -538,8 +581,7 @@ int main(int argc, char *argv[]) for (int t = 0; t < test.count; t++) { float totalTime = 0; - float totalRMSE = 0; - //float totalDeltaE = 0; + float totalError = 0; Mode mode = test.modes[t]; @@ -553,7 +595,7 @@ int main(int argc, char *argv[]) else if (mode == Mode_BC3_Normal) { format = nvtt::Format_BC3n; } - else if (mode == Mode_BC5_Normal || mode == Mode_BC5_Normal_Stereographic || mode == Mode_BC5_Normal_Paraboloid || mode == Mode_BC5_Normal_DualParaboloid) { + else if (mode == Mode_BC5_Normal || mode == Mode_BC5_Normal_Stereographic || mode == Mode_BC5_Normal_Paraboloid || mode == Mode_BC5_Normal_Quartic) { format = nvtt::Format_BC5; } @@ -663,9 +705,12 @@ int main(int argc, char *argv[]) else if (mode == Mode_BC5_Normal_Paraboloid) { tmp.transformNormals(nvtt::NormalTransform_Paraboloid); } - else if (mode == Mode_BC5_Normal_DualParaboloid) { - tmp.transformNormals(nvtt::NormalTransform_DualParaboloid); + else if (mode == Mode_BC5_Normal_Quartic) { + tmp.transformNormals(nvtt::NormalTransform_Quartic); } + /*else if (mode == Mode_BC5_Normal_DualParaboloid) { + tmp.transformNormals(nvtt::NormalTransform_DualParaboloid); + }*/ printf("Compressing: \t'%s'\n", set.fileNames[i]); @@ -740,9 +785,12 @@ int main(int argc, char *argv[]) else if (mode == Mode_BC5_Normal_Paraboloid) { img_out.reconstructNormals(nvtt::NormalTransform_Paraboloid); } - else if (mode == Mode_BC5_Normal_DualParaboloid) { - img_out.reconstructNormals(nvtt::NormalTransform_DualParaboloid); + else if (mode == Mode_BC5_Normal_Quartic) { + img_out.reconstructNormals(nvtt::NormalTransform_Quartic); } + /*else if (mode == Mode_BC5_Normal_DualParaboloid) { + tmp.transformNormals(nvtt::NormalTransform_DualParaboloid); + }*/ nvtt::TexImage diff = nvtt::diff(img, img_out, 1.0f); @@ -810,18 +858,22 @@ int main(int argc, char *argv[]) } // Output RMSE. - float rmse = nvtt::rmsError(img, img_out); - if (set.type == ImageType_HDR) rmse *= 4; - totalRMSE += rmse; - printf(" RMSE: \t%.4f\n", rmse); - - //float deltae = nvtt::cieLabError(img, img_out); - //totalDeltaE += deltae; - //printf(" CIE-Lab DeltaE:\t%.4f\n", deltae); + float error; + if (errorMode == ErrorMode_RMSE) { + error = nvtt::rmsError(img, img_out); + if (set.type == ImageType_HDR) error *= 4; + } + else if (errorMode == ErrorMode_CieLab) { + error = nvtt::cieLabError(img, img_out); + } + else if (errorMode == ErrorMode_AngularRMSE) { + error = nvtt::angularError(img, img_out); + } + totalError += error; + printf(" Error: \t%.4f\n", error); - graphWriter << rmse; - //graphWriter << deltae; + graphWriter << error; if (i != set.fileCount-1) graphWriter << ","; @@ -873,14 +925,11 @@ int main(int argc, char *argv[]) fflush(stdout); } - totalRMSE /= set.fileCount; - //totalDeltaE /= set.fileCount; - //totalDiff /= set.fileCount; + totalError /= set.fileCount; printf("Total Results:\n"); printf(" Total Time: \t%.3f sec\n", totalTime); - printf(" Average RMSE: \t%.4f\n", totalRMSE); - //printf(" Average CIE-Lab DeltaE:\t%.4f\n", totalDeltaE); + printf(" Average Error: \t%.4f\n", totalError); if (t != test.count-1) graphWriter << "|"; }