The diff method shows the per pixel difference while the dssim method
also outputs the dssim value.
---
 libavfilter/Makefile     |   1 +
 libavfilter/allfilters.c |   1 +
 libavfilter/dssim.c      | 318 +++++++++++++++++++++++++++++++++++++++++++++++
 libavfilter/vf_compare.c | 274 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 594 insertions(+)
 create mode 100644 libavfilter/dssim.c
 create mode 100644 libavfilter/vf_compare.c

diff --git a/libavfilter/Makefile b/libavfilter/Makefile
index 530aa57..06c9766 100644
--- a/libavfilter/Makefile
+++ b/libavfilter/Makefile
@@ -41,6 +41,7 @@ OBJS-$(CONFIG_ANULLSINK_FILTER)              += 
asink_anullsink.o
 
 OBJS-$(CONFIG_BLACKFRAME_FILTER)             += vf_blackframe.o
 OBJS-$(CONFIG_BOXBLUR_FILTER)                += vf_boxblur.o
+OBJS-$(CONFIG_COMPARE_FILTER)                += vf_compare.o dssim.o
 OBJS-$(CONFIG_COPY_FILTER)                   += vf_copy.o
 OBJS-$(CONFIG_CROP_FILTER)                   += vf_crop.o
 OBJS-$(CONFIG_CROPDETECT_FILTER)             += vf_cropdetect.o
diff --git a/libavfilter/allfilters.c b/libavfilter/allfilters.c
index 94b3115..c6fd312 100644
--- a/libavfilter/allfilters.c
+++ b/libavfilter/allfilters.c
@@ -52,6 +52,7 @@ void avfilter_register_all(void)
 
     REGISTER_FILTER (BLACKFRAME,  blackframe,  vf);
     REGISTER_FILTER (BOXBLUR,     boxblur,     vf);
+    REGISTER_FILTER (COMPARE,     compare,     vf);
     REGISTER_FILTER (COPY,        copy,        vf);
     REGISTER_FILTER (CROP,        crop,        vf);
     REGISTER_FILTER (CROPDETECT,  cropdetect,  vf);
