// Compute a modified version of the PSNR that takes into account only errors // that can be detected by the Human Visual System. Only one channel. // Color model must be YUV444 or YUV420. // return psnr * 1024 or INFINITE_PSNR (=0) public int computePSNR_HVS_M( int[] src, int[] dst, int x, int y, int w, int h, int channelIdx, ColorModelType type) { if ((type != ColorModelType.YUV420) && (type != ColorModelType.YUV422) && (type != ColorModelType.YUV444)) throw new IllegalArgumentException("Invalid image type: must be YUV 420, 422 or 444"); if ((channelIdx != 0) && (channelIdx != 1) && (channelIdx != 2)) throw new IllegalArgumentException( "Invalid channel index: must be 0 for Y, 1 for U or 2 for V"); if ((x < 0) || (y < 0)) throw new IllegalArgumentException("Illegal argument: x and y must be positive or null"); if ((w <= 0) || (h <= 0)) throw new IllegalArgumentException("Illegal argument: w and h must be positive"); if (src == dst) return 0; final int mse = computeCSFDeltaAvg(src, dst, x, y, w, h, channelIdx); if (mse <= 0) return Global.INFINITE_VALUE; // Formula: double mse = (double) (sum) / size // double psnr = 10 * Math.log10(255d*255d/mse); // Calculate PSNR << 10 with 1024 * 10 * (log10(65025L) = 49286 return 49286 - Global.ten_log10(mse); }
// return psnr * 1024 or INFINITE_PSNR (=0) public int computePSNR( int[] data1, int[] data2, int x, int y, int w, int h, ColorModelType type) { final long lsum = this.computeDeltaSum(data1, data2, x, y, w, h, type); // Rescale to avoid overflow final int isum = (int) ((lsum + 50) / 100); if (isum <= 0) return Global.INFINITE_VALUE; // Formula: double mse = (double) (sum) / size // double psnr = 10 * Math.log10(255d*255d/mse); // or double psnr = 10 * (Math.log10(65025) + (Math.log10(size) - Math.log10(sum)) // Calculate PSNR << 10 with 1024 * 10 * (log10(65025L) = 49286 // 1024*10*log10(100) = 20480 final int iterations = ((w - x) >> this.downSampling) * ((h - y) >> this.downSampling); return 49286 + (Global.ten_log10(iterations) - Global.ten_log10(isum)) - 20480; }
private int computeCSFDeltaAvg( int[] src, int[] dst, int x0, int y0, int w, int h, int channelIdx) { final int[] csf; final int[] mask_csf; if (channelIdx == 0) { csf = CSF_Y_1024; mask_csf = MASK_CSF_Y_1024; } else if (channelIdx == 1) { csf = CSF_Cb_1024; mask_csf = MASK_CSF_Cb_1024; } else { csf = CSF_Cr_1024; mask_csf = MASK_CSF_Cr_1024; } long lsum = 0; final int[] dct_s = new int[64]; final int[] dct_d = new int[64]; int pixels = 0; final DCT8 dct = new DCT8(); IndexedIntArray iia_s = new IndexedIntArray(dct_s, 0); IndexedIntArray iia_d = new IndexedIntArray(dct_d, 0); int[] s_means_64 = new int[4]; int[] d_means_64 = new int[4]; int[] s_vars_1024 = new int[4]; int[] d_vars_1024 = new int[4]; final int st = this.stride << this.downSampling; final int inc = 1 << this.downSampling; final int inc7 = 7 * inc; for (int y = y0; y < h - 7; y += inc7) { for (int x = x0; x < w - 7; x += inc7) { s_means_64[0] = s_means_64[1] = s_means_64[2] = s_means_64[3] = 0; d_means_64[0] = d_means_64[1] = d_means_64[2] = d_means_64[3] = 0; s_vars_1024[0] = s_vars_1024[1] = s_vars_1024[2] = s_vars_1024[3] = 0; d_vars_1024[0] = d_vars_1024[1] = d_vars_1024[2] = d_vars_1024[3] = 0; int s_gmean64 = 0; int d_gmean64 = 0; int s_gvar64 = 0; int d_gvar64 = 0; // Populate DCT arrays for (int i = 0; i < 8; i++) { final int i8 = i << 3; final int offs = (y + i) * st + (x * inc); for (int j = 0; j < 8; j++) { final int idx1 = i8 + j; final int idx2 = offs + (j * inc); final int sub = ((i & 12) >> 2) + ((j & 12) >> 1); dct_s[idx1] = src[idx2]; dct_d[idx1] = dst[idx2]; s_gmean64 += dct_s[idx1]; d_gmean64 += dct_d[idx1]; s_means_64[sub] += (dct_s[idx1] << 2); d_means_64[sub] += (dct_d[idx1] << 2); } } // Compute variance for (int i = 0; i < 8; i++) { final int i8 = i << 3; for (int j = 0; j < 8; j++) { final int s = dct_s[i8 + j] << 6; final int d = dct_d[i8 + j] << 6; final int sub = ((i & 12) >> 2) + ((j & 12) >> 1); s_gvar64 += (s - s_gmean64) * (s - s_gmean64); d_gvar64 += (d - d_gmean64) * (d - d_gmean64); s_vars_1024[sub] += (s - s_means_64[sub]) * (s - s_means_64[sub]); d_vars_1024[sub] += (d - d_means_64[sub]) * (d - d_means_64[sub]); } } // Replace s_gvar64 /= (63*64) and s_vars_1024[i] *= 16/15 with 63*64*16/15 = 275251/64 // Since s_vars_1024[i] is scaled by 4 (s_means_1024 scaled by 64*64 instead of 1024), // use rescaling factor 275251/256. if (s_gvar64 > 0) { long sum = (long) (s_vars_1024[0] + s_vars_1024[1] + s_vars_1024[2] + s_vars_1024[3]); s_gvar64 = (int) (sum / 256 * 275251 / s_gvar64); } if (d_gvar64 > 0) { long sum = (long) (d_vars_1024[0] + d_vars_1024[1] + d_vars_1024[2] + d_vars_1024[3]); d_gvar64 = (int) (sum / 256 * 275251 / d_gvar64); } // Perform forward DCT (gain is 1<<5) iia_s.index = 0; dct.forward(iia_s, iia_s); iia_d.index = 0; dct.forward(iia_d, iia_d); // Offset DCT gain dct_s[0] >>= 5; dct_d[0] >>= 5; long s_mask_1024 = 0; long d_mask_1024 = 0; // Compute masks for (int i = 0; i < 8; i++) { final int i8 = i << 3; final int j0 = (i == 0) ? 1 : 0; for (int j = j0; j < 8; j++) { final int idx = i8 + j; // Offset DCT gain dct_s[idx] >>= 5; dct_d[idx] >>= 5; s_mask_1024 += (dct_s[idx] * dct_s[idx] * mask_csf[idx]); d_mask_1024 += (dct_d[idx] * dct_d[idx] * mask_csf[idx]); } } if (d_mask_1024 * d_gvar64 > s_mask_1024 * s_gvar64) s_mask_1024 = (long) (Global.sqrt((int) d_mask_1024) >> 8) * (long) (Global.sqrt(d_gvar64) >> 7); else s_mask_1024 = (long) (Global.sqrt((int) s_mask_1024) >> 8) * (long) (Global.sqrt(s_gvar64) >> 7); // Calculate error for (int i = 0; i < 8; i++) { final int i8 = i << 3; for (int j = 0; j < 8; j++) { final int idx = i8 + j; long err1024 = ((long) Math.abs(dct_s[idx] - dct_d[idx])) << 10; if ((i != 0) || (j != 0)) err1024 = (err1024 * mask_csf[idx] < s_mask_1024) ? 0 : err1024 - (s_mask_1024 / mask_csf[idx]); final long val1024 = (err1024 * csf[idx] + 512) >> 10; lsum += ((val1024 * val1024) >> 10); pixels++; } } } } return (pixels == 0) ? 0 : (int) (((lsum + 512) >> 10) / pixels); }