www.pudn.com > AuditoryToolbox.zip > dtw.c
/* * function [error,path1,path2] = dtw(s1, s2) * [error,path1,path2] = dtw(s1, s2) * Dynamic Time Warping search... by Malcolm Slaney * After this routine * s1 approximately = s2(path1) * s2 approximately = s1(path2) * and error is a scalar with the lowest energy found. * Both s1 and s2 are warped with respect to their columns * (each column stays the same, but shifts in position) * * (c) Interval Research Corporation, 1995-1997 * Thanks to Tony Robinson (Cambridge Univ.) for the structure * of this routine. His algorithm was kindly posted to * comp.speech on 29 Dec. 1994. * * To test this routine * >> warp=[1 1 1 1 2 2 2 2 1 1 1 1 .5 .5 .5 .5] * >> d1=randn(1,16); * >> d2=d1(min(16,floor(cumsum(warp)))); * >> [err,p1,p2] = dtw(d1,d2); * * Unless your random numbers are really bizarre, you should get something * like this * p1 = * Columns 1 through 12 * 1 2 3 4 4 5 5 6 7 8 9 10 * * Columns 13 through 16 * * 11 12 13 15 * * p2 = * Columns 1 through 12 * * 1 2 3 4 6 8 9 10 11 12 13 14 * * Columns 13 through 16 * * 15 15 16 16 */ #include#include #ifndef HUGE #define HUGE 1e30 #endif #define s1(r,c) v1[(r-1) + (c-1)*(d)] #define s2(r,c) v2[(r-1) + (c-1)*(d)] #define ldist(i,j) larray[(i-1)+(j-1)*l1] #define gdist(i,j) garray[(i-1)+(j-1)*l1] #define path(i,j) parray[(i-1)+(j-1)*l1] #define path1(i) p1vector[i-1] #define path2(i) p2vector[i-1] #ifdef MATLAB #define DATA double #define INDEX short #else #define DATA float #define INDEX char #endif DATA dtw(DATA *v1, int l1, DATA *v2, int l2, int d, INDEX **pp1, INDEX **pp2); DATA dtw(DATA *v1, int l1, DATA *v2, int l2, int d, INDEX **pp1, INDEX **pp2){ float ldist; int i, j, k, p1, p2;/* General Indices */ int topPath, midPath, botPath; float *larray; /* Local Distance Array */ float *garray; /* Global Distance Array */ char *parray; /* Path Array */ INDEX *p1vector; /* Path 1 vector */ INDEX *p2vector; /* Path 1 vector */ DATA error; /* Return value */ /* * Now figure out all the relative distances all at once. We'll * never use the outside corners of this array, but it's easier * to calculate all at once. * ldist=zeros(l1, l2); * for i=1:l1 * for j=1:l2 * ldist(i,j) = sum((s1(:,i)-s2(:,j)).^2); * end * end */ larray = (float *)malloc(sizeof(*larray)*l1*l2); if (!larray) return 0; for (i=1; i<=l1; i++){ for (j=1; j<=l2; j++){ float diff, sum; sum = 0; for (k=1; k<=d; k++){ diff = s1(k,i)-s2(k,j); sum += diff*diff; } ldist(i,j) = sum; } } /* We're only going to allow slopes of 2,1,.5. */ topPath=1; midPath=2; botPath=3; /* We'll fill the global distance array with infinities. */ garray = (float *)malloc(sizeof(*garray)*l1*l2); parray = (char *)malloc(sizeof(*parray)*l1*l2); if (!garray || !parray) return 0; for (i=1; i<=l1; i++){ for (j=1; j<=l2; j++){ gdist(i, j) = HUGE; path(i,j) = -1; } } /* The first two directions are fixed. They have to be the * middle path. */ gdist(1,1) = ldist(1,1); path(1,1) = midPath; gdist(2,2) = gdist(1,1) + ldist(2,2); path(2,2) = midPath; for (i=3; i<=l1; i++){ for (j=3; j<=l2; j++){ register float top, mid, bot; top = gdist(i-2,j-1) + ldist(i-1,j) + ldist(i,j); mid = gdist(i-1,j-1) + ldist(i,j); bot = gdist(i-1,j-2) + ldist(i,j-1) + ldist(i,j); if (top < mid && top < bot){ gdist(i,j) = top; path(i,j) = topPath; } else if (bot < top && bot < mid){ gdist(i,j) = bot; path(i,j) = botPath; } else { gdist(i,j) = mid; path(i,j) = midPath; } } } error = gdist(l1,l2); /* Now backtrack through the array looking for the path that got * us the minimum distance. Luckily we left a string of * directions that we can use to find our way back to the origin. */ p1 = l1; p2 = l2; p1vector = malloc(l1*sizeof(*p1vector)); p2vector = malloc(l2*sizeof(*p2vector)); if (!p1vector || !p2vector) return 0; while (p1 > 0 && p2 > 0){ int direct; path1(p1) = p2; path2(p2) = p1; direct = path(p1,p2); if (direct == topPath){ p1 = p1 - 1; path1(p1) = p2; path2(p2) = p1; } else if (direct == botPath){ p2 = p2 - 1; path1(p1) = p2; path2(p2) = p1; } p1 = p1 - 1; p2 = p2 - 1; } if (pp1) *pp1 = p1vector; if (pp2) *pp2 = p2vector; return error; } #ifdef MAIN #include /* * The following is useful test code... generate a known warping * signal, generate two arrays of sort of random data, and then * use d1 and d2 as dtw input. */ void smooth(float data[][2], int l); float warp[] = {1, 1, 1, 1, 2, 2, 2, 2, 1, 1, 1, 1, .5, .5, .5, .5}; #define l2 (sizeof(warp)/sizeof(warp[0])) #define min(x,y) ((x)<(y)?(x):(y)) #define max(x,y) ((x)>(y)?(x):(y)) #define d(i,j) data[min(max(0,i),l-1)][j] void smooth(float data[][2], int l){ int i; for (i=0; i #include #include "mex.h" #define mxGetSize(m) (mxGetN(m) * mxGetM(m)) /* =====================================================================*/ /* Function to determine if arguments are valid. */ /* Also fill in some pointers we will need later. */ static int CheckArguments(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { int k; if (nrhs < 2 || nlhs < 1){ printf("Incorrect calling syntax:\n [error,p1,p2] = "); printf("dtw(s1,s2)\n"); printf(" where s1 is KxN and S2 is KxM in size\n"); return 1; } /*------------ Check inputs are compatible ------------------------------ */ k = mxGetM(pS1); if (k != mxGetM(pS2)){ printf("Two input arrays are not the same height\n"); return 1; } return 0; } #define kCommandSize 20 void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { int i; register double error; mxArray *mp1 = 0, *mp2 = 0, *ep = 0; INDEX *md1, *md2; if (CheckArguments(nlhs, plhs, nrhs, prhs) ){ mexErrMsgTxt("dtw argument checking failed."); return ; } if (nlhs > 1){ mp1 = mxCreateDoubleMatrix(1, mxGetN(pS1), mxREAL); plhs[1] = mp1; } if (nlhs > 2){ mp2 = mxCreateDoubleMatrix(1, mxGetN(pS2), mxREAL); plhs[2] = mp2; } error = dtw(mxGetPr(pS1), mxGetN(pS1), mxGetPr(pS2), mxGetN(pS2), mxGetM(pS1), &md1, &md2); if (nlhs > 0){ plhs[0] = mxCreateDoubleMatrix(1, 1, mxREAL); if (plhs[0]) *(mxGetPr(plhs[0])) = error; } if (mp1){ double *rp = mxGetPr(mp1); int len = mxGetN(pS1); for (i=0;i