C ++ image filtering algorithm
I'm busy trying to implement an image filtering algorithm that works like this: The filter is a two-dimensional array of size N (N must be an odd number), so it has N * N elements. An example of a size 3 filter:
0.25 1 0.25
0.25 0 0.25
0.25 1 0.25
For each unsigned char (pixel) in the image data, place the center of the filter array at the current working pixel. Then, for each pixel that the filter covers in the image, find the weighted sum of all pixels covered by the filter (i.e., each filter value multiplied by the pixel it currently covers) and set the current pixel value of the working image to that weighted amount, do this for each pixel in the image. If the filter pixel falls out of the range of the image's 2D array (i.e. left, right, top, bottom) then it should wrap around the corresponding edge of the image.
So I have the following code:
Image Image::operator%(const Filter & g) {
Image filtered = *this;
std::vector<std::vector<float>> filter = g.get_filter();
Image::iterator beg = filtered.begin();
Image::iterator end = filtered.end();
unsigned char* pixel_data = filtered.data.get();
int pixel_index = 0;
while(beg != end) {
// current working pixel value
unsigned char* c_pixel = *beg;
float weight = 0;
// starting x and y position (top left) relative to the centre
// of the filter at index 'pixel'
int start_y = pixel_index - (g.get_size()-1) / 2;
int start_x = pixel_index - (g.get_size()-1) / 2;
for(int row = 0; row < g.get_size(); ++row) {
std::vector<float> r = filter.at(row);
int c_row = start_y + row;
if(c_row >= height) {
c_row %= height;
} else if(c_row < 0) {
c_row += height;
}
for(int col = 0; col < g.get_size(); ++col) {
// current column of filter relative
// to the image pixel
int c_col = start_x + col;
if(c_col >= width) {
c_col %= width;
} else if(c_col < 0) {
c_col += width;
}
weight += pixel_data[this->index(c_col, c_row)]*r.at(col);
}
}
*c_pixel = weight;
++beg;
++pixel_index;
}
return filtered;
}
In case you're wondering, this->index(c_col, c_row)
treats a 1D array as a 2D array:
int Image::index(int x, int y) {
return width*y + x;
}
... and the image data is protected std::unique_ptr<unsigned char[]>
. This code is giving me some weird result. The resulting image has vertical stripes of different pixel colors, somewhat reminiscent of the original image color. I don't know what I am doing wrong, because this method is tested on paper, but not in code. I'll be happy to add any additional information if required. :)
source to share
My first problem is the pixel format of the image. You say the result std::unique_ptr<unsigned char[]>
, but the weight is calculated and recorded using floats. Your method index
returns the index without considering the size of the pixel data {1_BYTE (monochrome), 3_BYTE (RGB8), 4_Byte (RGBA8)}. pixel_data
- char (Byte), so I'm not sure if you are indexing the pixel data correctly without taking into account the pixel size and ignoring the alpha (if necessary).
Another problem is that if you are using RGB (a) data, then the conversion from INT-> Float will not scale properly. Multiplying with float will scale the pixel as a solid number, not channels individually. This will cause the channels to flow into each other and, as a rule, are not correct.
The next step is to create a filter that reads and writes data as pixels with RGB channels (ignoring alpha) to make sure your filter gets through. Then you will write a filter that removes the RGB channel by setting it to 0 or 255. (Red channel, Blue channel, Green channel)
Once you are confident that you can control RGB separately and correctly, you can start applying the scales.
The first attempt will be slow. Eventually you will find out that you can use MASK to capture the R_B channel separately from the G-channel, and you won't worry about overflow. This magic looks like this in general:
UInt32 WeightPixel(UInt32 value, float weight)
{
const UInt32 MASK1 = (UInt32)0x00ff00ff; // _R_B
const UInt32 MASK2 = (UInt32)0xff00ff00; // A_G_
int f2 = (int)(256 * weight); // Scale weight into char range
// >> 8 is just divide by 256
return (UInt32)(((((value & MASK1) * f2)) >> 8) & MASK1)
| (UInt32)(((((value & MASK2) * f2)) >> 8) & MASK2);
}
source to share