diff options
author | Andy Green <andy.green@linaro.org> | 2012-11-13 12:03:28 +0800 |
---|---|---|
committer | Andy Green <andy.green@linaro.org> | 2012-11-13 12:33:12 +0800 |
commit | 062393cccc44efe8ec70f140dc081e8181451a72 (patch) | |
tree | 08cc0425cbb45838850cabb0af137adeb3e09bc3 | |
parent | 2893b6fdb63491eaa85786e53651bf3889dd2429 (diff) |
introduce lava-png still frame assessment
This adds a second executable that does 2D FFTs on two PNGs and
generates a FoM representing the degree of similarity between
the spectra.
Signed-off-by: Andy Green <andy.green@linaro.org>
-rw-r--r-- | Makefile.am | 2 | ||||
-rw-r--r-- | README.build | 2 | ||||
-rw-r--r-- | README.lava-png | 77 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | lava-png/Makefile.am | 7 | ||||
-rw-r--r-- | lava-png/lava-png.c | 335 | ||||
-rw-r--r-- | lava-png/png-helper.h | 48 | ||||
-rw-r--r-- | lava-png/png.c | 271 |
8 files changed, 742 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am index 049def1..ec66ec2 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1 +1 @@ -SUBDIRS = lava-fft +SUBDIRS = lava-fft lava-png diff --git a/README.build b/README.build index 83e2ae2..e29ad12 100644 --- a/README.build +++ b/README.build @@ -8,6 +8,8 @@ Dependencies (packaged in Ubuntu and Fedora at least): - libtool - libfftw3 (aka fftw3 in Fedora) - libfftw3-dev (aka fftw3-devel in Fedora) + - libpng + - libpng-dev Build procedure: diff --git a/README.lava-png b/README.lava-png new file mode 100644 index 0000000..0113e67 --- /dev/null +++ b/README.lava-png @@ -0,0 +1,77 @@ +Introduction +------------ + +lava-png is a tool for comparing png files with each other in the frequency +domain and arriving at a "Figure of Merit" describing how similar they are. + +If you compare the same png to itself, the FoM is a perfect 0.0. Any +differences between the images will tend to increase the FoM result. + +The FoM is issued as a single float on stdout, which is the worst (highest) +result from any of the individual colour channel results. + + +Restrictions +------------ + +The two pngs being compared must have the same dimensions and number of +colour channels. Otherwise it supports any dimensions, 8 and 16 bits/channel +pngs, and number of channels. + + +Usage +----- + +$ cat captured.png | lava-png reference.png + + +Extract single frames for h.264 mov +----------------------------------- + +$ mkdir -p capture +$ ffmpeg -i $INPUT_FILE -frames $NUM_FRAMES -sameq -f image2 capture/sent-%05d.png + + +Considerations about media sources +---------------------------------- + +Eg, h.264 decode from different codecs may give slightly different results. In +a digital-digital capture like HDMI the FoM can potentially be always exactly +0.000... to acheive that you'll need to use a "golden capture" as the reference +instead of the original file. That already has the acceptable set of codec +artifacts stored in the reference. + +The other alternative is to use a pristine source as the reference, a different +(ffmpeg) PC-side codec to extract frames and store a golden error number that +represents "correct" delta between the codecs. + +Some tests (3D unit synthetic tests, composed desktop) don't exist as a source +file. For those you'll need a golden capture as the reference. + + +Fault simulation +---------------- + +There's an optional --fault / -f switch which takes a fault index, and +messes with the captured png before comparing it. + + --fault 1 - simulates image capture off by 1 pixel to the right + --fault 2 - simulates low probability random noise pixels + + +Performance +----------- + +Compares using frame 360 of big buck bunny 1080p h.264 + + 0.000 same file + + 0.022 -t 2 (simulated two noise pixels in the frame) + + 0.098 -t 1 (simulated shifted right by one pixel) + + 0.451 compare against frame 359 instead + + 0.589 png -> JPG in gimp -> png + + 1.000 compare against frame 1 instead (all black frame) diff --git a/configure.ac b/configure.ac index b827916..c654556 100644 --- a/configure.ac +++ b/configure.ac @@ -31,7 +31,7 @@ AC_FUNC_MALLOC AC_FUNC_REALLOC #AC_CHECK_FUNCS([poll memset ]) -AC_CONFIG_FILES([Makefile lava-fft/Makefile]) +AC_CONFIG_FILES([Makefile lava-fft/Makefile lava-png/Makefile]) AC_OUTPUT diff --git a/lava-png/Makefile.am b/lava-png/Makefile.am new file mode 100644 index 0000000..3f05ed8 --- /dev/null +++ b/lava-png/Makefile.am @@ -0,0 +1,7 @@ +bin_PROGRAMS=lava-png +lava_png_SOURCES=lava-png.c png.c +lava_png_CFLAGS=-fPIC -Wall -Werror -D_FORTIFY_SOURCE=2 -fstack-protector -std=gnu99 -pedantic -DINSTALL_DATADIR=\"${datarootdir}\" +lava_png_LDFLAGS=-fPIC +lava_png_LDADD=-lfftw3 -lm -lpng + + diff --git a/lava-png/lava-png.c b/lava-png/lava-png.c new file mode 100644 index 0000000..dae5652 --- /dev/null +++ b/lava-png/lava-png.c @@ -0,0 +1,335 @@ +/* + * Author: Andy Green <andy.green@linaro.org> + * Copyright (C) 2012 Linaro, LTD + * + * 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-1301, USA. + * + */ + +#include <unistd.h> +#include <stdlib.h> +#include <stdio.h> +#include "./png-helper.h" +#include <fftw3.h> +#include <math.h> +#include <getopt.h> +#include <termio.h> +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +void add_faults(struct png *png, int faults, int fftx) +{ + int n, x, y, i, count = 0; + unsigned char *row; + + switch (faults) { + case 1: /* shifted right 1 pixel, rightmost lost, leftmost black */ + n = 0; + for (y = 0; y < png->height; y++) { + row = png->rows[y] + ((png->width - 1) * png->bytespp * png->channels); + for (x = png->width - 1; x > 0; x--) { + for (i = 0; i < png->channels; i++) { + switch (png->bytespp) { + case 1: + *row = *(row - (png->bytespp * png->channels)); + row--; + break; + case 2: + *(unsigned short *)row = *(unsigned short *)(row - (png->bytespp * png->channels * 2)); + row -= 2; + break; + } + } + } + switch (png->bytespp) { + case 1: + *row = 0; + break; + case 2: + *(unsigned short *)row = 0; + break; + } + + n += fftx; + } + break; + + case 2: /* low probability random noise */ + n = 0; + for (y = 0; y < png->height; y++) { + + if (rand() < RAND_MAX - RAND_MAX / 2048) { + n += fftx; + continue; + } + + row = png->rows[y] + ((png->width - 1) * png->bytespp * png->channels); + for (x = png->width - 1; x > 0; x--) { + + if (rand() < RAND_MAX - RAND_MAX / 2048) + continue; + + count++; + for (i = 0; i < png->channels; i++) { + switch (png->bytespp) { + case 1: + *row = rand(); + row--; + break; + case 2: + *(unsigned short *)row = rand(); + row -=2; + break; + } + } + } + + n += fftx; + } + fprintf(stderr, "Fault 2 added %d noise pixels\n", count); + break; + } +} + +fftw_complex * alloc_2d(int width, int height) +{ + return fftw_malloc(sizeof(fftw_complex) * width * height); +} + +int nearest_2n(int n) +{ + int i = 1 << 30; + + while (i > 1) { + if (n & i) { + if (n == i) + return i; + return i << 1; + } + i >>= 1; + } + return 1; +} + +int load_fft_data(struct png *png, fftw_complex *data, fftw_complex *result, + int fftx, int ffty, int channel) +{ + int x, y, n; + unsigned char *row; + fftw_plan plan; + + plan = fftw_plan_dft_2d(fftx, ffty, data, result, + FFTW_FORWARD, FFTW_ESTIMATE); + if (plan == NULL) { + fprintf(stderr, "Failed to get plan\n"); + return 9; + } + + n = 0; + for (y = 0; y < png->height; y++) { + row = png->rows[y] + (png->bytespp * channel); + for (x = 0; x < png->width; x++) { + + switch (png->bytespp) { + case 1: + data[n + x][0] = *row; + break; + case 2: + data[n + x][0] = *(unsigned short *)row; + break; + } + + row += png->bytespp * png->channels; + } + n += fftx; + } + + fftw_execute(plan); + fftw_destroy_plan(plan); + + return 0; +} + +static struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "fault", required_argument, NULL, 'f' }, + +}; + +int main(int argc, char **argv) +{ + struct png png, png_ref; + fftw_complex *data, *ref_fft_result, *fft_result; + int i, n = 1, x, y; + double m0, m1, m2, delta, fom; + int fftx, ffty; + int fault = 0; + + while (n >= 0) { + n = getopt_long(argc, argv, "hf:", options, NULL); + if (n < 0) + continue; + switch (n) { + case 'f': + fault = atoi(optarg); + break; + default: + case 'h': +usage: + fprintf(stderr, + "Usage: \n" + "Assess capture against reference png\n" + " cat capture.png | lava-video ref.png\n" + ); + return 1; + } + } + + /* capture image is always piped in */ + + if (isatty(0)) + goto usage; + + /* reference image path always required */ + + if (argc <= optind) + goto usage; + + create_png(&png); + create_png(&png_ref); + + /* pull in capture png */ + + if (read_png_file("stdin", &png)) + return 2; + + fftx = nearest_2n(png.width); + ffty = nearest_2n(png.height); + + /* if requested, synthesize faults with it */ + + add_faults(&png, fault, fftx); + + /* pull in reference png */ + + if (read_png_file(argv[optind], &png_ref)) + return 60; + + /* check pngs are compatible */ + + if (png_ref.channels != png.channels) { + fprintf(stderr, "Txform channels mismatch\n"); + return 63; + } + if (png_ref.width != png.width) { + fprintf(stderr, "Txform width mismatch\n"); + return 63; + } + if (png_ref.height != png.height) { + fprintf(stderr, "Txform height mismatch\n"); + return 63; + } + + /* allocate fft arrays */ + + ref_fft_result = alloc_2d(fftx, ffty); + if (ref_fft_result == NULL) { + fprintf(stderr, "Failed to allocate fft array\n"); + return 9; + } + fft_result = alloc_2d(fftx, ffty); + if (fft_result == NULL) { + fprintf(stderr, "Failed to allocate fft array\n"); + return 9; + } + data = alloc_2d(fftx, ffty); + if (data == NULL) { + fprintf(stderr, "Failed to allocate fft array\n"); + return 9; + } + + fom = 0; + + /* + * for each colour channel + */ + + for (i = 0; i < png.channels; i++) { + + /* clear down 2^n FFT vs image margins */ + + for (y = 0; y < sizeof(data) / sizeof(data[0]); y++) { + data[y][0] = 0; + data[y][0] = 1; + } + + load_fft_data(&png, &data[0], &fft_result[0], fftx, ffty, i); + load_fft_data(&png_ref, &data[0], &ref_fft_result[0], + fftx, ffty, i); + + /* + * assess what we computed vs reference + */ + + delta = 0; + n = 0; + for (y = 0; y < ffty / 2; y++) { + for (x = 0; x < fftx / 2; x++) { + m1 = sqrt((fft_result[n + x][0] * + fft_result[n + x][0]) + + (fft_result[n + x][1] * + fft_result[n + x][1])); + + m2 = sqrt((ref_fft_result[n + x][0] * + ref_fft_result[n + x][0]) + + (ref_fft_result[n + x][1] * + ref_fft_result[n + x][1])); + + m0 = m1 - m2; + if (m0 < 0) + m0 = -m0; + delta += m0 / m2; + } + n += fftx; + } + + delta /= ffty / 2 * fftx / 2; + + fprintf(stderr, "Ch%d: %f\n", i, delta); + if (delta > fom) + fom = delta; + } + + /* isssue worst channel FoM */ + + printf("%.3f\n", fom); + + /* cleanup */ + + destroy_png(&png); + destroy_png(&png_ref); + + fftw_free(data); + fftw_free(fft_result); + fftw_free(ref_fft_result); + + return 0; +} + diff --git a/lava-png/png-helper.h b/lava-png/png-helper.h new file mode 100644 index 0000000..640cf1c --- /dev/null +++ b/lava-png/png-helper.h @@ -0,0 +1,48 @@ +/* + * Author: Andy Green <andy.green@linaro.org> + * Copyright (C) 2012 Linaro, LTD + * + * 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-1301, USA. + * + */ + +#include <png.h> + +struct png { + int width; + int height; + png_byte color_type; + png_byte bit_depth; + int number_of_passes; + png_bytep *rows; + + png_structp png_ptr; + png_infop info_ptr; + + /* filled in */ + + unsigned long bytes_per_row; + int channels; + int bytespp; +}; + +extern void create_png(struct png *png); +extern void destroy_png(struct png *png); +extern int read_png_file(char *file_name, struct png *png); +extern int write_png_file(char *file_name, struct png *png); +extern int allocate_png(struct png *png, int width, int height); +extern int png_get_channel_bytes(struct png *png); + diff --git a/lava-png/png.c b/lava-png/png.c new file mode 100644 index 0000000..0f926ad --- /dev/null +++ b/lava-png/png.c @@ -0,0 +1,271 @@ +/* + * PNG access code based on example from http://zarb.org/~gc/html/libpng.html + * which had the attribution message --> + * + * Copyright 2002-2010 Guillaume Cottenceau. + * + * This software may be freely redistributed under the terms + * of the X11 license. + * + * <-- note that version has leak and other problems, This version is + * under GPLv2. + * + * Original Author: Guillaume Cottenceau + * Author: Andy Green <andy.green@linaro.org> + * Copyright 2002-2010 Guillaume Cottenceau. + * Copyright (C) 2012 Linaro, LTD + * + * 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-1301, USA. + * + */ + +#include <unistd.h> +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include "./png-helper.h" + +#define PNG_DEBUG 3 + + +void create_png(struct png *png) +{ + memset(png, 0, sizeof png); + + png->color_type = PNG_COLOR_TYPE_RGB; + png->bit_depth = 16; + png->number_of_passes = 1; +} + +void destroy_png(struct png *png) +{ + int y; + + /* cleanup heap allocation */ + for (y = 0; y < png->height; y++) + free(png->rows[y]); + + free(png->rows); + + png_destroy_write_struct(&png->png_ptr, &png->info_ptr); +} + + +int allocate_png(struct png *png, int width, int height) +{ + int y; + png_byte *p; + + png->width = width; + png->height = height; + + /* initialize stuff */ + png->png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + + if (!png->png_ptr) { + fprintf(stderr, "png_create_write_struct failed"); + return 2; + } + + png->info_ptr = png_create_info_struct(png->png_ptr); + if (!png->info_ptr) { + fprintf(stderr, "png_create_info_struct failed"); + png_destroy_write_struct(&png->png_ptr, NULL); + return 3; + } + + png_set_IHDR(png->png_ptr, png->info_ptr, + png->width, + png->height, + png->bit_depth, + png->color_type, + PNG_INTERLACE_NONE, + PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + png->rows = (png_bytep*)malloc(sizeof(png_bytep) * png->height); + if (png->rows == NULL) { + png_destroy_write_struct(&png->png_ptr, &png->info_ptr); + return 1; + } + + png->bytes_per_row = png_get_rowbytes(png->png_ptr, png->info_ptr); + png->channels = png_get_channels(png->png_ptr, png->info_ptr); + png->bytespp = png->bytes_per_row / png->channels / png->width; + + for (y = 0; y < png->height; y++) { + p = (png_byte *)malloc(png->bytes_per_row); + if (p == NULL) { + while (y--) + free(png->rows[y]); + free(png->rows); + png->rows = NULL; + png_destroy_write_struct(&png->png_ptr, &png->info_ptr); + return 2; + } + png->rows[y] = p; + } + + return 0; +} + +int read_png_file(char *file_name, struct png *png) +{ + unsigned char header[8]; + png_structp png_ptr = NULL; + png_infop info_ptr = NULL; + int ret = 0; + FILE *fp = stdin; + + /* open file and test for it being a png */ + + if (strcmp(file_name, "stdin")) + fp = fopen(file_name, "rb"); + if (!fp) { + fprintf(stderr, "File %s could not be opened for reading", + file_name); + return 1; + } + if (fread(header, 1, sizeof header, fp) != sizeof header) { + fprintf(stderr, "Problem reading magic\n"); + ret = 2; + goto bail1; + } + if (png_sig_cmp(header, 0, 8)) { + fprintf(stderr, "File %s is not recognized as a PNG file", + file_name); + ret = 3; + goto bail1; + } + + /* initialize stuff */ + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, + NULL, NULL, NULL); + + if (!png_ptr) { + fprintf(stderr, "png_create_read_struct failed"); + ret = 4; + goto bail1; + } + + info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + fprintf(stderr, "png_create_info_struct failed"); + ret = 5; + goto bail1; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + fprintf(stderr, "Error during init_io"); + ret = 6; + goto bail1; + } + + png_init_io(png_ptr, fp); + png_set_sig_bytes(png_ptr, 8); + + png_read_info(png_ptr, info_ptr); + + png->width = png_get_image_width(png_ptr, info_ptr); + png->height = png_get_image_height(png_ptr, info_ptr); + png->color_type = png_get_color_type(png_ptr, info_ptr); + png->bit_depth = png_get_bit_depth(png_ptr, info_ptr); + + png->number_of_passes = png_set_interlace_handling(png_ptr); + png_read_update_info(png_ptr, info_ptr); + + + /* read file */ + if (setjmp(png_jmpbuf(png_ptr))) { + fprintf(stderr, "Error during read_image"); + ret = 7; + goto bail1; + } + + if (allocate_png(png, png->width, png->height)) { + fprintf(stderr, "Error during png memory allocation\n"); + ret = 8; + goto bail1; + } + + png_read_image(png_ptr, png->rows); + +bail1: + png_destroy_read_struct(&png_ptr, &info_ptr, NULL); + fclose(fp); + + return ret; +} + + +int write_png_file(char *file_name, struct png *png) +{ + FILE *fp; + int ret = 0; + + if (strcmp(file_name, "stdout") == 0) + fp = stdout; + else + fp = fopen(file_name, "wb"); + if (!fp) { + fprintf(stderr, "File %s could not be opened for writing", + file_name); + return 1; + } + + if (setjmp(png_jmpbuf(png->png_ptr))) { + fprintf(stderr, "Error during init_io"); + ret = 4; + goto bail1; + } + + png_init_io(png->png_ptr, fp); + + /* write header */ + if (setjmp(png_jmpbuf(png->png_ptr))) { + fprintf(stderr, "Error during writing header"); + ret = 5; + goto bail1; + } + + png_write_info(png->png_ptr, png->info_ptr); + + /* write bytes */ + if (setjmp(png_jmpbuf(png->png_ptr))) { + fprintf(stderr, "Error during writing bytes"); + ret = 6; + goto bail1; + } + + png_write_image(png->png_ptr, png->rows); + + /* end write */ + if (setjmp(png_jmpbuf(png->png_ptr))) { + fprintf(stderr, "Error during end of write"); + ret = 7; + goto bail1; + } + + png_write_end(png->png_ptr, NULL); + +bail1: + if (fp != stdout) + fclose(fp); + + return ret; +} + |