123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580 |
- /*
- * 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;
- }
|