/* GStreamer * * Copyright (C) 2014 Mathieu Duponchelle * Copyright (C) 2015 Raspberry Pi Foundation * Author: Thibault Saunier * * 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 #include #include #include #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; struct _GssimPrivate { 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; }; /* *INDENT-OFF* */ G_DEFINE_TYPE_WITH_PRIVATE (Gssim, gssim, GST_TYPE_OBJECT) /* *INDENT-ON* */ enum { PROP_FIRST_PROP = 1, N_PROPS }; 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; 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; } static void gssim_init (Gssim * self) { self->priv = gssim_get_instance_private (self); 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); }