tiny_ssim.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. /*
  2. * Copyright (c) 2016 The WebM project authors. All Rights Reserved.
  3. *
  4. * Use of this source code is governed by a BSD-style license
  5. * that can be found in the LICENSE file in the root of the source
  6. * tree. An additional intellectual property rights grant can be found
  7. * in the file PATENTS. All contributing project authors may
  8. * be found in the AUTHORS file in the root of the source tree.
  9. */
  10. #include <errno.h>
  11. #include <math.h>
  12. #include <stdio.h>
  13. #include <stdlib.h>
  14. #include <string.h>
  15. #include "vpx/vpx_codec.h"
  16. #include "vpx/vpx_integer.h"
  17. #include "./y4minput.h"
  18. static void ssim_parms_8x8(unsigned char *s, int sp, unsigned char *r, int rp,
  19. uint32_t *sum_s, uint32_t *sum_r, uint32_t *sum_sq_s,
  20. uint32_t *sum_sq_r, uint32_t *sum_sxr) {
  21. int i, j;
  22. for (i = 0; i < 8; i++, s += sp, r += rp) {
  23. for (j = 0; j < 8; j++) {
  24. *sum_s += s[j];
  25. *sum_r += r[j];
  26. *sum_sq_s += s[j] * s[j];
  27. *sum_sq_r += r[j] * r[j];
  28. *sum_sxr += s[j] * r[j];
  29. }
  30. }
  31. }
  32. static const int64_t cc1 = 26634; // (64^2*(.01*255)^2
  33. static const int64_t cc2 = 239708; // (64^2*(.03*255)^2
  34. static double similarity(uint32_t sum_s, uint32_t sum_r, uint32_t sum_sq_s,
  35. uint32_t sum_sq_r, uint32_t sum_sxr, int count) {
  36. int64_t ssim_n, ssim_d;
  37. int64_t c1, c2;
  38. // scale the constants by number of pixels
  39. c1 = (cc1 * count * count) >> 12;
  40. c2 = (cc2 * count * count) >> 12;
  41. ssim_n = (2 * sum_s * sum_r + c1) *
  42. ((int64_t)2 * count * sum_sxr - (int64_t)2 * sum_s * sum_r + c2);
  43. ssim_d = (sum_s * sum_s + sum_r * sum_r + c1) *
  44. ((int64_t)count * sum_sq_s - (int64_t)sum_s * sum_s +
  45. (int64_t)count * sum_sq_r - (int64_t)sum_r * sum_r + c2);
  46. return ssim_n * 1.0 / ssim_d;
  47. }
  48. static double ssim_8x8(unsigned char *s, int sp, unsigned char *r, int rp) {
  49. uint32_t sum_s = 0, sum_r = 0, sum_sq_s = 0, sum_sq_r = 0, sum_sxr = 0;
  50. ssim_parms_8x8(s, sp, r, rp, &sum_s, &sum_r, &sum_sq_s, &sum_sq_r, &sum_sxr);
  51. return similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64);
  52. }
  53. // We are using a 8x8 moving window with starting location of each 8x8 window
  54. // on the 4x4 pixel grid. Such arrangement allows the windows to overlap
  55. // block boundaries to penalize blocking artifacts.
  56. static double ssim2(unsigned char *img1, unsigned char *img2, int stride_img1,
  57. int stride_img2, int width, int height) {
  58. int i, j;
  59. int samples = 0;
  60. double ssim_total = 0;
  61. // sample point start with each 4x4 location
  62. for (i = 0; i <= height - 8;
  63. i += 4, img1 += stride_img1 * 4, img2 += stride_img2 * 4) {
  64. for (j = 0; j <= width - 8; j += 4) {
  65. double v = ssim_8x8(img1 + j, stride_img1, img2 + j, stride_img2);
  66. ssim_total += v;
  67. samples++;
  68. }
  69. }
  70. ssim_total /= samples;
  71. return ssim_total;
  72. }
  73. static uint64_t calc_plane_error(uint8_t *orig, int orig_stride, uint8_t *recon,
  74. int recon_stride, unsigned int cols,
  75. unsigned int rows) {
  76. unsigned int row, col;
  77. uint64_t total_sse = 0;
  78. int diff;
  79. for (row = 0; row < rows; row++) {
  80. for (col = 0; col < cols; col++) {
  81. diff = orig[col] - recon[col];
  82. total_sse += diff * diff;
  83. }
  84. orig += orig_stride;
  85. recon += recon_stride;
  86. }
  87. return total_sse;
  88. }
  89. #define MAX_PSNR 100
  90. static double mse2psnr(double samples, double peak, double mse) {
  91. double psnr;
  92. if (mse > 0.0)
  93. psnr = 10.0 * log10(peak * peak * samples / mse);
  94. else
  95. psnr = MAX_PSNR; // Limit to prevent / 0
  96. if (psnr > MAX_PSNR) psnr = MAX_PSNR;
  97. return psnr;
  98. }
  99. typedef enum { RAW_YUV, Y4M } input_file_type;
  100. typedef struct input_file {
  101. FILE *file;
  102. input_file_type type;
  103. unsigned char *buf;
  104. y4m_input y4m;
  105. vpx_image_t img;
  106. int w;
  107. int h;
  108. } input_file_t;
  109. // Open a file and determine if its y4m or raw. If y4m get the header.
  110. static int open_input_file(const char *file_name, input_file_t *input, int w,
  111. int h) {
  112. char y4m_buf[4];
  113. size_t r1;
  114. input->type = RAW_YUV;
  115. input->buf = NULL;
  116. input->file = strcmp(file_name, "-") ? fopen(file_name, "rb") : stdin;
  117. if (input->file == NULL) return -1;
  118. r1 = fread(y4m_buf, 1, 4, input->file);
  119. if (r1 == 4) {
  120. if (memcmp(y4m_buf, "YUV4", 4) == 0) input->type = Y4M;
  121. switch (input->type) {
  122. case Y4M:
  123. y4m_input_open(&input->y4m, input->file, y4m_buf, 4, 0);
  124. input->w = input->y4m.pic_w;
  125. input->h = input->y4m.pic_h;
  126. // Y4M alloc's its own buf. Init this to avoid problems if we never
  127. // read frames.
  128. memset(&input->img, 0, sizeof(input->img));
  129. break;
  130. case RAW_YUV:
  131. fseek(input->file, 0, SEEK_SET);
  132. input->w = w;
  133. input->h = h;
  134. input->buf = malloc(w * h * 3 / 2);
  135. break;
  136. }
  137. }
  138. return 0;
  139. }
  140. static void close_input_file(input_file_t *in) {
  141. if (in->file) fclose(in->file);
  142. if (in->type == Y4M) {
  143. vpx_img_free(&in->img);
  144. } else {
  145. free(in->buf);
  146. }
  147. }
  148. static size_t read_input_file(input_file_t *in, unsigned char **y,
  149. unsigned char **u, unsigned char **v) {
  150. size_t r1 = 0;
  151. switch (in->type) {
  152. case Y4M:
  153. r1 = y4m_input_fetch_frame(&in->y4m, in->file, &in->img);
  154. *y = in->img.planes[0];
  155. *u = in->img.planes[1];
  156. *v = in->img.planes[2];
  157. break;
  158. case RAW_YUV:
  159. r1 = fread(in->buf, in->w * in->h * 3 / 2, 1, in->file);
  160. *y = in->buf;
  161. *u = in->buf + in->w * in->h;
  162. *v = in->buf + 5 * in->w * in->h / 4;
  163. break;
  164. }
  165. return r1;
  166. }
  167. int main(int argc, char *argv[]) {
  168. FILE *framestats = NULL;
  169. int w = 0, h = 0, tl_skip = 0, tl_skips_remaining = 0;
  170. double ssimavg = 0, ssimyavg = 0, ssimuavg = 0, ssimvavg = 0;
  171. double psnrglb = 0, psnryglb = 0, psnruglb = 0, psnrvglb = 0;
  172. double psnravg = 0, psnryavg = 0, psnruavg = 0, psnrvavg = 0;
  173. double *ssimy = NULL, *ssimu = NULL, *ssimv = NULL;
  174. uint64_t *psnry = NULL, *psnru = NULL, *psnrv = NULL;
  175. size_t i, n_frames = 0, allocated_frames = 0;
  176. int return_value = 0;
  177. input_file_t in[2];
  178. if (argc < 2) {
  179. fprintf(stderr,
  180. "Usage: %s file1.{yuv|y4m} file2.{yuv|y4m}"
  181. "[WxH tl_skip={0,1,3}]\n",
  182. argv[0]);
  183. return_value = 1;
  184. goto clean_up;
  185. }
  186. if (argc > 3) {
  187. sscanf(argv[3], "%dx%d", &w, &h);
  188. }
  189. if (open_input_file(argv[1], &in[0], w, h) < 0) {
  190. fprintf(stderr, "File %s can't be opened or parsed!\n", argv[2]);
  191. goto clean_up;
  192. }
  193. if (w == 0 && h == 0) {
  194. // If a y4m is the first file and w, h is not set grab from first file.
  195. w = in[0].w;
  196. h = in[0].h;
  197. }
  198. if (open_input_file(argv[2], &in[1], w, h) < 0) {
  199. fprintf(stderr, "File %s can't be opened or parsed!\n", argv[2]);
  200. goto clean_up;
  201. }
  202. if (in[0].w != in[1].w || in[0].h != in[1].h || in[0].w != w ||
  203. in[0].h != h || w == 0 || h == 0) {
  204. fprintf(stderr,
  205. "Failing: Image dimensions don't match or are unspecified!\n");
  206. return_value = 1;
  207. goto clean_up;
  208. }
  209. // Number of frames to skip from file1.yuv for every frame used. Normal values
  210. // 0, 1 and 3 correspond to TL2, TL1 and TL0 respectively for a 3TL encoding
  211. // in mode 10. 7 would be reasonable for comparing TL0 of a 4-layer encoding.
  212. if (argc > 4) {
  213. sscanf(argv[4], "%d", &tl_skip);
  214. if (argc > 5) {
  215. framestats = fopen(argv[5], "w");
  216. if (!framestats) {
  217. fprintf(stderr, "Could not open \"%s\" for writing: %s\n", argv[5],
  218. strerror(errno));
  219. return_value = 1;
  220. goto clean_up;
  221. }
  222. }
  223. }
  224. if (w & 1 || h & 1) {
  225. fprintf(stderr, "Invalid size %dx%d\n", w, h);
  226. return_value = 1;
  227. goto clean_up;
  228. }
  229. while (1) {
  230. size_t r1, r2;
  231. unsigned char *y[2], *u[2], *v[2];
  232. r1 = read_input_file(&in[0], &y[0], &u[0], &v[0]);
  233. if (r1) {
  234. // Reading parts of file1.yuv that were not used in temporal layer.
  235. if (tl_skips_remaining > 0) {
  236. --tl_skips_remaining;
  237. continue;
  238. }
  239. // Use frame, but skip |tl_skip| after it.
  240. tl_skips_remaining = tl_skip;
  241. }
  242. r2 = read_input_file(&in[1], &y[1], &u[1], &v[1]);
  243. if (r1 && r2 && r1 != r2) {
  244. fprintf(stderr, "Failed to read data: %s [%d/%d]\n", strerror(errno),
  245. (int)r1, (int)r2);
  246. return_value = 1;
  247. goto clean_up;
  248. } else if (r1 == 0 || r2 == 0) {
  249. break;
  250. }
  251. #define psnr_and_ssim(ssim, psnr, buf0, buf1, w, h) \
  252. ssim = ssim2(buf0, buf1, w, w, w, h); \
  253. psnr = calc_plane_error(buf0, w, buf1, w, w, h);
  254. if (n_frames == allocated_frames) {
  255. allocated_frames = allocated_frames == 0 ? 1024 : allocated_frames * 2;
  256. ssimy = realloc(ssimy, allocated_frames * sizeof(*ssimy));
  257. ssimu = realloc(ssimu, allocated_frames * sizeof(*ssimu));
  258. ssimv = realloc(ssimv, allocated_frames * sizeof(*ssimv));
  259. psnry = realloc(psnry, allocated_frames * sizeof(*psnry));
  260. psnru = realloc(psnru, allocated_frames * sizeof(*psnru));
  261. psnrv = realloc(psnrv, allocated_frames * sizeof(*psnrv));
  262. }
  263. psnr_and_ssim(ssimy[n_frames], psnry[n_frames], y[0], y[1], w, h);
  264. psnr_and_ssim(ssimu[n_frames], psnru[n_frames], u[0], u[1], w / 2, h / 2);
  265. psnr_and_ssim(ssimv[n_frames], psnrv[n_frames], v[0], v[1], w / 2, h / 2);
  266. n_frames++;
  267. }
  268. if (framestats) {
  269. fprintf(framestats,
  270. "ssim,ssim-y,ssim-u,ssim-v,psnr,psnr-y,psnr-u,psnr-v\n");
  271. }
  272. for (i = 0; i < n_frames; ++i) {
  273. double frame_ssim;
  274. double frame_psnr, frame_psnry, frame_psnru, frame_psnrv;
  275. frame_ssim = 0.8 * ssimy[i] + 0.1 * (ssimu[i] + ssimv[i]);
  276. ssimavg += frame_ssim;
  277. ssimyavg += ssimy[i];
  278. ssimuavg += ssimu[i];
  279. ssimvavg += ssimv[i];
  280. frame_psnr =
  281. mse2psnr(w * h * 6 / 4, 255.0, (double)psnry[i] + psnru[i] + psnrv[i]);
  282. frame_psnry = mse2psnr(w * h * 4 / 4, 255.0, (double)psnry[i]);
  283. frame_psnru = mse2psnr(w * h * 1 / 4, 255.0, (double)psnru[i]);
  284. frame_psnrv = mse2psnr(w * h * 1 / 4, 255.0, (double)psnrv[i]);
  285. psnravg += frame_psnr;
  286. psnryavg += frame_psnry;
  287. psnruavg += frame_psnru;
  288. psnrvavg += frame_psnrv;
  289. psnryglb += psnry[i];
  290. psnruglb += psnru[i];
  291. psnrvglb += psnrv[i];
  292. if (framestats) {
  293. fprintf(framestats, "%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf\n", frame_ssim,
  294. ssimy[i], ssimu[i], ssimv[i], frame_psnr, frame_psnry,
  295. frame_psnru, frame_psnrv);
  296. }
  297. }
  298. ssimavg /= n_frames;
  299. ssimyavg /= n_frames;
  300. ssimuavg /= n_frames;
  301. ssimvavg /= n_frames;
  302. printf("VpxSSIM: %lf\n", 100 * pow(ssimavg, 8.0));
  303. printf("SSIM: %lf\n", ssimavg);
  304. printf("SSIM-Y: %lf\n", ssimyavg);
  305. printf("SSIM-U: %lf\n", ssimuavg);
  306. printf("SSIM-V: %lf\n", ssimvavg);
  307. puts("");
  308. psnravg /= n_frames;
  309. psnryavg /= n_frames;
  310. psnruavg /= n_frames;
  311. psnrvavg /= n_frames;
  312. printf("AvgPSNR: %lf\n", psnravg);
  313. printf("AvgPSNR-Y: %lf\n", psnryavg);
  314. printf("AvgPSNR-U: %lf\n", psnruavg);
  315. printf("AvgPSNR-V: %lf\n", psnrvavg);
  316. puts("");
  317. psnrglb = psnryglb + psnruglb + psnrvglb;
  318. psnrglb = mse2psnr((double)n_frames * w * h * 6 / 4, 255.0, psnrglb);
  319. psnryglb = mse2psnr((double)n_frames * w * h * 4 / 4, 255.0, psnryglb);
  320. psnruglb = mse2psnr((double)n_frames * w * h * 1 / 4, 255.0, psnruglb);
  321. psnrvglb = mse2psnr((double)n_frames * w * h * 1 / 4, 255.0, psnrvglb);
  322. printf("GlbPSNR: %lf\n", psnrglb);
  323. printf("GlbPSNR-Y: %lf\n", psnryglb);
  324. printf("GlbPSNR-U: %lf\n", psnruglb);
  325. printf("GlbPSNR-V: %lf\n", psnrvglb);
  326. puts("");
  327. printf("Nframes: %d\n", (int)n_frames);
  328. clean_up:
  329. close_input_file(&in[0]);
  330. close_input_file(&in[1]);
  331. if (framestats) fclose(framestats);
  332. free(ssimy);
  333. free(ssimu);
  334. free(ssimv);
  335. free(psnry);
  336. free(psnru);
  337. free(psnrv);
  338. return return_value;
  339. }