/* * Copyright 2013 The LibYuv Project Authors. All rights reserved. * * Use of this source code is governed by a BSD-style license * that can be found in the LICENSE file in the root of the source * tree. An additional intellectual property rights grant can be found * in the file PATENTS. All contributing project authors may * be found in the AUTHORS file in the root of the source tree. */ // Get PSNR or SSIM for video sequence. Assuming RAW 4:2:0 Y:Cb:Cr format // To build: g++ -O3 -o psnr psnr.cc ssim.cc psnr_main.cc // or VisualC: cl /Ox psnr.cc ssim.cc psnr_main.cc // // To enable OpenMP and SSE2 // gcc: g++ -msse2 -O3 -fopenmp -o psnr psnr.cc ssim.cc psnr_main.cc // vc: cl /arch:SSE2 /Ox /openmp psnr.cc ssim.cc psnr_main.cc // // Usage: psnr org_seq rec_seq -s width height [-skip skip_org skip_rec] #ifndef _CRT_SECURE_NO_WARNINGS #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include #ifdef _OPENMP #include #endif #include "./psnr.h" #include "./ssim.h" #ifdef HAVE_JPEG #include "libyuv/compare.h" #include "libyuv/convert.h" #endif struct metric { double y, u, v, all; double min_y, min_u, min_v, min_all; double global_y, global_u, global_v, global_all; int min_frame; }; // options bool verbose = false; bool quiet = false; bool show_name = false; bool do_swap_uv = false; bool do_psnr = false; bool do_ssim = false; bool do_mse = false; bool do_lssim = false; int image_width = 0, image_height = 0; int fileindex_org = 0; // argv argument contains the source file name. int fileindex_rec = 0; // argv argument contains the destination file name. int num_rec = 0; int num_skip_org = 0; int num_skip_rec = 0; int num_frames = 0; #ifdef _OPENMP int num_threads = 0; #endif // Parse PYUV format. ie name.1920x800_24Hz_P420.yuv bool ExtractResolutionFromFilename(const char* name, int* width_ptr, int* height_ptr) { // Isolate the .width_height. section of the filename by searching for a // dot or underscore followed by a digit. for (int i = 0; name[i]; ++i) { if ((name[i] == '.' || name[i] == '_') && name[i + 1] >= '0' && name[i + 1] <= '9') { int n = sscanf(name + i + 1, "%dx%d", width_ptr, height_ptr); // NOLINT if (2 == n) { return true; } } } #ifdef HAVE_JPEG // Try parsing file as a jpeg. FILE* const file_org = fopen(name, "rb"); if (file_org == NULL) { fprintf(stderr, "Cannot open %s\n", name); return false; } fseek(file_org, 0, SEEK_END); size_t total_size = ftell(file_org); fseek(file_org, 0, SEEK_SET); uint8_t* const ch_org = new uint8_t[total_size]; memset(ch_org, 0, total_size); size_t bytes_org = fread(ch_org, sizeof(uint8_t), total_size, file_org); fclose(file_org); if (bytes_org == total_size) { if (0 == libyuv::MJPGSize(ch_org, total_size, width_ptr, height_ptr)) { delete[] ch_org; return true; } } delete[] ch_org; #endif // HAVE_JPEG return false; } // Scale Y channel from 16..240 to 0..255. // This can be useful when comparing codecs that are inconsistant about Y uint8_t ScaleY(uint8_t y) { int ny = (y - 16) * 256 / 224; if (ny < 0) { ny = 0; } if (ny > 255) { ny = 255; } return static_cast(ny); } // MSE = Mean Square Error double GetMSE(double sse, double size) { return sse / size; } void PrintHelp(const char* program) { printf("%s [-options] org_seq rec_seq [rec_seq2.. etc]\n", program); #ifdef HAVE_JPEG printf("jpeg or raw YUV 420 supported.\n"); #endif printf("options:\n"); printf( " -s .... specify YUV size, mandatory if none of the " "sequences have the\n"); printf( " resolution embedded in their filename (ie. " "name.1920x800_24Hz_P420.yuv)\n"); printf(" -psnr .................. compute PSNR (default)\n"); printf(" -ssim .................. compute SSIM\n"); printf(" -mse ................... compute MSE\n"); printf(" -swap .................. Swap U and V plane\n"); printf(" -skip ...... Number of frame to skip of org and rec\n"); printf(" -frames .......... Number of frames to compare\n"); #ifdef _OPENMP printf(" -t ............... Number of threads\n"); #endif printf(" -n ..................... Show file name\n"); printf(" -v ..................... verbose++\n"); printf(" -q ..................... quiet\n"); printf(" -h ..................... this help\n"); exit(0); } void ParseOptions(int argc, const char* argv[]) { if (argc <= 1) { PrintHelp(argv[0]); } for (int c = 1; c < argc; ++c) { if (!strcmp(argv[c], "-v")) { verbose = true; } else if (!strcmp(argv[c], "-q")) { quiet = true; } else if (!strcmp(argv[c], "-n")) { show_name = true; } else if (!strcmp(argv[c], "-psnr")) { do_psnr = true; } else if (!strcmp(argv[c], "-mse")) { do_mse = true; } else if (!strcmp(argv[c], "-ssim")) { do_ssim = true; } else if (!strcmp(argv[c], "-lssim")) { do_ssim = true; do_lssim = true; } else if (!strcmp(argv[c], "-swap")) { do_swap_uv = true; } else if (!strcmp(argv[c], "-h") || !strcmp(argv[c], "-help")) { PrintHelp(argv[0]); } else if (!strcmp(argv[c], "-s") && c + 2 < argc) { image_width = atoi(argv[++c]); // NOLINT image_height = atoi(argv[++c]); // NOLINT } else if (!strcmp(argv[c], "-skip") && c + 2 < argc) { num_skip_org = atoi(argv[++c]); // NOLINT num_skip_rec = atoi(argv[++c]); // NOLINT } else if (!strcmp(argv[c], "-frames") && c + 1 < argc) { num_frames = atoi(argv[++c]); // NOLINT #ifdef _OPENMP } else if (!strcmp(argv[c], "-t") && c + 1 < argc) { num_threads = atoi(argv[++c]); // NOLINT #endif } else if (argv[c][0] == '-') { fprintf(stderr, "Unknown option. %s\n", argv[c]); } else if (fileindex_org == 0) { fileindex_org = c; } else if (fileindex_rec == 0) { fileindex_rec = c; num_rec = 1; } else { ++num_rec; } } if (fileindex_org == 0 || fileindex_rec == 0) { fprintf(stderr, "Missing filenames\n"); PrintHelp(argv[0]); } if (num_skip_org < 0 || num_skip_rec < 0) { fprintf(stderr, "Skipped frames incorrect\n"); PrintHelp(argv[0]); } if (num_frames < 0) { fprintf(stderr, "Number of frames incorrect\n"); PrintHelp(argv[0]); } if (image_width == 0 || image_height == 0) { int org_width, org_height; int rec_width, rec_height; bool org_res_avail = ExtractResolutionFromFilename(argv[fileindex_org], &org_width, &org_height); bool rec_res_avail = ExtractResolutionFromFilename(argv[fileindex_rec], &rec_width, &rec_height); if (org_res_avail) { if (rec_res_avail) { if ((org_width == rec_width) && (org_height == rec_height)) { image_width = org_width; image_height = org_height; } else { fprintf(stderr, "Sequences have different resolutions.\n"); PrintHelp(argv[0]); } } else { image_width = org_width; image_height = org_height; } } else if (rec_res_avail) { image_width = rec_width; image_height = rec_height; } else { fprintf(stderr, "Missing dimensions.\n"); PrintHelp(argv[0]); } } } bool UpdateMetrics(uint8_t* ch_org, uint8_t* ch_rec, const int y_size, const int uv_size, const size_t total_size, int number_of_frames, metric* cur_distortion_psnr, metric* distorted_frame, bool do_psnr) { const int uv_offset = (do_swap_uv ? uv_size : 0); const uint8_t* const u_org = ch_org + y_size + uv_offset; const uint8_t* const u_rec = ch_rec + y_size; const uint8_t* const v_org = ch_org + y_size + (uv_size - uv_offset); const uint8_t* const v_rec = ch_rec + y_size + uv_size; if (do_psnr) { #ifdef HAVE_JPEG double y_err = static_cast( libyuv::ComputeSumSquareError(ch_org, ch_rec, y_size)); double u_err = static_cast( libyuv::ComputeSumSquareError(u_org, u_rec, uv_size)); double v_err = static_cast( libyuv::ComputeSumSquareError(v_org, v_rec, uv_size)); #else double y_err = ComputeSumSquareError(ch_org, ch_rec, y_size); double u_err = ComputeSumSquareError(u_org, u_rec, uv_size); double v_err = ComputeSumSquareError(v_org, v_rec, uv_size); #endif const double total_err = y_err + u_err + v_err; cur_distortion_psnr->global_y += y_err; cur_distortion_psnr->global_u += u_err; cur_distortion_psnr->global_v += v_err; cur_distortion_psnr->global_all += total_err; distorted_frame->y = ComputePSNR(y_err, static_cast(y_size)); distorted_frame->u = ComputePSNR(u_err, static_cast(uv_size)); distorted_frame->v = ComputePSNR(v_err, static_cast(uv_size)); distorted_frame->all = ComputePSNR(total_err, static_cast(total_size)); } else { distorted_frame->y = CalcSSIM(ch_org, ch_rec, image_width, image_height); distorted_frame->u = CalcSSIM(u_org, u_rec, (image_width + 1) / 2, (image_height + 1) / 2); distorted_frame->v = CalcSSIM(v_org, v_rec, (image_width + 1) / 2, (image_height + 1) / 2); distorted_frame->all = (distorted_frame->y + distorted_frame->u + distorted_frame->v) / total_size; distorted_frame->y /= y_size; distorted_frame->u /= uv_size; distorted_frame->v /= uv_size; if (do_lssim) { distorted_frame->all = CalcLSSIM(distorted_frame->all); distorted_frame->y = CalcLSSIM(distorted_frame->y); distorted_frame->u = CalcLSSIM(distorted_frame->u); distorted_frame->v = CalcLSSIM(distorted_frame->v); } } cur_distortion_psnr->y += distorted_frame->y; cur_distortion_psnr->u += distorted_frame->u; cur_distortion_psnr->v += distorted_frame->v; cur_distortion_psnr->all += distorted_frame->all; bool ismin = false; if (distorted_frame->y < cur_distortion_psnr->min_y) { cur_distortion_psnr->min_y = distorted_frame->y; } if (distorted_frame->u < cur_distortion_psnr->min_u) { cur_distortion_psnr->min_u = distorted_frame->u; } if (distorted_frame->v < cur_distortion_psnr->min_v) { cur_distortion_psnr->min_v = distorted_frame->v; } if (distorted_frame->all < cur_distortion_psnr->min_all) { cur_distortion_psnr->min_all = distorted_frame->all; cur_distortion_psnr->min_frame = number_of_frames; ismin = true; } return ismin; } int main(int argc, const char* argv[]) { ParseOptions(argc, argv); if (!do_psnr && !do_ssim) { do_psnr = true; } #ifdef _OPENMP if (num_threads) { omp_set_num_threads(num_threads); } if (verbose) { printf("OpenMP %d procs\n", omp_get_num_procs()); } #endif // Open original file (first file argument) FILE* const file_org = fopen(argv[fileindex_org], "rb"); if (file_org == NULL) { fprintf(stderr, "Cannot open %s\n", argv[fileindex_org]); exit(1); } // Open all files to compare to FILE** file_rec = new FILE*[num_rec]; memset(file_rec, 0, num_rec * sizeof(FILE*)); // NOLINT for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { file_rec[cur_rec] = fopen(argv[fileindex_rec + cur_rec], "rb"); if (file_rec[cur_rec] == NULL) { fprintf(stderr, "Cannot open %s\n", argv[fileindex_rec + cur_rec]); fclose(file_org); for (int i = 0; i < cur_rec; ++i) { fclose(file_rec[i]); } delete[] file_rec; exit(1); } } const int y_size = image_width * image_height; const int uv_size = ((image_width + 1) / 2) * ((image_height + 1) / 2); const size_t total_size = y_size + 2 * uv_size; // NOLINT #if defined(_MSC_VER) _fseeki64( file_org, static_cast<__int64>(num_skip_org) * static_cast<__int64>(total_size), SEEK_SET); #else fseek(file_org, num_skip_org * total_size, SEEK_SET); #endif for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { #if defined(_MSC_VER) _fseeki64( file_rec[cur_rec], static_cast<__int64>(num_skip_rec) * static_cast<__int64>(total_size), SEEK_SET); #else fseek(file_rec[cur_rec], num_skip_rec * total_size, SEEK_SET); #endif } uint8_t* const ch_org = new uint8_t[total_size]; uint8_t* const ch_rec = new uint8_t[total_size]; if (ch_org == NULL || ch_rec == NULL) { fprintf(stderr, "No memory available\n"); fclose(file_org); for (int i = 0; i < num_rec; ++i) { fclose(file_rec[i]); } delete[] ch_org; delete[] ch_rec; delete[] file_rec; exit(1); } metric* const distortion_psnr = new metric[num_rec]; metric* const distortion_ssim = new metric[num_rec]; for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { metric* cur_distortion_psnr = &distortion_psnr[cur_rec]; cur_distortion_psnr->y = 0.0; cur_distortion_psnr->u = 0.0; cur_distortion_psnr->v = 0.0; cur_distortion_psnr->all = 0.0; cur_distortion_psnr->min_y = kMaxPSNR; cur_distortion_psnr->min_u = kMaxPSNR; cur_distortion_psnr->min_v = kMaxPSNR; cur_distortion_psnr->min_all = kMaxPSNR; cur_distortion_psnr->min_frame = 0; cur_distortion_psnr->global_y = 0.0; cur_distortion_psnr->global_u = 0.0; cur_distortion_psnr->global_v = 0.0; cur_distortion_psnr->global_all = 0.0; distortion_ssim[cur_rec] = cur_distortion_psnr[cur_rec]; } if (verbose) { printf("Size: %dx%d\n", image_width, image_height); } if (!quiet) { printf("Frame"); if (do_psnr) { printf("\t PSNR-Y \t PSNR-U \t PSNR-V \t PSNR-All \t Frame"); } if (do_ssim) { printf("\t SSIM-Y\t SSIM-U\t SSIM-V\t SSIM-All\t Frame"); } if (show_name) { printf("\tName\n"); } else { printf("\n"); } } int number_of_frames; for (number_of_frames = 0;; ++number_of_frames) { if (num_frames && number_of_frames >= num_frames) { break; } size_t bytes_org = fread(ch_org, sizeof(uint8_t), total_size, file_org); if (bytes_org < total_size) { #ifdef HAVE_JPEG // Try parsing file as a jpeg. uint8_t* const ch_jpeg = new uint8_t[bytes_org]; memcpy(ch_jpeg, ch_org, bytes_org); memset(ch_org, 0, total_size); if (0 != libyuv::MJPGToI420(ch_jpeg, bytes_org, ch_org, image_width, ch_org + y_size, (image_width + 1) / 2, ch_org + y_size + uv_size, (image_width + 1) / 2, image_width, image_height, image_width, image_height)) { delete[] ch_jpeg; break; } delete[] ch_jpeg; #else break; #endif // HAVE_JPEG } for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { size_t bytes_rec = fread(ch_rec, sizeof(uint8_t), total_size, file_rec[cur_rec]); if (bytes_rec < total_size) { #ifdef HAVE_JPEG // Try parsing file as a jpeg. uint8_t* const ch_jpeg = new uint8_t[bytes_rec]; memcpy(ch_jpeg, ch_rec, bytes_rec); memset(ch_rec, 0, total_size); if (0 != libyuv::MJPGToI420(ch_jpeg, bytes_rec, ch_rec, image_width, ch_rec + y_size, (image_width + 1) / 2, ch_rec + y_size + uv_size, (image_width + 1) / 2, image_width, image_height, image_width, image_height)) { delete[] ch_jpeg; break; } delete[] ch_jpeg; #else break; #endif // HAVE_JPEG } if (verbose) { printf("%5d", number_of_frames); } if (do_psnr) { metric distorted_frame = {}; metric* cur_distortion_psnr = &distortion_psnr[cur_rec]; bool ismin = UpdateMetrics(ch_org, ch_rec, y_size, uv_size, total_size, number_of_frames, cur_distortion_psnr, &distorted_frame, true); if (verbose) { printf("\t%10.6f", distorted_frame.y); printf("\t%10.6f", distorted_frame.u); printf("\t%10.6f", distorted_frame.v); printf("\t%10.6f", distorted_frame.all); printf("\t%5s", ismin ? "min" : ""); } } if (do_ssim) { metric distorted_frame = {}; metric* cur_distortion_ssim = &distortion_ssim[cur_rec]; bool ismin = UpdateMetrics(ch_org, ch_rec, y_size, uv_size, total_size, number_of_frames, cur_distortion_ssim, &distorted_frame, false); if (verbose) { printf("\t%10.6f", distorted_frame.y); printf("\t%10.6f", distorted_frame.u); printf("\t%10.6f", distorted_frame.v); printf("\t%10.6f", distorted_frame.all); printf("\t%5s", ismin ? "min" : ""); } } if (verbose) { if (show_name) { printf("\t%s", argv[fileindex_rec + cur_rec]); } printf("\n"); } } } // Final PSNR computation. for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { metric* cur_distortion_psnr = &distortion_psnr[cur_rec]; metric* cur_distortion_ssim = &distortion_ssim[cur_rec]; if (number_of_frames > 0) { const double norm = 1. / static_cast(number_of_frames); cur_distortion_psnr->y *= norm; cur_distortion_psnr->u *= norm; cur_distortion_psnr->v *= norm; cur_distortion_psnr->all *= norm; cur_distortion_ssim->y *= norm; cur_distortion_ssim->u *= norm; cur_distortion_ssim->v *= norm; cur_distortion_ssim->all *= norm; } if (do_psnr) { const double global_psnr_y = ComputePSNR(cur_distortion_psnr->global_y, static_cast(y_size) * number_of_frames); const double global_psnr_u = ComputePSNR(cur_distortion_psnr->global_u, static_cast(uv_size) * number_of_frames); const double global_psnr_v = ComputePSNR(cur_distortion_psnr->global_v, static_cast(uv_size) * number_of_frames); const double global_psnr_all = ComputePSNR(cur_distortion_psnr->global_all, static_cast(total_size) * number_of_frames); printf("Global:\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", global_psnr_y, global_psnr_u, global_psnr_v, global_psnr_all, number_of_frames); if (show_name) { printf("\t%s", argv[fileindex_rec + cur_rec]); } printf("\n"); } if (!quiet) { printf("Avg:"); if (do_psnr) { printf("\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", cur_distortion_psnr->y, cur_distortion_psnr->u, cur_distortion_psnr->v, cur_distortion_psnr->all, number_of_frames); } if (do_ssim) { printf("\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", cur_distortion_ssim->y, cur_distortion_ssim->u, cur_distortion_ssim->v, cur_distortion_ssim->all, number_of_frames); } if (show_name) { printf("\t%s", argv[fileindex_rec + cur_rec]); } printf("\n"); } if (!quiet) { printf("Min:"); if (do_psnr) { printf("\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", cur_distortion_psnr->min_y, cur_distortion_psnr->min_u, cur_distortion_psnr->min_v, cur_distortion_psnr->min_all, cur_distortion_psnr->min_frame); } if (do_ssim) { printf("\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", cur_distortion_ssim->min_y, cur_distortion_ssim->min_u, cur_distortion_ssim->min_v, cur_distortion_ssim->min_all, cur_distortion_ssim->min_frame); } if (show_name) { printf("\t%s", argv[fileindex_rec + cur_rec]); } printf("\n"); } if (do_mse) { double global_mse_y = GetMSE(cur_distortion_psnr->global_y, static_cast(y_size) * number_of_frames); double global_mse_u = GetMSE(cur_distortion_psnr->global_u, static_cast(uv_size) * number_of_frames); double global_mse_v = GetMSE(cur_distortion_psnr->global_v, static_cast(uv_size) * number_of_frames); double global_mse_all = GetMSE(cur_distortion_psnr->global_all, static_cast(total_size) * number_of_frames); printf("MSE:\t%10.6f\t%10.6f\t%10.6f\t%10.6f\t%5d", global_mse_y, global_mse_u, global_mse_v, global_mse_all, number_of_frames); if (show_name) { printf("\t%s", argv[fileindex_rec + cur_rec]); } printf("\n"); } } fclose(file_org); for (int cur_rec = 0; cur_rec < num_rec; ++cur_rec) { fclose(file_rec[cur_rec]); } delete[] distortion_psnr; delete[] distortion_ssim; delete[] ch_org; delete[] ch_rec; delete[] file_rec; return 0; }