validate: Add a gst-validate-images-check tool

Summary:
That is a new tool that uses ssim algorithm to compare images

+ Add a GstValidateVideo internal library adding an helper Gssim class

Depends on D210

Reviewers: Mathieu_Du

Differential Revision: http://phabricator.freedesktop.org/D211
This commit is contained in:
Thibault Saunier 2015-05-25 13:41:04 +02:00
parent bf610de0d6
commit 1dc3816943
12 changed files with 1703 additions and 0 deletions

View file

@ -5,6 +5,7 @@ SUBDIRS = \
data \
gst \
plugins \
gst-libs \
launcher \
tools \
pkgconfig \

View file

@ -185,6 +185,14 @@ PKG_CHECK_MODULES(GDK, gdk-3.0, HAVE_GDK=yes, HAVE_GDK=no)
AC_SUBST(GDK_CFLAGS)
AC_SUBST(GDK_LIBS)
PKG_CHECK_MODULES(CAIRO, "cairo", HAVE_CAIRO=yes, HAVE_CAIRO=no)
AC_SUBST(CAIRO_CFLAGS)
AC_SUBST(CAIRO_LIBS)
AM_CONDITIONAL(HAVE_CAIRO, test ! "x$HAVE_CAIRO" = "xno")
if test "x$HAVE_CAIRO" != "xyes"; then
AC_MSG_NOTICE([Cairo is needed for the gst-validate-images-tool])
fi
dnl checks for gstreamer
AG_GST_CHECK_GST_CHECK($GST_API_VERSION, [$GST_REQ], no)
@ -307,6 +315,9 @@ plugins/Makefile
plugins/fault_injection/Makefile
plugins/gapplication/Makefile
plugins/gtk/Makefile
gst-libs/Makefile
gst-libs/gst/Makefile
gst-libs/gst/video/Makefile
tests/Makefile
tests/check/Makefile
pkgconfig/Makefile

View file

@ -0,0 +1 @@
SUBDIRS = gst

View file

@ -0,0 +1,3 @@
if HAVE_CAIRO
SUBDIRS = video
endif

View file

@ -0,0 +1,12 @@
libgstvalidatevideo_@GST_API_VERSION@_la_SOURCES = gstvalidatessim.c gssim.c
libgstvalidatevideo_@GST_API_VERSION@include_HEADERS = gstvalidatessim.h gssim.h
libgstvalidatevideo_@GST_API_VERSION@_la_CFLAGS = $(GST_ALL_CFLAGS) $(CAIRO_CFLAGS) $(GST_VIDEO_CFLAGS) -I$(top_builddir)
libgstvalidatevideo_@GST_API_VERSION@_la_LIBADD = $(GST_ALL_LIBS) $(CAIRO_LIBS) $(GST_VIDEO_LIBS) $(top_builddir)/gst/validate/libgstvalidate-@GST_API_VERSION@.la
libgstvalidatevideo_@GST_API_VERSION@_la_LDFLAGS = $(GST_ALL_LDFLAGS) $(CAIRO_LDFLAGS) $(GST_VIDEO_LDFLAGS)
lib_LTLIBRARIES = libgstvalidatevideo-@GST_API_VERSION@.la
libgstvalidatevideo_@GST_API_VERSION@includedir = $(includedir)/gstreamer-@GST_API_VERSION@/lib/validate/video
CLEANFILES =

View file

