Search code examples
c#image-processing

Image Manipulation using Average Kernel


Ive been working on a code that takes an image, turns it into grayscale, and then does image manipulation depending on which button is pressed. When a button is pressed (ex. Average 3x3, Prewitt 5x5), it calls a 2D Multiplication function, which loops over the grayscale image, while looping over the kernel, adding all the values in the matrix. If any value is over 255, it sets it to 255. Then using the SetPixel on a temporary bitmap variable, which is finally put into the picturebox. When i run the program, I select an image and it shows it (as grayscale), but after choosing one of the filters, the program freezes for around 30 seconds, and then nothing changes, no filter is applied. Ive tried debugging and i cant seem to locate what the problem is!

EDIT: The initial question has been solved( I had to refresh the picturebox for the new image to show properly. But i am facing another problem here with regards to the prewitt kernel.

i get this error "Additional information: Value of '-6' is not valid for 'red'. 'red' should be greater than or equal to 0 and less than or equal to 255."

And i am not sure what to change in my code to fix this.

Initializing:

public partial class Form1 : Form
{
    private Image img;
    Bitmap grayscaleimage;
    double[][] AVGKernel = new double[11][];
    double[][] PrewittKernel = new double[11][];
    int[] AVGKernal1DH = new int[11];
    int[] AVGKernal1DV = new int[11];

    Bitmap tempBitmap;

    public Form1()
    {
        InitializeComponent();
        for (int i = 0; i < 11; i++)
        {
            AVGKernel[i] = new double[11];
            PrewittKernel[i] = new double[11];
            for (int j = 0; j < 11; j++)
            {
                AVGKernel[i][j] = 0;
                PrewittKernel[i][j] = 0;
                AVGKernal1DH[j] = 0;
                AVGKernal1DV[j] = 0;
            }
        }
}

The open button and turning the picture into grayscale:

private void OpenImageButton(object sender, EventArgs e)
    {
        OpenFileDialog openFileDialog = new OpenFileDialog();
        if (openFileDialog.ShowDialog() == DialogResult.OK)
        {
            this.img = Image.FromFile(openFileDialog.FileName);

            grayscaleimage = new Bitmap(img);
            int rgb;
            Color c;

            for (int y = 0; y < grayscaleimage.Height; y++)
                for (int x = 0; x < grayscaleimage.Width; x++)
                {
                    c = grayscaleimage.GetPixel(x, y);
                    rgb = (int)((c.R + c.G + c.B) / 3);
                    grayscaleimage.SetPixel(x, y, Color.FromArgb(rgb, rgb, rgb));
                }
            this.pictureBox1.BackgroundImage = grayscaleimage;
            pictureBox1.BackgroundImageLayout = ImageLayout.Zoom;
        }
    }

an example of the many buttons available:

private void button5_Click(object sender, EventArgs e)
    {
        AVGKernel[0][0] = 1; AVGKernel[0][1] = 1; AVGKernel[0][2] = 1; AVGKernel[0][3] = 1; AVGKernel[0][4] = 1;
        AVGKernel[1][0] = 1; AVGKernel[1][1] = 1; AVGKernel[1][2] = 1; AVGKernel[1][3] = 1; AVGKernel[1][4] = 1;
        AVGKernel[2][0] = 1; AVGKernel[2][1] = 1; AVGKernel[2][2] = 1; AVGKernel[2][3] = 1; AVGKernel[2][4] = 1;
        AVGKernel[3][0] = 1; AVGKernel[3][1] = 1; AVGKernel[3][2] = 1; AVGKernel[3][3] = 1; AVGKernel[3][4] = 1;
        AVGKernel[4][0] = 1; AVGKernel[4][1] = 1; AVGKernel[4][2] = 1; AVGKernel[4][3] = 1; AVGKernel[4][4] = 1;

        kernal2DMultiplication(AVGKernel, 5);
        this.pictureBox1.BackgroundImage = tempBitmap;

    }

Prewitt 5x5 kernel

 private void button13_Click(object sender, EventArgs e)
    {
        PrewittKernel[0][0] = 2; PrewittKernel[0][1] = 1; PrewittKernel[0][2] = 0; PrewittKernel[0][3] = -1; PrewittKernel[0][4] = -2;
        PrewittKernel[1][0] = 2; PrewittKernel[1][1] = 1; PrewittKernel[1][2] = 0; PrewittKernel[1][3] = -1; PrewittKernel[1][4] = -2;
        PrewittKernel[2][0] = 2; PrewittKernel[2][1] = 1; PrewittKernel[2][2] = 0; PrewittKernel[2][3] = -1; PrewittKernel[2][4] = -2;
        PrewittKernel[3][0] = 2; PrewittKernel[3][1] = 1; PrewittKernel[3][2] = 0; PrewittKernel[3][3] = -1; PrewittKernel[3][4] = -2;
        PrewittKernel[4][0] = 2; PrewittKernel[4][1] = 1; PrewittKernel[4][2] = 0; PrewittKernel[4][3] = -1; PrewittKernel[4][4] = -2;

        kernal2DMultiplication(PrewittKernel, 5);
        this.pictureBox1.BackgroundImage = tempBitmap;
        this.pictureBox1.Refresh();
    }

and finally, the function being called:

private void kernal2DMultiplication(double[][] kernel, int size)
    {
        tempBitmap = grayscaleimage;
        double nrgb = 0;

        for (int i = 0; i < grayscaleimage.Width - size / 2; i++)
        {
            for (int j = 0; j < grayscaleimage.Height - size / 2; j++)
            {
                if (i >= size / 2 && j >= size / 2)
                {
                    for (int k = 0; k < size; k++)
                        for (int l = 0; l < size; l++)

                            nrgb += grayscaleimage.GetPixel(i + k - (size / 2), j + l - (size / 2)).R * kernel[k][l];
                            nrgb = nrgb / (size * size);

                    if (nrgb > 255)
                        nrgb = 255;

                    tempBitmap.SetPixel(i, j, Color.FromArgb((int)nrgb, (int)nrgb, (int)nrgb));

                }

            }

        }
    }

Solution

  • The problem is that you are modifying the BackgroundImage bitmap in-place rather than copying, modifying the copy, and then setting the copy to be the BackgroundImage:

        this.pictureBox1.BackgroundImage = grayscaleimage; // initially
    
        tempBitmap = grayscaleimage; 
    
        // Make changes to tempBitmap
    
        this.pictureBox1.BackgroundImage = tempBitmap; // actually still the same pointer
    

    The BackgroundImage setter is not forcing a redraw of the control in this case. To force this yourself, call Refresh():

        this.pictureBox1.Refresh();
    

    To improver performance, look into replacing multiple calls to SetPixel with a single call to LockBits. See e.g. here.