1 /* 2 * Copyright 2013 The LibYuv 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 11 // Get PSNR or SSIM for video sequence. Assuming RAW 4:2:0 Y:Cb:Cr format 12 // To build: g++ -O3 -o psnr psnr.cc ssim.cc psnr_main.cc 13 // or VisualC: cl /Ox psnr.cc ssim.cc psnr_main.cc 14 // 15 // To enable OpenMP and SSE2 16 // gcc: g++ -msse2 -O3 -fopenmp -o psnr psnr.cc ssim.cc psnr_main.cc 17 // vc: cl /arch:SSE2 /Ox /openmp psnr.cc ssim.cc psnr_main.cc 18 // 19 // Usage: psnr org_seq rec_seq -s width height [-skip skip_org skip_rec] 20 21 #ifndef _CRT_SECURE_NO_WARNINGS 22 #define _CRT_SECURE_NO_WARNINGS 23 #endif 24 25 #include <stddef.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #ifdef _OPENMP 30 #include <omp.h> 31 #endif 32 33 #include "./psnr.h" 34 #include "./ssim.h" 35 36 struct metric { 37 double y, u, v, all; 38 double min_y, min_u, min_v, min_all; 39 double global_y, global_u, global_v, global_all; 40 int min_frame; 41 }; 42 43 // options 44 bool verbose = false; 45 bool quiet = false; 46 bool show_name = false; 47 bool do_swap_uv = false; 48 bool do_psnr = false; 49 bool do_ssim = false; 50 bool do_mse = false; 51 bool do_lssim = false; 52 int image_width = 0, image_height = 0; 53 int fileindex_org = 0; // argv argument contains the source file name. 54 int fileindex_rec = 0; // argv argument contains the destination file name. 55 int num_rec = 0; 56 int num_skip_org = 0; 57 int num_skip_rec = 0; 58 int num_frames = 0; 59 #ifdef _OPENMP 60 int num_threads = 0; 61 #endif 62 63 // Parse PYUV format. ie name.1920x800_24Hz_P420.yuv 64 bool ExtractResolutionFromFilename(const char* name, 65 int* width_ptr, 66 int* height_ptr) { 67 // Isolate the .width_height. section of the filename by searching for a 68 // dot or underscore followed by a digit. 69 for (int i = 0; name[i]; ++i) { 70 if ((name[i] == '.' || name[i] == '_') && 71 name[i + 1] >= '0' && name[i + 1] <= '9') { 72 int n = sscanf(name + i + 1, "%dx%d", width_ptr, height_ptr); // NOLINT 73 if (2 == n) { 74 return true; 75 } 76 } 77 } 78 return false; 79 } 80 81 // Scale Y channel from 16..240 to 0..255. 82 // This can be useful when comparing codecs that are inconsistant about Y 83 uint8 ScaleY(uint8 y) { 84 int ny = (y - 16) * 256 / 224; 85 if (ny < 0) ny = 0; 86 if (ny > 255) ny = 255; 87 return static_cast<uint8>(ny); 88 } 89 90 // MSE = Mean Square Error 91 double GetMSE(double sse, double size) { 92 return sse / size; 93 } 94 95 void PrintHelp(const char * program) { 96 printf("%s [-options] org_seq rec_seq [rec_seq2.. etc]\n", program); 97 printf("options:\n"); 98 printf(" -s <width> <height> .... specify YUV size, mandatory if none of the " 99 "sequences have the\n"); 100 printf(" resolution embedded in their filename (ie. " 101 "name.1920x800_24Hz_P420.yuv)\n"); 102 printf(" -psnr .................. compute PSNR (default)\n"); 103 printf(" -ssim .................. compute SSIM\n"); 104 printf(" -mse ................... compute MSE\n"); 105 printf(" -swap .................. Swap U and V plane\n"); 106 printf(" -skip <org> <rec> ...... Number of frame to skip of org and rec\n"); 107 printf(" -frames <num> .......... Number of frames to compare\n"); 108 #ifdef _OPENMP 109 printf(" -t <num> ............... Number of threads\n"); 110 #endif 111 printf(" -n ..................... Show file name\n"); 112 printf(" -v ..................... verbose++\n"); 113 printf(" -q ..................... quiet\n"); 114 printf(" -h ..................... this help\n"); 115 exit(0); 116 } 117 118 void ParseOptions(int argc, const char* argv[]) { 119 if (argc <= 1) PrintHelp(argv[0]); 120 for (int c = 1; c < argc; ++c) { 121 if (!strcmp(argv[c], "-v")) { 122 verbose = true; 123 } else if (!strcmp(argv[c], "-q")) { 124 quiet = true; 125 } else if (!strcmp(argv[c], "-n")) { 126 show_name = true; 127 } else if (!strcmp(argv[c], "-psnr")) { 128 do_psnr = true; 129 } else if (!strcmp(argv[c], "-mse")) { 130 do_mse = true; 131 } else if (!strcmp(argv[c], "-ssim")) { 132 do_ssim = true; 133 } else if (!strcmp(argv[c], "-lssim")) { 134 do_ssim = true; 135 do_lssim = true; 136 } else if (!strcmp(argv[c], "-swap")) { 137 do_swap_uv = true; 138 } else if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) { 139 PrintHelp(argv[0]); 140 } else if (!strcmp(argv[c], "-s") && c + 2 < argc) { 141 image_width = atoi(argv[++c]); // NOLINT 142 image_height = atoi(argv[++c]); // NOLINT 143 } else if (!strcmp(argv[c], "-skip") && c + 2 < argc) { 144 num_skip_org = atoi(argv[++c]); // NOLINT 145 num_skip_rec = atoi(argv[++c]); // NOLINT 146 } else if (!strcmp(argv[c], "-frames") && c + 1 < argc) { 147 num_frames = atoi(argv[++c]); // NOLINT 148 #ifdef _OPENMP 149 } else if (!strcmp(argv[c], "-t") && c + 1 < argc) { 150 num_threads = atoi(argv[++c]); // NOLINT 151 #endif 152 } else if (argv[c][0] == '-') { 153 fprintf(stderr, "Unknown option. %s\n", argv[c]); 154 } else if (fileindex_org == 0) { 155 fileindex_org = c; 156 } else if (fileindex_rec == 0) { 157 fileindex_rec = c; 158 num_rec = 1; 159 } else { 160 ++num_rec; 161 } 162 } 163 if (fileindex_org == 0 || fileindex_rec == 0) { 164 fprintf(stderr, "Missing filenames\n"); 165 PrintHelp(argv[0]); 166 } 167 if (num_skip_org < 0 || num_skip_rec < 0) { 168 fprintf(stderr, "Skipped frames incorrect\n"); 169 PrintHelp(argv[0]); 170 } 171 if (num_frames < 0) { 172 fprintf(stderr, "Number of frames incorrect\n"); 173 PrintHelp(argv[0]); 174 } 175 if (image_width == 0 || image_height == 0) { 176 int org_width, org_height; 177 int rec_width, rec_height; 178 bool org_res_avail = ExtractResolutionFromFilename(argv[fileindex_org], 179 &org_width, 180 &org_height); 181 bool rec_res_avail = ExtractResolutionFromFilename(argv[fileindex_rec], 182 &rec_width, 183 &rec_height); 184 if (org_res_avail) { 185 if (rec_res_avail) { 186 if ((org_width == rec_width) && (org_height == rec_height)) { 187 image_width = org_width; 188 image_height = org_height; 189 } else { 190 fprintf(stderr, "Sequences have different resolutions.\n"); 191 PrintHelp(argv[0]); 192 } 193 } else { 194 image_width = org_width; 195 image_height = org_height; 196 } 197 } else if (rec_res_avail) { 198 image_width = rec_width; 199 image_height = rec_height; 200 } else { 201 fprintf(stderr, "Missing dimensions.\n"); 202 PrintHelp(argv[0]); 203 } 204 } 205 } 206 207 bool UpdateMetrics(uint8* ch_org, uint8* ch_rec, 208 const int y_size, const int uv_size, const size_t total_size, 209 int number_of_frames, 210 metric* cur_distortion_psnr, 211 metric* distorted_frame, bool do_psnr) { 212 const int uv_offset = (do_swap_uv ? uv_size : 0); 213 const uint8* const u_org = ch_org + y_size + uv_offset; 214 const uint8* const u_rec = ch_rec + y_size; 215 const uint8* const v_org = ch_org + y_size + (uv_size - uv_offset); 216 const uint8* const v_rec = ch_rec + y_size + uv_size; 217 if (do_psnr) { 218 double y_err = ComputeSumSquareError(ch_org, ch_rec, y_size); 219 double u_err = ComputeSumSquareError(u_org, u_rec, uv_size); 220 double v_err = ComputeSumSquareError(v_org, v_rec, uv_size); 221 const double total_err = y_err + u_err + v_err; 222 cur_distortion_psnr->global_y += y_err; 223 cur_distortion_psnr->global_u += u_err; 224 cur_distortion_psnr->global_v += v_err; 225 cur_distortion_psnr->global_all += total_err; 226 distorted_frame->y = ComputePSNR(y_err, static_cast<double>(y_size)); 227 distorted_frame->u = ComputePSNR(u_err, static_cast<double>(uv_size)); 228 distorted_frame->v = ComputePSNR(v_err, static_cast<double>(uv_size)); 229 distorted_frame->all = ComputePSNR(total_err, 230 static_cast<double>(total_size)); 231 } else { 232 distorted_frame->y = CalcSSIM(ch_org, ch_rec, image_width, image_height); 233 distorted_frame->u = CalcSSIM(u_org, u_rec, image_width / 2, 234 image_height / 2); 235 distorted_frame->v = CalcSSIM(v_org, v_rec, image_width / 2, 236 image_height / 2); 237 distorted_frame->all = 238 (distorted_frame->y + distorted_frame->u + distorted_frame->v) 239 / total_size; 240 distorted_frame->y /= y_size; 241 distorted_frame->u /= uv_size; 242 distorted_frame->v /= uv_size; 243 244 if (do_lssim) { 245 distorted_frame->all = CalcLSSIM(distorted_frame->all); 246 distorted_frame->y = CalcLSSIM(distorted_frame->y); 247 distorted_frame->u = CalcLSSIM(distorted_frame->u); 248 distorted_frame->v = CalcLSSIM(distorted_frame->v); 249 } 250 } 251 252 cur_distortion_psnr->y += distorted_frame->y; 253 cur_distortion_psnr->u += distorted_frame->u; 254 cur_distortion_psnr->v += distorted_frame->v; 255 cur_distortion_psnr->all += distorted_frame->all; 256 257 bool ismin = false; 258 if (distorted_frame->y < cur_distortion_psnr->min_y) 259 cur_distortion_psnr->min_y = distorted_frame->y; 260 if (distorted_frame->u < cur_distortion_psnr->min_u) 261 cur_distortion_psnr->min_u = distorted_frame->u; 262 if (distorted_frame->v < cur_distortion_psnr->min_v) 263 cur_distortion_psnr->min_v = distorted_frame->v; 264 if (distorted_frame->all < cur_distortion_psnr->min_all) { 265 cur_distortion_psnr->min_all = distorted_frame->all; 266 cur_distortion_psnr->min_frame = number_of_frames; 267 ismin = true; 268 } 269 return ismin; 270 } 271 272 int main(int argc, const char* argv[]) { 273 ParseOptions(argc, argv); 274 if (!do_psnr && !do_ssim) { 275 do_psnr = true; 276 } 277 278 #ifdef _OPENMP 279 if (num_threads) { 280 omp_set_num_threads(num_threads); 281 } 282 if (verbose) { 283 printf("OpenMP %d procs\n", omp_get_num_procs()); 284 } 285 #endif 286 // Open original file (first file argument) 287 FILE* const file_org = fopen(argv[fileindex_org], "rb"); 288 if (file_org == NULL) { 289 fprintf(stderr, "Cannot open %s\n", argv[fileindex_org]); 290 exit(1); 291 } 292 293 // Open all files to compare to 294 FILE** file_rec = new FILE* [num_rec]; 295 memset(file_rec, 0, num_rec * sizeof(FILE*)); // NOLINT 296 for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { 297 file_rec[cur_rec] = fopen(argv[fileindex_rec + cur_rec], "rb"); 298 if (file_rec[cur_rec] == NULL) { 299 fprintf(stderr, "Cannot open %s\n", argv[fileindex_rec + cur_rec]); 300 fclose(file_org); 301 for (int i = 0; i < cur_rec; ++i) { 302 fclose(file_rec[i]); 303 } 304 delete[] file_rec; 305 exit(1); 306 } 307 } 308 309 const int y_size = image_width * image_height; 310 const int uv_size = ((image_width + 1) / 2) * ((image_height + 1) / 2); 311 const size_t total_size = y_size + 2 * uv_size; // NOLINT 312 #if defined(_MSC_VER) 313 _fseeki64(file_org, 314 static_cast<__int64>(num_skip_org) * 315 static_cast<__int64>(total_size), SEEK_SET); 316 #else 317 fseek(file_org, num_skip_org * total_size, SEEK_SET); 318 #endif 319 for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { 320 #if defined(_MSC_VER) 321 _fseeki64(file_rec[cur_rec], 322 static_cast<__int64>(num_skip_rec) * 323 static_cast<__int64>(total_size), 324 SEEK_SET); 325 #else 326 fseek(file_rec[cur_rec], num_skip_rec * total_size, SEEK_SET); 327 #endif 328 } 329 330 uint8* const ch_org = new uint8[total_size]; 331 uint8* const ch_rec = new uint8[total_size]; 332 if (ch_org == NULL || ch_rec == NULL) { 333 fprintf(stderr, "No memory available\n"); 334 fclose(file_org); 335 for (int i = 0; i < num_rec; ++i) { 336 fclose(file_rec[i]); 337 } 338 delete[] ch_org; 339 delete[] ch_rec; 340 delete[] file_rec; 341 exit(1); 342 } 343 344 metric* const distortion_psnr = new metric[num_rec]; 345 metric* const distortion_ssim = new metric[num_rec]; 346 for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { 347 metric* cur_distortion_psnr = &distortion_psnr[cur_rec]; 348 cur_distortion_psnr->y = 0.0; 349 cur_distortion_psnr->u = 0.0; 350 cur_distortion_psnr->v = 0.0; 351 cur_distortion_psnr->all = 0.0; 352 cur_distortion_psnr->min_y = kMaxPSNR; 353 cur_distortion_psnr->min_u = kMaxPSNR; 354 cur_distortion_psnr->min_v = kMaxPSNR; 355 cur_distortion_psnr->min_all = kMaxPSNR; 356 cur_distortion_psnr->min_frame = 0; 357 cur_distortion_psnr->global_y = 0.0; 358 cur_distortion_psnr->global_u = 0.0; 359 cur_distortion_psnr->global_v = 0.0; 360 cur_distortion_psnr->global_all = 0.0; 361 distortion_ssim[cur_rec] = cur_distortion_psnr[cur_rec]; 362 } 363 364 if (verbose) { 365 printf("Size: %dx%d\n", image_width, image_height); 366 } 367 368 if (!quiet) { 369 printf("Frame"); 370 if (do_psnr) { 371 printf("\t PSNR-Y \t PSNR-U \t PSNR-V \t PSNR-All \t Frame"); 372 } 373 if (do_ssim) { 374 printf("\t SSIM-Y\t SSIM-U\t SSIM-V\t SSIM-All\t Frame"); 375 } 376 if (show_name) { 377 printf("\tName\n"); 378 } else { 379 printf("\n"); 380 } 381 } 382 383 int number_of_frames; 384 for (number_of_frames = 0; ; ++number_of_frames) { 385 if (num_frames && number_of_frames >= num_frames) 386 break; 387 388 size_t bytes_org = fread(ch_org, sizeof(uint8), total_size, file_org); 389 if (bytes_org < total_size) 390 break; 391 392 for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { 393 size_t bytes_rec = fread(ch_rec, sizeof(uint8), 394 total_size, file_rec[cur_rec]); 395 if (bytes_rec < total_size) 396 break; 397 398 if (verbose) { 399 printf("%5d", number_of_frames); 400 } 401 if (do_psnr) { 402 metric distorted_frame; 403 metric* cur_distortion_psnr = &distortion_psnr[cur_rec]; 404 bool ismin = UpdateMetrics(ch_org, ch_rec, 405 y_size, uv_size, total_size, 406 number_of_frames, 407 cur_distortion_psnr, 408 &distorted_frame, true); 409 if (verbose) { 410 printf("\t%10.6f", distorted_frame.y); 411 printf("\t%10.6f", distorted_frame.u); 412 printf("\t%10.6f", distorted_frame.v); 413 printf("\t%10.6f", distorted_frame.all); 414 printf("\t%5s", ismin ? "min" : ""); 415 } 416 } 417 if (do_ssim) { 418 metric distorted_frame; 419 metric* cur_distortion_ssim = &distortion_ssim[cur_rec]; 420 bool ismin = UpdateMetrics(ch_org, ch_rec, 421 y_size, uv_size, total_size, 422 number_of_frames, 423 cur_distortion_ssim, 424 &distorted_frame, false); 425 if (verbose) { 426 printf("\t%10.6f", distorted_frame.y); 427 printf("\t%10.6f", distorted_frame.u); 428 printf("\t%10.6f", distorted_frame.v); 429 printf("\t%10.6f", distorted_frame.all); 430 printf("\t%5s", ismin ? "min" : ""); 431 } 432 } 433 if (verbose) { 434 if (show_name) { 435 printf("\t%s", argv[fileindex_rec + cur_rec]); 436 } 437 printf("\n"); 438 } 439 } 440 } 441 442 // Final PSNR computation. 443 for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { 444 metric* cur_distortion_psnr = &distortion_psnr[cur_rec]; 445 metric* cur_distortion_ssim = &distortion_ssim[cur_rec]; 446 if (number_of_frames > 0) { 447 const double norm = 1. / static_cast<double>(number_of_frames); 448 cur_distortion_psnr->y *= norm; 449 cur_distortion_psnr->u *= norm; 450 cur_distortion_psnr->v *= norm; 451 cur_distortion_psnr->all *= norm; 452 cur_distortion_ssim->y *= norm; 453 cur_distortion_ssim->u *= norm; 454 cur_distortion_ssim->v *= norm; 455 cur_distortion_ssim->all *= norm; 456 } 457 458 if (do_psnr) { 459 const double global_psnr_y = ComputePSNR( 460 cur_distortion_psnr->global_y, 461 static_cast<double>(y_size) * number_of_frames); 462 const double global_psnr_u = ComputePSNR( 463 cur_distortion_psnr->global_u, 464 static_cast<double>(uv_size) * number_of_frames); 465 const double global_psnr_v = ComputePSNR( 466 cur_distortion_psnr->global_v, 467 static_cast<double>(uv_size) * number_of_frames); 468 const double global_psnr_all = ComputePSNR( 469 cur_distortion_psnr->global_all, 470 static_cast<double>(total_size) * number_of_frames); 471 printf("Global:\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", 472 global_psnr_y, 473 global_psnr_u, 474 global_psnr_v, 475 global_psnr_all, 476 number_of_frames); 477 if (show_name) { 478 printf("\t%s", argv[fileindex_rec + cur_rec]); 479 } 480 printf("\n"); 481 } 482 483 if (!quiet) { 484 printf("Avg:"); 485 if (do_psnr) { 486 printf("\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", 487 cur_distortion_psnr->y, 488 cur_distortion_psnr->u, 489 cur_distortion_psnr->v, 490 cur_distortion_psnr->all, 491 number_of_frames); 492 } 493 if (do_ssim) { 494 printf("\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", 495 cur_distortion_ssim->y, 496 cur_distortion_ssim->u, 497 cur_distortion_ssim->v, 498 cur_distortion_ssim->all, 499 number_of_frames); 500 } 501 if (show_name) { 502 printf("\t%s", argv[fileindex_rec + cur_rec]); 503 } 504 printf("\n"); 505 } 506 if (!quiet) { 507 printf("Min:"); 508 if (do_psnr) { 509 printf("\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", 510 cur_distortion_psnr->min_y, 511 cur_distortion_psnr->min_u, 512 cur_distortion_psnr->min_v, 513 cur_distortion_psnr->min_all, 514 cur_distortion_psnr->min_frame); 515 } 516 if (do_ssim) { 517 printf("\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", 518 cur_distortion_ssim->min_y, 519 cur_distortion_ssim->min_u, 520 cur_distortion_ssim->min_v, 521 cur_distortion_ssim->min_all, 522 cur_distortion_ssim->min_frame); 523 } 524 if (show_name) { 525 printf("\t%s", argv[fileindex_rec + cur_rec]); 526 } 527 printf("\n"); 528 } 529 530 if (do_mse) { 531 double global_mse_y = GetMSE(cur_distortion_psnr->global_y, 532 static_cast<double>(y_size) * number_of_frames); 533 double global_mse_u = GetMSE(cur_distortion_psnr->global_u, 534 static_cast<double>(uv_size) * number_of_frames); 535 double global_mse_v = GetMSE(cur_distortion_psnr->global_v, 536 static_cast<double>(uv_size) * number_of_frames); 537 double global_mse_all = GetMSE(cur_distortion_psnr->global_all, 538 static_cast<double>(total_size) * number_of_frames); 539 printf("MSE:\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", 540 global_mse_y, 541 global_mse_u, 542 global_mse_v, 543 global_mse_all, 544 number_of_frames); 545 if (show_name) { 546 printf("\t%s", argv[fileindex_rec + cur_rec]); 547 } 548 printf("\n"); 549 } 550 } 551 fclose(file_org); 552 for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { 553 fclose(file_rec[cur_rec]); 554 } 555 delete[] distortion_psnr; 556 delete[] distortion_ssim; 557 delete[] ch_org; 558 delete[] ch_rec; 559 delete[] file_rec; 560 return 0; 561 } 562