Home | History | Annotate | Download | only in util
      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