in tmk/cpp/algo/tmkfv.cpp [570:676]
void TMKFeatureVectors::findPairOffsetsModuloPeriods(
const TMKFeatureVectors& fva,
const TMKFeatureVectors& fvb,
BestOffsets& bestOffsets,
ValuesAtBestOffsets& valuesAtBestOffsets,
bool printDetails) {
int numPeriods = fva._periods.size();
int numFourierCoefficients = fva._fourierCoefficients.size();
// We assume areCompatible has already been called on fva and fvb.
bestOffsets = BestOffsets(numPeriods);
valuesAtBestOffsets = ValuesAtBestOffsets(numPeriods);
if (numPeriods == 0 || numFourierCoefficients == 0) {
return;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// We assume all cos/sin features are already L2-normalized.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
std::vector<std::vector<float>> dotCosCos =
facebook::tmk::libvec::allocateRank2(numPeriods, numFourierCoefficients);
std::vector<std::vector<float>> dotSinSin =
facebook::tmk::libvec::allocateRank2(numPeriods, numFourierCoefficients);
std::vector<std::vector<float>> dotSinCos =
facebook::tmk::libvec::allocateRank2(numPeriods, numFourierCoefficients);
std::vector<std::vector<float>> dotCosSin =
facebook::tmk::libvec::allocateRank2(numPeriods, numFourierCoefficients);
for (int i = 0; i < numPeriods; i++) {
for (int j = 0; j < numFourierCoefficients; j++) {
const std::vector<float>& fvaCos = fva._cosFeatures[i][j];
const std::vector<float>& fvaSin = fva._sinFeatures[i][j];
const std::vector<float>& fvbCos = fvb._cosFeatures[i][j];
const std::vector<float>& fvbSin = fvb._sinFeatures[i][j];
dotCosCos[i][j] = facebook::tmk::libvec::computeDot(fvaCos, fvbCos);
dotSinSin[i][j] = facebook::tmk::libvec::computeDot(fvaSin, fvbSin);
dotSinCos[i][j] = facebook::tmk::libvec::computeDot(fvaSin, fvbCos);
dotCosSin[i][j] = facebook::tmk::libvec::computeDot(fvaCos, fvbSin);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
for (int i = 0; i < numPeriods; i++) {
int period = fva._periods[i];
std::vector<float> K_deltas(period);
for (int offset = 0; offset < period; offset++) {
float delta = 2.0 * M_PI * (float)offset / (float)period;
float K_delta = dotCosCos[i][0];
#if 0
// Direct evaluation
for (int j = 1; j < numFourierCoefficients; j++) {
K_delta += cos(j * delta) * (dotCosCos[i][j] + dotSinSin[i][j]);
K_delta += sin(j * delta) * (dotSinCos[i][j] - dotCosSin[i][j]);
}
#else
/**
* We noticed that sin and cos were the dominant cost to this function.
* We can try using an incremental algorithm for equally spaced exp(1j)
* as recommended in Numerical Recipes. A good demonstration of precision:
* http://steve.hollasch.net/cgindex/math/inccos.html
* this is based on the addition formulae. We replace cos(d) with
* sin(d/2) to avoid truncation error using the double angle formula.
*
* Initial tests show 1/3 improvement in performance.
**/
float cos_jd = 1;
float sin_jd = 0;
float beta = sin(delta);
float alpha = sin(delta / 2);
alpha = 2 * alpha * alpha;
for (int j = 1; j < numFourierCoefficients; j++) {
// a bit magical because we're starting at j = 1
float n_cos_jd = (alpha * cos_jd) + (beta * sin_jd);
float n_sin_jd = (alpha * sin_jd) - (beta * cos_jd);
cos_jd -= n_cos_jd;
sin_jd -= n_sin_jd;
K_delta += cos_jd * (dotCosCos[i][j] + dotSinSin[i][j]);
K_delta += sin_jd * (dotSinCos[i][j] - dotCosSin[i][j]);
}
#endif
if (printDetails) {
printf("TODK %d %d %.6f %.6f\n", period, offset, delta, K_delta);
}
K_deltas[offset] = K_delta;
}
// Now find the offset with the largest K_delta for this period.
// This is the best alignment of the two videos modulo this period.
float max_K_delta = K_deltas[0];
float maxIndex = 0;
for (int offset = 1; offset < period; offset++) {
if (K_deltas[offset] > max_K_delta) {
max_K_delta = K_deltas[offset];
maxIndex = offset;
}
}
bestOffsets[i] = maxIndex;
valuesAtBestOffsets[i] = max_K_delta;
}
}