@ -0,0 +1,453 @@
/* GStreamer
*
* Copyright (C) 2014 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>
* Copyright (C) 2015 Raspberry Pi Foundation
* Author: Thibault Saunier <thibault.saunier@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <glib-object.h>
#include <glib.h>
#include <math.h>
#include <stdlib.h>
#include "gssim.h"
typedef gfloat (*SSimWeightFunc) (Gssim * self, gint y, gint x);
typedef struct _SSimWindowCache
{
gint x_window_start;
gint x_weight_start;
gint x_window_end;
gint y_window_start;
gint y_weight_start;
gint y_window_end;
gfloat element_summ;
} SSimWindowCache;
/* *INDENT-OFF* */
G_DEFINE_TYPE (Gssim, gssim, G_TYPE_OBJECT)
/* *INDENT-ON* */
enum
{
PROP_FIRST_PROP = 1,
N_PROPS
};
struct _GssimPriv
{
gint width;
gint height;
gint windowsize;
gint windowtype;
SSimWindowCache *windows;
gfloat *weights;
gfloat const1;
gfloat const2;
gfloat sigma;
gfloat *orgmu;
GstVideoConverter *converter;
GstVideoInfo in_info, out_info;
};
static void
gssim_calculate_mu (Gssim * self, guint8 * buf)
{
gint oy, ox, iy, ix;
for (oy = 0; oy < self->priv->height; oy++) {
for (ox = 0; ox < self->priv->width; ox++) {
gfloat mu = 0;
gfloat elsumm;
gint weight_y_base, weight_x_base;
gint weight_offset;
gint pixel_offset;
gint winstart_y;
gint wghstart_y;
gint winend_y;
gint winstart_x;
gint wghstart_x;
gint winend_x;
gfloat weight;
gint source_offset;
source_offset = oy * self->priv->width + ox;
winstart_x = self->priv->windows[source_offset].x_window_start;
wghstart_x = self->priv->windows[source_offset].x_weight_start;
winend_x = self->priv->windows[source_offset].x_window_end;
winstart_y = self->priv->windows[source_offset].y_window_start;
wghstart_y = self->priv->windows[source_offset].y_weight_start;
winend_y = self->priv->windows[source_offset].y_window_end;
elsumm = self->priv->windows[source_offset].element_summ;
switch (self->priv->windowtype) {
case 0:
for (iy = winstart_y; iy <= winend_y; iy++) {
pixel_offset = iy * self->priv->width;
for (ix = winstart_x; ix <= winend_x; ix++)
mu += buf[pixel_offset + ix];
}
mu = mu / elsumm;
break;
case 1:
weight_y_base = wghstart_y - winstart_y;
weight_x_base = wghstart_x - winstart_x;
for (iy = winstart_y; iy <= winend_y; iy++) {
pixel_offset = iy * self->priv->width;
weight_offset = (weight_y_base + iy) * self->priv->windowsize +
weight_x_base;
for (ix = winstart_x; ix <= winend_x; ix++) {
weight = self->priv->weights[weight_offset + ix];
mu += weight * buf[pixel_offset + ix];
}
}
mu = mu / elsumm;
break;
}
self->priv->orgmu[oy * self->priv->width + ox] = mu;
}
}
}
static gfloat
ssim_weight_func_none (Gssim * self, gint y, gint x)
{
return 1;
}
static gfloat
ssim_weight_func_gauss (Gssim * self, gint y, gint x)
{
gfloat coord = sqrt (x * x + y * y);
return exp (-1 * (coord * coord) / (2 * self->priv->sigma *
self->priv->sigma)) / (self->priv->sigma * sqrt (2 * G_PI));
}
static gboolean
gssim_regenerate_windows (Gssim * self)
{
gint windowiseven;
gint y, x, y2, x2;
SSimWeightFunc func;
gfloat normal_summ = 0;
gint normal_count = 0;
g_free (self->priv->weights);
self->priv->weights =
g_new (gfloat, self->priv->windowsize * self->priv->windowsize);
windowiseven =
((gint) self->priv->windowsize / 2) * 2 == self->priv->windowsize ? 1 : 0;
g_free (self->priv->windows);
self->priv->windows =
g_new (SSimWindowCache, self->priv->height * self->priv->width);
switch (self->priv->windowtype) {
case 0:
func = ssim_weight_func_none;
break;
case 1:
func = ssim_weight_func_gauss;
break;
default:
self->priv->windowtype = 1;
func = ssim_weight_func_gauss;
}
for (y = 0; y < self->priv->windowsize; y++) {
gint yoffset = y * self->priv->windowsize;
for (x = 0; x < self->priv->windowsize; x++) {
self->priv->weights[yoffset + x] =
func (self, x - self->priv->windowsize / 2 + windowiseven,
y - self->priv->windowsize / 2 + windowiseven);
normal_summ += self->priv->weights[yoffset + x];
normal_count++;
}
}
for (y = 0; y < self->priv->height; y++) {
for (x = 0; x < self->priv->width; x++) {
SSimWindowCache win;
gint element_count = 0;
win.x_window_start = x - self->priv->windowsize / 2 + windowiseven;
win.x_weight_start = 0;
if (win.x_window_start < 0) {
win.x_weight_start = -win.x_window_start;
win.x_window_start = 0;
}
win.x_window_end = x + self->priv->windowsize / 2;
if (win.x_window_end >= self->priv->width)
win.x_window_end = self->priv->width - 1;
win.y_window_start = y - self->priv->windowsize / 2 + windowiseven;
win.y_weight_start = 0;
if (win.y_window_start < 0) {
win.y_weight_start = -win.y_window_start;
win.y_window_start = 0;
}
win.y_window_end = y + self->priv->windowsize / 2;
if (win.y_window_end >= self->priv->height)
win.y_window_end = self->priv->height - 1;
win.element_summ = 0;
element_count = (win.y_window_end - win.y_window_start + 1) *
(win.x_window_end - win.x_window_start + 1);
if (element_count == normal_count)
win.element_summ = normal_summ;
else {
for (y2 = win.y_weight_start; y2 < self->priv->windowsize; y2++) {
for (x2 = win.x_weight_start; x2 < self->priv->windowsize; x2++) {
win.element_summ +=
self->priv->weights[y2 * self->priv->windowsize + x2];
}
}
}
self->priv->windows[(y * self->priv->width + x)] = win;
}
}
/* FIXME: while 0.01 and 0.03 are pretty much static, the 255 implies that
* we're working with 8-bit-per-color-component format, which may not be true
*/
self->priv->const1 = 0.01 * 255 * 0.01 * 255;
self->priv->const2 = 0.03 * 255 * 0.03 * 255;
return TRUE;
}
void
gssim_compare (Gssim * self, guint8 * org, guint8 * mod,
guint8 * out, gfloat * mean, gfloat * lowest, gfloat * highest)
{
gint oy, ox, iy, ix;
gfloat cumulative_ssim = 0;
*lowest = G_MAXFLOAT;
*highest = -G_MAXFLOAT;
if (self->priv->windows == NULL)
gssim_regenerate_windows (self);
gssim_calculate_mu (self, org);
for (oy = 0; oy < self->priv->height; oy++) {
for (ox = 0; ox < self->priv->width; ox++) {
gfloat mu_o = 0, mu_m = 0;
gdouble sigma_o = 0, sigma_m = 0, sigma_om = 0;
gfloat tmp1, tmp2;
gfloat elsumm = 0;
gint weight_y_base, weight_x_base;
gint weight_offset;
gint pixel_offset;
gint winstart_y;
gint wghstart_y;
gint winend_y;
gint winstart_x;
gint wghstart_x;
gint winend_x;
gfloat weight;
gint source_offset;
source_offset = oy * self->priv->width + ox;
winstart_x = self->priv->windows[source_offset].x_window_start;
wghstart_x = self->priv->windows[source_offset].x_weight_start;
winend_x = self->priv->windows[source_offset].x_window_end;
winstart_y = self->priv->windows[source_offset].y_window_start;
wghstart_y = self->priv->windows[source_offset].y_weight_start;
winend_y = self->priv->windows[source_offset].y_window_end;
elsumm = self->priv->windows[source_offset].element_summ;
switch (self->priv->windowtype) {
case 0:
for (iy = winstart_y; iy <= winend_y; iy++) {
pixel_offset = iy * self->priv->width;
for (ix = winstart_x; ix <= winend_x; ix++) {
mu_m += mod[pixel_offset + ix];
}
}
mu_m = mu_m / elsumm;
mu_o = self->priv->orgmu[oy * self->priv->width + ox];
for (iy = winstart_y; iy <= winend_y; iy++) {
pixel_offset = iy * self->priv->width;
for (ix = winstart_x; ix <= winend_x; ix++) {
tmp1 = org[pixel_offset + ix] - mu_o;
tmp2 = mod[pixel_offset + ix] - mu_m;
sigma_o += tmp1 * tmp1;
sigma_m += tmp2 * tmp2;
sigma_om += tmp1 * tmp2;
}
}
break;
case 1:
weight_y_base = wghstart_y - winstart_y;
weight_x_base = wghstart_x - winstart_x;
for (iy = winstart_y; iy <= winend_y; iy++) {
pixel_offset = iy * self->priv->width;
weight_offset = (weight_y_base + iy) * self->priv->windowsize +
weight_x_base;
for (ix = winstart_x; ix <= winend_x; ix++) {
weight = self->priv->weights[weight_offset + ix];
mu_o += weight * org[pixel_offset + ix];
mu_m += weight * mod[pixel_offset + ix];
}
}
mu_m = mu_m / elsumm;
mu_o = self->priv->orgmu[oy * self->priv->width + ox];
for (iy = winstart_y; iy <= winend_y; iy++) {
gfloat *weights_with_offset;
guint8 *org_with_offset, *mod_with_offset;
gfloat wt1, wt2;
pixel_offset = iy * self->priv->width;
weight_offset = (weight_y_base + iy) * self->priv->windowsize +
weight_x_base;
weights_with_offset = &self->priv->weights[weight_offset];
org_with_offset = &org[pixel_offset];
mod_with_offset = &mod[pixel_offset];
for (ix = winstart_x; ix <= winend_x; ix++) {
weight = weights_with_offset[ix];
tmp1 = org_with_offset[ix] - mu_o;
tmp2 = mod_with_offset[ix] - mu_m;
wt1 = weight * tmp1;
wt2 = weight * tmp2;
sigma_o += wt1 * tmp1;
sigma_m += wt2 * tmp2;
sigma_om += wt1 * tmp2;
}
}
break;
}
sigma_o = sqrt (sigma_o / elsumm);
sigma_m = sqrt (sigma_m / elsumm);
sigma_om = sigma_om / elsumm;
tmp1 =
(2 * mu_o * mu_m + self->priv->const1) * (2 * sigma_om +
self->priv->const2) / ((mu_o * mu_o + mu_m * mu_m +
self->priv->const1) * (sigma_o * sigma_o + sigma_m * sigma_m +
self->priv->const2));
/* SSIM can go negative, that's why it is
127 + index * 128 instead of index * 255 */
if (out)
out[oy * self->priv->width + ox] = 127 + tmp1 * 128;
*lowest = MIN (*lowest, tmp1);
*highest = MAX (*highest, tmp1);
cumulative_ssim += tmp1;
}
}
*mean = cumulative_ssim / (self->priv->width * self->priv->height);
}
gboolean
gssim_configure (Gssim * self, gint width, gint height)
{
if (width == self->priv->width && height == self->priv->height)
return FALSE;
self->priv->width = width;
self->priv->height = height;
g_free (self->priv->windows);
self->priv->windows = NULL;
if (self->priv->orgmu)
g_free (self->priv->orgmu);
self->priv->orgmu = g_new (gfloat, width * height);
return TRUE;
}
static void
gssim_get_property (GObject * object,
guint property_id, GValue * value, GParamSpec * pspec)
{
//Gssim *self = GSSIM (object);
switch (property_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gssim_set_property (GObject * object,
guint property_id, const GValue * value, GParamSpec * pspec)
{
//Gssim *self = GSSIM (object);
switch (property_id) {
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
break;
}
}
static void
gssim_finalize (GObject * object)
{
Gssim *self = GSSIM (object);
void (*chain_up) (GObject *) =
((GObjectClass *) gssim_parent_class)->finalize;
g_free (self->priv->orgmu);
g_free (self->priv->windows);
chain_up (object);
}
static void
gssim_class_init (GssimClass * klass)
{
GObjectClass *oclass = G_OBJECT_CLASS (klass);
oclass->get_property = gssim_get_property;
oclass->set_property = gssim_set_property;
oclass->finalize = gssim_finalize;
g_type_class_add_private (klass, sizeof (GssimPriv));
}
static void
gssim_init (Gssim * self)
{
self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, GSSIM_TYPE, GssimPriv);
self->priv->windowsize = 11;
self->priv->windowtype = 1;
self->priv->windows = NULL;
self->priv->sigma = 1.5;
}
Gssim *
gssim_new (void)
{
return g_object_new (GSSIM_TYPE, NULL);
}

View file

@ -0,0 +1,62 @@
/* GStreamer
*
* Copyright (C) 2014 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>
* Copyright (C) 2015 Raspberry Pi Foundation
* Author: Thibault Saunier <thibault.saunier@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef _GSSIM_H
#define _GSSIM_H
#include <glib.h>
#include <gst/gst.h>
#include <glib-object.h>
#include <gst/video/video.h>
G_BEGIN_DECLS
typedef struct _GssimPriv GssimPriv;
typedef struct {
GObject parent;
GssimPriv *priv;
} Gssim;
typedef struct {
GObjectClass parent;
} GssimClass;
#define GSSIM_TYPE (gssim_get_type ())
#define GSSIM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSSIM_TYPE, Gssim))
#define GSSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSSIM_TYPE, GssimClass))
#define IS_GSSIM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSSIM_TYPE))
#define IS_GSSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSSIM_TYPE))
#define GSSIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSSIM_TYPE, GssimClass))
GType gssim_get_type (void);
Gssim * gssim_new (void);
void gssim_compare (Gssim * self, guint8 * org, guint8 * mod,
guint8 * out, gfloat * mean, gfloat * lowest,
gfloat * highest);
gboolean gssim_configure (Gssim * self, gint width, gint height);
G_END_DECLS
#endif

View file

@ -0,0 +1,962 @@
/* GStreamer
*
* Copyright (C) 2015 Raspberry Pi Foundation
* Author: Thibault Saunier <thibault.saunier@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <glib-object.h>
#include <glib.h>
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include "gstvalidatessim.h"
#include "gssim.h"
#include <cairo.h>
#include <gio/gio.h>
#include <gst/gst.h>
#include <gst/video/video.h>
#define GST_CAT_DEFAULT gstvalidatessim_debug
GST_DEBUG_CATEGORY_STATIC (GST_CAT_DEFAULT);
#define SIMILARITY_ISSUE_WITH_PREVIOUS g_quark_from_static_string ("ssim::image-not-similar-enough-with-theoretical-reference")
#define SIMILARITY_ISSUE g_quark_from_static_string ("ssim::image-not-similar-enough")
#define GENERAL_INPUT_ERROR g_quark_from_static_string ("ssim::general-file-error")
#define WRONG_FORMAT g_quark_from_static_string ("ssim::wrong-format")
G_DEFINE_TYPE_WITH_CODE (GstValidateSsim, gst_validate_ssim,
G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (GST_TYPE_VALIDATE_REPORTER, NULL));
enum
{
PROP_FIRST_PROP = 1,
PROP_RUNNER,
PROP_LAST
};
typedef struct
{
GstVideoConverter *converter;
GstVideoInfo in_info;
GstVideoInfo out_info;
} SSimConverterInfo;
static void
ssim_convert_info_free (SSimConverterInfo * info)
{
if (info->converter)
gst_video_converter_free (info->converter);
g_slice_free (SSimConverterInfo, info);
}
struct _GstValidateSsimPriv
{
gint width;
gint height;
Gssim *ssim;
GList *converters;
GstVideoInfo out_info;
SSimConverterInfo outconverter_info;
gfloat min_avg_similarity;
gfloat min_lowest_similarity;
GHashTable *ref_frames_cache;
};
static gboolean
gst_validate_ssim_convert (GstValidateSsim * self, SSimConverterInfo * info,
GstVideoFrame * frame, GstVideoFrame * converted_frame)
{
gboolean res = TRUE;
GstBuffer *outbuf = NULL;
g_return_val_if_fail (info != NULL, FALSE);
outbuf = gst_buffer_new_allocate (NULL, info->out_info.size, NULL);
if (!gst_video_frame_map (converted_frame, &info->out_info, outbuf,
GST_MAP_WRITE)) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could not map output converted_frame");
goto fail;
}
gst_video_converter_frame (info->converter, frame, converted_frame);
done:
if (outbuf)
gst_buffer_unref (outbuf);
return res;
fail:
res = FALSE;
goto done;
}
static void
gst_validate_ssim_save_out (GstValidateSsim * self, GstBuffer * buffer,
const gchar * ref_file, const gchar * file, const gchar * outfolder)
{
GstVideoFrame frame, converted;
if (!g_file_test (outfolder, G_FILE_TEST_IS_DIR)) {
if (g_mkdir_with_parents (outfolder, 0755) != 0) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could not create output directory %s", outfolder);
return;
}
}
if (self->priv->outconverter_info.converter == NULL ||
self->priv->width != self->priv->outconverter_info.out_info.width ||
self->priv->height != self->priv->outconverter_info.out_info.height) {
if (self->priv->outconverter_info.converter)
gst_video_converter_free (self->priv->outconverter_info.converter);
gst_video_info_init (&self->priv->outconverter_info.in_info);
gst_video_info_set_format (&self->priv->outconverter_info.in_info,
GST_VIDEO_FORMAT_GRAY8, self->priv->width, self->priv->height);
gst_video_info_init (&self->priv->outconverter_info.out_info);
gst_video_info_set_format (&self->priv->outconverter_info.out_info,
GST_VIDEO_FORMAT_RGBx, self->priv->width, self->priv->height);
self->priv->outconverter_info.converter =
gst_video_converter_new (&self->priv->outconverter_info.in_info,
&self->priv->outconverter_info.out_info, NULL);
}
if (!gst_video_frame_map (&frame, &self->priv->outconverter_info.in_info,
buffer, GST_MAP_READ)) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could not map output frame");
return;
}
if (gst_validate_ssim_convert (self, &self->priv->outconverter_info,
&frame, &converted)) {
cairo_status_t status;
cairo_surface_t *surface;
gchar *bn1 = g_path_get_basename (ref_file);
gchar *bn2 = g_path_get_basename (file);
gchar *fname = g_strdup_printf ("%s.VS.%s.result.png", bn1, bn2);
gchar *outfile = g_build_path (G_DIR_SEPARATOR_S, outfolder, fname, NULL);
surface =
cairo_image_surface_create_for_data (GST_VIDEO_FRAME_PLANE_DATA
(&converted, 0), CAIRO_FORMAT_RGB24, GST_VIDEO_FRAME_WIDTH (&converted),
GST_VIDEO_FRAME_HEIGHT (&converted),
GST_VIDEO_FRAME_PLANE_STRIDE (&converted, 0));
if ((status = cairo_surface_write_to_png (surface, outfile)) !=
CAIRO_STATUS_SUCCESS) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could not save %s" " cairo status is %s", outfile,
cairo_status_to_string (status));
}
cairo_surface_destroy (surface);
gst_video_frame_unmap (&frame);
gst_video_frame_unmap (&converted);
g_free (bn1);
g_free (bn2);
g_free (fname);
g_free (outfile);
}
}
static gboolean
gst_validate_ssim_configure (GstValidateSsim * self, gint width, gint height)
{
if (width == self->priv->width && height == self->priv->height)
return FALSE;
gssim_configure (self->priv->ssim, width, height);
self->priv->width = width;
self->priv->height = height;
gst_video_info_init (&self->priv->out_info);
gst_video_info_set_format (&self->priv->out_info, GST_VIDEO_FORMAT_I420,
self->priv->width, self->priv->height);
return TRUE;
}
static void
gst_validate_ssim_configure_converter (GstValidateSsim * self, gint index,
gboolean force, GstVideoFormat in_format, gint width, gint height)
{
SSimConverterInfo *info = g_list_nth_data (self->priv->converters, index);
if (!info) {
info = g_slice_new0 (SSimConverterInfo);
self->priv->converters =
g_list_insert (self->priv->converters, info, index);
}
if (force || info->in_info.height != height || info->in_info.width != width ||
info->in_info.finfo->format != in_format) {
gst_video_info_init (&info->in_info);
gst_video_info_set_format (&info->in_info, in_format, width, height);
if (info->converter)
gst_video_converter_free (info->converter);
info->out_info = self->priv->out_info;
if (gst_video_info_is_equal (&info->in_info, &info->out_info))
info->converter = NULL;
else
info->converter =
gst_video_converter_new (&info->in_info, &info->out_info, NULL);
}
}
static GstVideoFormat
_get_format_from_surface (cairo_surface_t * surface)
{
#if G_BYTE_ORDER == G_BIG_ENDIAN
if (cairo_surface_get_content (surface) == CAIRO_CONTENT_COLOR_ALPHA)
return GST_VIDEO_FORMAT_BGRA;
else
return GST_VIDEO_FORMAT_BGRx;
#else
if (cairo_surface_get_content (surface) == CAIRO_CONTENT_COLOR_ALPHA)
return GST_VIDEO_FORMAT_ARGB;
else
return GST_VIDEO_FORMAT_RGBx;
#endif
}
void
gst_validate_ssim_compare_frames (GstValidateSsim * self,
GstVideoFrame * ref_frame, GstVideoFrame * frame, GstBuffer ** outbuf,
gfloat * mean, gfloat * lowest, gfloat * highest)
{
gboolean reconf;
guint8 *outdata = NULL;
GstMapInfo map1, map2, outmap;
GstVideoFrame converted_frame1, converted_frame2;
SSimConverterInfo *convinfo1, *convinfo2;
reconf =
gst_validate_ssim_configure (self, ref_frame->info.width,
ref_frame->info.height);
gst_validate_ssim_configure_converter (self, 0, reconf,
ref_frame->info.finfo->format, ref_frame->info.width,
ref_frame->info.height);
gst_validate_ssim_configure_converter (self, 1, reconf,
frame->info.finfo->format, frame->info.width, frame->info.height);
convinfo1 = (SSimConverterInfo *) g_list_nth_data (self->priv->converters, 0);
if (convinfo1->converter)
gst_validate_ssim_convert (self, convinfo1, ref_frame, &converted_frame1);
else
converted_frame1 = *ref_frame;
convinfo2 = (SSimConverterInfo *) g_list_nth_data (self->priv->converters, 0);
if (convinfo2->converter)
gst_validate_ssim_convert (self, convinfo2, frame, &converted_frame2);
else
converted_frame2 = *frame;
if (!gst_buffer_map (converted_frame1.buffer, &map1, GST_MAP_READ)) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could not map reference frame");
return;
}
if (!gst_buffer_map (converted_frame2.buffer, &map2, GST_MAP_READ)) {
gst_buffer_unmap (converted_frame1.buffer, &map1);
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could not map compared frame");
return;
}
if (outbuf) {
*outbuf = gst_buffer_new_and_alloc (GST_ROUND_UP_4 (self->priv->width) *
self->priv->height);
if (!gst_buffer_map (*outbuf, &outmap, GST_MAP_WRITE)) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could not map output frame");
gst_buffer_unref (*outbuf);
gst_buffer_unmap (converted_frame1.buffer, &map1);
gst_buffer_unmap (converted_frame2.buffer, &map2);
*outbuf = NULL;
return;
}
outdata = outmap.data;
}
gssim_compare (self->priv->ssim, map1.data, map2.data, outdata, mean,
lowest, highest);
gst_buffer_unmap (ref_frame->buffer, &map1);
gst_buffer_unmap (frame->buffer, &map2);
if (convinfo1->converter)
gst_video_frame_unmap (&converted_frame1);
if (convinfo2->converter)
gst_video_frame_unmap (&converted_frame2);
if (outbuf)
gst_buffer_unmap (*outbuf, &outmap);
}
static gboolean
gst_validate_ssim_get_frame_from_png (GstValidateSsim * self, const char *file,
GstVideoFrame * frame)
{
guint8 *data;
GstBuffer *buf;
GstVideoInfo info;
cairo_surface_t *surface = NULL;
surface = cairo_image_surface_create_from_png (file);
if (surface == NULL
|| (cairo_surface_status (surface) != CAIRO_STATUS_SUCCESS)) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, "Could not open %s: %s",
file, cairo_status_to_string (cairo_surface_status (surface)));
return FALSE;
}
gst_video_info_init (&info);
gst_video_info_set_format (&info,
_get_format_from_surface (surface),
cairo_image_surface_get_width (surface),
cairo_image_surface_get_height (surface));
data = cairo_image_surface_get_data (surface);
buf = gst_buffer_new_wrapped_full (GST_MEMORY_FLAG_READONLY,
data, info.size, 0, info.size, surface,
(GDestroyNotify) cairo_surface_destroy);
if (!gst_video_frame_map (frame, &info, buf, GST_MAP_READ)) {
gst_buffer_unref (buf);
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could not map input frame");
return FALSE;
}
gst_buffer_unref (buf);
return TRUE;
}
static gboolean
gst_validate_ssim_get_frame_from_file (GstValidateSsim * self, const char *file,
GstVideoFrame * frame)
{
gchar *data;
gsize length;
GstBuffer *buf;
GstVideoInfo info;
GstVideoFormat format;
gint strv_length, width, height;
gboolean res = TRUE;
gchar **splited_name = NULL, **splited_size = NULL, *strformat;
GError *error = NULL;
if (g_str_has_suffix (file, ".png")) {
return gst_validate_ssim_get_frame_from_png (self, file, frame);
}
splited_name = g_strsplit (file, ".", -1);
strv_length = g_strv_length (splited_name);
strformat = splited_name[strv_length - 1];
format = gst_video_format_from_string (strformat);
if (format == GST_VIDEO_FORMAT_UNKNOWN) {
GST_VALIDATE_REPORT (self, WRONG_FORMAT, "Unknown format: %s", strformat);
goto fail;
}
splited_size = g_strsplit (splited_name[strv_length - 2], "x", -1);
if (g_strv_length (splited_size) != 2) {
GST_VALIDATE_REPORT (self, WRONG_FORMAT,
"Can not determine video size from filename: %s ", file);
goto fail;
}
errno = 0;
width = g_ascii_strtoull (splited_size[0], NULL, 10);
if (errno) {
GST_VALIDATE_REPORT (self, WRONG_FORMAT,
"Can not determine video size from filename: %s ", file);
goto fail;
}
errno = 0;
height = g_ascii_strtoull (splited_size[1], NULL, 10);
if (errno) {
GST_VALIDATE_REPORT (self, WRONG_FORMAT,
"Can not determine video size from filename: %s ", file);
goto fail;
}
gst_video_info_init (&info);
gst_video_info_set_format (&info, format, width, height);
if (!g_file_get_contents (file, &data, &length, &error)) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR, "Could not open %s: %s",
file, error->message);
g_error_free (error);
goto fail;
}
buf = gst_buffer_new_wrapped (data, length);
if (!gst_video_frame_map (frame, &info, buf, GST_MAP_READ)) {
gst_buffer_unref (buf);
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could not map input frame");
goto fail;
}
gst_buffer_unref (buf);
done:
g_strfreev (splited_name);
g_strfreev (splited_size);
return res;
fail:
res = FALSE;
goto done;
}
static gboolean
_filename_get_timestamp (GstValidateSsim * self, const gchar * filename,
GstClockTime * ts)
{
guint h, m, s, ns;
gchar *other = g_strdup (filename);
if (sscanf (filename, "%" GST_TIME_FORMAT "%s", &h, &m, &s, &ns, other) < 4) {
GST_INFO_OBJECT (self, "Can not sscanf %s", filename);
g_free (other);
return FALSE;
}
g_free (other);
*ts = (h * 3600 + m * 60 + s) * GST_SECOND + ns;
return TRUE;
}
typedef struct
{
gchar *path;
GstClockTime ts;
} Frame;
static void
_free_frame (Frame * frame)
{
g_free (frame->path);
}
static gint
_sort_frames (Frame * a, Frame * b)
{
if (a->ts < b->ts)
return -1;
if (a->ts == b->ts)
return 0;
return 1;
}
static Frame *
_find_frame (GstValidateSsim * self, GArray * frames, GstClockTime ts,
gboolean get_next)
{
guint i;
Frame *lframe = &g_array_index (frames, Frame, 0);
for (i = 1; i < frames->len; i++) {
Frame *iframe = &g_array_index (frames, Frame, i);
if (ts >= lframe->ts && iframe->ts > ts) {
if (get_next)
return iframe;
return lframe;
} else if (i + 1 == frames->len) {
return iframe;
}
lframe = iframe;
}
return NULL;
}
static GArray *
_get_ref_frame_cache (GstValidateSsim * self, const gchar * ref_file)
{
GFile *ref_dir_file = NULL;
GFileInfo *info;
GFileEnumerator *fenum;
GArray *frames = NULL;
gchar *ref_dir = NULL;
ref_dir = g_path_get_dirname (ref_file);
frames = g_hash_table_lookup (self->priv->ref_frames_cache, ref_file);
if (frames)
goto done;
ref_dir_file = g_file_new_for_path (ref_dir);
if (!(fenum = g_file_enumerate_children (ref_dir_file,
"standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL))) {
GST_INFO ("%s is not a folder", ref_dir);
goto done;
}
for (info = g_file_enumerator_next_file (fenum, NULL, NULL);
info; info = g_file_enumerator_next_file (fenum, NULL, NULL)) {
Frame iframe;
const gchar *display_name = g_file_info_get_display_name (info);
if (!_filename_get_timestamp (self, display_name, &iframe.ts)) {
g_object_unref (info);
continue;
}
iframe.path = g_build_path (G_DIR_SEPARATOR_S,
ref_dir, g_file_info_get_name (info), NULL);
g_object_unref (info);
if (!frames) {
frames = g_array_new (TRUE, TRUE, sizeof (Frame));
g_array_set_clear_func (frames, (GDestroyNotify) _free_frame);
}
g_array_append_val (frames, iframe);
}
if (frames) {
g_array_sort (frames, (GCompareFunc) _sort_frames);
g_hash_table_insert (self->priv->ref_frames_cache, g_strdup (ref_dir),
frames);
}
done:
g_clear_object (&ref_dir_file);
g_free (ref_dir);
return frames;
}
static gchar *
_get_ref_file_path (GstValidateSsim * self, const gchar * ref_file,
const gchar * file, gboolean get_next)
{
Frame *frame;
GArray *frames;
gchar *real_ref_file = NULL, *fbname = NULL;
GstClockTime file_ts;
if (!g_strrstr (ref_file, "*"))
return g_strdup (ref_file);
fbname = g_path_get_basename (file);
if (!_filename_get_timestamp (self, fbname, &file_ts)) {
goto done;
}
frames = _get_ref_frame_cache (self, ref_file);
if (frames) {
frame = _find_frame (self, frames, file_ts, get_next);
if (frame)
real_ref_file = g_strdup (frame->path);
}
done:
g_free (fbname);
return real_ref_file;
}
static gboolean
gst_validate_ssim_compare_image_file (GstValidateSsim * self,
const gchar * ref_file, const gchar * file, gfloat * mean, gfloat * lowest,
gfloat * highest, const gchar * outfolder)
{
GstBuffer *outbuf = NULL, **poutbuf = NULL;
gboolean res = TRUE;
GstVideoFrame ref_frame, frame;
gchar *real_ref_file = NULL;
real_ref_file = _get_ref_file_path (self, ref_file, file, FALSE);
if (!real_ref_file) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"Could find ref file for %s", ref_file);
goto fail;
}
if (!gst_validate_ssim_get_frame_from_file (self, real_ref_file, &ref_frame))
goto fail;
if (!gst_validate_ssim_get_frame_from_file (self, file, &frame)) {
gst_video_frame_unmap (&ref_frame);
goto fail;
}
if (outfolder) {
poutbuf = &outbuf;
}
gst_validate_ssim_compare_frames (self, &ref_frame, &frame,
poutbuf, mean, lowest, highest);
if (*mean < self->priv->min_avg_similarity) {
gst_video_frame_unmap (&ref_frame);
gst_video_frame_unmap (&frame);
if (g_strcmp0 (ref_file, real_ref_file)) {
gchar *tmpref = real_ref_file;
real_ref_file = _get_ref_file_path (self, ref_file, file, TRUE);
GST_VALIDATE_REPORT (self, SIMILARITY_ISSUE_WITH_PREVIOUS,
"\nComparing %s with %s failed, (mean %f "
" min %f), checking next %s\n", tmpref, file,
*mean, *lowest, real_ref_file);
g_free (tmpref);
res = gst_validate_ssim_compare_image_file (self,
real_ref_file, file, mean, lowest, highest, outfolder);
goto done;
}
GST_VALIDATE_REPORT (self, SIMILARITY_ISSUE,
"Average similarity '%f' between %s and %s inferior"
" than the minimum average: %f", *mean,
real_ref_file, file, self->priv->min_avg_similarity);
goto fail;
}
if (*lowest < self->priv->min_lowest_similarity) {
GST_VALIDATE_REPORT (self, SIMILARITY_ISSUE,
"Lowest similarity '%f' between %s and %s inferior"
" than the minimum lowest similarity: %f", *lowest,
real_ref_file, file, self->priv->min_lowest_similarity);
gst_video_frame_unmap (&ref_frame);
gst_video_frame_unmap (&frame);
goto fail;
}
gst_video_frame_unmap (&ref_frame);
gst_video_frame_unmap (&frame);
done:
g_free (real_ref_file);
if (outbuf)
gst_buffer_unref (outbuf);
return res;
fail:
res = FALSE;
if (outbuf)
gst_validate_ssim_save_out (self, outbuf, real_ref_file, file, outfolder);
goto done;
}
static gboolean
_check_directory (GstValidateSsim * self, const gchar * ref_dir,
const gchar * compared_dir, gfloat * mean, gfloat * lowest,
gfloat * highest, const gchar * outfolder)
{
gint nfiles = 0, nnotfound = 0, nfailures = 0;
gboolean res = TRUE;
GFileInfo *info;
GFileEnumerator *fenum;
GFile *file = g_file_new_for_path (ref_dir);
if (!(fenum = g_file_enumerate_children (file,
"standard::*", G_FILE_QUERY_INFO_NONE, NULL, NULL))) {
GST_INFO ("%s is not a folder", ref_dir);
res = FALSE;
goto done;
}
for (info = g_file_enumerator_next_file (fenum, NULL, NULL);
info; info = g_file_enumerator_next_file (fenum, NULL, NULL)) {
if (g_file_info_get_file_type (info) == G_FILE_TYPE_REGULAR ||
g_file_info_get_file_type (info) == G_FILE_TYPE_SYMBOLIC_LINK) {
gchar *compared_file = g_build_path (G_DIR_SEPARATOR_S,
compared_dir, g_file_info_get_name (info), NULL);
gchar *ref_file = NULL;
if (!g_file_test (compared_file, G_FILE_TEST_IS_REGULAR)) {
GST_INFO_OBJECT (self, "Could not find file %s", compared_file);
nnotfound++;
res = FALSE;
} else {
ref_file =
g_build_path (G_DIR_SEPARATOR_S, ref_dir,
g_file_info_get_name (info), NULL);
if (!gst_validate_ssim_compare_image_files (self, ref_file,
compared_file, mean, lowest, highest, outfolder)) {
nfailures++;
res = FALSE;
} else {
nfiles++;
}
}
gst_validate_printf (NULL,
"<position: %s avg: %f min: %f (Passed: %d failed: %d, %d not found)>\r",
g_file_info_get_display_name (info), *mean, *lowest,
nfiles, nfailures, nnotfound);
g_free (compared_file);
g_free (ref_file);
}
g_object_unref (info);
}
done:
gst_object_unref (file);
if (fenum)
gst_object_unref (fenum);
return res;
}
gboolean
gst_validate_ssim_compare_image_files (GstValidateSsim * self,
const gchar * ref_file, const gchar * file, gfloat * mean, gfloat * lowest,
gfloat * highest, const gchar * outfolder)
{
if (g_file_test (ref_file, G_FILE_TEST_IS_DIR)) {
if (!g_file_test (file, G_FILE_TEST_IS_DIR)) {
GST_VALIDATE_REPORT (self, GENERAL_INPUT_ERROR,
"%s is a directory but %s is not", ref_file, file);
return FALSE;
}
return _check_directory (self, ref_file, file, mean, lowest, highest,
outfolder);
} else {
return gst_validate_ssim_compare_image_file (self, ref_file, file, mean,
lowest, highest, outfolder);
}
}
static void
gst_validate_ssim_get_property (GObject * object,
guint property_id, GValue * value, GParamSpec * pspec)
{
switch (property_id) {
case PROP_RUNNER:
/* we assume the runner is valid as long as this scenario is,
* no ref taken */
g_value_set_object (value,
gst_validate_reporter_get_runner (GST_VALIDATE_REPORTER (object)));
break;
default:
break;
}
}
static void
gst_validate_ssim_set_property (GObject * object,
guint property_id, const GValue * value, GParamSpec * pspec)
{
switch (property_id) {
case PROP_RUNNER:
/* we assume the runner is valid as long as this scenario is,
* no ref taken */
gst_validate_reporter_set_runner (GST_VALIDATE_REPORTER (object),
g_value_get_object (value));
break;
default:
break;
}
}
static void
gst_validate_ssim_dispose (GObject * object)
{
GstValidateSsim *self = GST_VALIDATE_SSIM (object);
void (*chain_up) (GObject *) =
((GObjectClass *) gst_validate_ssim_parent_class)->dispose;
gst_object_unref (self->priv->ssim);
chain_up (object);
}
static void
gst_validate_ssim_finalize (GObject * object)
{
GstValidateSsim *self = GST_VALIDATE_SSIM (object);
void (*chain_up) (GObject *) =
((GObjectClass *) gst_validate_ssim_parent_class)->finalize;
g_list_free_full (self->priv->converters,
(GDestroyNotify) ssim_convert_info_free);
if (self->priv->outconverter_info.converter)
gst_video_converter_free (self->priv->outconverter_info.converter);
g_hash_table_unref (self->priv->ref_frames_cache);
chain_up (object);
}
static gpointer
_register_issues (gpointer data)
{
gst_validate_issue_register (gst_validate_issue_new (SIMILARITY_ISSUE,
"Compared images where not similar enough",
"The images checker detected that the images"
" it is comparing did not have the similarity"
" level as defined with min-avg-similarity or"
" min-lowest-similarity", GST_VALIDATE_REPORT_LEVEL_CRITICAL));
gst_validate_issue_register (gst_validate_issue_new
(SIMILARITY_ISSUE_WITH_PREVIOUS,
"Comparison with theoretically reference image failed",
" In a case were we have reference frames with the following"
" timestamps: [0.00, 0.10, 0.20, 0.30] comparing a frame with"
" 0.05 as a timestamp will be done with the first frame. "
" If that fails, it will report a ssim::image-not-similar-enough-with-theoretical-reference"
" warning and try with the second reference frame.",
GST_VALIDATE_REPORT_LEVEL_WARNING));
gst_validate_issue_register (gst_validate_issue_new (GENERAL_INPUT_ERROR,
"Something went wrong handling image files",
"An error accured when working with input files",
GST_VALIDATE_REPORT_LEVEL_CRITICAL));
gst_validate_issue_register (gst_validate_issue_new (WRONG_FORMAT,
"The format or dimensions of the compared images do not match ",
"The format or dimensions of the compared images do not match ",
GST_VALIDATE_REPORT_LEVEL_CRITICAL));
return NULL;
}
static void
gst_validate_ssim_class_init (GstValidateSsimClass * klass)
{
GObjectClass *oclass = G_OBJECT_CLASS (klass);
static GOnce _once = G_ONCE_INIT;
GST_DEBUG_CATEGORY_INIT (gstvalidatessim_debug, "validatessim", 0,
"Validate ssim plugin");
oclass->get_property = gst_validate_ssim_get_property;
oclass->set_property = gst_validate_ssim_set_property;
oclass->dispose = gst_validate_ssim_dispose;
oclass->finalize = gst_validate_ssim_finalize;
g_type_class_add_private (klass, sizeof (GstValidateSsimPriv));
g_once (&_once, _register_issues, NULL);
g_object_class_install_property (oclass, PROP_RUNNER,
g_param_spec_object ("validate-runner", "VALIDATE Runner",
"The Validate runner to " "report errors to",
GST_TYPE_VALIDATE_RUNNER,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE));
}
static void
gst_validate_ssim_init (GstValidateSsim * self)
{
self->priv =
G_TYPE_INSTANCE_GET_PRIVATE (self, GST_VALIDATE_SSIM_TYPE,
GstValidateSsimPriv);
self->priv->ssim = gssim_new ();
self->priv->ref_frames_cache = g_hash_table_new_full (g_str_hash,
g_str_equal, g_free, (GDestroyNotify) g_array_unref);
}
GstValidateSsim *
gst_validate_ssim_new (GstValidateRunner * runner,
gfloat min_avg_similarity, gfloat min_lowest_similarity)
{
GstValidateSsim *self =
g_object_new (GST_VALIDATE_SSIM_TYPE, "validate-runner", runner, NULL);
self->priv->min_avg_similarity = min_avg_similarity;
self->priv->min_lowest_similarity = min_lowest_similarity;
gst_validate_reporter_set_name (GST_VALIDATE_REPORTER (self),
g_strdup ("gst-validate-images-checker"));
return self;
}