diff --git a/libavfilter/dssim.c b/libavfilter/dssim.c
new file mode 100644
index 0000000..65c8ed6
--- /dev/null
+++ b/libavfilter/dssim.c
@@ -0,0 +1,318 @@
+/*
+ * Copyright (c) 2011 porneL. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials
+ * provided with the distribution.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "dssim.h"
+#include "avfilter.h"
+#include "libavutil/common.h"
+
+typedef struct {
+    float l, A, b, a;
+} LABA;
+
+static const float D65x = 0.9505f, D65y = 1.0f, D65z = 1.089f;
+
+static inline LABA rgba_to_laba(const float gamma, const uint8_t *pix)
+{
+    float b = powf(pix[0] / 255.0f, 1.0f / gamma),
+          g = powf(pix[1] / 255.0f, 1.0f / gamma),
+          r = powf(pix[2] / 255.0f, 1.0f / gamma),
+          a = pix[3] / 255.0f;
+
+    float fx            = (r * 0.4124f + g * 0.3576f + b * 0.1805f) / D65x;
+    float fy            = (r * 0.2126f + g * 0.7152f + b * 0.0722f) / D65y;
+    float fz            = (r * 0.0193f + g * 0.1192f + b * 0.9505f) / D65z;
+    const float epsilon = 216.0 / 24389.0;
+
+    fx =
+        ((fx > epsilon) ? powf(fx,
+                               (1.0f / 3.0f)) : (7.787f * fx + 16.0f / 
116.0f));
+    fy =
+        ((fy > epsilon) ? powf(fy,
+                               (1.0f / 3.0f)) : (7.787f * fy + 16.0f / 
116.0f));
+    fz =
+        ((fz > epsilon) ? powf(fz,
+                               (1.0f / 3.0f)) : (7.787f * fz + 16.0f / 
116.0f));
+    return (LABA) {
+               (116.0f * fy - 16.0f) / 100.0 * a,
+               (86.2f + 500.0f * (fx - fy)) / 220.0 * a, /* 86 is a fudge to 
make the value positive */
+               (107.9f + 200.0f * (fy - fz)) / 220.0 * a, /* 107 is a fudge to 
make the value positive */
+               a
+    };
+}
+
+/* Macros to avoid repeating every line 4 times */
+
+#define LABA_OP(dst, X, op, Y) dst = (LABA) { \
+        (X).l op(Y).l, \
+        (X).A op(Y).A, \
+        (X).b op(Y).b, \
+        (X).a op(Y).a } \
+
+#define LABA_OPC(dst, X, op, Y) dst = (LABA) { \
+        (X).l op(Y), \
+        (X).A op(Y), \
+        (X).b op(Y), \
+        (X).a op(Y) } \
+
+#define LABA_OP1(dst, op, Y) dst = (LABA) { \
+        dst.l op(Y).l, \
+        dst.A op(Y).A, \
+        dst.b op(Y).b, \
+        dst.a op(Y).a } \
+
+
+typedef void rowcallback (LABA *, int width);
+
+static void square_row(LABA *row, int width)
+{
+    for (int i = 0; i < width; i++)
+        LABA_OP(row[i], row[i], *, row[i]);
+}
+
+/*
+ * Blurs image horizontally (width 2*size+1) and writes it transposed to dst
+ * (called twice gives 2d blur)
+ * Callback is executed on every row before blurring
+ */
+static void transposing_1d_blur(LABA *restrict src,
+                                LABA *restrict dst,
+                                int width,
+                                int height,
+                                const int size,
+                                rowcallback *const callback)
+{
+    const float sizef = size;
+    int i, j;
+
+    for (j = 0; j < height; j++) {
+        LABA *restrict row = src + j * width;
+        LABA sum;
+
+        // preprocess line
+        if (callback)
+            callback(row, width);
+
+        // accumulate sum for pixels outside line
+        LABA_OPC(sum, row[0], *, sizef);
+        for (i = 0; i < size; i++)
+            LABA_OP1(sum, +=, row[i]);
+
+        // blur with left side outside line
+        for (i = 0; i < size; i++) {
+            LABA_OP1(sum, -=, row[0]);
+            LABA_OP1(sum, +=, row[i + size]);
+
+            LABA_OPC(dst[i * height + j], sum, /, sizef * 2.0f);
+        }
+
+        for (i = size; i < width - size; i++) {
+            LABA_OP1(sum, -=, row[i - size]);
+            LABA_OP1(sum, +=, row[i + size]);
+
+            LABA_OPC(dst[i * height + j], sum, /, sizef * 2.0f);
+        }
+
+        // blur with right side outside line
+        for (i = width - size; i < width; i++) {
+            LABA_OP1(sum, -=, row[i - size]);
+            LABA_OP1(sum, +=, row[width - 1]);
+
+            LABA_OPC(dst[i * height + j], sum, /, sizef * 2.0f);
+        }
+    }
+}
+
+/*
+ * Filters image with callback and blurs (lousy approximate of gaussian)
+ * it proportionally to
+ */
+static void blur(LABA *restrict src, LABA *restrict tmp, LABA *restrict dst,
+                 int width, int height, rowcallback *const callback)
+{
+    int small = 1, big = 1;
+    if (FFMIN(height, width) > 100)
+        big++;
+    if (FFMIN(height, width) > 200)
+        big++;
+    if (FFMIN(height, width) > 500)
+        small++;
+    if (FFMIN(height, width) > 800)
+        big++;
+
+    transposing_1d_blur(src, tmp, width, height, 1, callback);
+    transposing_1d_blur(tmp, dst, height, width, 1, NULL);
+    transposing_1d_blur(src, tmp, width, height, small, NULL);
+    transposing_1d_blur(tmp, dst, height, width, small, NULL);
+    transposing_1d_blur(dst, tmp, width, height, big, NULL);
+    transposing_1d_blur(tmp, dst, height, width, big, NULL);
+}
+
+/*
+ * Conversion is not reversible
+ */
+static inline LABA convert_pixel(const uint8_t *pix, float gamma, int i, int j)
+{
+    LABA f1 = rgba_to_laba(gamma, pix);
+
+    // Compose image on coloured background to better judge dissimilarity with 
various backgrounds
+    int n = i ^ j;
+    if (n & 4) {
+        f1.l += 1.0 - f1.a; // using premultiplied alpha
+    }
+    if (n & 8) {
+        f1.A += 1.0 - f1.a;
+    }
+    if (n & 16) {
+        f1.b += 1.0 - f1.a;
+    }
+
+    // Since alpha is already blended with other channels,
+    // lower amplitude of alpha to lower score for alpha difference
+    f1.a *= 0.75;
+
+    // SSIM is supposed to be applied only to luma,
+    // lower amplitude of chroma to lower score for chroma difference
+    // (chroma is not ignored completely, because IMHO it also matters)
+    f1.A *= 0.75;
+    f1.b *= 0.75;
+
+    return f1;
+}
+
+/*
+ * Algorithm based on Rabah Mehdi's C++ implementation
+ *
+ * frees memory in read_info structs.
+ * saves dissimilarity visualisation as ssimfilename (pass NULL if not needed)
+ */
+void ff_dssim_frame(AVFilterContext *ctx,
+                    AVFilterBufferRef *dst, AVFilterBufferRef *src)
+{
+    CmpContext *s = ctx->priv;
+
+    uint8_t *dp = dst->data[0];
+    uint8_t *sp = src->data[0];
+
+    float gamma1, gamma2;
+
+    const double c1   = 0.01 * 0.01, c2 = 0.03 * 0.03;
+    const float gamma = 1.0 / 2.2;
+    double avgminssim = 0;
+
+    int width  = FFMIN(dst->video->w, src->video->w),
+        height = FFMIN(dst->video->h, src->video->h);
+
+    LABA *restrict img1      = av_malloc(width * height * sizeof(LABA));
+    LABA *restrict img2      = av_malloc(width * height * sizeof(LABA));
+    LABA *restrict img1_img2 = av_malloc(width * height * sizeof(LABA));
+    LABA *tmp                = av_malloc(width * height * sizeof(LABA));
+    LABA *restrict sigma12   = av_malloc(width * height * sizeof(LABA));
+    LABA *restrict sigma1_sq = av_malloc(width * height * sizeof(LABA));
+    LABA *restrict sigma2_sq = av_malloc(width * height * sizeof(LABA));
+    LABA *restrict mu1 = img1_img2;
+    LABA *restrict mu2 = img1;
+
+    int i, j, k = 0;
+
+    gamma1 = gamma2 = 0.45455;
+
+    for (j = 0; j < height; j++) {
+        uint8_t *d = dp, *s = sp;
+        for (i = 0; i < width; i++, k++) {
+            LABA f1 = convert_pixel(d, gamma1, i, j);
+            LABA f2 = convert_pixel(s, gamma2, i, j);
+
+            img1[k] = f1;
+            img2[k] = f2;
+
+            // part of computation
+            LABA_OP(img1_img2[k], f1, *, f2);
+            d += 4;
+            s += 4;
+        }
+        dp += dst->linesize[0];
+        sp += src->linesize[0];
+    }
+
+    blur(img1_img2, tmp, sigma12, width, height, NULL);
+
+    blur(img1, tmp, mu1, width, height, NULL);
+
+    blur(img1, tmp, sigma1_sq, width, height, square_row);
+
+    blur(img2, tmp, mu2, width, height, NULL);
+
+    blur(img2, tmp, sigma2_sq, width, height, square_row);
+    av_free(img2);
+    av_freep(&tmp);
+
+#define SSIM(r) \
+    ((2.0 * (mu1[k].r * mu2[k].r) + c1) * \
+     (2.0 * (sigma12[k].r - (mu1[k].r * mu2[k].r)) + c2)) / \
+    (((mu1[k].r * mu1[k].r) + (mu2[k].r * mu2[k].r) + c1) * \
+     ((sigma1_sq[k].r - (mu1[k].r * mu1[k].r)) + \
+      (sigma2_sq[k].r - (mu2[k].r * mu2[k].r)) + c2))
+
+    k = 0;
+    dp = dst->data[0];
+
+    for (j = 0; j < height; j++) {
+        uint8_t *d = dp;
+        for (i = 0; i < width; i++, k++) {
+            LABA ssim = (LABA) {SSIM(l), SSIM(A), SSIM(b), SSIM(a)};
+
+            double minssim = FFMIN(FFMIN(ssim.l, ssim.A),
+                                   FFMIN(ssim.b, ssim.a));
+            float  max     = 1.0 - FFMIN(FFMIN(ssim.l, ssim.A), ssim.b);
+            float maxsq = max * max;
+            float r, g, b;
+
+            avgminssim += minssim;
+
+            r = (1.0 - ssim.a) + maxsq;
+            g = max + maxsq;
+            b = max * 0.5f + (1.0 - ssim.a) * 0.5f + maxsq;
+
+            d[0] = av_clip_uint8(powf(r, gamma) * 256.0f);
+            d[1] = av_clip_uint8(powf(g, gamma) * 256.0f);
+            d[2] = av_clip_uint8(powf(b, gamma) * 256.0f);
+            d[3] = 255;
+            d += 4;
+        }
+        dp += dst->linesize[0];
+    }
+
+    av_free(mu1);
+    av_free(mu2);
+    av_free(sigma12);
+    av_free(sigma1_sq);
+    av_free(sigma2_sq);
+
+    avgminssim /= (double)width * height;
+
+    s->avg += 1.0 / (avgminssim) - 1.0;
+    s->n++;
+
+    av_log(NULL, AV_LOG_INFO, "pts %"PRId64" dssim value %.4f\n",
+           dst->pts, 1.0 / (avgminssim) - 1.0);
+}
diff --git a/libavfilter/vf_compare.c b/libavfilter/vf_compare.c
new file mode 100644
index 0000000..9095bf6
--- /dev/null
+++ b/libavfilter/vf_compare.c
@@ -0,0 +1,274 @@
+/*
+ * Copyright (c) 2012 Luca Barbato
+ *
+ * This file is part of Libav.
+ *
+ * Libav 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.
+ *
+ * Libav 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 Libav; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * @file
+ * compare two videos.
+ */
+
+#include "libavutil/opt.h"
+#include "libavutil/common.h"
+#include "libavutil/pixdesc.h"
+#include "libavutil/avstring.h"
+#include "libavutil/avassert.h"
+#include "libavutil/parseutils.h"
+#include "avfilter.h"
+#include "internal.h"
+#include "formats.h"
+#include "video.h"
+#include "dssim.h"
+
+#define MAIN 0
+#define REF  1
+
+enum {
+    CMP_DIFF,
+    CMP_DSSIM,
+    NB_CMP
+};
+
+#define OFFSET(x) offsetof(CmpContext, x)
+#define V AV_OPT_FLAG_VIDEO_PARAM
+static const AVOption options[] = {
+    { "method",    "",  OFFSET(comparator), AV_OPT_TYPE_INT,{ .i64 = CMP_DIFF 
}, 0, NB_CMP, V, "compare" },
+    {   "diff", "Output the different pixels", 0, AV_OPT_TYPE_CONST, {.i64 = 
CMP_DIFF}, INT_MIN, INT_MAX, V, "compare" },
+    {   "dssim","Calculate the dssim and highlight it", 0, AV_OPT_TYPE_CONST, 
{.i64 = CMP_DSSIM},  INT_MIN, INT_MAX, V, "compare" },
+    { NULL },
+};
+
+static const AVClass class = {
+    .class_name = "compare filter",
+    .item_name  = av_default_item_name,
+    .option     = options,
+    .version    = LIBAVUTIL_VERSION_INT,
+};
+
+static void diff_frame(AVFilterContext *ctx,
+                       AVFilterBufferRef *dst, AVFilterBufferRef *src)
+{
+    uint8_t *dp = dst->data[0];
+    uint8_t *sp = src->data[0];
+    int i, j;
+    int width, height;
+
+    width  = FFMIN(dst->video->w, src->video->w);
+    height = FFMIN(dst->video->h, src->video->h);
+    for (i = 0; i < height; i++) {
+        uint8_t *d = dp, *s = sp;
+        for (j = 0; j < width; j++) {
+            int dr, dg, db;
+            dr = d[0]-s[0];
+            dg = d[1]-s[1];
+            db = d[2]-s[2];
+            d[0] = 128 - dr;
+            d[1] = 128 - dg;
+            d[2] = 128 - db;
+            d[3] = 255;
+            d += 4;
+            s += 4;
+        }
+        dp += dst->linesize[0];
+        sp += src->linesize[0];
+    }
+}
+
+static av_cold int init(AVFilterContext *ctx, const char *args)
+{
+    CmpContext *s = ctx->priv;
+    int ret;
+
+    s->class = &class;
+    av_opt_set_defaults(s);
+
+    if ((ret = av_set_options_string(s, args, "=", ":")) < 0) {
+        av_log(ctx, AV_LOG_ERROR, "Error parsing options string %s.\n", args);
+        return ret;
+    }
+
+    switch (s->comparator) {
+    case CMP_DIFF:
+        s->compare = diff_frame;
+        break;
+    case CMP_DSSIM:
+        s->compare = ff_dssim_frame;
+        break;
+    default:
+        return AVERROR_INVALIDDATA;
+    }
+
+    return 0;
+}
+
+static av_cold void uninit(AVFilterContext *ctx)
+{
+    CmpContext *s = ctx->priv;
+
+    if (s->n)
+        av_log(NULL, AV_LOG_INFO, "Average metric: %.4f\n", s->avg/s->n);
+
+    avfilter_unref_bufferp(&s->main);
+    avfilter_unref_bufferp(&s->ref);
+}
+
+static int query_formats(AVFilterContext *ctx)
+{
+    const enum AVPixelFormat pix_fmts[] = { AV_PIX_FMT_RGB32, AV_PIX_FMT_NONE 
};
+
+    AVFilterFormats *formats = ff_make_format_list(pix_fmts);
+
+    ff_formats_ref(formats, &ctx->inputs [MAIN ]->out_formats);
+    ff_formats_ref(formats, &ctx->inputs [REF  ]->out_formats);
+    ff_formats_ref(formats, &ctx->outputs[MAIN ]->in_formats);
+
+    return 0;
+}
+
+static int config_output(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+
+    outlink->w = ctx->inputs[MAIN]->w;
+    outlink->h = ctx->inputs[MAIN]->h;
+    outlink->time_base = ctx->inputs[MAIN]->time_base;
+
+    return 0;
+}
+
+static int null_start_frame(AVFilterLink *inlink, AVFilterBufferRef *buf)
+{
+    return 0;
+}
+
+static int null_draw_slice(AVFilterLink *inlink, int y, int h, int slice_dir)
+{
+    return 0;
+}
+
+static int end_frame_main(AVFilterLink *inlink)
+{
+    CmpContext *s = inlink->dst->priv;
+
+    av_assert0(!s->main);
+    s->main         = inlink->cur_buf;
+    inlink->cur_buf = NULL;
+
+    return 0;
+}
+
+static int end_frame_ref(AVFilterLink *inlink)
+{
+    CmpContext *s = inlink->dst->priv;
+
+    av_assert0(!s->ref);
+    s->ref          = inlink->cur_buf;
+    inlink->cur_buf = NULL;
+
+    return 0;
+}
+
+static int output_frame(AVFilterContext *ctx)
+{
+    CmpContext *s = ctx->priv;
+    AVFilterLink *outlink = ctx->outputs[0];
+    int ret = ff_start_frame(outlink, s->main);
+    if (ret >= 0)
+        ret = ff_draw_slice(outlink, 0, outlink->h, 1);
+    if (ret >= 0)
+        ret = ff_end_frame(outlink);
+    s->main = NULL;
+
+    return ret;
+}
+
+static int request_frame(AVFilterLink *outlink)
+{
+    AVFilterContext *ctx = outlink->src;
+    CmpContext    *s = ctx->priv;
+    int ret = 0;
+
+    /* get a frame on the main input */
+    if (!s->main) {
+        ret = ff_request_frame(ctx->inputs[MAIN]);
+        if (ret < 0)
+            return ret;
+    }
+
+    /* get a new frame on the reference input */
+    if (!s->ref) {
+        ret = ff_request_frame(ctx->inputs[REF]);
+        if (ret < 0)
+            return ret;
+    }
+
+    s->compare(ctx, s->main, s->ref);
+
+    avfilter_unref_bufferp(&s->ref);
+
+    return output_frame(ctx);
+}
+
+static const AVFilterPad vf_compare_inputs[] = {
+    {
+        .name         = "main",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .start_frame  = null_start_frame,
+        .draw_slice   = null_draw_slice,
+        .end_frame    = end_frame_main,
+        .min_perms    = AV_PERM_READ,
+        .rej_perms    = AV_PERM_REUSE2 | AV_PERM_PRESERVE,
+        .needs_fifo   = 1,
+    },
+    {
+        .name         = "ref",
+        .type         = AVMEDIA_TYPE_VIDEO,
+        .start_frame  = null_start_frame,
+        .draw_slice   = null_draw_slice,
+        .end_frame    = end_frame_ref,
+        .min_perms    = AV_PERM_READ,
+        .rej_perms    = AV_PERM_REUSE2,
+        .needs_fifo   = 1,
+    },
+    { NULL }
+};
+
+static const AVFilterPad vf_compare_outputs[] = {
+    {
+        .name          = "default",
+        .type          = AVMEDIA_TYPE_VIDEO,
+        .config_props  = config_output,
+        .request_frame = request_frame,
+    },
+    { NULL }
+};
+
+AVFilter avfilter_vf_compare = {
+    .name      = "compare",
+    .description = NULL_IF_CONFIG_SMALL("Compare two video sources"),
+
+    .init      = init,
+    .uninit    = uninit,
+
+    .priv_size = sizeof(CmpContext),
+
+    .query_formats = query_formats,
+
+    .inputs    = vf_compare_inputs,
+    .outputs   = vf_compare_outputs,
+};
-- 
1.7.12

_______________________________________________
libav-devel mailing list
libav-devel@libav.org
https://lists.libav.org/mailman/listinfo/libav-devel

Reply via email to