mirror of
https://github.com/webmproject/libwebp.git
synced 2024-11-20 04:18:26 +01:00
add LSIM metric to WebPPictureDistortion()
LSIM stands for "local similarity": before matching a compressed pixel to the source, we search around in the source and minimise the squared error. So, this is close to PSNR calculation, but mitigates some of its limitations (pure translation and noise for instance). There's a new -print_lsim option to cwebp too. Change-Id: Ia38561034c7a90e71d2ea0f55bb1de527eda245b
This commit is contained in:
parent
c3aa215afa
commit
f86e6abe1f
3
README
3
README
@ -158,8 +158,9 @@ options:
|
|||||||
-crop <x> <y> <w> <h> .. crop picture with the given rectangle
|
-crop <x> <y> <w> <h> .. crop picture with the given rectangle
|
||||||
-resize <w> <h> ........ resize picture (after any cropping)
|
-resize <w> <h> ........ resize picture (after any cropping)
|
||||||
-map <int> ............. print map of extra info.
|
-map <int> ............. print map of extra info.
|
||||||
-print_ssim ............ prints averaged SSIM distortion.
|
|
||||||
-print_psnr ............ prints averaged PSNR distortion.
|
-print_psnr ............ prints averaged PSNR distortion.
|
||||||
|
-print_ssim ............ prints averaged SSIM distortion.
|
||||||
|
-print_lsim ............ prints local-similarity distortion.
|
||||||
-d <file.pgm> .......... dump the compressed output (PGM file).
|
-d <file.pgm> .......... dump the compressed output (PGM file).
|
||||||
-alpha_method <int> .... Transparency-compression method (0..1)
|
-alpha_method <int> .... Transparency-compression method (0..1)
|
||||||
-alpha_filter <string> . predictive filtering for alpha plane.
|
-alpha_filter <string> . predictive filtering for alpha plane.
|
||||||
|
@ -833,8 +833,9 @@ static void HelpLong(void) {
|
|||||||
printf(" -444 / -422 / -gray ..... Change colorspace\n");
|
printf(" -444 / -422 / -gray ..... Change colorspace\n");
|
||||||
#endif
|
#endif
|
||||||
printf(" -map <int> ............. print map of extra info.\n");
|
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(" -print_psnr ............ prints averaged PSNR distortion.\n");
|
||||||
|
printf(" -print_ssim ............ prints averaged SSIM distortion.\n");
|
||||||
|
printf(" -print_lsim ............ prints local-similarity distortion.\n");
|
||||||
printf(" -d <file.pgm> .......... dump the compressed output (PGM file).\n");
|
printf(" -d <file.pgm> .......... dump the compressed output (PGM file).\n");
|
||||||
printf(" -alpha_method <int> .... Transparency-compression method (0..1)\n");
|
printf(" -alpha_method <int> .... Transparency-compression method (0..1)\n");
|
||||||
printf(" -alpha_filter <string> . predictive filtering for alpha plane.\n");
|
printf(" -alpha_filter <string> . predictive filtering for alpha plane.\n");
|
||||||
@ -898,7 +899,7 @@ int main(int argc, const char *argv[]) {
|
|||||||
int resize_w = 0, resize_h = 0;
|
int resize_w = 0, resize_h = 0;
|
||||||
int show_progress = 0;
|
int show_progress = 0;
|
||||||
WebPPicture picture;
|
WebPPicture picture;
|
||||||
int print_distortion = 0; // 1=PSNR, 2=SSIM
|
int print_distortion = -1; // -1=off, 0=PSNR, 1=SSIM, 2=LSIM
|
||||||
WebPPicture original_picture; // when PSNR or SSIM is requested
|
WebPPicture original_picture; // when PSNR or SSIM is requested
|
||||||
WebPConfig config;
|
WebPConfig config;
|
||||||
WebPAuxStats stats;
|
WebPAuxStats stats;
|
||||||
@ -928,12 +929,15 @@ int main(int argc, const char *argv[]) {
|
|||||||
} else if (!strcmp(argv[c], "-d") && c < argc - 1) {
|
} else if (!strcmp(argv[c], "-d") && c < argc - 1) {
|
||||||
dump_file = argv[++c];
|
dump_file = argv[++c];
|
||||||
config.show_compressed = 1;
|
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")) {
|
} else if (!strcmp(argv[c], "-print_psnr")) {
|
||||||
|
config.show_compressed = 1;
|
||||||
|
print_distortion = 0;
|
||||||
|
} else if (!strcmp(argv[c], "-print_ssim")) {
|
||||||
config.show_compressed = 1;
|
config.show_compressed = 1;
|
||||||
print_distortion = 1;
|
print_distortion = 1;
|
||||||
|
} else if (!strcmp(argv[c], "-print_lsim")) {
|
||||||
|
config.show_compressed = 1;
|
||||||
|
print_distortion = 2;
|
||||||
} else if (!strcmp(argv[c], "-short")) {
|
} else if (!strcmp(argv[c], "-short")) {
|
||||||
short_output++;
|
short_output++;
|
||||||
} else if (!strcmp(argv[c], "-s") && c < argc - 2) {
|
} else if (!strcmp(argv[c], "-s") && c < argc - 2) {
|
||||||
@ -1149,7 +1153,7 @@ int main(int argc, const char *argv[]) {
|
|||||||
if (picture.extra_info_type > 0) {
|
if (picture.extra_info_type > 0) {
|
||||||
AllocExtraInfo(&picture);
|
AllocExtraInfo(&picture);
|
||||||
}
|
}
|
||||||
if (print_distortion > 0) { // Save original picture for later comparison
|
if (print_distortion >= 0) { // Save original picture for later comparison
|
||||||
WebPPictureCopy(&picture, &original_picture);
|
WebPPictureCopy(&picture, &original_picture);
|
||||||
}
|
}
|
||||||
if (!WebPEncode(&config, &picture)) {
|
if (!WebPEncode(&config, &picture)) {
|
||||||
@ -1179,12 +1183,13 @@ int main(int argc, const char *argv[]) {
|
|||||||
PrintExtraInfoLossy(&picture, short_output, in_file);
|
PrintExtraInfoLossy(&picture, short_output, in_file);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!quiet && !short_output && print_distortion > 0) { // print distortion
|
if (!quiet && !short_output && print_distortion >= 0) { // print distortion
|
||||||
|
static const char* distortion_names[] = { "PSNR", "SSIM", "LSIM" };
|
||||||
float values[5];
|
float values[5];
|
||||||
WebPPictureDistortion(&picture, &original_picture,
|
WebPPictureDistortion(&picture, &original_picture,
|
||||||
(print_distortion == 1) ? 0 : 1, values);
|
print_distortion, values);
|
||||||
fprintf(stderr, "%s: Y:%.2f U:%.2f V:%.2f A:%.2f Total:%.2f\n",
|
fprintf(stderr, "%s: Y:%.2f U:%.2f V:%.2f A:%.2f Total:%.2f\n",
|
||||||
(print_distortion == 1) ? "PSNR" : "SSIM",
|
distortion_names[print_distortion],
|
||||||
values[0], values[1], values[2], values[3], values[4]);
|
values[0], values[1], values[2], values[3], values[4]);
|
||||||
}
|
}
|
||||||
return_value = 0;
|
return_value = 0;
|
||||||
|
@ -178,6 +178,9 @@ Compute and report average PSNR (Peak-Signal-To-Noise ratio).
|
|||||||
.B \-print_ssim
|
.B \-print_ssim
|
||||||
Compute and report average SSIM (structural similarity metric)
|
Compute and report average SSIM (structural similarity metric)
|
||||||
.TP
|
.TP
|
||||||
|
.B \-print_lsim
|
||||||
|
Compute and report local similarity metric.
|
||||||
|
.TP
|
||||||
.B \-progress
|
.B \-progress
|
||||||
Report encoding progress in percent.
|
Report encoding progress in percent.
|
||||||
.TP
|
.TP
|
||||||
|
@ -906,54 +906,122 @@ void WebPCleanupTransparentArea(WebPPicture* pic) {
|
|||||||
#undef SIZE
|
#undef SIZE
|
||||||
#undef SIZE2
|
#undef SIZE2
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// local-min distortion
|
||||||
|
//
|
||||||
|
// For every pixel in the *reference* picture, we search for the local best
|
||||||
|
// match in the compressed image. This is not a symmetrical measure.
|
||||||
|
|
||||||
|
// search radius. Shouldn't be too large.
|
||||||
|
#define RADIUS 2
|
||||||
|
|
||||||
|
static double AccumulateLSIM(const uint8_t* src, int src_stride,
|
||||||
|
const uint8_t* ref, int ref_stride,
|
||||||
|
int w, int h) {
|
||||||
|
int x, y;
|
||||||
|
double total_sse = 0.;
|
||||||
|
for (y = 0; y < h; ++y) {
|
||||||
|
const int y0 = (y - RADIUS < 0) ? 0 : y - RADIUS;
|
||||||
|
const int y1 = (y + RADIUS + 1 >= h) ? h : y + RADIUS + 1;
|
||||||
|
for (x = 0; x < w; ++x) {
|
||||||
|
const int x0 = (x - RADIUS < 0) ? 0 : x - RADIUS;
|
||||||
|
const int x1 = (x + RADIUS + 1 >= w) ? w : x + RADIUS + 1;
|
||||||
|
double best_sse = 255. * 255.;
|
||||||
|
const double value = (double)ref[y * ref_stride + x];
|
||||||
|
int i, j;
|
||||||
|
for (j = y0; j < y1; ++j) {
|
||||||
|
const uint8_t* s = src + j * src_stride;
|
||||||
|
for (i = x0; i < x1; ++i) {
|
||||||
|
const double sse = (double)(s[i] - value) * (s[i] - value);
|
||||||
|
if (sse < best_sse) best_sse = sse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
total_sse += best_sse;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total_sse;
|
||||||
|
}
|
||||||
|
#undef RADIUS
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
// Distortion
|
// Distortion
|
||||||
|
|
||||||
// Max value returned in case of exact similarity.
|
// Max value returned in case of exact similarity.
|
||||||
static const double kMinDistortion_dB = 99.;
|
static const double kMinDistortion_dB = 99.;
|
||||||
|
static float GetPSNR(const double v) {
|
||||||
|
return (float)((v > 0.) ? -4.3429448 * log(v / (255 * 255.))
|
||||||
|
: kMinDistortion_dB);
|
||||||
|
}
|
||||||
|
|
||||||
int WebPPictureDistortion(const WebPPicture* pic1, const WebPPicture* pic2,
|
int WebPPictureDistortion(const WebPPicture* src, const WebPPicture* ref,
|
||||||
int type, float result[5]) {
|
int type, float result[5]) {
|
||||||
int c;
|
|
||||||
DistoStats stats[5];
|
DistoStats stats[5];
|
||||||
int has_alpha;
|
int has_alpha;
|
||||||
|
int uv_w, uv_h;
|
||||||
|
|
||||||
if (pic1 == NULL || pic2 == NULL ||
|
if (src == NULL || ref == NULL ||
|
||||||
pic1->width != pic2->width || pic1->height != pic2->height ||
|
src->width != ref->width || src->height != ref->height ||
|
||||||
pic1->y == NULL || pic2->y == NULL ||
|
src->y == NULL || ref->y == NULL ||
|
||||||
pic1->u == NULL || pic2->u == NULL ||
|
src->u == NULL || ref->u == NULL ||
|
||||||
pic1->v == NULL || pic2->v == NULL ||
|
src->v == NULL || ref->v == NULL ||
|
||||||
result == NULL) {
|
result == NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
// TODO(skal): provide distortion for ARGB too.
|
// TODO(skal): provide distortion for ARGB too.
|
||||||
if (pic1->use_argb == 1 || pic1->use_argb != pic2->use_argb) {
|
if (src->use_argb == 1 || src->use_argb != ref->use_argb) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
has_alpha = !!(pic1->colorspace & WEBP_CSP_ALPHA_BIT);
|
has_alpha = !!(src->colorspace & WEBP_CSP_ALPHA_BIT);
|
||||||
if (has_alpha != !!(pic2->colorspace & WEBP_CSP_ALPHA_BIT) ||
|
if (has_alpha != !!(ref->colorspace & WEBP_CSP_ALPHA_BIT) ||
|
||||||
(has_alpha && (pic1->a == NULL || pic2->a == NULL))) {
|
(has_alpha && (src->a == NULL || ref->a == NULL))) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
memset(stats, 0, sizeof(stats));
|
memset(stats, 0, sizeof(stats));
|
||||||
VP8SSIMAccumulatePlane(pic1->y, pic1->y_stride,
|
|
||||||
pic2->y, pic2->y_stride,
|
uv_w = HALVE(src->width);
|
||||||
pic1->width, pic1->height, &stats[0]);
|
uv_h = HALVE(src->height);
|
||||||
VP8SSIMAccumulatePlane(pic1->u, pic1->uv_stride,
|
if (type >= 2) {
|
||||||
pic2->u, pic2->uv_stride,
|
float sse[4];
|
||||||
(pic1->width + 1) >> 1, (pic1->height + 1) >> 1,
|
sse[0] = AccumulateLSIM(src->y, src->y_stride,
|
||||||
&stats[1]);
|
ref->y, ref->y_stride, src->width, src->height);
|
||||||
VP8SSIMAccumulatePlane(pic1->v, pic1->uv_stride,
|
sse[1] = AccumulateLSIM(src->u, src->uv_stride,
|
||||||
pic2->v, pic2->uv_stride,
|
ref->u, ref->uv_stride, uv_w, uv_h);
|
||||||
(pic1->width + 1) >> 1, (pic1->height + 1) >> 1,
|
sse[2] = AccumulateLSIM(src->v, src->uv_stride,
|
||||||
&stats[2]);
|
ref->v, ref->uv_stride, uv_w, uv_h);
|
||||||
|
sse[3] = has_alpha ? AccumulateLSIM(src->a, src->a_stride,
|
||||||
|
ref->a, ref->a_stride,
|
||||||
|
src->width, src->height)
|
||||||
|
: 0;
|
||||||
|
result[0] = GetPSNR(sse[0] / (src->width * src->height));
|
||||||
|
result[1] = GetPSNR(sse[1] / (uv_w * uv_h));
|
||||||
|
result[2] = GetPSNR(sse[2] / (uv_w * uv_h));
|
||||||
|
result[3] = GetPSNR(sse[3] / (src->width * src->height));
|
||||||
|
{
|
||||||
|
double total_sse = sse[0] + sse[1] + sse[2];
|
||||||
|
int total_pixels = src->width * src->height + 2 * uv_w * uv_h;
|
||||||
if (has_alpha) {
|
if (has_alpha) {
|
||||||
VP8SSIMAccumulatePlane(pic1->a, pic1->a_stride,
|
total_pixels += src->width * src->height;
|
||||||
pic2->a, pic2->a_stride,
|
total_sse += sse[3];
|
||||||
pic1->width, pic1->height, &stats[3]);
|
}
|
||||||
|
result[4] = GetPSNR(total_sse / total_pixels);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int c;
|
||||||
|
VP8SSIMAccumulatePlane(src->y, src->y_stride,
|
||||||
|
ref->y, ref->y_stride,
|
||||||
|
src->width, src->height, &stats[0]);
|
||||||
|
VP8SSIMAccumulatePlane(src->u, src->uv_stride,
|
||||||
|
ref->u, ref->uv_stride,
|
||||||
|
uv_w, uv_h, &stats[1]);
|
||||||
|
VP8SSIMAccumulatePlane(src->v, src->uv_stride,
|
||||||
|
ref->v, ref->uv_stride,
|
||||||
|
uv_w, uv_h, &stats[2]);
|
||||||
|
if (has_alpha) {
|
||||||
|
VP8SSIMAccumulatePlane(src->a, src->a_stride,
|
||||||
|
ref->a, ref->a_stride,
|
||||||
|
src->width, src->height, &stats[3]);
|
||||||
}
|
}
|
||||||
for (c = 0; c <= 4; ++c) {
|
for (c = 0; c <= 4; ++c) {
|
||||||
if (type == 1) {
|
if (type == 1) {
|
||||||
@ -962,12 +1030,12 @@ int WebPPictureDistortion(const WebPPicture* pic1, const WebPPicture* pic2,
|
|||||||
: kMinDistortion_dB);
|
: kMinDistortion_dB);
|
||||||
} else {
|
} else {
|
||||||
const double v = VP8SSIMGetSquaredError(&stats[c]);
|
const double v = VP8SSIMGetSquaredError(&stats[c]);
|
||||||
result[c] = (float)((v > 0.) ? -4.3429448 * log(v / (255 * 255.))
|
result[c] = GetPSNR(v);
|
||||||
: kMinDistortion_dB);
|
|
||||||
}
|
}
|
||||||
// Accumulate forward
|
// Accumulate forward
|
||||||
if (c < 4) VP8SSIMAddStats(&stats[c], &stats[4]);
|
if (c < 4) VP8SSIMAddStats(&stats[c], &stats[4]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -359,13 +359,13 @@ WEBP_EXTERN(void) WebPPictureFree(WebPPicture* picture);
|
|||||||
// Returns false in case of memory allocation error.
|
// Returns false in case of memory allocation error.
|
||||||
WEBP_EXTERN(int) WebPPictureCopy(const WebPPicture* src, WebPPicture* dst);
|
WEBP_EXTERN(int) WebPPictureCopy(const WebPPicture* src, WebPPicture* dst);
|
||||||
|
|
||||||
// Compute PSNR or SSIM distortion between two pictures.
|
// Compute PSNR, SSIM or LSIM distortion metric between two pictures.
|
||||||
// Result is in dB, stores in result[] in the Y/U/V/Alpha/All order.
|
// Result is in dB, stores in result[] in the Y/U/V/Alpha/All order.
|
||||||
// Returns false in case of error (pic1 and pic2 don't have same dimension, ...)
|
// Returns false in case of error (src and ref don't have same dimension, ...)
|
||||||
// Warning: this function is rather CPU-intensive.
|
// Warning: this function is rather CPU-intensive.
|
||||||
WEBP_EXTERN(int) WebPPictureDistortion(
|
WEBP_EXTERN(int) WebPPictureDistortion(
|
||||||
const WebPPicture* pic1, const WebPPicture* pic2,
|
const WebPPicture* src, const WebPPicture* ref,
|
||||||
int metric_type, // 0 = PSNR, 1 = SSIM
|
int metric_type, // 0 = PSNR, 1 = SSIM, 2 = LSIM
|
||||||
float result[5]);
|
float result[5]);
|
||||||
|
|
||||||
// self-crops a picture to the rectangle defined by top/left/width/height.
|
// self-crops a picture to the rectangle defined by top/left/width/height.
|
||||||
|
Loading…
Reference in New Issue
Block a user