tiny_ssim.c 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  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 <assert.h>
  11. #include <errno.h>
  12. #include <math.h>
  13. #include <stdio.h>
  14. #include <stdlib.h>
  15. #include <string.h>
  16. #include "vpx/vpx_codec.h"
  17. #include "vpx/vpx_integer.h"
  18. #include "./y4minput.h"
  19. #include "vpx_dsp/ssim.h"
  20. #include "vpx_ports/mem.h"
  21. static const int64_t cc1 = 26634; // (64^2*(.01*255)^2
  22. static const int64_t cc2 = 239708; // (64^2*(.03*255)^2
  23. static const int64_t cc1_10 = 428658; // (64^2*(.01*1023)^2
  24. static const int64_t cc2_10 = 3857925; // (64^2*(.03*1023)^2
  25. static const int64_t cc1_12 = 6868593; // (64^2*(.01*4095)^2
  26. static const int64_t cc2_12 = 61817334; // (64^2*(.03*4095)^2
  27. #if CONFIG_VP9_HIGHBITDEPTH
  28. static uint64_t calc_plane_error16(uint16_t *orig, int orig_stride,
  29. uint16_t *recon, int recon_stride,
  30. unsigned int cols, unsigned int rows) {
  31. unsigned int row, col;
  32. uint64_t total_sse = 0;
  33. int diff;
  34. if (orig == NULL || recon == NULL) {
  35. assert(0);
  36. return 0;
  37. }
  38. for (row = 0; row < rows; row++) {
  39. for (col = 0; col < cols; col++) {
  40. diff = orig[col] - recon[col];
  41. total_sse += diff * diff;
  42. }
  43. orig += orig_stride;
  44. recon += recon_stride;
  45. }
  46. return total_sse;
  47. }
  48. #endif // CONFIG_VP9_HIGHBITDEPTH
  49. static uint64_t calc_plane_error(uint8_t *orig, int orig_stride, uint8_t *recon,
  50. int recon_stride, unsigned int cols,
  51. unsigned int rows) {
  52. unsigned int row, col;
  53. uint64_t total_sse = 0;
  54. int diff;
  55. if (orig == NULL || recon == NULL) {
  56. assert(0);
  57. return 0;
  58. }
  59. for (row = 0; row < rows; row++) {
  60. for (col = 0; col < cols; col++) {
  61. diff = orig[col] - recon[col];
  62. total_sse += diff * diff;
  63. }
  64. orig += orig_stride;
  65. recon += recon_stride;
  66. }
  67. return total_sse;
  68. }
  69. #define MAX_PSNR 100
  70. static double mse2psnr(double samples, double peak, double mse) {
  71. double psnr;
  72. if (mse > 0.0)
  73. psnr = 10.0 * log10(peak * peak * samples / mse);
  74. else
  75. psnr = MAX_PSNR; // Limit to prevent / 0
  76. if (psnr > MAX_PSNR) psnr = MAX_PSNR;
  77. return psnr;
  78. }
  79. typedef enum { RAW_YUV, Y4M } input_file_type;
  80. typedef struct input_file {
  81. FILE *file;
  82. input_file_type type;
  83. unsigned char *buf;
  84. y4m_input y4m;
  85. vpx_image_t img;
  86. int w;
  87. int h;
  88. int bit_depth;
  89. int frame_size;
  90. } input_file_t;
  91. // Open a file and determine if its y4m or raw. If y4m get the header.
  92. static int open_input_file(const char *file_name, input_file_t *input, int w,
  93. int h, int bit_depth) {
  94. char y4m_buf[4];
  95. input->w = w;
  96. input->h = h;
  97. input->bit_depth = bit_depth;
  98. input->type = RAW_YUV;
  99. input->buf = NULL;
  100. input->file = strcmp(file_name, "-") ? fopen(file_name, "rb") : stdin;
  101. if (input->file == NULL) return -1;
  102. if (fread(y4m_buf, 1, 4, input->file) != 4) return -1;
  103. if (memcmp(y4m_buf, "YUV4", 4) == 0) input->type = Y4M;
  104. switch (input->type) {
  105. case Y4M:
  106. y4m_input_open(&input->y4m, input->file, y4m_buf, 4, 0);
  107. input->w = input->y4m.pic_w;
  108. input->h = input->y4m.pic_h;
  109. input->bit_depth = input->y4m.bit_depth;
  110. // Y4M alloc's its own buf. Init this to avoid problems if we never
  111. // read frames.
  112. memset(&input->img, 0, sizeof(input->img));
  113. break;
  114. case RAW_YUV:
  115. fseek(input->file, 0, SEEK_SET);
  116. input->w = w;
  117. input->h = h;
  118. // handle odd frame sizes
  119. input->frame_size = w * h + ((w + 1) / 2) * ((h + 1) / 2) * 2;
  120. if (bit_depth > 8) {
  121. input->frame_size *= 2;
  122. }
  123. input->buf = malloc(input->frame_size);
  124. break;
  125. }
  126. return 0;
  127. }
  128. static void close_input_file(input_file_t *in) {
  129. if (in->file) fclose(in->file);
  130. if (in->type == Y4M) {
  131. vpx_img_free(&in->img);
  132. } else {
  133. free(in->buf);
  134. }
  135. }
  136. static size_t read_input_file(input_file_t *in, unsigned char **y,
  137. unsigned char **u, unsigned char **v, int bd) {
  138. size_t r1 = 0;
  139. switch (in->type) {
  140. case Y4M:
  141. r1 = y4m_input_fetch_frame(&in->y4m, in->file, &in->img);
  142. *y = in->img.planes[0];
  143. *u = in->img.planes[1];
  144. *v = in->img.planes[2];
  145. break;
  146. case RAW_YUV:
  147. if (bd < 9) {
  148. r1 = fread(in->buf, in->frame_size, 1, in->file);
  149. *y = in->buf;
  150. *u = in->buf + in->w * in->h;
  151. *v = *u + ((1 + in->w) / 2) * ((1 + in->h) / 2);
  152. } else {
  153. r1 = fread(in->buf, in->frame_size, 1, in->file);
  154. *y = in->buf;
  155. *u = in->buf + (in->w * in->h) * 2;
  156. *v = *u + 2 * ((1 + in->w) / 2) * ((1 + in->h) / 2);
  157. }
  158. break;
  159. }
  160. return r1;
  161. }
  162. static void ssim_parms_8x8(const uint8_t *s, int sp, const uint8_t *r, int rp,
  163. uint32_t *sum_s, uint32_t *sum_r, uint32_t *sum_sq_s,
  164. uint32_t *sum_sq_r, uint32_t *sum_sxr) {
  165. int i, j;
  166. if (s == NULL || r == NULL || sum_s == NULL || sum_r == NULL ||
  167. sum_sq_s == NULL || sum_sq_r == NULL || sum_sxr == NULL) {
  168. assert(0);
  169. return;
  170. }
  171. for (i = 0; i < 8; i++, s += sp, r += rp) {
  172. for (j = 0; j < 8; j++) {
  173. *sum_s += s[j];
  174. *sum_r += r[j];
  175. *sum_sq_s += s[j] * s[j];
  176. *sum_sq_r += r[j] * r[j];
  177. *sum_sxr += s[j] * r[j];
  178. }
  179. }
  180. }
  181. #if CONFIG_VP9_HIGHBITDEPTH
  182. static void highbd_ssim_parms_8x8(const uint16_t *s, int sp, const uint16_t *r,
  183. int rp, uint32_t *sum_s, uint32_t *sum_r,
  184. uint32_t *sum_sq_s, uint32_t *sum_sq_r,
  185. uint32_t *sum_sxr) {
  186. int i, j;
  187. if (s == NULL || r == NULL || sum_s == NULL || sum_r == NULL ||
  188. sum_sq_s == NULL || sum_sq_r == NULL || sum_sxr == NULL) {
  189. assert(0);
  190. return;
  191. }
  192. for (i = 0; i < 8; i++, s += sp, r += rp) {
  193. for (j = 0; j < 8; j++) {
  194. *sum_s += s[j];
  195. *sum_r += r[j];
  196. *sum_sq_s += s[j] * s[j];
  197. *sum_sq_r += r[j] * r[j];
  198. *sum_sxr += s[j] * r[j];
  199. }
  200. }
  201. }
  202. #endif // CONFIG_VP9_HIGHBITDEPTH
  203. static double similarity(uint32_t sum_s, uint32_t sum_r, uint32_t sum_sq_s,
  204. uint32_t sum_sq_r, uint32_t sum_sxr, int count,
  205. uint32_t bd) {
  206. double ssim_n, ssim_d;
  207. int64_t c1 = 0, c2 = 0;
  208. if (bd == 8) {
  209. // scale the constants by number of pixels
  210. c1 = (cc1 * count * count) >> 12;
  211. c2 = (cc2 * count * count) >> 12;
  212. } else if (bd == 10) {
  213. c1 = (cc1_10 * count * count) >> 12;
  214. c2 = (cc2_10 * count * count) >> 12;
  215. } else if (bd == 12) {
  216. c1 = (cc1_12 * count * count) >> 12;
  217. c2 = (cc2_12 * count * count) >> 12;
  218. } else {
  219. assert(0);
  220. }
  221. ssim_n = (2.0 * sum_s * sum_r + c1) *
  222. (2.0 * count * sum_sxr - 2.0 * sum_s * sum_r + c2);
  223. ssim_d = ((double)sum_s * sum_s + (double)sum_r * sum_r + c1) *
  224. ((double)count * sum_sq_s - (double)sum_s * sum_s +
  225. (double)count * sum_sq_r - (double)sum_r * sum_r + c2);
  226. return ssim_n / ssim_d;
  227. }
  228. static double ssim_8x8(const uint8_t *s, int sp, const uint8_t *r, int rp) {
  229. uint32_t sum_s = 0, sum_r = 0, sum_sq_s = 0, sum_sq_r = 0, sum_sxr = 0;
  230. ssim_parms_8x8(s, sp, r, rp, &sum_s, &sum_r, &sum_sq_s, &sum_sq_r, &sum_sxr);
  231. return similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64, 8);
  232. }
  233. #if CONFIG_VP9_HIGHBITDEPTH
  234. static double highbd_ssim_8x8(const uint16_t *s, int sp, const uint16_t *r,
  235. int rp, uint32_t bd) {
  236. uint32_t sum_s = 0, sum_r = 0, sum_sq_s = 0, sum_sq_r = 0, sum_sxr = 0;
  237. highbd_ssim_parms_8x8(s, sp, r, rp, &sum_s, &sum_r, &sum_sq_s, &sum_sq_r,
  238. &sum_sxr);
  239. return similarity(sum_s, sum_r, sum_sq_s, sum_sq_r, sum_sxr, 64, bd);
  240. }
  241. #endif // CONFIG_VP9_HIGHBITDEPTH
  242. // We are using a 8x8 moving window with starting location of each 8x8 window
  243. // on the 4x4 pixel grid. Such arrangement allows the windows to overlap
  244. // block boundaries to penalize blocking artifacts.
  245. static double ssim2(const uint8_t *img1, const uint8_t *img2, int stride_img1,
  246. int stride_img2, int width, int height) {
  247. int i, j;
  248. int samples = 0;
  249. double ssim_total = 0;
  250. // sample point start with each 4x4 location
  251. for (i = 0; i <= height - 8;
  252. i += 4, img1 += stride_img1 * 4, img2 += stride_img2 * 4) {
  253. for (j = 0; j <= width - 8; j += 4) {
  254. double v = ssim_8x8(img1 + j, stride_img1, img2 + j, stride_img2);
  255. ssim_total += v;
  256. samples++;
  257. }
  258. }
  259. ssim_total /= samples;
  260. return ssim_total;
  261. }
  262. #if CONFIG_VP9_HIGHBITDEPTH
  263. static double highbd_ssim2(const uint8_t *img1, const uint8_t *img2,
  264. int stride_img1, int stride_img2, int width,
  265. int height, uint32_t bd) {
  266. int i, j;
  267. int samples = 0;
  268. double ssim_total = 0;
  269. // sample point start with each 4x4 location
  270. for (i = 0; i <= height - 8;
  271. i += 4, img1 += stride_img1 * 4, img2 += stride_img2 * 4) {
  272. for (j = 0; j <= width - 8; j += 4) {
  273. double v =
  274. highbd_ssim_8x8(CONVERT_TO_SHORTPTR(img1 + j), stride_img1,
  275. CONVERT_TO_SHORTPTR(img2 + j), stride_img2, bd);
  276. ssim_total += v;
  277. samples++;
  278. }
  279. }
  280. ssim_total /= samples;
  281. return ssim_total;
  282. }
  283. #endif // CONFIG_VP9_HIGHBITDEPTH
  284. int main(int argc, char *argv[]) {
  285. FILE *framestats = NULL;
  286. int bit_depth = 8;
  287. int w = 0, h = 0, tl_skip = 0, tl_skips_remaining = 0;
  288. double ssimavg = 0, ssimyavg = 0, ssimuavg = 0, ssimvavg = 0;
  289. double psnrglb = 0, psnryglb = 0, psnruglb = 0, psnrvglb = 0;
  290. double psnravg = 0, psnryavg = 0, psnruavg = 0, psnrvavg = 0;
  291. double *ssimy = NULL, *ssimu = NULL, *ssimv = NULL;
  292. uint64_t *psnry = NULL, *psnru = NULL, *psnrv = NULL;
  293. size_t i, n_frames = 0, allocated_frames = 0;
  294. int return_value = 0;
  295. input_file_t in[2];
  296. double peak = 255.0;
  297. memset(in, 0, sizeof(in));
  298. if (argc < 2) {
  299. fprintf(stderr,
  300. "Usage: %s file1.{yuv|y4m} file2.{yuv|y4m}"
  301. "[WxH tl_skip={0,1,3} frame_stats_file bits]\n",
  302. argv[0]);
  303. return 1;
  304. }
  305. if (argc > 3) {
  306. sscanf(argv[3], "%dx%d", &w, &h);
  307. }
  308. if (argc > 6) {
  309. sscanf(argv[6], "%d", &bit_depth);
  310. }
  311. if (open_input_file(argv[1], &in[0], w, h, bit_depth) < 0) {
  312. fprintf(stderr, "File %s can't be opened or parsed!\n", argv[1]);
  313. goto clean_up;
  314. }
  315. if (w == 0 && h == 0) {
  316. // If a y4m is the first file and w, h is not set grab from first file.
  317. w = in[0].w;
  318. h = in[0].h;
  319. bit_depth = in[0].bit_depth;
  320. }
  321. if (bit_depth == 10) peak = 1023.0;
  322. if (bit_depth == 12) peak = 4095.0;
  323. if (open_input_file(argv[2], &in[1], w, h, bit_depth) < 0) {
  324. fprintf(stderr, "File %s can't be opened or parsed!\n", argv[2]);
  325. goto clean_up;
  326. }
  327. if (in[0].w != in[1].w || in[0].h != in[1].h || in[0].w != w ||
  328. in[0].h != h || w == 0 || h == 0) {
  329. fprintf(stderr,
  330. "Failing: Image dimensions don't match or are unspecified!\n");
  331. return_value = 1;
  332. goto clean_up;
  333. }
  334. if (in[0].bit_depth != in[1].bit_depth) {
  335. fprintf(stderr,
  336. "Failing: Image bit depths don't match or are unspecified!\n");
  337. return_value = 1;
  338. goto clean_up;
  339. }
  340. bit_depth = in[0].bit_depth;
  341. // Number of frames to skip from file1.yuv for every frame used. Normal
  342. // values 0, 1 and 3 correspond to TL2, TL1 and TL0 respectively for a 3TL
  343. // encoding in mode 10. 7 would be reasonable for comparing TL0 of a 4-layer
  344. // encoding.
  345. if (argc > 4) {
  346. sscanf(argv[4], "%d", &tl_skip);
  347. if (argc > 5) {
  348. framestats = fopen(argv[5], "w");
  349. if (!framestats) {
  350. fprintf(stderr, "Could not open \"%s\" for writing: %s\n", argv[5],
  351. strerror(errno));
  352. return_value = 1;
  353. goto clean_up;
  354. }
  355. }
  356. }
  357. while (1) {
  358. size_t r1, r2;
  359. unsigned char *y[2], *u[2], *v[2];
  360. r1 = read_input_file(&in[0], &y[0], &u[0], &v[0], bit_depth);
  361. if (r1) {
  362. // Reading parts of file1.yuv that were not used in temporal layer.
  363. if (tl_skips_remaining > 0) {
  364. --tl_skips_remaining;
  365. continue;
  366. }
  367. // Use frame, but skip |tl_skip| after it.
  368. tl_skips_remaining = tl_skip;
  369. }
  370. r2 = read_input_file(&in[1], &y[1], &u[1], &v[1], bit_depth);
  371. if (r1 && r2 && r1 != r2) {
  372. fprintf(stderr, "Failed to read data: %s [%d/%d]\n", strerror(errno),
  373. (int)r1, (int)r2);
  374. return_value = 1;
  375. goto clean_up;
  376. } else if (r1 == 0 || r2 == 0) {
  377. break;
  378. }
  379. #if CONFIG_VP9_HIGHBITDEPTH
  380. #define psnr_and_ssim(ssim, psnr, buf0, buf1, w, h) \
  381. if (bit_depth < 9) { \
  382. ssim = ssim2(buf0, buf1, w, w, w, h); \
  383. psnr = calc_plane_error(buf0, w, buf1, w, w, h); \
  384. } else { \
  385. ssim = highbd_ssim2(CONVERT_TO_BYTEPTR(buf0), CONVERT_TO_BYTEPTR(buf1), w, \
  386. w, w, h, bit_depth); \
  387. psnr = calc_plane_error16(CAST_TO_SHORTPTR(buf0), w, \
  388. CAST_TO_SHORTPTR(buf1), w, w, h); \
  389. }
  390. #else
  391. #define psnr_and_ssim(ssim, psnr, buf0, buf1, w, h) \
  392. ssim = ssim2(buf0, buf1, w, w, w, h); \
  393. psnr = calc_plane_error(buf0, w, buf1, w, w, h);
  394. #endif // CONFIG_VP9_HIGHBITDEPTH
  395. if (n_frames == allocated_frames) {
  396. allocated_frames = allocated_frames == 0 ? 1024 : allocated_frames * 2;
  397. ssimy = realloc(ssimy, allocated_frames * sizeof(*ssimy));
  398. ssimu = realloc(ssimu, allocated_frames * sizeof(*ssimu));
  399. ssimv = realloc(ssimv, allocated_frames * sizeof(*ssimv));
  400. psnry = realloc(psnry, allocated_frames * sizeof(*psnry));
  401. psnru = realloc(psnru, allocated_frames * sizeof(*psnru));
  402. psnrv = realloc(psnrv, allocated_frames * sizeof(*psnrv));
  403. }
  404. psnr_and_ssim(ssimy[n_frames], psnry[n_frames], y[0], y[1], w, h);
  405. psnr_and_ssim(ssimu[n_frames], psnru[n_frames], u[0], u[1], (w + 1) / 2,
  406. (h + 1) / 2);
  407. psnr_and_ssim(ssimv[n_frames], psnrv[n_frames], v[0], v[1], (w + 1) / 2,
  408. (h + 1) / 2);
  409. n_frames++;
  410. }
  411. if (framestats) {
  412. fprintf(framestats,
  413. "ssim,ssim-y,ssim-u,ssim-v,psnr,psnr-y,psnr-u,psnr-v\n");
  414. }
  415. for (i = 0; i < n_frames; ++i) {
  416. double frame_ssim;
  417. double frame_psnr, frame_psnry, frame_psnru, frame_psnrv;
  418. frame_ssim = 0.8 * ssimy[i] + 0.1 * (ssimu[i] + ssimv[i]);
  419. ssimavg += frame_ssim;
  420. ssimyavg += ssimy[i];
  421. ssimuavg += ssimu[i];
  422. ssimvavg += ssimv[i];
  423. frame_psnr =
  424. mse2psnr(w * h * 6 / 4, peak, (double)psnry[i] + psnru[i] + psnrv[i]);
  425. frame_psnry = mse2psnr(w * h * 4 / 4, peak, (double)psnry[i]);
  426. frame_psnru = mse2psnr(w * h * 1 / 4, peak, (double)psnru[i]);
  427. frame_psnrv = mse2psnr(w * h * 1 / 4, peak, (double)psnrv[i]);
  428. psnravg += frame_psnr;
  429. psnryavg += frame_psnry;
  430. psnruavg += frame_psnru;
  431. psnrvavg += frame_psnrv;
  432. psnryglb += psnry[i];
  433. psnruglb += psnru[i];
  434. psnrvglb += psnrv[i];
  435. if (framestats) {
  436. fprintf(framestats, "%lf,%lf,%lf,%lf,%lf,%lf,%lf,%lf\n", frame_ssim,
  437. ssimy[i], ssimu[i], ssimv[i], frame_psnr, frame_psnry,
  438. frame_psnru, frame_psnrv);
  439. }
  440. }
  441. ssimavg /= n_frames;
  442. ssimyavg /= n_frames;
  443. ssimuavg /= n_frames;
  444. ssimvavg /= n_frames;
  445. printf("VpxSSIM: %lf\n", 100 * pow(ssimavg, 8.0));
  446. printf("SSIM: %lf\n", ssimavg);
  447. printf("SSIM-Y: %lf\n", ssimyavg);
  448. printf("SSIM-U: %lf\n", ssimuavg);
  449. printf("SSIM-V: %lf\n", ssimvavg);
  450. puts("");
  451. psnravg /= n_frames;
  452. psnryavg /= n_frames;
  453. psnruavg /= n_frames;
  454. psnrvavg /= n_frames;
  455. printf("AvgPSNR: %lf\n", psnravg);
  456. printf("AvgPSNR-Y: %lf\n", psnryavg);
  457. printf("AvgPSNR-U: %lf\n", psnruavg);
  458. printf("AvgPSNR-V: %lf\n", psnrvavg);
  459. puts("");
  460. psnrglb = psnryglb + psnruglb + psnrvglb;
  461. psnrglb = mse2psnr((double)n_frames * w * h * 6 / 4, peak, psnrglb);
  462. psnryglb = mse2psnr((double)n_frames * w * h * 4 / 4, peak, psnryglb);
  463. psnruglb = mse2psnr((double)n_frames * w * h * 1 / 4, peak, psnruglb);
  464. psnrvglb = mse2psnr((double)n_frames * w * h * 1 / 4, peak, psnrvglb);
  465. printf("GlbPSNR: %lf\n", psnrglb);
  466. printf("GlbPSNR-Y: %lf\n", psnryglb);
  467. printf("GlbPSNR-U: %lf\n", psnruglb);
  468. printf("GlbPSNR-V: %lf\n", psnrvglb);
  469. puts("");
  470. printf("Nframes: %d\n", (int)n_frames);
  471. clean_up:
  472. close_input_file(&in[0]);
  473. close_input_file(&in[1]);
  474. if (framestats) fclose(framestats);
  475. free(ssimy);
  476. free(ssimu);
  477. free(ssimv);
  478. free(psnry);
  479. free(psnru);
  480. free(psnrv);
  481. return return_value;
  482. }