/**
* @file filter.c
* @brief various filtering algorithm
* @author Patrick Roth - roth@stettbacher.ch
* @copyright Stettbacher Signal Processing AG
*
* @remarks
*
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*
*/
#include
#include
#include
#include
#include
#define MAX_GAUSS_KERNEL_SIZE 25 ///< maximum gaussian kernel size (must be an odd number)
/**
* Filter pixel with given 3x3 kernel. Fixed-point is used.
*
* @param a11 kernel weight at position 1/1
* @param a12 kernel weight at position 1/2
* @param a13 kernel weight at position 1/3
* @param a21 kernel weight at position 2/1
* @param a22 kernel weight at position 2/2
* @param a23 kernel weight at position 2/3
* @param a31 kernel weight at position 3/1
* @param a32 kernel weight at position 3/2
* @param a33 kernel weight at position 3/3
* @param p11 pixel value at position 1/1
* @param p12 pixel value at position 1/2
* @param p13 pixel value at position 1/3
* @param p21 pixel value at position 2/1
* @param p22 pixel value at position 2/2
* @param p23 pixel value at position 2/3
* @param p31 pixel value at position 3/1
* @param p32 pixel value at position 3/2
* @param p33 pixel value at position 3/3
* @param shift_fact The shifting factor defines how many number of bits the kernel and pixel were shifted to left.
* @return filtered pixel value
*/
static inline int16_t calc_filter3x3(const int16_t a11, const int16_t a12, const int16_t a13,
const int16_t a21, const int16_t a22, const int16_t a23,
const int16_t a31, const int16_t a32, const int16_t a33,
const int16_t p11, const int16_t p12, const int16_t p13,
const int16_t p21, const int16_t p22, const int16_t p23,
const int16_t p31, const int16_t p32, const int16_t p33,
const int shift_fact) {
int16_t out;
out = (a11*p11 + a12*p12 + a13*p13 + a21*p21 + a22*p22 + a23*p23 + a31*p31 + a32*p32 + a33*p33) >> shift_fact;
return out;
}
/**
* Apply sobel kernel to given 3x3 pixels.
*
* @param p11 pixel value at position 1/1
* @param p12 pixel value at position 1/2
* @param p13 pixel value at position 1/3
* @param p21 pixel value at position 2/1
* @param p22 pixel value at position 2/2
* @param p23 pixel value at position 2/3
* @param p31 pixel value at position 3/1
* @param p32 pixel value at position 3/2
* @param p33 pixel value at position 3/3
* @return filtered pixel value
*
* NOTE
* For performance reasons the return value is ret = abs(sobel_x) + (abs sobel_y) instead of ret = sqrt(abs(sobel_x) + (abs sobel_y))
*/
static inline int16_t calc_filter_sobel(const int16_t p11, const int16_t p12, const int16_t p13,
const int16_t p21, const int16_t p22, const int16_t p23,
const int16_t p31, const int16_t p32, const int16_t p33) {
int16_t sobel_y, sobel_x;
sobel_x = (-1)*p11 + p13 - 2*p21 + 2*p23 - p31 + p33;
if(sobel_x < 0) {
sobel_x = sobel_x * -1;
}
sobel_y = (-1)*p11 - 2*p12 - p13 + p31 + 2*p32 + p33;
if(sobel_y < 0) {
sobel_y = sobel_y * -1;
}
return (sobel_x+sobel_y);
}
/**
* Filter pixel at given image by applying NxN kernel. Each pixel at the image is a 32 bit signed value. The step size
* defines the offset to the next pixel. E. g. a step size of 3 means the image uses 3 32-bit channels.
* The image and the kernel uses fixed-point values. Therfore a shifting factor is used.
*
* NOTE
* The kernel heigth and width must be an odd value (e. g. 3x5 is accepted). This function doesn't make any sanity checks
* due to performance reason.
*
* @param img start image address
* @param height image height in number of pixels
* @param width image width in number of pixels
* @param step_size step size
* @param coord_y y pixel coordinate to apply kernel
* @param coord_x x pixel coordinate to apply kernel
* @param a pointer to filter kernel (the kernel values must be shifted correctly)
* @param kernel_height kernel height in number of pixels
* @param kernel_width kernel width in number of pixels
* @param shift_fact shifting factor
* @return filtered pixel value
*/
static int16_t calc_filterNxN(const int16_t *img, const int height, const int width, const int step_size,
const int coord_y, const int coord_x, const int16_t *a, const int kernel_height, const int kernel_width,
const int shift_fact) {
int y, x, y_start, y_end, x_start, x_end;
int index_img, index_kernel;
int64_t out;
y_start = coord_y-kernel_height/2;
y_end = y_start+kernel_height;
x_start = coord_x-kernel_width/2;
x_end = x_start+kernel_width;
index_kernel = 0;
out = 0;
for(y = y_start; y < y_end; y++) {
index_img = (y*width + x_start)*step_size;
for(x = x_start; x < x_end; x++) {
out += a[index_kernel]*img[index_img];
index_img += step_size;
index_kernel++;
}
}
return (out>>shift_fact);
}
/**
* Apply sobel filter (edge detection) on given input image of type: 3 channels, 16 bit signed fixed-point per channel
*
* @param img_sobel On return: sobel filtered image
* @param img_in input image to be filtered
* @param height image height in number of pixels
* @param width image width in number of pixels
* @param skip_ch1 set to 0 if channel 1 should be filtered
* @param skip_ch2 set to 0 if channel 2 should be filtered
* @param skip_ch3 set to 0 if channel 3 should be filtered
*/
void filter_sobel_3s16(int16_t *img_sobel, const int16_t *img_in, const int height, const int width,
const int skip_ch1, const int skip_ch2, const int skip_ch3) {
int y, x, index_upper, index_center, index_lower, index_left, index_right;
for(y = 1; y < (height-1); y++) {
index_upper = (y-1)*width*3;
index_center = y*width*3;
index_lower = (y+1)*width*3;
for(x = 1; x < (width-1); x++) {
if(!skip_ch1) {
img_sobel[index_center+3] = calc_filter_sobel( img_in[index_upper], img_in[index_upper+3], img_in[index_upper+6],
img_in[index_center], img_in[index_center+3], img_in[index_center+6],
img_in[index_lower], img_in[index_lower+3], img_in[index_lower+6]);
}
else {
img_sobel[index_center+3] = 0;
}
index_upper++;
index_center++;
index_lower++;
if(!skip_ch2) {
img_sobel[index_center+3] = calc_filter_sobel( img_in[index_upper], img_in[index_upper+3], img_in[index_upper+6],
img_in[index_center], img_in[index_center+3], img_in[index_center+6],
img_in[index_lower], img_in[index_lower+3], img_in[index_lower+6]);
}
else {
img_sobel[index_center+3] = 0;
}
index_upper++;
index_center++;
index_lower++;
if(!skip_ch3) {
img_sobel[index_center+3] = calc_filter_sobel( img_in[index_upper], img_in[index_upper+3], img_in[index_upper+6],
img_in[index_center], img_in[index_center+3], img_in[index_center+6],
img_in[index_lower], img_in[index_lower+3], img_in[index_lower+6]);
}
else {
img_sobel[index_center+3] = 0;
}
index_upper++;
index_center++;
index_lower++;
}
}
/*
* Image border are set to 0
*/
index_upper = 0;
index_lower = (height-1)*width*3;
for(x = 0; x < width; x++) {
// horizontal upper border
img_sobel[index_upper] = 0;
img_sobel[index_upper+1] = 0;
img_sobel[index_upper+2] = 0;
index_upper += 3;
// horizontal lower border
img_sobel[index_lower] = 0;
img_sobel[index_lower+1] = 0;
img_sobel[index_lower+2] = 0;
index_lower += 3;
}
index_left = 0;
index_right = width*3-3;
for(y = 0; y < height; y++) {
// verticel left border
img_sobel[index_left] = 0;;
img_sobel[index_left+1] = 0;
img_sobel[index_left+2] = 0;
index_left += width*3;
// verticel right border
img_sobel[index_right] = 0;;
img_sobel[index_right+1] = 0;
img_sobel[index_right+2] = 0;
index_right += width*3;
}
}
/**
* Apply gauss low-pass filter on given input image of type: 3 channels, 16 bit signed fixed-point per channel.
* An odd kernel size is expected.
*
* @param img_gauss On return: gauss low-pass filtered image
* @param img_in input image to be filtered
* @param height image height in number of pixels
* @param width image width in number of pixels
* @param kernel_size filter kernel size (minimum of 3, maximum see @ref MAX_GAUSS_KERNEL_SIZE)
* @param spread gaussian curve spreading value, as higher as bigger spread (spread = sigma)
* @param skip_ch1 set to 0 if channel 1 should be filtered
* @param skip_ch2 set to 0 if channel 2 should be filtered
* @param skip_ch3 set to 0 if channel 3 should be filtered
*/
void filter_gauss_3s16(int16_t *img_gauss, const int16_t *img_in, const int height, const int width, int kernel_size, const float spread,
const int skip_ch1, const int skip_ch2, const int skip_ch3) {
int y, x, index, index_upper, index_center, index_lower;
int kernel_start, kernel_end;
float a[MAX_GAUSS_KERNEL_SIZE*MAX_GAUSS_KERNEL_SIZE];
int16_t a_s16[MAX_GAUSS_KERNEL_SIZE*MAX_GAUSS_KERNEL_SIZE];
float sum;
int x_start, x_end, y_start, y_end;
const int shift_fact = 10; // 2^10 = 1024--> about 3 digits
if((kernel_size%2) == 0) {
// even kernel size!!
kernel_size++;
}
if(kernel_size < 3) {
kernel_size = 3;
}
else if(kernel_size > MAX_GAUSS_KERNEL_SIZE) {
kernel_size = MAX_GAUSS_KERNEL_SIZE;
}
/*
* Generate 2D-gaussian kernel depending on given size.
* Norm kernel, that sum over each kernel element is 1.0.
*/
kernel_start = -1*kernel_size/2;
kernel_end = -1*kernel_start;
index = 0;
sum = 0;
for(y = kernel_start; y <= kernel_end; y++) {
for(x = kernel_start; x <= kernel_end; x++) {
a[index] = (1.0/(2*M_PI*spread*spread))*expf((-1)*((x*x+y*y)/(2*spread*spread)));
sum += a[index];
index++;
}
}
index = 0;
for(y = 0; y < kernel_size; y++) {
for(x = 0; x < kernel_size; x++) {
a[index] /= sum;
a_s16[index] = (int16_t)roundf(a[index] * (1< %d\n", y, x, a[index], a_s16[index]);
index++;
}
}
/*
* This loop filters the image without border region.
*/
y_start = kernel_size/2;
y_end = height-y_start;
x_start = kernel_size/2;
x_end = width-x_start;
for(y = y_start; y < y_end; y++) {
index = (y*width+x_start)*3;
index_upper = (y-1)*width*3;
index_center = y*width*3;
index_lower = (y+1)*width*3;
for(x = x_start; x < x_end; x++) {
if(!skip_ch1) {
if(kernel_size == 3) {
img_gauss[index_center] = calc_filter3x3( a_s16[0], a_s16[1], a_s16[2], a_s16[3], a_s16[4], a_s16[5], a_s16[6], a_s16[7], a_s16[8],
img_in[index_upper], img_in[index_upper+3], img_in[index_upper+6],
img_in[index_center], img_in[index_center+3], img_in[index_center+6],
img_in[index_lower], img_in[index_lower+3], img_in[index_lower+6],
shift_fact);
}
else {
img_gauss[index] = calc_filterNxN(img_in, height, width, 3, y, x, a_s16, kernel_size, kernel_size, shift_fact);
}
}
else {
img_gauss[index] = 0;
}
index++;
index_upper++;
index_center++;
index_lower++;
if(!skip_ch2) {
if(kernel_size == 3) {
img_gauss[index_center] = calc_filter3x3( a_s16[0], a_s16[1], a_s16[2], a_s16[3], a_s16[4], a_s16[5], a_s16[6], a_s16[7], a_s16[8],
img_in[index_upper], img_in[index_upper+3], img_in[index_upper+6],
img_in[index_center], img_in[index_center+3], img_in[index_center+6],
img_in[index_lower], img_in[index_lower+3], img_in[index_lower+6],
shift_fact);
}
else {
img_gauss[index] = calc_filterNxN(img_in+1, height, width, 3, y, x, a_s16, kernel_size, kernel_size, shift_fact);
}
}
else {
img_gauss[index] = 0;
}
index++;
index_upper++;
index_center++;
index_lower++;
if(!skip_ch3) {
if(kernel_size == 3) {
img_gauss[index_center] = calc_filter3x3( a_s16[0], a_s16[1], a_s16[2], a_s16[3], a_s16[4], a_s16[5], a_s16[6], a_s16[7], a_s16[8],
img_in[index_upper], img_in[index_upper+3], img_in[index_upper+6],
img_in[index_center], img_in[index_center+3], img_in[index_center+6],
img_in[index_lower], img_in[index_lower+3], img_in[index_lower+6],
shift_fact);
}
else {
img_gauss[index] = calc_filterNxN(img_in+2, height, width, 3, y, x, a_s16, kernel_size, kernel_size, shift_fact);
}
}
else {
img_gauss[index] = 0;
}
index++;
index_upper++;
index_center++;
index_lower++;
}
}
/*
* Image border are not filtered
*/
// handler upper horizontal border area
index = 0;
for(y = 0; y < y_start; y++) {
for(x = 0; x < width; x++) {
img_gauss[index] = img_in[index];
img_gauss[index+1] = img_in[index+1];
img_gauss[index+2] = img_in[index+2];
index += 3;
}
}
// handler lower horizontal border area
index = y_end*width*3;
for(y = y_end; y < height; y++) {
for(x = 0; x < width; x++) {
img_gauss[index] = img_in[index];
img_gauss[index+1] = img_in[index+1];
img_gauss[index+2] = img_in[index+2];
index += 3;
}
}
// handler left vertical border area
for(y = 0; y < height; y++) {
index = y*width*3;
for(x = 0; x < x_start; x++) {
img_gauss[index] = img_in[index];
img_gauss[index+1] = img_in[index+1];
img_gauss[index+2] = img_in[index+2];
index += 3;
}
}
// handler right vertical border area
for(y = 0; y < height; y++) {
index = (y*width+x_end)*3;
for(x = x_end; x < width; x++) {
img_gauss[index] = img_in[index];
img_gauss[index+1] = img_in[index+1];
img_gauss[index+2] = img_in[index+2];
index += 3;
}
}
}
/**
* Generate binary image from given image of type: 3 channels, 16 bit signed fixed-point per channel.
* All pixel values above the given threshold are set to the defined value. Otherwise the values are set to 0.
*
* @param img_gauss On return: gauss low-pass filtered image
* @param img_in input image to be filtered
* @param height image height in number of pixels
* @param width image width in number of pixels
* @param threshold binary threshold
* @param bin_value Pixel values above given threshold are set to this value. Values below the threshold are set to 0.
* @param skip_ch1 set to 0 if channel 1 should be filtered
* @param skip_ch2 set to 0 if channel 2 should be filtered
* @param skip_ch3 set to 0 if channel 3 should be filtered
*/
void filter_binary_3s16(int8_t *img_bin, const int16_t *img_in, const int height, const int width, const int16_t threshold, const int8_t bin_value,
const int skip_ch1, const int skip_ch2, const int skip_ch3) {
int y, x, index;
index = 0;
for(y = 0; y < height; y++) {
for(x = 0; x < width; x++) {
if(!skip_ch1) {
if(img_in[index] >= threshold) {
img_bin[index] = bin_value;
}
else {
img_bin[index] = 0;
}
}
else {
img_bin[index] = 0;
}
index++;
if(!skip_ch2) {
if(img_in[index] >= threshold) {
img_bin[index] = bin_value;
}
else {
img_bin[index] = 0;
}
}
else {
img_bin[index] = 0;
}
index++;
if(!skip_ch3) {
if(img_in[index] >= threshold) {
img_bin[index] = bin_value;
}
else {
img_bin[index] = 0;
}
}
else {
img_bin[index] = 0;
}
index++;
}
}
}