diff options
author | Mathieu Duponchelle <mathieu.duponchelle@opencreed.com> | 2016-11-16 13:18:53 -0300 |
---|---|---|
committer | Thibault Saunier <thibault.saunier@osg.samsung.com> | 2016-11-17 13:25:33 -0300 |
commit | 087d9bc37606e98fc4413bfa2baa1d9e9efda9bf (patch) | |
tree | 3a8837a8261f2e9412044955a888768598b31545 | |
parent | a7b0669ce8fe62e1584ff582fe000c2538e808cc (diff) |
Adds a new Image Quality Assessment plugin.
It only offers one metric for now, "dssim", available if
https://github.com/pornel/dssim was installed on the system
at the time the plugin was compiled.
The spearman correlation for dssim against the TID2008 dataset
is 0.81, against 0.70 for the old ssim implementation, and
it runs 15 times faster.
https://bugzilla.gnome.org/show_bug.cgi?id=751324
-rw-r--r-- | configure.ac | 19 | ||||
-rw-r--r-- | docs/plugins/Makefile.am | 1 | ||||
-rw-r--r-- | docs/plugins/gst-plugins-bad-plugins-docs.sgml | 1 | ||||
-rw-r--r-- | docs/plugins/gst-plugins-bad-plugins-sections.txt | 12 | ||||
-rw-r--r-- | ext/Makefile.am | 7 | ||||
-rw-r--r-- | ext/iqa/Makefile.am | 27 | ||||
-rw-r--r-- | ext/iqa/iqa.c | 343 | ||||
-rw-r--r-- | ext/iqa/iqa.h | 64 |
8 files changed, 474 insertions, 0 deletions
diff --git a/configure.ac b/configure.ac index c48b86cda..35ef31f8a 100644 --- a/configure.ac +++ b/configure.ac @@ -358,6 +358,23 @@ AC_SUBST(EXIF_LIBS) AC_SUBST(EXIF_CFLAGS) AM_CONDITIONAL(USE_EXIF, test "x$HAVE_EXIF" = "xyes") +AG_GST_CHECK_FEATURE(IQA, [iqa], iqa , [ + PKG_CHECK_MODULES(DSSIM, dssim, [ + HAVE_DSSIM="yes" + HAVE_IQA="yes" + ], [ + HAVE_DSSIM="no" + HAVE_IQA="no" + ]) + + AM_CONDITIONAL(HAVE_DSSIM, test "x$HAVE_DSSIM" = "xyes") + if test "x$HAVE_DSSIM" = "xyes"; then + AC_DEFINE(HAVE_DSSIM, 1, [Define if you have dssim library]) + fi + AC_SUBST(DSSIM_LIBS) + AC_SUBST(DSSIM_CFLAGS) +]) + dnl Orc ORC_CHECK([0.4.17]) @@ -3525,6 +3542,7 @@ AM_CONDITIONAL(USE_GSM, false) AM_CONDITIONAL(USE_GTK3, false) AM_CONDITIONAL(USE_GTK3_GL, false) AM_CONDITIONAL(USE_HLS, false) +AM_CONDITIONAL(USE_IQA, false) AM_CONDITIONAL(USE_KATE, false) AM_CONDITIONAL(USE_KMS, false) AM_CONDITIONAL(USE_TIGER, false) @@ -3846,6 +3864,7 @@ ext/flite/Makefile ext/fluidsynth/Makefile ext/gsm/Makefile ext/hls/Makefile +ext/iqa/Makefile ext/kate/Makefile ext/ladspa/Makefile ext/lv2/Makefile diff --git a/docs/plugins/Makefile.am b/docs/plugins/Makefile.am index 0dc14f88f..0cce8594a 100644 --- a/docs/plugins/Makefile.am +++ b/docs/plugins/Makefile.am @@ -75,6 +75,7 @@ EXTRA_HFILES = \ $(top_srcdir)/ext/dts/gstdtsdec.h \ $(top_srcdir)/ext/faac/gstfaac.h \ $(top_srcdir)/ext/faad/gstfaad.h \ + $(top_srcdir)/ext/iqa/iqa.h \ $(top_srcdir)/ext/kate/gstkateenc.h \ $(top_srcdir)/ext/kate/gstkatedec.h \ $(top_srcdir)/ext/kate/gstkateparse.h \ diff --git a/docs/plugins/gst-plugins-bad-plugins-docs.sgml b/docs/plugins/gst-plugins-bad-plugins-docs.sgml index 70b1ea017..199d66fb6 100644 --- a/docs/plugins/gst-plugins-bad-plugins-docs.sgml +++ b/docs/plugins/gst-plugins-bad-plugins-docs.sgml @@ -104,6 +104,7 @@ <xi:include href="xml/element-glvideomixerelement.xml" /> <xi:include href="xml/element-glvideomixer.xml" /> <xi:include href="xml/element-glviewconvert.xml" /> + <xi:include href="xml/element-iqa.xml" /> <xi:include href="xml/element-jpegparse.xml" /> <xi:include href="xml/element-kaleidoscope.xml" /> <xi:include href="xml/element-liveadder.xml" /> diff --git a/docs/plugins/gst-plugins-bad-plugins-sections.txt b/docs/plugins/gst-plugins-bad-plugins-sections.txt index 811c0e5f0..9596f4389 100644 --- a/docs/plugins/gst-plugins-bad-plugins-sections.txt +++ b/docs/plugins/gst-plugins-bad-plugins-sections.txt @@ -2282,6 +2282,18 @@ gst_interlace_get_type </SECTION> <SECTION> +<FILE>element-iqa</FILE> +<TITLE>IQA</TITLE> +Iqa +<SUBSECTION Standard> +IqaClass +IQA +GST_TYPE_IQA +iqa_get_type +gst_iqa_plugin_init +</SECTION> + +<SECTION> <FILE>element-ivfparse</FILE> <TITLE>ivfparse</TITLE> GstIvfParse diff --git a/ext/Makefile.am b/ext/Makefile.am index 16821e0be..6dfc6bc5a 100644 --- a/ext/Makefile.am +++ b/ext/Makefile.am @@ -142,6 +142,12 @@ else GSM_DIR= endif +if USE_IQA +IQA_DIR = iqa +else +IQA_DIR = +endif + if USE_KATE KATE_DIR=kate else @@ -457,6 +463,7 @@ SUBDIRS=\ $(FLUIDSYNTH_DIR) \ $(GSM_DIR) \ $(G729_DIR) \ + $(IQA_DIR) \ $(KATE_DIR) \ $(LADSPA_DIR) \ $(LV2_DIR) \ diff --git a/ext/iqa/Makefile.am b/ext/iqa/Makefile.am new file mode 100644 index 000000000..f114d8d09 --- /dev/null +++ b/ext/iqa/Makefile.am @@ -0,0 +1,27 @@ +plugin_LTLIBRARIES = libgstiqa.la + +libgstiqa_la_SOURCES = \ + iqa.c + +libgstiqa_la_CFLAGS = \ + -I$(top_srcdir)/gst-libs \ + -I$(top_builddir)/gst-libs \ + $(GST_PLUGINS_BASE_CFLAGS) \ + $(GST_BASE_CFLAGS) $(GST_CFLAGS) + +libgstiqa_la_CFLAGS += $(DSSIM_CFLAGS) + +libgstiqa_la_LIBADD = \ + $(top_builddir)/gst-libs/gst/base/libgstbadbase-$(GST_API_VERSION).la \ + $(top_builddir)/gst-libs/gst/video/libgstbadvideo-$(GST_API_VERSION).la \ + $(GST_PLUGINS_BASE_LIBS) \ + $(GST_BASE_LIBS) $(GST_LIBS) + +libgstiqa_la_LIBADD += $(DSSIM_LIBS) + +libgstiqa_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) +libgstiqa_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) + +noinst_HEADERS = \ + iqa.h + diff --git a/ext/iqa/iqa.c b/ext/iqa/iqa.c new file mode 100644 index 000000000..5ba60b35d --- /dev/null +++ b/ext/iqa/iqa.c @@ -0,0 +1,343 @@ +/* Image Quality Assessment plugin + * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-iqa + * @short_description: Image Quality Assessment plugin. + * + * IQA will perform full reference image quality assessment, with the + * first added pad being the reference. + * + * It will perform comparisons on video streams with the same geometry. + * + * The image output will be the heat map of differences, between + * the two pads with the highest measured difference. + * + * For each reference frame, IQA will post a message containing + * a structure named IQA. + * + * The only metric supported for now is "dssim", which will be available + * if https://github.com/pornel/dssim was installed on the system + * at the time that plugin was compiled. + * + * For each metric activated, this structure will contain another + * structure, named after the metric. + * + * The message will also contain a "time" field. + * + * For example, if do-dssim is set to true, and there are + * two compared streams, the emitted structure will look like this: + * + * IQA, dssim=(structure)"dssim\,\ sink_1\=\(double\)0.053621271267184856\,\ + * sink_2\=\(double\)0.0082939683976297474\;", + * time=(guint64)0; + * + * <refsect2> + * <title>Example launch line</title> + * |[ + * gst-launch-1.0 -m uridecodebin uri=file:///test/file/1 ! iqa name=iqa do-dssim=true \ + * ! videoconvert ! autovideosink uridecodebin uri=file:///test/file/2 ! iqa. + * ]| This pipeline will output messages to the console for each set of compared frames. + * </refsect2> + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "iqa.h" + +#ifdef HAVE_DSSIM +#include "dssim.h" +#endif + +GST_DEBUG_CATEGORY_STATIC (gst_iqa_debug); +#define GST_CAT_DEFAULT gst_iqa_debug + +#define SINK_FORMATS " { AYUV, BGRA, ARGB, RGBA, ABGR, Y444, Y42B, YUY2, UYVY, "\ + " YVYU, I420, YV12, NV12, NV21, Y41B, RGB, BGR, xRGB, xBGR, "\ + " RGBx, BGRx } " + +#define SRC_FORMAT " { RGBA } " + +static GstStaticPadTemplate src_factory = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (SRC_FORMAT)) + ); + +enum +{ + PROP_0, + PROP_DO_SSIM, + PROP_LAST, +}; + +static GstStaticPadTemplate sink_factory = GST_STATIC_PAD_TEMPLATE ("sink_%u", + GST_PAD_SINK, + GST_PAD_REQUEST, + GST_STATIC_CAPS (GST_VIDEO_CAPS_MAKE (SINK_FORMATS)) + ); + + +/* GstIqa */ + +#define gst_iqa_parent_class parent_class +G_DEFINE_TYPE (GstIqa, gst_iqa, GST_TYPE_VIDEO_AGGREGATOR); + +#ifdef HAVE_DSSIM + +inline static unsigned char +to_byte (float in) +{ + if (in <= 0) + return 0; + if (in >= 255.f / 256.f) + return 255; + return in * 256.f; +} + +static void +do_dssim (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp, + GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname) +{ + dssim_attr *attr = dssim_create_attr (); + gint y; + unsigned char **ptrs, **ptrs2; + GstMapInfo ref_info; + GstMapInfo cmp_info; + GstMapInfo out_info; + dssim_image *ref_image; + dssim_image *cmp_image; + double dssim; + dssim_ssim_map map_meta; + float *map; + gint i; + dssim_rgba *out; + GstStructure *dssim_structure; + + gst_structure_get (msg_structure, "dssim", GST_TYPE_STRUCTURE, + &dssim_structure, NULL); + + dssim_set_save_ssim_maps (attr, 1, 1); + if (ref->info.width != cmp->info.width || + ref->info.height != cmp->info.height) { + GST_WARNING_OBJECT (self, + "Cannot compare two images with a different geometry yet"); + return; + } + + gst_buffer_map (ref->buffer, &ref_info, GST_MAP_READ); + gst_buffer_map (cmp->buffer, &cmp_info, GST_MAP_READ); + gst_buffer_map (outbuf, &out_info, GST_MAP_WRITE); + out = (dssim_rgba *) out_info.data; + + ptrs = g_malloc (sizeof (char **) * ref->info.height); + ptrs2 = g_malloc (sizeof (char **) * cmp->info.height); + + for (y = 0; y < ref->info.height; y++) { + ptrs[y] = ref_info.data + (ref->info.width * 4 * y); + } + + ref_image = + dssim_create_image (attr, ptrs, DSSIM_RGBA, ref->info.width, + ref->info.height, 0.45455); + + ptrs2 = g_malloc (sizeof (char **) * cmp->info.height); + + for (y = 0; y < cmp->info.height; y++) { + ptrs2[y] = cmp_info.data + (cmp->info.width * 4 * y); + } + + cmp_image = + dssim_create_image (attr, ptrs2, DSSIM_RGBA, cmp->info.width, + cmp->info.height, 0.45455); + dssim = dssim_compare (attr, ref_image, cmp_image); + + map_meta = dssim_pop_ssim_map (attr, 0, 0); + + if (dssim > self->max_dssim) { + map = map_meta.data; + + for (i = 0; i < map_meta.width * map_meta.height; i++) { + const float max = 1.0 - map[i]; + const float maxsq = max * max; + out[i] = (dssim_rgba) { + .r = to_byte (max * 3.0),.g = to_byte (maxsq * 6.0),.b = + to_byte (max / ((1.0 - map_meta.dssim) * 4.0)),.a = 255,}; + } + self->max_dssim = dssim; + } + + gst_structure_set (dssim_structure, padname, G_TYPE_DOUBLE, dssim, NULL); + gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE, + dssim_structure, NULL); + gst_structure_free (dssim_structure); + + g_free (ptrs); + g_free (ptrs2); + gst_buffer_unmap (ref->buffer, &ref_info); + gst_buffer_unmap (cmp->buffer, &cmp_info); + gst_buffer_unmap (outbuf, &out_info); + dssim_dealloc_image (ref_image); + dssim_dealloc_image (cmp_image); + dssim_dealloc_attr (attr); +} +#else +static void +do_dssim (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp, + GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname) +{ +} +#endif + +static void +compare_frames (GstIqa * self, GstVideoFrame * ref, GstVideoFrame * cmp, + GstBuffer * outbuf, GstStructure * msg_structure, gchar * padname) +{ + if (self->do_dssim) + do_dssim (self, ref, cmp, outbuf, msg_structure, padname); +} + +static GstFlowReturn +gst_iqa_aggregate_frames (GstVideoAggregator * vagg, GstBuffer * outbuf) +{ + GList *l; + GstVideoFrame *ref_frame = NULL; + GstIqa *self = GST_IQA (vagg); + GstStructure *msg_structure = gst_structure_new_empty ("IQA"); + GstMessage *m = gst_message_new_element (GST_OBJECT (self), msg_structure); + GstAggregator *agg = GST_AGGREGATOR (vagg); + + if (self->do_dssim) { + gst_structure_set (msg_structure, "dssim", GST_TYPE_STRUCTURE, + gst_structure_new_empty ("dssim"), NULL); + self->max_dssim = 0.0; + } + + GST_OBJECT_LOCK (vagg); + for (l = GST_ELEMENT (vagg)->sinkpads; l; l = l->next) { + GstVideoAggregatorPad *pad = l->data; + + if (pad->aggregated_frame != NULL) { + if (!ref_frame) { + ref_frame = pad->aggregated_frame; + } else { + gchar *padname = gst_pad_get_name (pad); + GstVideoFrame *cmp_frame = pad->aggregated_frame; + + compare_frames (self, ref_frame, cmp_frame, outbuf, msg_structure, + padname); + g_free (padname); + } + } + } + + GST_OBJECT_UNLOCK (vagg); + + /* We only post the message here, because we can't post it while the object + * is locked. + */ + gst_structure_set (msg_structure, "time", GST_TYPE_CLOCK_TIME, + agg->segment.position, NULL); + gst_element_post_message (GST_ELEMENT (self), m); + return GST_FLOW_OK; +} + +static void +_set_property (GObject * object, guint prop_id, const GValue * value, + GParamSpec * pspec) +{ + GstIqa *self = GST_IQA (object); + + switch (prop_id) { + case PROP_DO_SSIM: + self->do_dssim = g_value_get_boolean (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +_get_property (GObject * object, + guint prop_id, GValue * value, GParamSpec * pspec) +{ + GstIqa *self = GST_IQA (object); + + switch (prop_id) { + case PROP_DO_SSIM: + g_value_set_boolean (value, self->do_dssim); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +/* GObject boilerplate */ +static void +gst_iqa_class_init (GstIqaClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *gstelement_class = (GstElementClass *) klass; + GstVideoAggregatorClass *videoaggregator_class = + (GstVideoAggregatorClass *) klass; + + videoaggregator_class->aggregate_frames = gst_iqa_aggregate_frames; + + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&src_factory)); + gst_element_class_add_pad_template (gstelement_class, + gst_static_pad_template_get (&sink_factory)); + + gobject_class->set_property = _set_property; + gobject_class->get_property = _get_property; + +#ifdef HAVE_DSSIM + g_object_class_install_property (gobject_class, PROP_DO_SSIM, + g_param_spec_boolean ("do-dssim", "do-dssim", + "Run structural similarity checks", FALSE, G_PARAM_READWRITE)); +#endif + + gst_element_class_set_static_metadata (gstelement_class, "Iqa", + "Filter/Analyzer/Video", + "Provides various Image Quality Assessment metrics", + "Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk>"); +} + +static void +gst_iqa_init (GstIqa * self) +{ +} + +static gboolean +plugin_init (GstPlugin * plugin) +{ + GST_DEBUG_CATEGORY_INIT (gst_iqa_debug, "iqa", 0, "iqa"); + + return gst_element_register (plugin, "iqa", GST_RANK_PRIMARY, GST_TYPE_IQA); +} + +GST_PLUGIN_DEFINE (GST_VERSION_MAJOR, + GST_VERSION_MINOR, + iqa, + "Iqa", plugin_init, VERSION, GST_LICENSE, GST_PACKAGE_NAME, + GST_PACKAGE_ORIGIN) diff --git a/ext/iqa/iqa.h b/ext/iqa/iqa.h new file mode 100644 index 000000000..e879daa26 --- /dev/null +++ b/ext/iqa/iqa.h @@ -0,0 +1,64 @@ +/* Image Quality Assessment plugin + * Copyright (C) 2015 Mathieu Duponchelle <mathieu.duponchelle@collabora.co.uk> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_IQA_H__ +#define __GST_IQA_H__ + +#include <gst/gst.h> +#include <gst/video/video.h> +#include <gst/video/gstvideoaggregator.h> + +G_BEGIN_DECLS + +#define GST_TYPE_IQA (gst_iqa_get_type()) +#define GST_IQA(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_IQA, GstIqa)) +#define GST_IQA_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_IQA, GstIqaClass)) +#define GST_IS_IQA(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_IQA)) +#define GST_IS_IQA_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_IQA)) + +typedef struct _GstIqa GstIqa; +typedef struct _GstIqaClass GstIqaClass; + +/** + * GstIqa: + * + * The opaque #GstIqa structure. + */ +struct _GstIqa +{ + GstVideoAggregator videoaggregator; + + gboolean do_dssim; + double max_dssim; +}; + +struct _GstIqaClass +{ + GstVideoAggregatorClass parent_class; +}; + +GType gst_iqa_get_type (void); + +G_END_DECLS +#endif /* __GST_IQA_H__ */ + |