View file

@ -0,0 +1,73 @@
/* GStreamer
*
* Copyright (C) 2014 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>
* Copyright (C) 2015 Raspberry Pi Foundation
* Author: Thibault Saunier <thibault.saunier@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifndef _GST_VALIDATE_SSIM_H
#define _GST_VALIDATE_SSIM_H
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <glib.h>
#include <gst/gst.h>
#include <glib-object.h>
#include <gst/video/video.h>
#include <gst/validate/validate.h>
G_BEGIN_DECLS
typedef struct _GstValidateSsimPriv GstValidateSsimPriv;
typedef struct {
GObject parent;
GstValidateSsimPriv *priv;
} GstValidateSsim;
typedef struct {
GObjectClass parent;
} GstValidateSsimClass;
#define GST_VALIDATE_SSIM_TYPE (gst_validate_ssim_get_type ())
#define GST_VALIDATE_SSIM(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GST_VALIDATE_SSIM_TYPE, GstValidateSsim))
#define GST_VALIDATE_SSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GST_VALIDATE_SSIM_TYPE, GstValidateSsimClass))
#define IS_GST_VALIDATE_SSIM(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GST_VALIDATE_SSIM_TYPE))
#define IS_GST_VALIDATE_SSIM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GST_VALIDATE_SSIM_TYPE))
#define GST_VALIDATE_SSIM_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GST_VALIDATE_SSIM_TYPE, GstValidateSsimClass))
GType gst_validate_ssim_get_type (void);
GstValidateSsim * gst_validate_ssim_new (GstValidateRunner *runner,
gfloat min_avg_similarity,
gfloat min_lowest_similarity);
gboolean gst_validate_ssim_compare_image_files (GstValidateSsim *self, const gchar *ref_file,
const gchar * file, gfloat * mean, gfloat * lowest,
gfloat * highest, const gchar *outfolder);
void gst_validate_ssim_compare_frames (GstValidateSsim * self, GstVideoFrame *ref_frame,
GstVideoFrame *frame, GstBuffer **outbuf,
gfloat * mean, gfloat * lowest, gfloat * highest);
G_END_DECLS
#endif

View file

@ -1,6 +1,8 @@
gst-validate-1.0
gst-validate-transcoding-1.0
gst-validate-media-check-1.0
gst-validate-images-check-1.0
gst-validate-1.0-debug
gst-validate-media-check-1.0-debug
gst-validate-transcoding-1.0-debug
gst-validate-images-check-1.0-debug

View file

@ -34,4 +34,18 @@ gst_validate_media_check_@GST_API_VERSION@_SOURCES = gst-validate-media-check.c
gst_validate_media_check_@GST_API_VERSION@_debug_SOURCES = gst-validate-media-check.c
gst_validate_media_check_@GST_API_VERSION@_debug_LDFLAGS = -no-install
if HAVE_CAIRO
bin_PROGRAMS += gst-validate-images-check-@GST_API_VERSION@
noinst_PROGRAMS += gst-validate-images-check-@GST_API_VERSION@-debug
gst_validate_images_check_@GST_API_VERSION@_SOURCES = gst-validate-images-check.c
gst_validate_images_check_@GST_API_VERSION@_CFLAGS = $(AM_CFLAGS) -I$(top_builddir)/gst-libs/gst/video/
gst_validate_images_check_@GST_API_VERSION@_LDADD = $(LDADD) $(top_builddir)/gst-libs/gst/video/libgstvalidatevideo-@GST_API_VERSION@.la
gst_validate_images_check_@GST_API_VERSION@_debug_SOURCES = gst-validate-images-check.c
gst_validate_images_check_@GST_API_VERSION@_debug_CFLAGS = $(AM_CFLAGS) -I$(top_builddir)/gst-libs/gst/video/
gst_validate_images_check_@GST_API_VERSION@_debug_LDADD = $(LDADD) $(top_builddir)/gst-libs/gst/video/libgstvalidatevideo-@GST_API_VERSION@.la
gst_validate_images_check_@GST_API_VERSION@_debug_LDFLAGS = -no-install
endif
CLEANFILES = $(bin_SCRIPTS)

View file

@ -0,0 +1,109 @@
/* GStreamer
*
* Copyright (C) 2014 Mathieu Duponchelle <mathieu.duponchelle@opencreed.com>
* Copyright (C) 2015 Raspberry Pi Foundation
* Author: Thibault Saunier <thibault.saunier@collabora.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <gstvalidatessim.h>
#include <gst/gst.h>
#include <gst/validate/validate.h>
#include <gst/video/video.h>
int
main (int argc, char **argv)
{
GstValidateSsim *ssim;
gint rep_err, ret = 0;
GError *err = NULL;
GstValidateRunner *runner = NULL;
GOptionContext *ctx;
const gchar *outfolder = NULL;
gfloat mssim = 0, lowest = 1, highest = -1;
gdouble min_avg_similarity = 0.95, min_lowest_similarity = -1.0;
GOptionEntry options[] = {
{"min-avg-similarity", 'a', 0, G_OPTION_ARG_DOUBLE,
&min_avg_similarity,
"The minimum average similarity under which we consider"
"the test as failing",
NULL},
{"min-lowest-similarity", 'l', 0, G_OPTION_ARG_DOUBLE,
&min_lowest_similarity,
"The minimum 'lowest' similarity under which we consider"
"the test as failing",
NULL},
{"result-output-folder", 'r', 0, G_OPTION_ARG_STRING,
&outfolder,
"The folder in which to store resulting grey scale images"
" when the test failed. In that folder you will find"
" images with the structural difference between"
" the reference frame and the failed one",
NULL},
{NULL}
};
g_set_prgname ("gst-validate-mages-check-" GST_API_VERSION);
ctx = g_option_context_new ("/reference/file/path /compared/file/path");
g_option_context_set_summary (ctx,
"The gst-validate-images-check calculates SSIM (Structural SIMilarity) "
" index for the images. And according to min-lowest-similarity and"
" min-avg-similarity, it will concider the images similare enough"
" or report critical issues in the GstValidate reporting system");
g_option_context_add_main_entries (ctx, options, NULL);
if (!g_option_context_parse (ctx, &argc, &argv, &err)) {
g_printerr ("Error initializing: %s\n", err->message);
g_option_context_free (ctx);
return -1;
}
if (argc != 3) {
gchar *help = g_option_context_get_help (ctx, FALSE, NULL);
g_printerr ("%s", help);
g_free (help);
g_option_context_free (ctx);
return -1;
}
gst_init (&argc, &argv);
gst_validate_init ();
runner = gst_validate_runner_new ();
ssim =
gst_validate_ssim_new (runner, min_avg_similarity, min_lowest_similarity);
gst_validate_ssim_compare_image_files (ssim, argv[1], argv[2], &mssim,
&lowest, &highest, outfolder);
rep_err = gst_validate_runner_exit (runner, TRUE);
if (ret == 0) {
ret = rep_err;
if (rep_err != 0)
g_print ("Returning %d as error where found", rep_err);
}
g_object_unref (ssim);
g_object_unref (runner);
gst_validate_deinit ();
return ret;
}