#include "ErrorMetric.h" #include "FloatImage.h" #include "Filter.h" #include "nvmath/Matrix.h" #include "nvmath/Vector.inl" #include // FLT_MAX using namespace nv; float nv::rmsColorError(const FloatImage * ref, const FloatImage * img, bool alphaWeight) { if (!sameLayout(img, ref)) { return FLT_MAX; } nvDebugCheck(img->componentCount() == 4); nvDebugCheck(ref->componentCount() == 4); double mse = 0; const uint count = img->pixelCount(); for (uint i = 0; i < count; i++) { float r0 = ref->pixel(i + count * 0); float g0 = ref->pixel(i + count * 1); float b0 = ref->pixel(i + count * 2); float a0 = ref->pixel(i + count * 3); float r1 = img->pixel(i + count * 0); float g1 = img->pixel(i + count * 1); float b1 = img->pixel(i + count * 2); //float a1 = img->pixel(i + count * 3); float r = r0 - r1; float g = g0 - g1; float b = b0 - b1; float a = 1; if (alphaWeight) a = a0 * a0; // @@ a0*a1 or a0*a0 ? mse += (r * r) * a; mse += (g * g) * a; mse += (b * b) * a; } return float(sqrt(mse / count)); } float nv::rmsAlphaError(const FloatImage * ref, const FloatImage * img) { if (!sameLayout(img, ref)) { return FLT_MAX; } nvDebugCheck(img->componentCount() == 4 && ref->componentCount() == 4); double mse = 0; const uint count = img->pixelCount(); 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; mse += a * a; } return float(sqrt(mse / count)); } float nv::averageColorError(const FloatImage * ref, const FloatImage * img, bool alphaWeight) { if (!sameLayout(img, ref)) { return FLT_MAX; } nvDebugCheck(img->componentCount() == 4); nvDebugCheck(ref->componentCount() == 4); double mae = 0; const uint count = img->pixelCount(); 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 = fabsf(r0 - r1); float g = fabsf(g0 - g1); float b = fabsf(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 * ref, const FloatImage * img) { if (img == NULL || ref == NULL || img->width() != ref->width() || img->height() != ref->height()) { return FLT_MAX; } nvDebugCheck(img->componentCount() == 4 && ref->componentCount() == 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 += fabsf(a); } return float(mae / count); } float nv::rmsBilinearColorError(const FloatImage * ref, const FloatImage * img, FloatImage::WrapMode wm, bool alphaWeight) { nvDebugCheck(img->componentCount() == 4); nvDebugCheck(ref->componentCount() == 4); double mse = 0; const uint w0 = ref->width(); const uint h0 = ref->height(); const uint d0 = ref->depth(); const uint w1 = img->width(); const uint h1 = img->height(); const uint d1 = img->depth(); for (uint z = 0; z < d0; z++) { for (uint y = 0; y < h0; y++) { for (uint x = 0; x < w0; x++) { float r0 = ref->pixel(0, x, y, z); float g0 = ref->pixel(1, x, y, z); float b0 = ref->pixel(2, x, y, z); float a0 = ref->pixel(3, x, y, z); float fx = float(x) / w0; float fy = float(y) / h0; float fz = float(z) / d0; float r1 = img->sampleLinear(0, fx, fy, fz, wm); float g1 = img->sampleLinear(1, fx, fy, fz, wm); float b1 = img->sampleLinear(2, fx, fy, fz, wm); float a1 = img->sampleLinear(2, fx, fy, fz, wm); float dr = r0 - r1; float dg = g0 - g1; float db = b0 - b1; float da = a0 - a1; float w = 1; if (alphaWeight) w = a0 * a0; // @@ a0*a1 or a0*a0 ? mse += (dr * dr) * w; mse += (dg * dg) * w; mse += (db * db) * w; mse += (da * da); } } } int count = w0 * h0 * d0; return float(sqrt(mse / count)); } // Color space conversions based on: // http://www.brucelindbloom.com/ // Assumes input is in *linear* sRGB color space. static Vector3 rgbToXyz(Vector3::Arg c) { Vector3 xyz; xyz.x = 0.412453f * c.x + 0.357580f * c.y + 0.180423f * c.z; xyz.y = 0.212671f * c.x + 0.715160f * c.y + 0.072169f * c.z; xyz.z = 0.019334f * c.x + 0.119193f * c.y + 0.950227f * c.z; return xyz; } static Vector3 xyzToRgb(Vector3::Arg c) { Vector3 rgb; rgb.x = 3.2404542f * c.x - 1.5371385f * c.y - 0.4985314f * c.z; rgb.y = -0.9692660f * c.x + 1.8760108f * c.y + 0.0415560f * c.z; rgb.z = 0.0556434f * c.x - 0.2040259f * c.y + 1.0572252f * c.z; return rgb; } static float toLinear(float f) { return powf(f, 2.2f); } static float toGamma(float f) { // @@ Use sRGB space? return powf(f, 1.0f/2.2f); } static Vector3 toLinear(Vector3::Arg c) { return Vector3(toLinear(c.x), toLinear(c.y), toLinear(c.z)); } static Vector3 toGamma(Vector3::Arg c) { return Vector3(toGamma(c.x), toGamma(c.y), toGamma(c.z)); } static float f(float t) { const float epsilon = powf(6.0f/29.0f, 3); if (t > epsilon) { return powf(t, 1.0f/3.0f); } else { return 1.0f/3.0f * powf(29.0f/6.0f, 2) * t + 4.0f / 29.0f; } } static float finv(float t) { if (t > 6.0f / 29.0f) { return 3.0f * powf(6.0f / 29.0f, 2) * (t - 4.0f / 29.0f); } else { return powf(t, 3.0f); } } static Vector3 xyzToCieLab(Vector3::Arg c) { // Normalized white point. const float Xn = 0.950456f; const float Yn = 1.0f; const float Zn = 1.088754f; float Xr = c.x / Xn; float Yr = c.y / Yn; float Zr = c.z / Zn; float fx = f(Xr); float fy = f(Yr); float fz = f(Zr); float L = 116 * fx - 16; float a = 500 * (fx - fy); float b = 200 * (fy - fz); return Vector3(L, a, b); } static Vector3 rgbToCieLab(Vector3::Arg c) { return xyzToCieLab(rgbToXyz(toLinear(c))); } // h is hue-angle in radians static Vector3 cieLabToLCh(Vector3::Arg c) { return Vector3(c.x, sqrtf(c.y*c.y + c.z*c.z), atan2f(c.y, c.z)); } static void rgbToCieLab(const FloatImage * rgbImage, FloatImage * LabImage) { nvDebugCheck(rgbImage != NULL && LabImage != NULL); nvDebugCheck(rgbImage->width() == LabImage->width() && rgbImage->height() == LabImage->height()); nvDebugCheck(rgbImage->componentCount() >= 3 && LabImage->componentCount() >= 3); const uint w = rgbImage->width(); const uint h = LabImage->height(); const float * R = rgbImage->channel(0); const float * G = rgbImage->channel(1); const float * B = rgbImage->channel(2); float * L = LabImage->channel(0); float * a = LabImage->channel(1); float * b = LabImage->channel(2); const uint count = w*h; for (uint i = 0; i < count; i++) { Vector3 Lab = rgbToCieLab(Vector3(R[i], G[i], B[i])); L[i] = Lab.x; a[i] = Lab.y; b[i] = Lab.z; } } // Assumes input images are in linear sRGB space. float nv::cieLabError(const FloatImage * img0, const FloatImage * img1) { if (!sameLayout(img0, img1)) return FLT_MAX; nvDebugCheck(img0->componentCount() == 4 && img1->componentCount() == 4); const float * r0 = img0->channel(0); const float * g0 = img0->channel(1); const float * b0 = img0->channel(2); const float * r1 = img1->channel(0); const float * g1 = img1->channel(1); const float * b1 = img1->channel(2); double error = 0.0f; const uint count = img0->pixelCount(); for (uint i = 0; i < count; i++) { Vector3 lab0 = rgbToCieLab(Vector3(r0[i], g0[i], b0[i])); Vector3 lab1 = rgbToCieLab(Vector3(r1[i], g1[i], b1[i])); // @@ Measure Delta E. Vector3 delta = lab0 - lab1; error += length(delta); } return float(error / count); } // Assumes input images are in linear sRGB space. float nv::cieLab94Error(const FloatImage * img0, const FloatImage * img1) { if (!sameLayout(img0, img1)) return FLT_MAX; nvDebugCheck(img0->componentCount() == 4 && img1->componentCount() == 4); const float kL = 1; const float kC = 1; const float kH = 1; const float k1 = 0.045f; const float k2 = 0.015f; const float sL = 1; const float * r0 = img0->channel(0); const float * g0 = img0->channel(1); const float * b0 = img0->channel(2); const float * r1 = img1->channel(0); const float * g1 = img1->channel(1); const float * b1 = img1->channel(2); double error = 0.0f; const uint count = img0->pixelCount(); for (uint i = 0; i < count; ++i) { Vector3 lab0 = rgbToCieLab(Vector3(r0[i], g0[i], b0[i])); Vector3 lch0 = cieLabToLCh(lab0); Vector3 lab1 = rgbToCieLab(Vector3(r1[i], g1[i], b1[i])); Vector3 lch1 = cieLabToLCh(lab1); const float sC = 1 + k1*lch0.x; const float sH = 1 + k2*lch0.x; // @@ Measure Delta E using the 1994 definition Vector3 labDelta = lab0 - lab1; Vector3 lchDelta = lch0 - lch1; double deltaLsq = powf(lchDelta.x / (kL*sL), 2); double deltaCsq = powf(lchDelta.y / (kC*sC), 2); // avoid possible sqrt of negative value by computing (deltaH/(kH*sH))^2 double deltaHsq = powf(labDelta.y, 2) + powf(labDelta.z, 2) - powf(lchDelta.y, 2); deltaHsq /= powf(kH*sH, 2); error += sqrt(deltaLsq + deltaCsq + deltaHsq); } return float(error / count); } float nv::spatialCieLabError(const FloatImage * img0, const FloatImage * img1) { if (img0 == NULL || img1 == NULL || img0->width() != img1->width() || img0->height() != img1->height()) { return FLT_MAX; } nvDebugCheck(img0->componentCount() == 4 && img1->componentCount() == 4); uint w = img0->width(); uint h = img0->height(); uint d = img0->depth(); FloatImage lab0, lab1; // Original images in CIE-Lab space. lab0.allocate(3, w, h, d); lab1.allocate(3, w, h, d); // Convert input images to CIE-Lab. rgbToCieLab(img0, &lab0); rgbToCieLab(img1, &lab1); // @@ Convolve each channel by the corresponding filter. /* GaussianFilter LFilter(5); GaussianFilter aFilter(5); GaussianFilter bFilter(5); lab0.convolve(0, LFilter); lab0.convolve(1, aFilter); lab0.convolve(2, bFilter); lab1.convolve(0, LFilter); lab1.convolve(1, aFilter); lab1.convolve(2, bFilter); */ // @@ Measure Delta E between lab0 and lab1. return 0.0f; } // 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->componentCount() == 4 && img1->componentCount() == 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.0f * n0 - Vector3(1); n1 = 2.0f * 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->componentCount() == 4 && img1->componentCount() == 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.0f * n0 - Vector3(1); n1 = 2.0f * n1 - Vector3(1); n0 = normalizeSafe(n0, Vector3(0), 0.0f); n1 = normalizeSafe(n1, Vector3(0), 0.0f); float angle = acosf(clamp(dot(n0, n1), -1.0f, 1.0f)); error += angle * angle; } return float(sqrt(error / count)); }