/* * Copyright (c) 2017 Gerion Entrup * * This file is part of FFmpeg. * * FFmpeg is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * FFmpeg is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with FFmpeg; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ /** * @file * MPEG-7 video signature calculation and lookup filter */ #include "signature.h" #define HOUGH_MAX_OFFSET 90 #define MAX_FRAMERATE 60 #define DIR_PREV 0 #define DIR_NEXT 1 #define DIR_PREV_END 2 #define DIR_NEXT_END 3 #define STATUS_NULL 0 #define STATUS_END_REACHED 1 #define STATUS_BEGIN_REACHED 2 static void fill_l1distlut(uint8_t lut[]) { int i, j, tmp_i, tmp_j,count; uint8_t dist; for (i = 0, count = 0; i < 242; i++) { for (j = i + 1; j < 243; j++, count++) { /* ternary distance between i and j */ dist = 0; tmp_i = i; tmp_j = j; do { dist += FFABS((tmp_j % 3) - (tmp_i % 3)); tmp_j /= 3; tmp_i /= 3; } while (tmp_i > 0 || tmp_j > 0); lut[count] = dist; } } } static unsigned int intersection_word(const uint8_t *first, const uint8_t *second) { unsigned int val=0,i; for (i = 0; i < 28; i += 4) { val += av_popcount( (first[i] & second[i] ) << 24 | (first[i+1] & second[i+1]) << 16 | (first[i+2] & second[i+2]) << 8 | (first[i+3] & second[i+3]) ); } val += av_popcount( (first[28] & second[28]) << 16 | (first[29] & second[29]) << 8 | (first[30] & second[30]) ); return val; } static unsigned int union_word(const uint8_t *first, const uint8_t *second) { unsigned int val=0,i; for (i = 0; i < 28; i += 4) { val += av_popcount( (first[i] | second[i] ) << 24 | (first[i+1] | second[i+1]) << 16 | (first[i+2] | second[i+2]) << 8 | (first[i+3] | second[i+3]) ); } val += av_popcount( (first[28] | second[28]) << 16 | (first[29] | second[29]) << 8 | (first[30] | second[30]) ); return val; } static unsigned int get_l1dist(AVFilterContext *ctx, SignatureContext *sc, const uint8_t *first, const uint8_t *second) { unsigned int i; unsigned int dist = 0; uint8_t f, s; for (i = 0; i < SIGELEM_SIZE/5; i++) { if (first[i] != second[i]) { f = first[i]; s = second[i]; if (f > s) { /* little variation of gauss sum formula */ dist += sc->l1distlut[243*242/2 - (243-s)*(242-s)/2 + f - s - 1]; } else { dist += sc->l1distlut[243*242/2 - (243-f)*(242-f)/2 + s - f - 1]; } } } return dist; } /** * calculates the jaccard distance and evaluates a pair of coarse signatures as good * @return 0 if pair is bad, 1 otherwise */ static int get_jaccarddist(SignatureContext *sc, CoarseSignature *first, CoarseSignature *second) { int jaccarddist, i, composdist = 0, cwthcount = 0; for (i = 0; i < 5; i++) { if ((jaccarddist = intersection_word(first->data[i], second->data[i])) > 0) { jaccarddist /= union_word(first->data[i], second->data[i]); } if (jaccarddist >= sc->thworddist) { if (++cwthcount > 2) { /* more than half (5/2) of distances are too wide */ return 0; } } composdist += jaccarddist; if (composdist > sc->thcomposdist) { return 0; } } return 1; } /** * step through the coarsesignatures as long as a good candidate is found * @return 0 if no candidate is found, 1 otherwise */ static int find_next_coarsecandidate(SignatureContext *sc, CoarseSignature *secondstart, CoarseSignature **first, CoarseSignature **second, int start) { /* go one coarsesignature foreword */ if (!start) { if ((*second)->next) { *second = (*second)->next; } else if ((*first)->next) { *second = secondstart; *first = (*first)->next; } else { return 0; } } while (1) { if (get_jaccarddist(sc, *first, *second)) return 1; /* next signature */ if ((*second)->next) { *second = (*second)->next; } else if ((*first)->next) { *second = secondstart; *first = (*first)->next; } else { return 0; } } } /** * compares framesignatures and sorts out signatures with a l1 distance above a given threshold. * Then tries to find out offset and differences between framerates with a hough transformation */ static MatchingInfo* get_matching_parameters(AVFilterContext *ctx, SignatureContext *sc, FineSignature *first, FineSignature *second) { FineSignature *f, *s; size_t i, j, k, l, hmax = 0, score; int framerate, offset, l1dist; double m; MatchingInfo *cands = NULL, *c = NULL; struct { uint8_t size; unsigned int dist; FineSignature *a; uint8_t b_pos[COARSE_SIZE]; FineSignature *b[COARSE_SIZE]; } pairs[COARSE_SIZE]; typedef struct hspace_elem { int dist; size_t score; FineSignature *a; FineSignature *b; } hspace_elem; /* houghspace */ hspace_elem** hspace = av_malloc_array(MAX_FRAMERATE, sizeof(hspace_elem *)); /* initialize houghspace */ for (i = 0; i < MAX_FRAMERATE; i++) { hspace[i] = av_malloc_array(2 * HOUGH_MAX_OFFSET + 1, sizeof(hspace_elem)); for (j = 0; j < HOUGH_MAX_OFFSET; j++) { hspace[i][j].score = 0; hspace[i][j].dist = 99999; } } /* l1 distances */ for (i = 0, f = first; i < COARSE_SIZE && f->next; i++, f = f->next) { pairs[i].size = 0; pairs[i].dist = 99999; pairs[i].a = f; for (j = 0, s = second; j < COARSE_SIZE && s->next; j++, s = s->next) { /* l1 distance of finesignature */ l1dist = get_l1dist(ctx, sc, f->framesig, s->framesig); if (l1dist < sc->thl1) { if (l1dist < pairs[i].dist) { pairs[i].size = 1; pairs[i].dist = l1dist; pairs[i].b_pos[0] = j; pairs[i].b[0] = s; } else if (l1dist == pairs[i].dist) { pairs[i].b[pairs[i].size] = s; pairs[i].b_pos[pairs[i].size] = j; pairs[i].size++; } } } } /* last incomplete coarsesignature */ if (f->next == NULL) { for (; i < COARSE_SIZE; i++) { pairs[i].size = 0; pairs[i].dist = 99999; } } /* hough transformation */ for (i = 0; i < COARSE_SIZE; i++) { for (j = 0; j < pairs[i].size; j++) { for (k = i + 1; k < COARSE_SIZE; k++) { for (l = 0; l < pairs[k].size; l++) { if (pairs[i].b[j] != pairs[k].b[l]) { /* linear regression */ m = (pairs[k].b_pos[l]-pairs[i].b_pos[j]) / (k-i); /* good value between 0.0 - 2.0 */ framerate = (int) m*30 + 0.5; /* round up to 0 - 60 */ if (framerate>0 && framerate <= MAX_FRAMERATE) { offset = pairs[i].b_pos[j] - ((int) m*i + 0.5); /* only second part has to be rounded up */ if (offset > -HOUGH_MAX_OFFSET && offset < HOUGH_MAX_OFFSET) { if (pairs[i].dist < pairs[k].dist) { if (pairs[i].dist < hspace[framerate-1][offset+HOUGH_MAX_OFFSET].dist) { hspace[framerate-1][offset+HOUGH_MAX_OFFSET].dist = pairs[i].dist; hspace[framerate-1][offset+HOUGH_MAX_OFFSET].a = pairs[i].a; hspace[framerate-1][offset+HOUGH_MAX_OFFSET].b = pairs[i].b[j]; } } else { if (pairs[k].dist < hspace[framerate-1][offset+HOUGH_MAX_OFFSET].dist) { hspace[framerate-1][offset+HOUGH_MAX_OFFSET].dist = pairs[k].dist; hspace[framerate-1][offset+HOUGH_MAX_OFFSET].a = pairs[k].a; hspace[framerate-1][offset+HOUGH_MAX_OFFSET].b = pairs[k].b[l]; } } score = hspace[framerate-1][offset+HOUGH_MAX_OFFSET].score + 1; if (score > hmax ) hmax = score; hspace[framerate-1][offset+HOUGH_MAX_OFFSET].score = score; } } } } } } } if (hmax > 0) { hmax = (int) (0.7*hmax); for (i = 0; i < MAX_FRAMERATE; i++) { for (j = 0; j < HOUGH_MAX_OFFSET; j++) { if (hmax < hspace[i][j].score) { if (c == NULL) { c = av_malloc(sizeof(MatchingInfo)); if (!c) av_log(ctx, AV_LOG_FATAL, "Could not allocate memory"); cands = c; } else { c->next = av_malloc(sizeof(MatchingInfo)); if (!c->next) av_log(ctx, AV_LOG_FATAL, "Could not allocate memory"); c = c->next; } c->framerateratio = (i+1.0) / 30; c->score = hspace[i][j].score; c->offset = j-90; c->first = hspace[i][j].a; c->second = hspace[i][j].b; c->next = NULL; /* not used */ c->meandist = 0; c->matchframes = 0; c->whole = 0; } } } } for (i = 0; i < MAX_FRAMERATE; i++) { av_freep(&hspace[i]); } av_freep(&hspace); return cands; } static int iterate_frame(double frr, FineSignature **a, FineSignature **b, int fcount, int *bcount, int dir) { int step; /* between 1 and 2, because frr is between 1 and 2 */ step = ((int) 0.5 + fcount * frr) /* current frame */ -((int) 0.5 + (fcount-1) * frr);/* last frame */ if (dir == DIR_NEXT) { if (frr >= 1.0) { if ((*a)->next) { *a = (*a)->next; } else { return DIR_NEXT_END; } if (step == 1) { if ((*b)->next) { *b = (*b)->next; (*bcount)++; } else { return DIR_NEXT_END; } } else { if ((*b)->next && (*b)->next->next) { *b = (*b)->next->next; (*bcount)++; } else { return DIR_NEXT_END; } } } else { if ((*b)->next) { *b = (*b)->next; (*bcount)++; } else { return DIR_NEXT_END; } if (step == 1) { if ((*a)->next) { *a = (*a)->next; } else { return DIR_NEXT_END; } } else { if ((*a)->next && (*a)->next->next) { *a = (*a)->next->next; } else { return DIR_NEXT_END; } } } return DIR_NEXT; } else { if (frr >= 1.0) { if ((*a)->prev) { *a = (*a)->prev; } else { return DIR_PREV_END; } if (step == 1) { if ((*b)->prev) { *b = (*b)->prev; (*bcount)++; } else { return DIR_PREV_END; } } else { if ((*b)->prev && (*b)->prev->prev) { *b = (*b)->prev->prev; (*bcount)++; } else { return DIR_PREV_END; } } } else { if ((*b)->prev) { *b = (*b)->prev; (*bcount)++; } else { return DIR_PREV_END; } if (step == 1) { if ((*a)->prev) { *a = (*a)->prev; } else { return DIR_PREV_END; } } else { if ((*a)->prev && (*a)->prev->prev) { *a = (*a)->prev->prev; } else { return DIR_PREV_END; } } } return DIR_PREV; } } static MatchingInfo evaluate_parameters(AVFilterContext *ctx, SignatureContext *sc, MatchingInfo *infos, MatchingInfo bestmatch, int mode) { int dist, distsum = 0, bcount = 1, dir = DIR_NEXT; int fcount = 0, goodfcount = 0, gooda = 0, goodb = 0; double meandist, minmeandist = bestmatch.meandist; int tolerancecount = 0; FineSignature *a, *b, *aprev, *bprev; int status = STATUS_NULL; for (; infos != NULL; infos = infos->next) { a = infos->first; b = infos->second; while (1) { dist = get_l1dist(ctx, sc, a->framesig, b->framesig); if (dist > sc->thl1) { if (a->confidence >= 1 || b->confidence >= 1) { /* bad frame (because high different information) */ tolerancecount++; } if (tolerancecount > 2) { a = aprev; b = bprev; if (dir == DIR_NEXT) { /* turn around */ a = infos->first; b = infos->second; dir = DIR_PREV; } else { break; } } } else { /* good frame */ distsum += dist; goodfcount++; tolerancecount=0; aprev = a; bprev = b; if (a->confidence < 1) gooda++; if (b->confidence < 1) goodb++; } fcount++; dir = iterate_frame(infos->framerateratio, &a, &b, fcount, &bcount, dir); if (dir == DIR_NEXT_END) { status = STATUS_END_REACHED; a = infos->first; b = infos->second; dir = iterate_frame(infos->framerateratio, &a, &b, fcount, &bcount, DIR_PREV); } if (dir == DIR_PREV_END) { status |= STATUS_BEGIN_REACHED; break; } if (sc->thdi != 0 && bcount >= sc->thdi) { break; /* enough frames found */ } } if (bcount < sc->thdi) continue; /* matching sequence is too short */ if ((double) goodfcount / (double) fcount < sc->thit) continue; if ((double) goodfcount*0.5 < FFMAX(gooda, goodb)) continue; meandist = (double) goodfcount / (double) distsum; if (meandist < minmeandist || status == STATUS_END_REACHED | STATUS_BEGIN_REACHED || mode == MODE_FAST){ minmeandist = meandist; /* bestcandidate in this iteration */ bestmatch.meandist = meandist; bestmatch.matchframes = bcount; bestmatch.framerateratio = infos->framerateratio; bestmatch.score = infos->score; bestmatch.offset = infos->offset; bestmatch.first = infos->first; bestmatch.second = infos->second; bestmatch.whole = 0; /* will be set to true later */ bestmatch.next = NULL; } /* whole sequence is automatically best match */ if (status == (STATUS_END_REACHED | STATUS_BEGIN_REACHED)) { bestmatch.whole = 1; break; } /* first matching sequence is enough, finding the best one is not necessary */ if (mode == MODE_FAST) { break; } } return bestmatch; } static void sll_free(MatchingInfo *sll) { void *tmp; while (sll) { tmp = sll; sll = sll->next; av_freep(&tmp); } } static MatchingInfo lookup_signatures(AVFilterContext *ctx, SignatureContext *sc, StreamContext *first, StreamContext *second, int mode) { CoarseSignature *cs, *cs2; MatchingInfo *infos; MatchingInfo bestmatch; MatchingInfo *i; cs = first->coarsesiglist; cs2 = second->coarsesiglist; /* score of bestmatch is 0, if no match is found */ bestmatch.score = 0; bestmatch.meandist = 99999; bestmatch.whole = 0; fill_l1distlut(sc->l1distlut); /* stage 1: coarsesignature matching */ if (find_next_coarsecandidate(sc, second->coarsesiglist, &cs, &cs2, 1) == 0) return bestmatch; /* no candidate found */ do { av_log(ctx, AV_LOG_DEBUG, "Stage 1: got coarsesignature pair. " "indices of first frame: %"PRIu32" and %"PRIu32"\n", cs->first->index, cs2->first->index); /* stage 2: l1-distance and hough-transform */ av_log(ctx, AV_LOG_DEBUG, "Stage 2: calculate matching parameters\n"); infos = get_matching_parameters(ctx, sc, cs->first, cs2->first); if (av_log_get_level() == AV_LOG_DEBUG) { for (i = infos; i != NULL; i = i->next) { av_log(ctx, AV_LOG_DEBUG, "Stage 2: matching pair at %"PRIu32" and %"PRIu32", " "ratio %f, offset %d\n", i->first->index, i->second->index, i->framerateratio, i->offset); } } /* stage 3: evaluation */ av_log(ctx, AV_LOG_DEBUG, "Stage 3: evaluate\n"); if (infos) { bestmatch = evaluate_parameters(ctx, sc, infos, bestmatch, mode); av_log(ctx, AV_LOG_DEBUG, "Stage 3: best matching pair at %"PRIu32" and %"PRIu32", " "ratio %f, offset %d, score %d, %d frames matching\n", bestmatch.first->index, bestmatch.second->index, bestmatch.framerateratio, bestmatch.offset, bestmatch.score, bestmatch.matchframes); sll_free(infos); } } while (find_next_coarsecandidate(sc, second->coarsesiglist, &cs, &cs2, 0) && !bestmatch.whole); return bestmatch; }