[FFmpeg-devel] [PATCH] Add a tiny_ssim that is avified.
wm4
nfxjfg at googlemail.com
Sat Jun 20 11:55:41 CEST 2015
On Fri, 19 Jun 2015 08:44:51 -0400
"Ronald S. Bultje" <rsbultje at gmail.com> wrote:
> ---
> tests/Makefile | 5 +-
> tests/tiny_ssim.c | 153 +------------------------------------
> tests/tiny_ssim_avf.c | 159 +++++++++++++++++++++++++++++++++++++++
> tests/tiny_ssim_template.c | 183 +++++++++++++++++++++++++++++++++++++++++++++
> 4 files changed, 350 insertions(+), 150 deletions(-)
> create mode 100644 tests/tiny_ssim_avf.c
> create mode 100644 tests/tiny_ssim_template.c
>
> diff --git a/tests/Makefile b/tests/Makefile
> index cffa541..642e287 100644
> --- a/tests/Makefile
> +++ b/tests/Makefile
> @@ -188,7 +188,10 @@ $(FATE_EXTERN):
> @echo "$@ requires external samples and SAMPLES not specified"; false
> endif
>
> -FATE_UTILS = base64 tiny_psnr tiny_ssim
> +FATE_UTILS = base64 tiny_psnr tiny_ssim tiny_ssim_avf
> +
> +tests/tiny_ssim_avf: tests/tiny_ssim_avf.o
> + $(LD) $(LDFLAGS) $(LDEXEFLAGS) $(LD_O) $^ $(FFLIBS) $(FF_EXTRALIBS) $(FF_DEP_LIBS)
>
> TOOL = ffmpeg
>
> diff --git a/tests/tiny_ssim.c b/tests/tiny_ssim.c
> index 9f355a3..678c4dd 100644
> --- a/tests/tiny_ssim.c
> +++ b/tests/tiny_ssim.c
> @@ -27,154 +27,7 @@
> * overlapped 8x8 block sums, rather than the original gaussian weights.
> */
>
> -#include "config.h"
> -#include <inttypes.h>
> -#include <limits.h>
> -#include <math.h>
> -#include <stdio.h>
> -#include <stdlib.h>
> -
> -#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0)
> -#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
> -
> -#define BIT_DEPTH 8
> -#define PIXEL_MAX ((1 << BIT_DEPTH)-1)
> -typedef uint8_t pixel;
> -
> -/****************************************************************************
> - * structural similarity metric
> - ****************************************************************************/
> -static void ssim_4x4x2_core( const pixel *pix1, intptr_t stride1,
> - const pixel *pix2, intptr_t stride2,
> - int sums[2][4] )
> -{
> - int x,y,z;
> -
> - for( z = 0; z < 2; z++ )
> - {
> - uint32_t s1 = 0, s2 = 0, ss = 0, s12 = 0;
> - for( y = 0; y < 4; y++ )
> - for( x = 0; x < 4; x++ )
> - {
> - int a = pix1[x+y*stride1];
> - int b = pix2[x+y*stride2];
> - s1 += a;
> - s2 += b;
> - ss += a*a;
> - ss += b*b;
> - s12 += a*b;
> - }
> - sums[z][0] = s1;
> - sums[z][1] = s2;
> - sums[z][2] = ss;
> - sums[z][3] = s12;
> - pix1 += 4;
> - pix2 += 4;
> - }
> -}
> -
> -static float ssim_end1( int s1, int s2, int ss, int s12 )
> -{
> -/* Maximum value for 10-bit is: ss*64 = (2^10-1)^2*16*4*64 = 4286582784, which will overflow in some cases.
> - * s1*s1, s2*s2, and s1*s2 also obtain this value for edge cases: ((2^10-1)*16*4)^2 = 4286582784.
> - * Maximum value for 9-bit is: ss*64 = (2^9-1)^2*16*4*64 = 1069551616, which will not overflow. */
> -#if BIT_DEPTH > 9
> -#define type float
> - static const float ssim_c1 = .01*.01*PIXEL_MAX*PIXEL_MAX*64;
> - static const float ssim_c2 = .03*.03*PIXEL_MAX*PIXEL_MAX*64*63;
> -#else
> -#define type int
> - static const int ssim_c1 = (int)(.01*.01*PIXEL_MAX*PIXEL_MAX*64 + .5);
> - static const int ssim_c2 = (int)(.03*.03*PIXEL_MAX*PIXEL_MAX*64*63 + .5);
> -#endif
> - type fs1 = s1;
> - type fs2 = s2;
> - type fss = ss;
> - type fs12 = s12;
> - type vars = fss*64 - fs1*fs1 - fs2*fs2;
> - type covar = fs12*64 - fs1*fs2;
> - return (float)(2*fs1*fs2 + ssim_c1) * (float)(2*covar + ssim_c2)
> - / ((float)(fs1*fs1 + fs2*fs2 + ssim_c1) * (float)(vars + ssim_c2));
> -#undef type
> -}
> -
> -static float ssim_end4( int sum0[5][4], int sum1[5][4], int width )
> -{
> - float ssim = 0.0;
> - int i;
> -
> - for( i = 0; i < width; i++ )
> - ssim += ssim_end1( sum0[i][0] + sum0[i+1][0] + sum1[i][0] + sum1[i+1][0],
> - sum0[i][1] + sum0[i+1][1] + sum1[i][1] + sum1[i+1][1],
> - sum0[i][2] + sum0[i+1][2] + sum1[i][2] + sum1[i+1][2],
> - sum0[i][3] + sum0[i+1][3] + sum1[i][3] + sum1[i+1][3] );
> - return ssim;
> -}
> -
> -float ssim_plane(
> - pixel *pix1, intptr_t stride1,
> - pixel *pix2, intptr_t stride2,
> - int width, int height, void *buf, int *cnt )
> -{
> - int z = 0;
> - int x, y;
> - float ssim = 0.0;
> - int (*sum0)[4] = buf;
> - int (*sum1)[4] = sum0 + (width >> 2) + 3;
> - width >>= 2;
> - height >>= 2;
> - for( y = 1; y < height; y++ )
> - {
> - for( ; z <= y; z++ )
> - {
> - FFSWAP( void*, sum0, sum1 );
> - for( x = 0; x < width; x+=2 )
> - ssim_4x4x2_core( &pix1[4*(x+z*stride1)], stride1, &pix2[4*(x+z*stride2)], stride2, &sum0[x] );
> - }
> - for( x = 0; x < width-1; x += 4 )
> - ssim += ssim_end4( sum0+x, sum1+x, FFMIN(4,width-x-1) );
> - }
> -// *cnt = (height-1) * (width-1);
> - return ssim / ((height-1) * (width-1));
> -}
> -
> -
> -uint64_t ssd_plane( const uint8_t *pix1, const uint8_t *pix2, int size )
> -{
> - uint64_t ssd = 0;
> - int i;
> - for( i=0; i<size; i++ )
> - {
> - int d = pix1[i] - pix2[i];
> - ssd += d*d;
> - }
> - return ssd;
> -}
> -
> -static double ssd_to_psnr( uint64_t ssd, uint64_t denom )
> -{
> - return -10*log((double)ssd/(denom*255*255))/log(10);
> -}
> -
> -static double ssim_db( double ssim, double weight )
> -{
> - return 10*(log(weight)/log(10)-log(weight-ssim)/log(10));
> -}
> -
> -static void print_results(uint64_t ssd[3], double ssim[3], int frames, int w, int h)
> -{
> - printf( "PSNR Y:%.3f U:%.3f V:%.3f All:%.3f | ",
> - ssd_to_psnr( ssd[0], (uint64_t)frames*w*h ),
> - ssd_to_psnr( ssd[1], (uint64_t)frames*w*h/4 ),
> - ssd_to_psnr( ssd[2], (uint64_t)frames*w*h/4 ),
> - ssd_to_psnr( ssd[0] + ssd[1] + ssd[2], (uint64_t)frames*w*h*3/2 ) );
> - printf( "SSIM Y:%.5f U:%.5f V:%.5f All:%.5f (%.5f)",
> - ssim[0] / frames,
> - ssim[1] / frames,
> - ssim[2] / frames,
> - (ssim[0]*4 + ssim[1] + ssim[2]) / (frames*6),
> - ssim_db(ssim[0] * 4 + ssim[1] + ssim[2], frames*6));
> -}
> +#include "tests/tiny_ssim_template.c"
>
> int main(int argc, char* argv[])
> {
> @@ -222,7 +75,9 @@ int main(int argc, char* argv[])
> if( fread(buf[1], frame_size, 1, f[1]) != 1) break;
> for( i=0; i<3; i++ )
> {
> - ssd_one[i] = ssd_plane ( plane[0][i], plane[1][i], w*h>>2*!!i );
> + ssd_one[i] = ssd_plane ( plane[0][i], w>>!!i,
> + plane[1][i], w>>!!i,
> + w>>!!i, h>>!!i );
> ssim_one[i] = ssim_plane( plane[0][i], w>>!!i,
> plane[1][i], w>>!!i,
> w>>!!i, h>>!!i, temp, NULL );
> diff --git a/tests/tiny_ssim_avf.c b/tests/tiny_ssim_avf.c
> new file mode 100644
> index 0000000..f650178
> --- /dev/null
> +++ b/tests/tiny_ssim_avf.c
> @@ -0,0 +1,159 @@
> +/*
> + * Copyright (c) 2003-2013 Loren Merritt
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 USA
> + */
> +/*
> + * tiny_ssim.c
> + * Computes the Structural Similarity Metric between two rawYV12 video files.
> + * original algorithm:
> + * Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli,
> + * "Image quality assessment: From error visibility to structural similarity,"
> + * IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004.
> + *
> + * To improve speed, this implementation uses the standard approximation of
> + * overlapped 8x8 block sums, rather than the original gaussian weights.
> + */
> +
> +#include "tests/tiny_ssim_template.c"
> +
> +#include "libavformat/avformat.h"
> +#include "libavcodec/avcodec.h"
> +
> +struct inputStuff {
> + AVFormatContext *ctx;
> + AVCodecContext *avctx;
> + int videoStreamIndex;
> + AVFrame *pic;
> +};
> +
> +static int open_file(struct inputStuff *i, const char *file) {
> + int res;
> +
> + i->ctx = NULL;
> + if ((res = avformat_open_input(&i->ctx, file, NULL, NULL)))
> + return res;
> +
> + if ((res = avformat_find_stream_info(i->ctx, NULL)) < 0)
> + return res;
> +
> + AVCodec *codec;
> + if ((res = av_find_best_stream(i->ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0)) < 0)
> + return res;
> +
> + i->videoStreamIndex = res;
> + i->avctx = i->ctx->streams[i->videoStreamIndex]->codec;
> +
> + if ((res = avcodec_open2(i->avctx, codec, NULL)) < 0)
> + return res;
Using the same AVCodecContext for demuxing and decoding is not sane,
and as far as the API is concerned, is deprecated.
> + i->pic = av_frame_alloc();
Missing check. (Well, if anyone even cares about this in a tool like
this.)
> + return 0;
> +}
> +
> +static int read_frame(struct inputStuff *i) {
> + AVPacket pkt;
> + while (!av_read_frame(i->ctx, &pkt)) {
> + int got;
> + if (pkt.stream_index != i->videoStreamIndex)
> + continue;
> + av_dup_packet(&pkt);
Why the dup call? It doesn't seem to ave any purpose. Also, its return
value is not checked.
> + int res = avcodec_decode_video2(i->avctx, i->pic, &got, &pkt);
> + av_free_packet(&pkt);
> + if (res < 0) {
> + return res;
> + } else if (got > 0) {
> + return 0;
> + }
> + }
> +
> + if (i->avctx->codec->capabilities & CODEC_CAP_DELAY ||
> + i->avctx->active_thread_type & FF_THREAD_FRAME)
> + {
The check is unnecessary. Flushing always works; for codecs which don't
support it there's an internal check.
> + do {
> + pkt.data = NULL;
> + pkt.size = 0;
> + int got, res = avcodec_decode_video2(i->avctx, i->pic, &got, &pkt);
> + if (res < 0) {
> + return res;
> + } else if (got > 0) {
> + return 0;
> + } else
> + break;
> + } while (1);
> + }
> +
> + return -1;
> +}
> +
> +int main(int argc, char* argv[])
> +{
> + struct inputStuff f[2];
> + int *temp;
> + uint64_t ssd[3] = {0,0,0};
> + double ssim[3] = {0,0,0};
> + int frames;
> + int i;
> +
> + if( argc<3)
> + {
> + printf("tiny_ssim <file1> <file2>\n");
> + return -1;
> + }
> +
> + av_register_all();
> + if (open_file(&f[0], argv[1]) < 0 ||
> + open_file(&f[1], argv[2]) < 0) {
> + fprintf(stderr, "Failed to open one of the files\n");
> + return -2;
> + }
> +
> + temp = malloc((2*f[0].avctx->width+12)*sizeof(*temp));
> +
> + for( frames=0;; frames++ )
> + {
> + uint64_t ssd_one[3];
> + double ssim_one[3];
> + if (read_frame(&f[0]) < 0 ||
> + read_frame(&f[1]) < 0)
> + break;
> + for( i=0; i<3; i++ )
> + {
> + ssd_one[i] = ssd_plane ( f[0].pic->data[i], f[0].pic->linesize[i],
> + f[1].pic->data[i], f[1].pic->linesize[i],
> + f[0].pic->width>>!!i, f[0].pic->height>>!!i);
> + ssim_one[i] = ssim_plane( f[0].pic->data[i], f[0].pic->linesize[i],
> + f[1].pic->data[i], f[1].pic->linesize[i],
> + f[0].pic->width>>!!i, f[0].pic->height>>!!i,
> + temp, NULL);
> + ssd[i] += ssd_one[i];
> + ssim[i] += ssim_one[i];
> + }
> +
> + printf("Frame %d | ", frames);
> + print_results(ssd_one, ssim_one, 1, f[0].pic->width, f[0].pic->height);
> + printf(" \r");
> + fflush(stdout);
> + }
> +
> + if( !frames ) return 0;
> +
> + printf("Total %d frames | ", frames);
> + print_results(ssd, ssim, frames, f[0].avctx->width, f[0].avctx->height);
> + printf("\n");
> +
> + return 0;
> +}
> diff --git a/tests/tiny_ssim_template.c b/tests/tiny_ssim_template.c
> new file mode 100644
> index 0000000..2588a76
> --- /dev/null
> +++ b/tests/tiny_ssim_template.c
> @@ -0,0 +1,183 @@
> +/*
> + * Copyright (c) 2003-2013 Loren Merritt
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License as published by
> + * the Free Software Foundation; either version 2 of the License, or
> + * (at your option) any later version.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> + * GNU General Public License for more details.
> + *
> + * You should have received a copy of the GNU General Public License
> + * along with this program; if not, write to the Free Software
> + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110 USA
> + */
> +/*
> + * tiny_ssim.c
> + * Computes the Structural Similarity Metric between two rawYV12 video files.
> + * original algorithm:
> + * Z. Wang, A. C. Bovik, H. R. Sheikh and E. P. Simoncelli,
> + * "Image quality assessment: From error visibility to structural similarity,"
> + * IEEE Transactions on Image Processing, vol. 13, no. 4, pp. 600-612, Apr. 2004.
> + *
> + * To improve speed, this implementation uses the standard approximation of
> + * overlapped 8x8 block sums, rather than the original gaussian weights.
> + */
> +
> +#include "config.h"
> +#include <inttypes.h>
> +#include <limits.h>
> +#include <math.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +#define FFSWAP(type,a,b) do{type SWAP_tmp= b; b= a; a= SWAP_tmp;}while(0)
> +#define FFMIN(a,b) ((a) > (b) ? (b) : (a))
> +
> +#define BIT_DEPTH 8
> +#define PIXEL_MAX ((1 << BIT_DEPTH)-1)
> +typedef uint8_t pixel;
> +
> +/****************************************************************************
> + * structural similarity metric
> + ****************************************************************************/
> +static void ssim_4x4x2_core( const pixel *pix1, intptr_t stride1,
> + const pixel *pix2, intptr_t stride2,
> + int sums[2][4] )
> +{
> + int x,y,z;
> +
> + for( z = 0; z < 2; z++ )
> + {
> + uint32_t s1 = 0, s2 = 0, ss = 0, s12 = 0;
> + for( y = 0; y < 4; y++ )
> + for( x = 0; x < 4; x++ )
> + {
> + int a = pix1[x+y*stride1];
> + int b = pix2[x+y*stride2];
> + s1 += a;
> + s2 += b;
> + ss += a*a;
> + ss += b*b;
> + s12 += a*b;
> + }
> + sums[z][0] = s1;
> + sums[z][1] = s2;
> + sums[z][2] = ss;
> + sums[z][3] = s12;
> + pix1 += 4;
> + pix2 += 4;
> + }
> +}
> +
> +static float ssim_end1( int s1, int s2, int ss, int s12 )
> +{
> +/* Maximum value for 10-bit is: ss*64 = (2^10-1)^2*16*4*64 = 4286582784, which will overflow in some cases.
> + * s1*s1, s2*s2, and s1*s2 also obtain this value for edge cases: ((2^10-1)*16*4)^2 = 4286582784.
> + * Maximum value for 9-bit is: ss*64 = (2^9-1)^2*16*4*64 = 1069551616, which will not overflow. */
> +#if BIT_DEPTH > 9
> +#define type float
> + static const float ssim_c1 = .01*.01*PIXEL_MAX*PIXEL_MAX*64;
> + static const float ssim_c2 = .03*.03*PIXEL_MAX*PIXEL_MAX*64*63;
> +#else
> +#define type int
> + static const int ssim_c1 = (int)(.01*.01*PIXEL_MAX*PIXEL_MAX*64 + .5);
> + static const int ssim_c2 = (int)(.03*.03*PIXEL_MAX*PIXEL_MAX*64*63 + .5);
> +#endif
> + type fs1 = s1;
> + type fs2 = s2;
> + type fss = ss;
> + type fs12 = s12;
> + type vars = fss*64 - fs1*fs1 - fs2*fs2;
> + type covar = fs12*64 - fs1*fs2;
> + return (float)(2*fs1*fs2 + ssim_c1) * (float)(2*covar + ssim_c2)
> + / ((float)(fs1*fs1 + fs2*fs2 + ssim_c1) * (float)(vars + ssim_c2));
> +#undef type
> +}
> +
> +static float ssim_end4( int sum0[5][4], int sum1[5][4], int width )
> +{
> + float ssim = 0.0;
> + int i;
> +
> + for( i = 0; i < width; i++ )
> + ssim += ssim_end1( sum0[i][0] + sum0[i+1][0] + sum1[i][0] + sum1[i+1][0],
> + sum0[i][1] + sum0[i+1][1] + sum1[i][1] + sum1[i+1][1],
> + sum0[i][2] + sum0[i+1][2] + sum1[i][2] + sum1[i+1][2],
> + sum0[i][3] + sum0[i+1][3] + sum1[i][3] + sum1[i+1][3] );
> + return ssim;
> +}
> +
> +static float ssim_plane(
> + pixel *pix1, intptr_t stride1,
> + pixel *pix2, intptr_t stride2,
> + int width, int height, void *buf, int *cnt )
> +{
> + int z = 0;
> + int x, y;
> + float ssim = 0.0;
> + int (*sum0)[4] = buf;
> + int (*sum1)[4] = sum0 + (width >> 2) + 3;
> + width >>= 2;
> + height >>= 2;
> + for( y = 1; y < height; y++ )
> + {
> + for( ; z <= y; z++ )
> + {
> + FFSWAP( void*, sum0, sum1 );
> + for( x = 0; x < width; x+=2 )
> + ssim_4x4x2_core( &pix1[4*(x+z*stride1)], stride1, &pix2[4*(x+z*stride2)], stride2, &sum0[x] );
> + }
> + for( x = 0; x < width-1; x += 4 )
> + ssim += ssim_end4( sum0+x, sum1+x, FFMIN(4,width-x-1) );
> + }
> +// *cnt = (height-1) * (width-1);
> + return ssim / ((height-1) * (width-1));
> +}
> +
> +
> +static uint64_t ssd_plane( const uint8_t *pix1, int stridea, const uint8_t *pix2, int strideb, int w, int h )
> +{
> + uint64_t ssd = 0;
> + int x, y;
> +
> + for (y=0;y<h;y++)
> + {
> + for (x=0;x<w;x++)
> + {
> + int d = pix1[x] - pix2[x];
> + ssd += d * d;
> + }
> + pix1 += stridea;
> + pix2 += strideb;
> + }
> + return ssd;
> +}
> +
> +static double ssd_to_psnr( uint64_t ssd, uint64_t denom )
> +{
> + return -10*log((double)ssd/(denom*255*255))/log(10);
> +}
> +
> +static double ssim_db( double ssim, double weight )
> +{
> + return 10*(log(weight)/log(10)-log(weight-ssim)/log(10));
> +}
> +
> +static void print_results(uint64_t ssd[3], double ssim[3], int frames, int w, int h)
> +{
> + printf( "PSNR Y:%.3f U:%.3f V:%.3f All:%.3f | ",
> + ssd_to_psnr( ssd[0], (uint64_t)frames*w*h ),
> + ssd_to_psnr( ssd[1], (uint64_t)frames*w*h/4 ),
> + ssd_to_psnr( ssd[2], (uint64_t)frames*w*h/4 ),
> + ssd_to_psnr( ssd[0] + ssd[1] + ssd[2], (uint64_t)frames*w*h*3/2 ) );
> + printf( "SSIM Y:%.5f U:%.5f V:%.5f All:%.5f (%.5f)",
> + ssim[0] / frames,
> + ssim[1] / frames,
> + ssim[2] / frames,
> + (ssim[0]*4 + ssim[1] + ssim[2]) / (frames*6),
> + ssim_db(ssim[0] * 4 + ssim[1] + ssim[2], frames*6));
> +}
More information about the ffmpeg-devel
mailing list