snippetcsharpMinor
Applying a 'mean filter'
Viewed 0 times
applyingmeanfilter
Problem
I am just trying to apply a "mean filter" with C#. The working code below gets window size (3,5,7,9,...) from
I am new and I want your comments and suggestions about my code so I can improve myself.
Can I make it faster? How? Is there any unnecessary or absurd part in the code? Can it be improved in any other way?
I do not care about border pixels for now.
textBoxWinSize and applies the mean filter.I am new and I want your comments and suggestions about my code so I can improve myself.
Can I make it faster? How? Is there any unnecessary or absurd part in the code? Can it be improved in any other way?
I do not care about border pixels for now.
private void buttonApplyFilter_Click(object sender, EventArgs e)
{
int R, G, B,aveR,aveG,aveB;
double sumR,sumG,sumB;
Color pixel_value;
Bitmap bmp = new Bitmap(bmpOrig);
int WinSize = Int32.Parse(textBoxWinSize.Text);
int col,row,n,m;
for (col = (WinSize-1); col < (bmp.Width-WinSize); col++)
for (row = (WinSize-1); row < (bmp.Height-WinSize); row++)
{
sumB = 0;
sumG = 0;
sumR = 0;
for (m=-(WinSize-1)/2; m<=(WinSize-1)/2;m++)
for (n=-(WinSize-1)/2;n<=(WinSize-1)/2;n++)
{
pixel_value = bmp.GetPixel(col+m, row+n);
R = (pixel_value.R);
G =(pixel_value.G);
B = (pixel_value.B);
sumR = sumR + R;
sumG = sumG + G;
sumB = sumB + B;
}
aveR = (int)(sumR / (WinSize * WinSize));
aveG = (int)( sumG / (WinSize * WinSize));
aveB = (int)( sumB / (WinSize * WinSize));
bmp.SetPixel(col, row, Color.FromArgb(aveR, aveG, aveB));
}
pictureBox1.Image = bmp;
}Solution
First up, I think you're doing too much in the button click handler. The logic for applying the mean filter should be moved to its own method, something like
Now it can be re-used and unit tested more easily.
I think this line is wrong
in that
Assuming the
Say that instead of a bitmap, we have a 2D array of ints.
input
\begin{array}{c | c c c c}
& 0 & 1 & 2 & 3 \\
\hline
0 & 1 & 2 & 3 & -1 \\
1 & 4 & 1 & 6 & -3 \\
2 & 4 & 5 & 9 & -3 \\
3 & 4 & -2 & 6 & -3
\end{array}
We can compute another 2D array such that the \$(i, j)\$-th entry is the sum of entries in the submatrix \$(0, 0), (i, j)\$:
sums
\begin{array}{c | c c c c}
& 0 & 1 & 2 & 3 \\
\hline
0 & 1 & 3 & 6 & 5 \\
1 & 5 & 8 & 17 & 13 \\
2 & 9 & 17 & 35 & 28 \\
3 & 13 & 19 & 43 & 33
\end{array}
Now suppose we want to find the sum of the values in the 3x3 square around entry \$(i, j) = (2, 2)\$:
\begin{array}{c | c c c c}
& 0 & 1 & 2 & 3 \\
\hline
0 & 1 & 2 & 3 & -1 \\
1 & 4 & 1 & 6 & -3 \\
2 & 4 & 5 & \textbf{9} & -3 \\
3 & 4 & -2 & 6 & -3
\end{array}
We can do this quickly using the matrix we just computed. The answer is given by
In general, if
The beauty is, computing the sum of the surrounding
Getting back to the original problem of bitmaps, let's suppose we have a struct that will let us sum up the RGB values of the pixels. I named it
Putting it all together would look something like this
Running this on a 256x256 image, I got the following timings
Other things to try that might improve performance:
arrays in .NET slower than normal
arrays?
private static Bitmap ApplyMeanFilter(Bitmap input, int windowSize)Now it can be re-used and unit tested more easily.
I think this line is wrong
pixel_value = bmp.GetPixel(col+m, row+n)in that
bmp should be bmpOrig. The reasoning is that you're changing pixels in bmp as you go, which will have an effect on pixels that are processed later. That is, they won't be assigned the mean of their original surrounding pixels.Assuming the
bmp should be bmpOrig, we can optimise this quite a bit.Say that instead of a bitmap, we have a 2D array of ints.
input
\begin{array}{c | c c c c}
& 0 & 1 & 2 & 3 \\
\hline
0 & 1 & 2 & 3 & -1 \\
1 & 4 & 1 & 6 & -3 \\
2 & 4 & 5 & 9 & -3 \\
3 & 4 & -2 & 6 & -3
\end{array}
We can compute another 2D array such that the \$(i, j)\$-th entry is the sum of entries in the submatrix \$(0, 0), (i, j)\$:
var sums = new int[height, width];
for (var i = 0; i 0 ? sums[i - 1, j] + sum : sum;
}
}sums
\begin{array}{c | c c c c}
& 0 & 1 & 2 & 3 \\
\hline
0 & 1 & 3 & 6 & 5 \\
1 & 5 & 8 & 17 & 13 \\
2 & 9 & 17 & 35 & 28 \\
3 & 13 & 19 & 43 & 33
\end{array}
Now suppose we want to find the sum of the values in the 3x3 square around entry \$(i, j) = (2, 2)\$:
\begin{array}{c | c c c c}
& 0 & 1 & 2 & 3 \\
\hline
0 & 1 & 2 & 3 & -1 \\
1 & 4 & 1 & 6 & -3 \\
2 & 4 & 5 & \textbf{9} & -3 \\
3 & 4 & -2 & 6 & -3
\end{array}
We can do this quickly using the matrix we just computed. The answer is given by
sums[3, 3] - sums[3, 0] - sums[0, 3] + sums[0, 0]
= 33 - 13 - 5 + 1
= 16In general, if
halfWindow = (windowSize - 1) / 2 then the sum of values in the windowSize * windowSize square around entry \$(i, j)\$ is given bysums[i + halfWindow, j + halfWindow] -
sums[i - halfWindow - 1, j + halfWindow] -
sums[i + halfWindow, j - halfWindow - 1] +
sums[i - halfWindow - 1, j - halfWindow - 1]The beauty is, computing the sum of the surrounding
windowSize * windowSize values this way is independent of windowSize.Getting back to the original problem of bitmaps, let's suppose we have a struct that will let us sum up the RGB values of the pixels. I named it
Clr because I'm not very imaginative today; I'm sure you can think of a better name. Let's also assume we have operators +, - and / defined in the obvious way.Putting it all together would look something like this
var sums = new Clr[input.Height, input.Width];
for (var i = 0; i 0 ? sums[i - 1, j] + sum : sum;
}
}
var output = new Bitmap(input);
var windowArea = windowSize * windowSize;
var halfWindow = (windowSize - 1) / 2;
for (var i = windowSize - 1; i < input.Height - windowSize; i++)
{
for (var j = windowSize - 1; j < input.Width - windowSize; j++)
{
var clr = sums[i + halfWindow, j + halfWindow] -
sums[i - halfWindow - 1, j + halfWindow] -
sums[i + halfWindow, j - halfWindow - 1] +
sums[i - halfWindow - 1, j - halfWindow - 1];
output.SetPixel(j, i, (clr / windowArea).ToColor());
}
}
return output;Running this on a 256x256 image, I got the following timings
windowSize this original
3 00:00:00.0910750 00:00:00.4720078
5 00:00:00.0899366 00:00:01.2674758
7 00:00:00.0853111 00:00:02.2569419
9 00:00:00.0938911 00:00:03.4408123
11 00:00:00.0840854 00:00:04.9614386Other things to try that might improve performance:
- Use a
Clr[][]instead of aClr[,]. See Why are multi-dimensional
arrays in .NET slower than normal
arrays?
- Doing the above will let you move
sums[i + halfWindow]andsums[i - halfWindow - 1]into variables outside the inner loop.
- Compute the first row of
sumsseparately to remove the conditional in this linesums[i, j] = i > 0 ? sums[i - 1, j] + sum : sum;
Code Snippets
private static Bitmap ApplyMeanFilter(Bitmap input, int windowSize)pixel_value = bmp.GetPixel(col+m, row+n)var sums = new int[height, width];
for (var i = 0; i < height; i++)
{
var sum = 0;
for (var j = 0; j < width; j++)
{
sum += input[i, j];
sums[i, j] = i > 0 ? sums[i - 1, j] + sum : sum;
}
}sums[3, 3] - sums[3, 0] - sums[0, 3] + sums[0, 0]
= 33 - 13 - 5 + 1
= 16sums[i + halfWindow, j + halfWindow] -
sums[i - halfWindow - 1, j + halfWindow] -
sums[i + halfWindow, j - halfWindow - 1] +
sums[i - halfWindow - 1, j - halfWindow - 1]Context
StackExchange Code Review Q#70124, answer score: 4
Revisions (0)
No revisions yet.