Posted to tcl by kbk at Sun Sep 20 02:48:30 GMT 2009view pretty
/*
* Implementation of a Gaussian IIR filter after
* Ian T. Young and Lucas J. van Vliet. "Recursive implementation of
* the Gaussian filter." _Signal Processing_ 44 (1995), pp. 139-151.
*/
/* Initialize the filter coefficients for the IIR filter. */
static inline void
iir_coefficients(float sigma, /* Radius of the filter */
float* coefs) /* Output coefficients, normalized */
{
float q;
float b0, b1, b2, b3;
/* Correct the filter width (Eq. 11a in the paper). */
if (sigma > 2.5f) {
q = 0.98711f * sigma - 0.96330f;
} else {
q = 3.97156f - 4.14554f * sqrt(1.0f - 0.26891f * sigma);
}
/* Determine the filter coefficients */
b0 = 1.57825f + q * (2.44413f + q * (1.42810f + q * 0.422205f));
b1 = q * (2.44413f + q * (2.85619f + q * 1.26661f));
b2 = q * q * (-1.42810f + q * -1.26661f);
b3 = q * q * q * 0.422205f;
/* Determine the normalization constant (Eq. 10) */
coefs[0] = 1.0f - (b1 + b2 + b3) / b0;
/* Scale the filter coefficients */
coefs[1] = b1 / b0;
coefs[2] = b2 / b0;
coefs[3] = b3 / b0;
}
/* Apply the IIR filter to the input value and the last three output values */
static inline float
iir_kernel(float in, /* Input value */
float out_1, /* Last output value */
float out_2, /* Penultimate output value */
float out_3, /* Antepenultimate output value */
float* coefs) /* Filter coefficients */
{
return coefs[0] * in
+ coefs[1] * out_1 + coefs[2] * out_2 + coefs[3] * out_3;
}
/* Apply the IIR filter across a row of pixels, first left then right.
* Filter "in place": replace the row with a filtered copy.
* The 'z' array is expected to be at least 'pad' elements in length
* and is used to handle edge effects. */
static inline void
iir_row (unsigned width, /* Width of the image */
unsigned pad, /* Number of pixels of pad at the edges */
float* coefs, /* Filter coefficients */
float* row, /* One row of pixels to filter */
float* z) /* Temporary array for starting and stopping */
{
int i;
/* Start the filter running on the row in reverse to "prime the pump" */
z[0] = iir_kernel(row[pad-1], 0.0f, 0.0f, 0.0f, coefs);
z[1] = iir_kernel(row[pad-2], z[0], 0.0f, 0.0f, coefs);
z[2] = iir_kernel(row[pad-3], z[1], z[0], 0.0f, coefs);
for (i = 3; i < pad; ++i) {
z[i] = iir_kernel(row[pad-i-1], z[i-1], z[i-2], z[i-3], coefs);
}
/* Turn around and start the row. */
row[0] = iir_kernel(row[0], z[pad-1], z[pad-2], z[pad-3], coefs);
row[1] = iir_kernel(row[1], row[0], z[pad-1], z[pad-2], coefs);
row[2] = iir_kernel(row[2], row[1], row[0], z[pad-1], coefs);
for (i = 3; i+pad < width; ++i) {
row[i] = iir_kernel(row[i], row[i-1], row[i-2], row[i-3], coefs);
}
/* Stash the last 'pad' columns off in the 'z' array before
* overwriting them */
for (; i < width; ++i) {
z[i+pad-width] = row[i];
row[i] = iir_kernel(row[i], row[i-1], row[i-2], row[i-3], coefs);
}
/* Continue for an overrun of 'pad' columns */
z[pad-1] = iir_kernel(z[pad-1], row[width-1], row[width-2], row[width-3],
coefs);
z[pad-2] = iir_kernel(z[pad-2], z[pad-1], row[width-1], row[width-2],
coefs);
z[pad-3] = iir_kernel(z[pad-3], z[pad-2], z[pad-1], row[width-1], coefs);
for (i = pad-4; i >= 0; --i) {
z[i] = iir_kernel(z[i], z[i+1], z[i+2], z[i+3], coefs);
}
/* Now prime the filter in the opposite direction. */
for (i = 3; i < pad; ++i) {
z[i] = iir_kernel(z[i], z[i-1], z[i-2], z[i-3], coefs);
}
/* Turn around and start filling the row */
row[width-1] = iir_kernel(row[width-1], z[pad-1], z[pad-2], z[pad-3],
coefs);
row[width-2] = iir_kernel(row[width-2], row[width-1], z[pad-1], z[pad-2],
coefs);
row[width-3] = iir_kernel(row[width-3], row[width-2], row[width-1],
z[pad-1], coefs);
/* Run the filter from right to left */
for (i = width-4; i >= 0; --i) {
row[i] = iir_kernel(row[i], row[i+1], row[i+2], row[i+3], coefs);
}
}
/*
* Apply the IIR blur filter across the columns on one row of an image
*/
static inline void
iir_v_row(unsigned width, /* Width of the image */
float* coefs, /* Filter coefficients */
float* in_row, /* Input row */
float* out_row_1, /* Previous output row */
float* out_row_2, /* Penultimate output row */
float* out_row_3, /* Antepenultimate output row */
float* out_row) /* OUTPUT: current output row. May overwrite
* in_row */
{
unsigned i;
for (i = 0; i < width; ++i) {
out_row[i] = iir_kernel(in_row[i], out_row_1[i], out_row_2[i],
out_row_3[i], coefs);
}
}
/*
* Apply the IIR blur filter to an entire image
*/
void
iir_blur_image(unsigned height, /* Height of the image */
unsigned width, /* Width of the image */
float* image, /* Pixels of the image, row-major order */
float sigma, /* Sigma of the Gaussian filter */
unsigned pad) /* Half-length of the support */
{
int i;
float coefs[4];
float* y = malloc(pad * sizeof(float));
float* z = malloc(pad * width * sizeof(float));
float* zero = malloc(width * sizeof(float));
memset(zero, 0, width * sizeof(float));
/* Compute the coefficients */
iir_coefficients(sigma, coefs);
/* Start a filter running on the top fringe of the image in reverse
* to 'prime the pump.' */
iir_v_row(width, coefs, image + (pad-1)*width, zero, zero, zero, z);
iir_v_row(width, coefs, image + (pad-2)*width, z, zero, zero, z+width);
iir_v_row(width, coefs, image + (pad-3)*width, z+width, z, zero,
z+2*width);
for (i = 3; i < pad; ++i) {
iir_v_row(width, coefs, image+(pad-i-1)*width, z+(i-1)*width,
z+(i-2)*width, z+(i-3)*width, z+i*width);
}
/* Turn around and run the filter forward to the bottom fringe */
iir_v_row(width, coefs, image, z+(pad-1)*width, z+(pad-2)*width,
z+(pad-3)*width, image);
iir_v_row(width, coefs, image+width, image, z+(pad-1)*width,
z+(pad-2)*width, image+width);
iir_v_row(width, coefs, image+2*width, image+width, image,
z+(pad-1)*width, image+2*width);
for (i = 3; i + pad < height; ++i) {
iir_v_row(width, coefs, image+i*width, image+(i-1)*width,
image+(i-2)*width, image+(i-3)*width, image+i*width);
}
/* Safely store the last 'pad' rows in the 'z' array before
* overwriting them */
for (; i < height; ++i) {
memcpy(z+(i+pad-height)*width, image+i*width, width*sizeof(float));
iir_v_row(width, coefs, image+i*width, image+(i-1)*width,
image+(i-2)*width, image+(i-3)*width, image+i*width);
}
/* Continue for an overrun of 'pad' rows */
iir_v_row(width, coefs, z+(pad-1)*width, image+(height-1)*width,
image+(height-2)*width,
image+(height-3)*width, z+(pad-1)*width);
iir_v_row(width, coefs, z+(pad-2)*width, z+(pad-1)*width,
image+(height-1)*width, image+(height-2)*width,
z+(pad-2)*width);
iir_v_row(width, coefs, z+(pad-3)*width, z+(pad-2)*width,
z+(pad-1)*width, image+(height-1)*width, z+(pad-3)*width);
for (i = pad-4; i >= 0; --i) {
iir_v_row(width, coefs, z+i*width, z+(i+1)*width, z+(i+2)*width,
z+(i+3)*width, z+i*width);
}
/* Now start the filter in the upward direction */
for (i = 3; i < pad; ++i) {
iir_v_row(width, coefs, z+i*width, z+(i-1)*width, z+(i-2)*width,
z+(i-3)*width, z+i*width);
}
iir_v_row(width, coefs, image+(height-1)*width, z+(pad-1)*width,
z+(pad-2)*width, z+(pad-3)*width, image+(height-1)*width);
iir_v_row(width, coefs, image+(height-2)*width, image+(height-1)*width,
z+(pad-1)*width, z+(pad-2)*width, image+(height-2)*width);
iir_v_row(width, coefs, image+(height-3)*width, image+(height-2)*width,
image+(height-1)*width, z+(pad-1)*width, image+(height-3)*width);
/* Run the filter upward to the top of the image */
for (i=height-4; i >= 0; --i) {
iir_v_row(width, coefs, image+i*width, image+(i+1)*width,
image+(i+2)*width, image+(i+3)*width, image+i*width);
iir_row(width, pad, coefs, image+(i+3)*width, y);
}
iir_row(width, pad, coefs, image+2*width, y);
iir_row(width, pad, coefs, image+width, y);
iir_row(width, pad, coefs, image, y);
free(zero);
free(z);
free(y);
}