Polyphase Filter for Bandlimited Interpolation (C++)
This is a polyphase filter for resampling of arbitrary data. Consisting of just a header file and only one (templated) class, its use is quite easy.
#ifndef _RESAMPLE_H
#define _RESAMPLE_H
#define _USE_MATH_DEFINES
#include <math.h>
#include <algorithm>
using namespace std;
/*
* resampler - polyphase resampling with rational factors
* type TContainer - the type of your input buffer, e.g. double*, std::vector etc. must implement "operator[](int)"
* type TData - the data type your container holds
* type TInternal - internally used data type. use either double / float or int (signed types).
* int prec - when using integer types for TInternal, these are shifted left with this constant (default 12)
*/
template<class TContainer, class TData, class TInternal = double, int prec = 12>
class resampler
{
double *m_pfPrototype;
TInternal *m_pTPrototype;
TContainer m_data;
int m_iN, m_iTaps, m_iTaps2, m_iL, m_iM, m_iDataSize;
int m_iStride, m_iOffs;
double m_fBeta;
public:
/*
* ctor
* @param TContainer data
* @param int dataSize - input data buffer
* @param int iDataSize - length of the data buffer
* @param int L - output size
* @param int M - input size
* @param int Taps - number of data values used for one interpolation
* @param double beta - Slope control of the Kaiser window
*/
resampler(TContainer data, int iDataSize, int L, int M, int Taps = 6, double beta = 5.0)
{
m_data = data;
m_iDataSize = iDataSize;
m_iStride = 1;
m_iOffs = 0;
m_fBeta = beta;
int cd = gcd(L,M);
m_iL = L / cd; // upsampling factor
m_iM = M / cd; // downsampling factor
m_iTaps = Taps;
m_iTaps2 = m_iTaps/2;
m_iN = Taps * (m_iM > m_iL ? m_iM : m_iL) + 1;
// kernel creation
createPrototype<TInternal>();
};
virtual ~resampler()
{
delete m_pfPrototype;
delete m_pTPrototype;
}
int getOrder(){ return m_iN-1; } // returns the filter order
int getLength(){ return m_iN; } // returns the actual filter length
int getTaps(){ return m_iTaps; } // returns the number of values used per interpolation step
int getPrec(){ return prec; } // returns the integer factor values are multiplied with (if TInternal != float|double)
void setStride(int iStride){ m_iStride = iStride; }
void setOffset(int iOffs){ m_iOffs = iOffs; }
void setDataSize(int iSize){ m_iDataSize = iSize; }
void setData(TContainer data){ m_data = data; }
/*
* computes the resampled data on the target position.
* memory access is guaranteed via a modulo with the data size,
* which is also useful for circular buffers
*
* @param int i - output sample number
* @return TInternal the resampled value
*/
TInternal operator[](int i)
{
TInternal tmp = 0;
int j = (i*m_iM) / m_iL;
int _tmp1 = (((i%m_iDataSize)*m_iM) % m_iL) + (m_iN+1)/2 - 1;
int _tmp2 = m_iOffs + (j+1)*m_iStride;
for( int k=-m_iTaps2; k<m_iTaps2; k++)
{
int kx = _tmp2 - k*m_iStride;
kx = kx < 0 ? m_iDataSize+kx-1 : kx;
//assert(kx >= 0);
int hpos = k*m_iL + _tmp1;
tmp += m_pTPrototype[hpos] * (TInternal)m_data[kx%m_iDataSize];
}
return tmp;
}
private:
// greatest common divisor of x and y
int gcd(int x, int y)
{
if(x < y)
{
int tmp = x;
x = y;
y = tmp;
}
if(x%y == 0)
return y;
return gcd(y, x%y);
}
// BesselI0 -- Regular Modified Cylindrical Bessel Function (Bessel I).
// taken from http://peabody.sapp.org/class/dmp2/lab/kola/kola.c
double besseli0(double x)
{
double denominator;
double numerator;
double z;
if (x == 0.0) {
return 1.0;
} else {
z = x * x;
numerator = (z* (z* (z* (z* (z* (z* (z* (z* (z* (z* (z* (z* (z*
(z* 0.210580722890567e-22 + 0.380715242345326e-19 ) +
0.479440257548300e-16) + 0.435125971262668e-13 ) +
0.300931127112960e-10) + 0.160224679395361e-7 ) +
0.654858370096785e-5) + 0.202591084143397e-2 ) +
0.463076284721000e0) + 0.754337328948189e2 ) +
0.830792541809429e4) + 0.571661130563785e6 ) +
0.216415572361227e8) + 0.356644482244025e9 ) +
0.144048298227235e10);
denominator = (z*(z*(z-0.307646912682801e4)+
0.347626332405882e7)-0.144048298227235e10);
}
return -numerator/denominator;
}
double kaiser(double n, double beta = 5)
{
double arg = double(2*n)/double(m_iN)-1;
return besseli0(beta * sqrt(1.0-arg*arg)) / besseli0(beta);
}
template<typename T> void createPrototype()
{
m_pfPrototype = new T[m_iN];
memset(m_pfPrototype, 0, sizeof(T));
m_pTPrototype = new TInternal[m_iN];
memset(m_pTPrototype, 0, sizeof(TInternal));
if(m_iL != 1 || m_iM != 1)
{
double _iL = 1.0/double(m_iL);
double _iM = 1.0/double(m_iM);
double fc = .5 * (_iL < _iM ? _iL : _iM);
double scl = (1.0 < double(m_iL)/double(m_iM) ? 1.0 : double(m_iL)/double(m_iM));
for(int i=0; i<=m_iN-1; i++)
{
double n = double(i)-double(m_iN-1)/2.0;
double x = M_PI * n * 2*fc;
double kai = kaiser(n+double(m_iN/2), m_fBeta);
if(x == .0)
m_pfPrototype[i] = T(scl * kai);
else
m_pfPrototype[i] = T(scl * (sin(x) / x ) * kai);
}
}
else
{
m_pfPrototype[(m_iN+1)/2 /*round up*/] = (T)1;
}
// copy preliminarily to actual kernel array
for (int i=m_iN; --i; ) m_pTPrototype[i] = (TInternal) m_pfPrototype[i];
}
template<> void createPrototype<int>()
{
createPrototype<double>();
for (int i=0; i<m_iN; i++)
m_pTPrototype[i] = TInternal(pow(2.0, prec) * m_pfPrototype[i]);
}
template<> void createPrototype<short>()
{
createPrototype<double>();
for (int i=0; i<m_iN; i++)
m_pTPrototype[i] = TInternal(pow(2.0, prec) * m_pfPrototype[i]);
}
};
#endif //_RESAMPLE_HOne can use it as converter for simple 1D - arrays. Setup and the overloaded []-operator work as follows:
double x[SIZEX];
double y[SIZEY];
// fill the source array (x) ...
// resampler setup
resampler<double*,double> xResampled(
x,
SIZEX, // size of x
SIZEX, // upsample factor
SIZEY, // downsample factor
iTaps, // #values used for interpolation
fKaiserBeta // kaiser window sideband attenuation
);
// resample
for(int n = 0; n<SIZEY; n++)
y[n] = xResampled[n];
// voilà.Beyond that, the class accepts a stride and an offset, making it easy to perform resampling of images. The following example converts 2D - arrays of unsigned chars. In fact, you can resample anything that implements the []-operator. Also the internal used datatype is switchable, if one does not like time-consuming float calculations.
resampler<byte*, byte, int> srcResampled(
pImage, // a 2D-byte array
srcHeight*srcStride,
iOutSizeY,
iInSizeY
); // taps, beta default to 6, 5.0 respectively
srcResampled.setStride(srcStride);
int fac = srcResampled.getPrec();
for(int y = 0; y<iOutSizeY; y++)
{
for(int x = 0; x<iOutSizeX; x++)
{
srcResampled.setOffset(x);
*(pOutArray + x + y*iOutSizeX) = (byte) max(0, min(255, srcResampled[y]>>fac));
}
}