/* * SSIM(x,y)=(2*ux*uy + C1)*(2sxy + C2) / (ux^2 + uy^2 + C1)*(sx^2 + sy^2 + C2) * where, * ux = SUM(w*x) * sx = (SUM(w*(x-ux)^2)^0.5 * sxy = SUM(w*(x-ux)*(y-uy)) * * Returns mean SSIM. MSSIM(X,Y) = 1/M * SUM(SSIM(x,y)) */ float iqa_ssim(const unsigned char *ref, const unsigned char *cmp, int w, int h, int stride, int gaussian, const struct iqa_ssim_args *args) { int scale; int x,y,src_offset,offset; float *ref_f,*cmp_f; struct _kernel low_pass; struct _kernel window; float result; double ssim_sum=0.0; struct _map_reduce mr; /* Initialize algorithm parameters */ scale = _max( 1, _round( (float)_min(w,h) / 256.0f ) ); if (args) { if(args->f) scale = args->f; mr.map = _ssim_map; mr.reduce = _ssim_reduce; mr.context = (void*)&ssim_sum; } window.kernel = (float*)g_square_window; window.w = window.h = SQUARE_LEN; window.normalized = 1; window.bnd_opt = KBND_SYMMETRIC; if (gaussian) { window.kernel = (float*)g_gaussian_window; window.w = window.h = GAUSSIAN_LEN; } /* Convert image values to floats. Forcing stride = width. */ ref_f = (float*)malloc(w*h*sizeof(float)); cmp_f = (float*)malloc(w*h*sizeof(float)); if (!ref_f || !cmp_f) { if (ref_f) free(ref_f); if (cmp_f) free(cmp_f); return INFINITY; } for (y=0; y<h; ++y) { src_offset = y*stride; offset = y*w; for (x=0; x<w; ++x, ++offset, ++src_offset) { ref_f[offset] = (float)ref[src_offset]; cmp_f[offset] = (float)cmp[src_offset]; } } /* Scale the images down if required */ if (scale > 1) { /* Generate simple low-pass filter */ low_pass.kernel = (float*)malloc(scale*scale*sizeof(float)); if (!low_pass.kernel) { free(ref_f); free(cmp_f); return INFINITY; } low_pass.w = low_pass.h = scale; low_pass.normalized = 0; low_pass.bnd_opt = KBND_SYMMETRIC; for (offset=0; offset<scale*scale; ++offset) low_pass.kernel[offset] = 1.0f/(scale*scale); /* Resample */ if (_iqa_decimate(ref_f, w, h, scale, &low_pass, 0, 0, 0) || _iqa_decimate(cmp_f, w, h, scale, &low_pass, 0, &w, &h)) { /* Update w/h */ free(ref_f); free(cmp_f); free(low_pass.kernel); return INFINITY; } free(low_pass.kernel); } result = _iqa_ssim(ref_f, cmp_f, w, h, &window, &mr, args); free(ref_f); free(cmp_f); return result; }
int compute_ssim(const number_t *ref, const number_t *cmp, int w, int h, int ref_stride, int cmp_stride, double *score, double *l_score, double *c_score, double *s_score) { int ret = 1; int scale; int x,y,src_offset,offset; float *ref_f,*cmp_f; struct _kernel low_pass; struct _kernel window; float result = INFINITY; float l, c, s; double ssim_sum=0.0; struct _map_reduce mr; /* check stride */ int stride = ref_stride; /* stride in bytes */ if (stride != cmp_stride) { printf("error: for ssim, ref_stride (%d) != dis_stride (%d) bytes.\n", ref_stride, cmp_stride); fflush(stdout); goto fail_or_end; } stride /= sizeof(float); /* stride_ in pixels */ /* specify some default parameters */ const struct iqa_ssim_args *args = 0; /* 0 for default */ int gaussian = 1; /* 0 for 8x8 square window, 1 for 11x11 circular-symmetric Gaussian window (default) */ /* initialize algorithm parameters */ scale = _max( 1, _round( (float)_min(w,h) / 256.0f ) ); if (args) { if(args->f) { scale = args->f; } mr.map = _ssim_map; mr.reduce = _ssim_reduce; mr.context = (void*)&ssim_sum; } window.kernel = (float*)g_square_window; window.kernel_h = (float*)g_square_window_h; window.kernel_v = (float*)g_square_window_v; window.w = window.h = SQUARE_LEN; window.normalized = 1; window.bnd_opt = KBND_SYMMETRIC; if (gaussian) { window.kernel = (float*)g_gaussian_window; window.kernel_h = (float*)g_gaussian_window_h; window.kernel_v = (float*)g_gaussian_window_v; window.w = window.h = GAUSSIAN_LEN; } /* convert image values to floats, forcing stride = width. */ ref_f = (float*)malloc(w*h*sizeof(float)); cmp_f = (float*)malloc(w*h*sizeof(float)); if (!ref_f || !cmp_f) { if (ref_f) free(ref_f); if (cmp_f) free(cmp_f); printf("error: unable to malloc ref_f or cmp_f.\n"); fflush(stdout); goto fail_or_end; } for (y=0; y<h; ++y) { src_offset = y * stride; offset = y * w; for (x=0; x<w; ++x, ++offset, ++src_offset) { ref_f[offset] = (float)ref[src_offset]; cmp_f[offset] = (float)cmp[src_offset]; } } /* scale the images down if required */ if (scale > 1) { /* generate simple low-pass filter */ low_pass.kernel = (float*)malloc(scale*scale*sizeof(float)); low_pass.kernel_h = (float*)malloc(scale*sizeof(float)); /* zli-nflx */ low_pass.kernel_v = (float*)malloc(scale*sizeof(float)); /* zli-nflx */ if (!(low_pass.kernel && low_pass.kernel_h && low_pass.kernel_v)) { /* zli-nflx */ free(ref_f); free(cmp_f); if (low_pass.kernel) free(low_pass.kernel); /* zli-nflx */ if (low_pass.kernel_h) free(low_pass.kernel_h); /* zli-nflx */ if (low_pass.kernel_v) free(low_pass.kernel_v); /* zli-nflx */ printf("error: unable to malloc low-pass filter kernel.\n"); fflush(stdout); goto fail_or_end; } low_pass.w = low_pass.h = scale; low_pass.normalized = 0; low_pass.bnd_opt = KBND_SYMMETRIC; for (offset=0; offset<scale*scale; ++offset) low_pass.kernel[offset] = 1.0f/(scale*scale); for (offset=0; offset<scale; ++offset) /* zli-nflx */ low_pass.kernel_h[offset] = 1.0f/(scale); /* zli-nflx */ for (offset=0; offset<scale; ++offset) /* zli-nflx */ low_pass.kernel_v[offset] = 1.0f/(scale); /* zli-nflx */ /* resample */ if (_iqa_decimate(ref_f, w, h, scale, &low_pass, 0, 0, 0) || _iqa_decimate(cmp_f, w, h, scale, &low_pass, 0, &w, &h)) { /* update w/h */ free(ref_f); free(cmp_f); free(low_pass.kernel); free(low_pass.kernel_h); /* zli-nflx */ free(low_pass.kernel_v); /* zli-nflx */ printf("error: decimation fails on ref_f or cmp_f.\n"); fflush(stdout); goto fail_or_end; } free(low_pass.kernel); free(low_pass.kernel_h); /* zli-nflx */ free(low_pass.kernel_v); /* zli-nflx */ } result = _iqa_ssim(ref_f, cmp_f, w, h, &window, &mr, args, &l, &c, &s); free(ref_f); free(cmp_f); *score = (double)result; *l_score = (double)l; *c_score = (double)c; *s_score = (double)s; ret = 0; fail_or_end: return ret; }
int compute_ms_ssim(const float *ref, const float *cmp, int w, int h, int ref_stride, int cmp_stride, double *score, double* l_scores, double* c_scores, double* s_scores) { int ret = 1; int wang=1; /* set default to wang's ms_ssim */ int scales=SCALES; int gauss=1; const float *alphas=g_alphas, *betas=g_betas, *gammas=g_gammas; int idx,x,y,cur_w,cur_h; int offset,src_offset; float **ref_imgs, **cmp_imgs; /* Array of pointers to scaled images */ double msssim; float l, c, s; struct _kernel lpf, window; struct iqa_ssim_args s_args; struct _map_reduce mr; struct _context ms_ctx; /* check stride */ int stride = ref_stride; /* stride in bytes */ if (stride != cmp_stride) { printf("error: for ms_ssim, ref_stride (%d) != dis_stride (%d) bytes.\n", ref_stride, cmp_stride); fflush(stdout); goto fail_or_end; } stride /= sizeof(float); /* stride_ in pixels */ /* specify some default parameters */ const struct iqa_ms_ssim_args *args = 0; /* 0 for default */ /* initialize algorithm parameters */ if (args) { wang = args->wang; gauss = args->gaussian; scales = args->scales; if (args->alphas) alphas = args->alphas; if (args->betas) betas = args->betas; if (args->gammas) gammas = args->gammas; } /* make sure we won't scale below 1x1 */ cur_w = w; cur_h = h; for (idx=0; idx<scales; ++idx) { if ( gauss ? cur_w<GAUSSIAN_LEN || cur_h<GAUSSIAN_LEN : cur_w<LPF_LEN || cur_h<LPF_LEN ) { printf("error: scale below 1x1!\n"); goto fail_or_end; } cur_w /= 2; cur_h /= 2; } window.kernel = (float*)g_square_window; window.kernel_h = (float*)g_square_window_h; /* zli-nflx */ window.kernel_v = (float*)g_square_window_v; /* zli-nflx */ window.w = window.h = SQUARE_LEN; window.normalized = 1; window.bnd_opt = KBND_SYMMETRIC; if (gauss) { window.kernel = (float*)g_gaussian_window; window.kernel_h = (float*)g_gaussian_window_h; /* zli-nflx */ window.kernel_v = (float*)g_gaussian_window_v; /* zli-nflx */ window.w = window.h = GAUSSIAN_LEN; } mr.map = _ms_ssim_map; mr.reduce = _ms_ssim_reduce; /* allocate the scaled image buffers */ ref_imgs = (float**)malloc(scales*sizeof(float*)); cmp_imgs = (float**)malloc(scales*sizeof(float*)); if (!ref_imgs || !cmp_imgs) { if (ref_imgs) free(ref_imgs); if (cmp_imgs) free(cmp_imgs); printf("error: unable to malloc ref_imgs or cmp_imgs.\n"); fflush(stdout); goto fail_or_end; } if (_alloc_buffers(ref_imgs, w, h, scales)) { free(ref_imgs); free(cmp_imgs); printf("error: unable to _alloc_buffers on ref_imgs.\n"); fflush(stdout); goto fail_or_end; } if (_alloc_buffers(cmp_imgs, w, h, scales)) { _free_buffers(ref_imgs, scales); free(ref_imgs); free(cmp_imgs); printf("error: unable to _alloc_buffers on cmp_imgs.\n"); fflush(stdout); goto fail_or_end; } /* copy original images into first scale buffer, forcing stride = width. */ for (y=0; y<h; ++y) { src_offset = y * stride; offset = y * w; for (x=0; x<w; ++x, ++offset, ++src_offset) { ref_imgs[0][offset] = (float)ref[src_offset]; cmp_imgs[0][offset] = (float)cmp[src_offset]; } } /* create scaled versions of the images */ cur_w=w; cur_h=h; lpf.kernel = (float*)g_lpf; lpf.kernel_h = (float*)g_lpf_h; /* zli-nflx */ lpf.kernel_v = (float*)g_lpf_v; /* zli-nflx */ lpf.w = lpf.h = LPF_LEN; lpf.normalized = 1; lpf.bnd_opt = KBND_SYMMETRIC; for (idx=1; idx<scales; ++idx) { if (_iqa_decimate(ref_imgs[idx-1], cur_w, cur_h, 2, &lpf, ref_imgs[idx], 0, 0) || _iqa_decimate(cmp_imgs[idx-1], cur_w, cur_h, 2, &lpf, cmp_imgs[idx], &cur_w, &cur_h)) { _free_buffers(ref_imgs, scales); _free_buffers(cmp_imgs, scales); free(ref_imgs); free(cmp_imgs); printf("error: decimation fails on ref_imgs or cmp_imgs.\n"); fflush(stdout); goto fail_or_end; } } cur_w=w; cur_h=h; msssim = 1.0; for (idx=0; idx<scales; ++idx) { ms_ctx.l = 0; ms_ctx.c = 0; ms_ctx.s = 0; ms_ctx.alpha = alphas[idx]; ms_ctx.beta = betas[idx]; ms_ctx.gamma = gammas[idx]; if (!wang) { /* MS-SSIM* (Rouse/Hemami) */ s_args.alpha = 1.0f; s_args.beta = 1.0f; s_args.gamma = 1.0f; s_args.K1 = 0.0f; /* Force stabilization constants to 0 */ s_args.K2 = 0.0f; s_args.L = 255; s_args.f = 1; /* Don't resize */ mr.context = &ms_ctx; _iqa_ssim(ref_imgs[idx], cmp_imgs[idx], cur_w, cur_h, &window, &mr, &s_args, &l, &c, &s); } else { /* MS-SSIM (Wang) */ /* s_args.alpha = 1.0f; s_args.beta = 1.0f; s_args.gamma = 1.0f; s_args.K1 = 0.01f; s_args.K2 = 0.03f; s_args.L = 255; s_args.f = 1; // Don't resize mr.context = &ms_ctx; msssim *= _iqa_ssim(ref_imgs[idx], cmp_imgs[idx], cur_w, cur_h, &window, &mr, &s_args, &l, &c, &s); */ /* above is equivalent to passing default parameter: */ _iqa_ssim(ref_imgs[idx], cmp_imgs[idx], cur_w, cur_h, &window, NULL, NULL, &l, &c, &s); } msssim *= pow(l, alphas[idx]) * pow(c, betas[idx]) * pow(s, gammas[idx]); l_scores[idx] = l; c_scores[idx] = c; s_scores[idx] = s; if (msssim == INFINITY) { _free_buffers(ref_imgs, scales); _free_buffers(cmp_imgs, scales); free(ref_imgs); free(cmp_imgs); printf("error: ms_ssim is INFINITY.\n"); fflush(stdout); goto fail_or_end; } cur_w = cur_w/2 + (cur_w&1); cur_h = cur_h/2 + (cur_h&1); } _free_buffers(ref_imgs, scales); _free_buffers(cmp_imgs, scales); free(ref_imgs); free(cmp_imgs); *score = msssim; ret = 0; fail_or_end: return ret; }
/* * MS_SSIM(X,Y) = Lm(x,y)^aM * MULT[j=1->M]( Cj(x,y)^bj * Sj(x,y)^gj ) * where, * L = mean * C = variance * S = cross-correlation * * b1=g1=0.0448, b2=g2=0.2856, b3=g3=0.3001, b4=g4=0.2363, a5=b5=g5=0.1333 */ float iqa_ms_ssim(const unsigned char *ref, const unsigned char *cmp, int w, int h, int stride, const struct iqa_ms_ssim_args *args) { int wang=0; int scales=SCALES; int gauss=1; const float *alphas=g_alphas, *betas=g_betas, *gammas=g_gammas; int idx,x,y,cur_w,cur_h; int offset,src_offset; float **ref_imgs, **cmp_imgs; /* Array of pointers to scaled images */ float msssim; struct _kernel lpf, window; struct iqa_ssim_args s_args; struct _map_reduce mr; struct _context ms_ctx; if (args) { wang = args->wang; gauss = args->gaussian; scales = args->scales; if (args->alphas) alphas = args->alphas; if (args->betas) betas = args->betas; if (args->gammas) gammas = args->gammas; } /* Make sure we won't scale below 1x1 */ cur_w = w; cur_h = h; for (idx=0; idx<scales; ++idx) { if ( gauss ? cur_w<GAUSSIAN_LEN || cur_h<GAUSSIAN_LEN : cur_w<LPF_LEN || cur_h<LPF_LEN ) return INFINITY; cur_w /= 2; cur_h /= 2; } window.kernel = (float*)g_square_window; window.w = window.h = SQUARE_LEN; window.normalized = 1; window.bnd_opt = KBND_SYMMETRIC; if (gauss) { window.kernel = (float*)g_gaussian_window; window.w = window.h = GAUSSIAN_LEN; } mr.map = _ms_ssim_map; mr.reduce = _ms_ssim_reduce; /* Allocate the scaled image buffers */ ref_imgs = (float**)malloc(scales*sizeof(float*)); cmp_imgs = (float**)malloc(scales*sizeof(float*)); if (!ref_imgs || !cmp_imgs) { if (ref_imgs) free(ref_imgs); if (cmp_imgs) free(cmp_imgs); return INFINITY; } if (_alloc_buffers(ref_imgs, w, h, scales)) { free(ref_imgs); free(cmp_imgs); return INFINITY; } if (_alloc_buffers(cmp_imgs, w, h, scales)) { _free_buffers(ref_imgs, scales); free(ref_imgs); free(cmp_imgs); return INFINITY; } /* Copy original images into first scale buffer, forcing stride = width. */ for (y=0; y<h; ++y) { src_offset = y*stride; offset = y*w; for (x=0; x<w; ++x, ++offset, ++src_offset) { ref_imgs[0][offset] = (float)ref[src_offset]; cmp_imgs[0][offset] = (float)cmp[src_offset]; } } /* Create scaled versions of the images */ cur_w=w; cur_h=h; lpf.kernel = (float*)g_lpf; lpf.w = lpf.h = LPF_LEN; lpf.normalized = 1; lpf.bnd_opt = KBND_SYMMETRIC; for (idx=1; idx<scales; ++idx) { if (_iqa_decimate(ref_imgs[idx-1], cur_w, cur_h, 2, &lpf, ref_imgs[idx], 0, 0) || _iqa_decimate(cmp_imgs[idx-1], cur_w, cur_h, 2, &lpf, cmp_imgs[idx], &cur_w, &cur_h)) { _free_buffers(ref_imgs, scales); _free_buffers(cmp_imgs, scales); free(ref_imgs); free(cmp_imgs); return INFINITY; } } cur_w=w; cur_h=h; msssim = 1.0; for (idx=0; idx<scales; ++idx) { ms_ctx.l = 0; ms_ctx.c = 0; ms_ctx.s = 0; ms_ctx.alpha = alphas[idx]; ms_ctx.beta = betas[idx]; ms_ctx.gamma = gammas[idx]; if (!wang) { /* MS-SSIM* (Rouse/Hemami) */ s_args.alpha = 1.0f; s_args.beta = 1.0f; s_args.gamma = 1.0f; s_args.K1 = 0.0f; /* Force stabilization constants to 0 */ s_args.K2 = 0.0f; s_args.L = 255; s_args.f = 1; /* Don't resize */ mr.context = &ms_ctx; msssim *= _iqa_ssim(ref_imgs[idx], cmp_imgs[idx], cur_w, cur_h, &window, &mr, &s_args); } else { /* MS-SSIM (Wang) */ s_args.alpha = 1.0f; s_args.beta = 1.0f; s_args.gamma = 1.0f; s_args.K1 = 0.01f; s_args.K2 = 0.03f; s_args.L = 255; s_args.f = 1; /* Don't resize */ mr.context = &ms_ctx; msssim *= _iqa_ssim(ref_imgs[idx], cmp_imgs[idx], cur_w, cur_h, &window, &mr, &s_args); } if (msssim == INFINITY) break; cur_w = cur_w/2 + (cur_w&1); cur_h = cur_h/2 + (cur_h&1); } _free_buffers(ref_imgs, scales); _free_buffers(cmp_imgs, scales); free(ref_imgs); free(cmp_imgs); return msssim; }