mirror of
				https://github.com/webmproject/libwebp.git
				synced 2025-10-31 18:35:41 +01:00 
			
		
		
		
	add -print_psnr and -print_ssim options to cwebp.
These will report the 7x7-averaged PSNR or SSIM, using the new internal function WebPPictureDistortion(). This is for information only. These flags have no encoding impact. +misc opportunistic cosmetics Change-Id: I64c0a7eca679134d39062e438886274b22bb643f
This commit is contained in:
		
							
								
								
									
										2
									
								
								README
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								README
									
									
									
									
									
								
							| @@ -148,6 +148,8 @@ options: | ||||
|   -crop <x> <y> <w> <h> .. crop picture with the given rectangle | ||||
|   -resize <w> <h> ........ resize picture (after any cropping) | ||||
|   -map <int> ............. print map of extra info. | ||||
|   -print_ssim ............ prints averaged SSIM distortion. | ||||
|   -print_psnr ............ prints averaged PSNR distortion. | ||||
|   -d <file.pgm> .......... dump the compressed output (PGM file). | ||||
|   -alpha_method <int> .... Transparency-compression method (0..1) | ||||
|   -alpha_filter <string> . predictive filtering for alpha plane. | ||||
|   | ||||
| @@ -529,7 +529,8 @@ static void PrintValues(const int values[4]) { | ||||
|   fprintf(stderr, "|\n"); | ||||
| } | ||||
|  | ||||
| static void PrintExtraInfo(const WebPPicture* const pic, int short_output) { | ||||
| static void PrintExtraInfo(const WebPPicture* const pic, int short_output, | ||||
|                            const char* const file_name) { | ||||
|   const WebPAuxStats* const stats = pic->stats; | ||||
|   if (short_output) { | ||||
|     fprintf(stderr, "%7d %2.2f\n", stats->coded_size, stats->PSNR[3]); | ||||
| @@ -538,8 +539,11 @@ static void PrintExtraInfo(const WebPPicture* const pic, int short_output) { | ||||
|     const int num_i16 = stats->block_count[1]; | ||||
|     const int num_skip = stats->block_count[2]; | ||||
|     const int total = num_i4 + num_i16; | ||||
|     fprintf(stderr, | ||||
|             "%7d bytes Y-U-V-All-PSNR %2.2f %2.2f %2.2f   %2.2f dB\n", | ||||
|     fprintf(stderr, "File:      %s\n", file_name); | ||||
|     fprintf(stderr, "Dimension: %d x %d%s\n", | ||||
|             pic->width, pic->height, (pic->a != NULL) ? " (with alpha)" : ""); | ||||
|     fprintf(stderr, "Output:    " | ||||
|             "%d bytes Y-U-V-All-PSNR %2.2f %2.2f %2.2f   %2.2f dB\n", | ||||
|             stats->coded_size, | ||||
|             stats->PSNR[0], stats->PSNR[1], stats->PSNR[2], stats->PSNR[3]); | ||||
|     if (total > 0) { | ||||
| @@ -626,7 +630,7 @@ static int DumpPicture(const WebPPicture* const picture, const char* PGM_name) { | ||||
|   const int alpha_height = picture->a ? picture->height : 0; | ||||
|   const int height = picture->height + uv_height + alpha_height; | ||||
|   FILE* const f = fopen(PGM_name, "wb"); | ||||
|   if (!f) return 0; | ||||
|   if (f == NULL) return 0; | ||||
|   fprintf(f, "P5\n%d %d\n255\n", stride, height); | ||||
|   for (y = 0; y < picture->height; ++y) { | ||||
|     if (fwrite(picture->y + y * picture->y_stride, picture->width, 1, f) != 1) | ||||
| @@ -707,6 +711,8 @@ static void HelpLong(void) { | ||||
|   printf("  -444 / -422 / -gray ..... Change colorspace\n"); | ||||
| #endif | ||||
|   printf("  -map <int> ............. print map of extra info.\n"); | ||||
|   printf("  -print_ssim ............ prints averaged SSIM distortion.\n"); | ||||
|   printf("  -print_psnr ............ prints averaged PSNR distortion.\n"); | ||||
|   printf("  -d <file.pgm> .......... dump the compressed output (PGM file).\n"); | ||||
|   printf("  -alpha_method <int> .... Transparency-compression method (0..1)\n"); | ||||
|   printf("  -alpha_filter <string> . predictive filtering for alpha plane.\n"); | ||||
| @@ -766,11 +772,15 @@ int main(int argc, const char *argv[]) { | ||||
|   int resize_w = 0, resize_h = 0; | ||||
|   int show_progress = 0; | ||||
|   WebPPicture picture; | ||||
|   int print_distortion = 0;        // 1=PSNR, 2=SSIM | ||||
|   WebPPicture original_picture;    // when PSNR or SSIM is requested | ||||
|   WebPConfig config; | ||||
|   WebPAuxStats stats; | ||||
|   Stopwatch stop_watch; | ||||
|  | ||||
|   if (!WebPPictureInit(&picture) || !WebPConfigInit(&config)) { | ||||
|   if (!WebPPictureInit(&picture) || | ||||
|       !WebPPictureInit(&original_picture) || | ||||
|       !WebPConfigInit(&config)) { | ||||
|     fprintf(stderr, "Error! Version mismatch!\n"); | ||||
|     goto Error; | ||||
|   } | ||||
| @@ -792,6 +802,12 @@ int main(int argc, const char *argv[]) { | ||||
|     } else if (!strcmp(argv[c], "-d") && c < argc - 1) { | ||||
|       dump_file = argv[++c]; | ||||
|       config.show_compressed = 1; | ||||
|     } else if (!strcmp(argv[c], "-print_ssim")) { | ||||
|       config.show_compressed = 1; | ||||
|       print_distortion = 2; | ||||
|     } else if (!strcmp(argv[c], "-print_psnr")) { | ||||
|       config.show_compressed = 1; | ||||
|       print_distortion = 1; | ||||
|     } else if (!strcmp(argv[c], "-short")) { | ||||
|       short_output++; | ||||
|     } else if (!strcmp(argv[c], "-s") && c < argc - 2) { | ||||
| @@ -919,7 +935,7 @@ int main(int argc, const char *argv[]) { | ||||
|     StopwatchReadAndReset(&stop_watch); | ||||
|   } | ||||
|   if (!ReadPicture(in_file, &picture, keep_alpha)) { | ||||
|     fprintf(stderr, "Error! Cannot read input picture\n"); | ||||
|     fprintf(stderr, "Error! Cannot read input picture file '%s'\n", in_file); | ||||
|     goto Error; | ||||
|   } | ||||
|   picture.progress_hook = (show_progress && !quiet) ? ProgressReport : NULL; | ||||
| @@ -932,7 +948,7 @@ int main(int argc, const char *argv[]) { | ||||
|   // Open the output | ||||
|   if (out_file) { | ||||
|     out = fopen(out_file, "wb"); | ||||
|     if (!out) { | ||||
|     if (out == NULL) { | ||||
|       fprintf(stderr, "Error! Cannot open output file '%s'\n", out_file); | ||||
|       goto Error; | ||||
|     } else { | ||||
| @@ -969,6 +985,9 @@ int main(int argc, const char *argv[]) { | ||||
|   if (picture.extra_info_type > 0) { | ||||
|     AllocExtraInfo(&picture); | ||||
|   } | ||||
|   if (print_distortion > 0) {  // Save original picture for later comparison | ||||
|     WebPPictureCopy(&picture, &original_picture); | ||||
|   } | ||||
|   if (!WebPEncode(&config, &picture)) { | ||||
|     fprintf(stderr, "Error! Cannot encode picture as WebP\n"); | ||||
|     fprintf(stderr, "Error code: %d (%s)\n", | ||||
| @@ -984,13 +1003,23 @@ int main(int argc, const char *argv[]) { | ||||
|   if (dump_file) { | ||||
|     DumpPicture(&picture, dump_file); | ||||
|   } | ||||
|  | ||||
|   if (!quiet) { | ||||
|     PrintExtraInfo(&picture, short_output); | ||||
|     PrintExtraInfo(&picture, short_output, in_file); | ||||
|   } | ||||
|   if (!quiet && !short_output && print_distortion > 0) {  // print distortion | ||||
|     float values[5]; | ||||
|     WebPPictureDistortion(&picture, &original_picture, | ||||
|                           (print_distortion == 1) ? 0 : 1, values); | ||||
|     fprintf(stderr, "%s: Y:%.2f U:%.2f V:%.2f A:%.2f  Total:%.2f\n", | ||||
|             (print_distortion == 1) ? "PSNR" : "SSIM", | ||||
|             values[0], values[1], values[2], values[3], values[4]); | ||||
|   } | ||||
|  | ||||
|  Error: | ||||
|   free(picture.extra_info); | ||||
|   WebPPictureFree(&picture); | ||||
|   WebPPictureFree(&original_picture); | ||||
|   if (out != NULL) { | ||||
|     fclose(out); | ||||
|   } | ||||
|   | ||||
| @@ -168,6 +168,12 @@ Disable all assembly optimizations. | ||||
| .B \-v | ||||
| Print extra information (encoding time in particular). | ||||
| .TP | ||||
| .B \-print_psnr | ||||
| Compute and report average PSNR (Peak-Signal-To-Noise ratio). | ||||
| .TP | ||||
| .B \-print_ssim | ||||
| Compute and report average SSIM (structural similarity metric) | ||||
| .TP | ||||
| .B \-progress | ||||
| Report encoding progress in percent. | ||||
| .TP | ||||
|   | ||||
| @@ -234,14 +234,21 @@ static void DoFilter(const VP8EncIterator* const it, int level) { | ||||
| // SSIM metric | ||||
|  | ||||
| enum { KERNEL = 3 }; | ||||
| typedef struct { | ||||
|   double w, xm, ym, xxm, xym, yym; | ||||
| } SSIMStats; | ||||
| static const double kMinValue = 1.e-10;  // minimal threshold | ||||
|  | ||||
| static void Accumulate(const uint8_t* src1, int stride1, | ||||
|                        const uint8_t* src2, int stride2, | ||||
|                        int xo, int yo, int W, int H, | ||||
|                        SSIMStats* const stats) { | ||||
| void VP8SSIMAddStats(const DistoStats* const src, DistoStats* const dst) { | ||||
|   dst->w   += src->w; | ||||
|   dst->xm  += src->xm; | ||||
|   dst->ym  += src->ym; | ||||
|   dst->xxm += src->xxm; | ||||
|   dst->xym += src->xym; | ||||
|   dst->yym += src->yym; | ||||
| } | ||||
|  | ||||
| static void VP8SSIMAccumulate(const uint8_t* src1, int stride1, | ||||
|                               const uint8_t* src2, int stride2, | ||||
|                               int xo, int yo, int W, int H, | ||||
|                               DistoStats* const stats) { | ||||
|   const int ymin = (yo - KERNEL < 0) ? 0 : yo - KERNEL; | ||||
|   const int ymax = (yo + KERNEL > H - 1) ? H - 1 : yo + KERNEL; | ||||
|   const int xmin = (xo - KERNEL < 0) ? 0 : xo - KERNEL; | ||||
| @@ -263,7 +270,7 @@ static void Accumulate(const uint8_t* src1, int stride1, | ||||
|   } | ||||
| } | ||||
|  | ||||
| static double GetSSIM(const SSIMStats* const stats) { | ||||
| double VP8SSIMGet(const DistoStats* const stats) { | ||||
|   const double xmxm = stats->xm * stats->xm; | ||||
|   const double ymym = stats->ym * stats->ym; | ||||
|   const double xmym = stats->xm * stats->ym; | ||||
| @@ -281,26 +288,49 @@ static double GetSSIM(const SSIMStats* const stats) { | ||||
|   C2 = 58.5225 * w2; | ||||
|   fnum = (2 * xmym + C1) * (2 * sxy + C2); | ||||
|   fden = (xmxm + ymym + C1) * (sxx + syy + C2); | ||||
|   return (fden != 0) ? fnum / fden : 0.; | ||||
|   return (fden != 0.) ? fnum / fden : kMinValue; | ||||
| } | ||||
|  | ||||
| double VP8SSIMGetSquaredError(const DistoStats* const s) { | ||||
|   if (s->w > 0.) { | ||||
|     const double iw2 = 1. / (s->w * s->w); | ||||
|     const double sxx = s->xxm * s->w - s->xm * s->xm; | ||||
|     const double syy = s->yym * s->w - s->ym * s->ym; | ||||
|     const double sxy = s->xym * s->w - s->xm * s->ym; | ||||
|     const double SSE = iw2 * (sxx + syy - 2. * sxy); | ||||
|     if (SSE > kMinValue) return SSE; | ||||
|   } | ||||
|   return kMinValue; | ||||
| } | ||||
|  | ||||
| void VP8SSIMAccumulatePlane(const uint8_t* src1, int stride1, | ||||
|                             const uint8_t* src2, int stride2, | ||||
|                             int W, int H, DistoStats* const stats) { | ||||
|   int x, y; | ||||
|   for (y = 0; y < H; ++y) { | ||||
|     for (x = 0; x < W; ++x) { | ||||
|       VP8SSIMAccumulate(src1, stride1, src2, stride2, x, y, W, H, stats); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| static double GetMBSSIM(const uint8_t* yuv1, const uint8_t* yuv2) { | ||||
|   int x, y; | ||||
|   SSIMStats s = { .0, .0, .0, .0, .0, .0 }; | ||||
|   DistoStats s = { .0, .0, .0, .0, .0, .0 }; | ||||
|  | ||||
|   // compute SSIM in a 10 x 10 window | ||||
|   for (x = 3; x < 13; x++) { | ||||
|     for (y = 3; y < 13; y++) { | ||||
|       Accumulate(yuv1 + Y_OFF, BPS, yuv2 + Y_OFF, BPS, x, y, 16, 16, &s); | ||||
|       VP8SSIMAccumulate(yuv1 + Y_OFF, BPS, yuv2 + Y_OFF, BPS, x, y, 16, 16, &s); | ||||
|     } | ||||
|   } | ||||
|   for (x = 1; x < 7; x++) { | ||||
|     for (y = 1; y < 7; y++) { | ||||
|       Accumulate(yuv1 + U_OFF, BPS, yuv2 + U_OFF, BPS, x, y, 8, 8, &s); | ||||
|       Accumulate(yuv1 + V_OFF, BPS, yuv2 + V_OFF, BPS, x, y, 8, 8, &s); | ||||
|       VP8SSIMAccumulate(yuv1 + U_OFF, BPS, yuv2 + U_OFF, BPS, x, y, 8, 8, &s); | ||||
|       VP8SSIMAccumulate(yuv1 + V_OFF, BPS, yuv2 + V_OFF, BPS, x, y, 8, 8, &s); | ||||
|     } | ||||
|   } | ||||
|   return GetSSIM(&s); | ||||
|   return VP8SSIMGet(&s); | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
|  | ||||
| #include <assert.h> | ||||
| #include <stdlib.h> | ||||
| #include <math.h> | ||||
|  | ||||
| #include "./vp8enci.h" | ||||
|  | ||||
| @@ -659,7 +660,58 @@ void WebPCleanupTransparentArea(WebPPicture* const pic) { | ||||
| #undef SIZE2 | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Simplest call: | ||||
| // Distortion | ||||
|  | ||||
| // Max value returned in case of exact similarity. | ||||
| static const double kMinDistortion_dB = 99.; | ||||
|  | ||||
| int WebPPictureDistortion(const WebPPicture* const pic1, | ||||
|                           const WebPPicture* const pic2, | ||||
|                           int type, float result[5]) { | ||||
|   int c; | ||||
|   DistoStats stats[5]; | ||||
|  | ||||
|   if (pic1->width != pic2->width || | ||||
|       pic1->height != pic2->height || | ||||
|       (pic1->a == NULL) != (pic2->a == NULL)) { | ||||
|     return 0; | ||||
|   } | ||||
|  | ||||
|   memset(stats, 0, sizeof(stats)); | ||||
|   VP8SSIMAccumulatePlane(pic1->y, pic1->y_stride, | ||||
|                          pic2->y, pic2->y_stride, | ||||
|                          pic1->width, pic1->height, &stats[0]); | ||||
|   VP8SSIMAccumulatePlane(pic1->u, pic1->uv_stride, | ||||
|                          pic2->u, pic2->uv_stride, | ||||
|                          (pic1->width + 1) >> 1, (pic1->height + 1) >> 1, | ||||
|                          &stats[1]); | ||||
|   VP8SSIMAccumulatePlane(pic1->v, pic1->uv_stride, | ||||
|                          pic2->v, pic2->uv_stride, | ||||
|                          (pic1->width + 1) >> 1, (pic1->height + 1) >> 1, | ||||
|                          &stats[2]); | ||||
|   if (pic1->a != NULL) { | ||||
|     VP8SSIMAccumulatePlane(pic1->a, pic1->a_stride, | ||||
|                            pic2->a, pic2->a_stride, | ||||
|                            pic1->width, pic1->height, &stats[3]); | ||||
|   } | ||||
|   for (c = 0; c <= 4; ++c) { | ||||
|     if (type == 1) { | ||||
|       const double v = VP8SSIMGet(&stats[c]); | ||||
|       result[c] = (v < 1.) ? -10.0 * log10(1. - v) | ||||
|                            : kMinDistortion_dB; | ||||
|     } else { | ||||
|       const double v = VP8SSIMGetSquaredError(&stats[c]); | ||||
|       result[c] = (v > 0.) ? -4.3429448 * log(v / (255 * 255.)) | ||||
|                            : kMinDistortion_dB; | ||||
|     } | ||||
|     // Accumulate forward | ||||
|     if (c < 4) VP8SSIMAddStats(&stats[c], &stats[4]); | ||||
|   } | ||||
|   return 1; | ||||
| } | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
| // Simplest high-level calls: | ||||
|  | ||||
| typedef int (*Importer)(WebPPicture* const, const uint8_t* const, int); | ||||
|  | ||||
|   | ||||
| @@ -455,9 +455,20 @@ int VP8EncFinishLayer(VP8Encoder* const enc);    // finalize coding | ||||
| void VP8EncDeleteLayer(VP8Encoder* enc);         // reclaim memory | ||||
|  | ||||
|   // in filter.c | ||||
| extern void VP8InitFilter(VP8EncIterator* const it); | ||||
| extern void VP8StoreFilterStats(VP8EncIterator* const it); | ||||
| extern void VP8AdjustFilterStrength(VP8EncIterator* const it); | ||||
|  | ||||
| // SSIM utils | ||||
| typedef struct { double w, xm, ym, xxm, xym, yym; } DistoStats; | ||||
| void VP8SSIMAddStats(const DistoStats* const src, DistoStats* const dst); | ||||
| void VP8SSIMAccumulatePlane(const uint8_t* src1, int stride1, | ||||
|                             const uint8_t* src2, int stride2, | ||||
|                             int W, int H, DistoStats* const stats); | ||||
| double VP8SSIMGet(const DistoStats* const stats); | ||||
| double VP8SSIMGetSquaredError(const DistoStats* const stats); | ||||
|  | ||||
| // autofilter | ||||
| void VP8InitFilter(VP8EncIterator* const it); | ||||
| void VP8StoreFilterStats(VP8EncIterator* const it); | ||||
| void VP8AdjustFilterStrength(VP8EncIterator* const it); | ||||
|  | ||||
| //------------------------------------------------------------------------------ | ||||
|  | ||||
|   | ||||
| @@ -248,6 +248,15 @@ WEBP_EXTERN(void) WebPPictureFree(WebPPicture* const picture); | ||||
| WEBP_EXTERN(int) WebPPictureCopy(const WebPPicture* const src, | ||||
|                                  WebPPicture* const dst); | ||||
|  | ||||
| // Compute PSNR or SSIM distortion between two pictures. | ||||
| // Result is in dB, stores in result[] in the Y/U/V/Alpha/All order. | ||||
| // Returns 0 in case of error (pic1 and pic2 don't have same dimension, ...) | ||||
| // Warning: this function is rather CPU-intensive. | ||||
| WEBP_EXTERN(int) WebPPictureDistortion( | ||||
|     const WebPPicture* const pic1, const WebPPicture* const pic2, | ||||
|     int metric_type,           // 0 = PSNR, 1 = SSIM | ||||
|     float result[5]); | ||||
|  | ||||
| // self-crops a picture to the rectangle defined by top/left/width/height. | ||||
| // Returns 0 in case of memory allocation error, or if the rectangle is | ||||
| // outside of the source picture. | ||||
|   | ||||
		Reference in New Issue
	
	Block a user