summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndy Green <andy.green@linaro.org>2012-11-13 12:03:28 +0800
committerAndy Green <andy.green@linaro.org>2012-11-13 12:33:12 +0800
commit062393cccc44efe8ec70f140dc081e8181451a72 (patch)
tree08cc0425cbb45838850cabb0af137adeb3e09bc3
parent2893b6fdb63491eaa85786e53651bf3889dd2429 (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.am2
-rw-r--r--README.build2
-rw-r--r--README.lava-png77
-rw-r--r--configure.ac2
-rw-r--r--lava-png/Makefile.am7
-rw-r--r--lava-png/lava-png.c335
-rw-r--r--lava-png/png-helper.h48
-rw-r--r--lava-png/png.c271
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;
+}
+