Thread: Image Blur Filter

  1. #1
    Registered User Mcdom34's Avatar
    Join Date
    Jun 2012
    Location
    North Royalton, Ohio, United States
    Posts
    22

    Image Blur Filter

    I'm writing a program that takes in a `.raw` grayscale image and, pixel by pixel, blurs it. The resulting blurred image in then stored in an output file, specified by the user, of type `.raw`. I've got an idea of how to blur the image, as each pixel value must be the average of it and its neighboring pixels. However, when testing out the program, my resulting image is unreadable.

    Is my arithmetic correct? If so, am I using the correct data types for the additions?

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    
    int main()
    {
        FILE *fin, *fout;
        char path_in[64], path_out[64], **rev, px;
        int sum, width, height, read, i, j;
        
        printf("Input file name: ");
        scanf("%s", path_in);
        printf("Output file name: ");
        scanf("%s", path_out);
        
        printf("Width of image (in pixels): ");
        scanf("%d", &width);
        printf("Height of image (in pixels): ");
        scanf("%d", &height);
        
        fin = fopen(path_in, "rb");
        fout = fopen(path_out, "wb");
        
        rev = (char **)malloc(height * sizeof(char *));
        for(i = 0; i < height; i++)
            rev[i] = (char *)malloc(width * sizeof(char));
        //Store pixel values from image in a width x height unsigned char matrix
        for(i = 0; i < height; i++)
        {
            for(j = 0; j < width; j++)
            {
                read = fread(&px, sizeof(char), 1, fin);
                rev[i][j] = px;
            }
        }
        //Blur image using average of neighboring pixels
        sum = 0;
        for(i = 0; i < height; i++)
        {
            for(j = 0; j < width; j++)
            {
                //Top row of image
                if(i == 0)
                {
                    if(j == 0)
                        sum = (rev[i][j] + rev[i][j + 1] + 
                                rev[i + 1][j] + rev[i + 1][j + 1]) / 4;
                    else if(j == width - 1)
                        sum = (rev[i][j] + rev[i][j - 1] +
                                rev[i + 1][j] + rev[i + 1][j - 1]) / 4;
                    else
                        sum = (rev[i][j] + rev[i][j - 1] + rev[i][j + 1] +
                                rev[i + 1][j] + rev[i + 1][j - 1] + rev[i + 1][j + 1]) / 6;
                }
                //Bottom row of image
                else if(i == height - 1)
                {
                    if(j == 0)
                        sum = (rev[i][j] + rev[i][j + 1] + 
                                rev[i - 1][j] + rev[i - 1][j + 1]) / 4;
                    else if(j == width - 1)
                        sum = (rev[i][j] + rev[i][j - 1] +
                                rev[i - 1][j] + rev[i - 1][j - 1]) / 4;
                    else
                        sum = (rev[i][j] + rev[i][j - 1] + rev[i][j + 1] +
                                rev[i - 1][j] + rev[i - 1][j - 1] + rev[i - 1][j + 1]) / 6;
                }
                //Left side of image (excluding top or bottom row)
                else if(j == 0)
                    sum = (rev[i][j] + rev[i - 1][j] + rev[i + 1][j] +
                            rev[i][j + 1] + rev[i - 1][j + 1] + rev[i + 1][j + 1]) / 6;
                //Right side of image (excluding top or bottom row)
                else if(j == width - 1)
                    sum = (rev[i][j] + rev[i - 1][j] + rev[i + 1][j] + 
                            rev[i][j - 1] + rev[i - 1][j - 1] + rev[i + 1][j - 1]) / 6;
                rev[i][j] = (unsigned char)sum;
            }
        }
        //Write each pixel in rev to output file
        for(i = 0; i < height; i++)
        {
            for(j = 0; j < width; j++)
            {
                if(j < width && i < height)
                    fwrite(&rev[i][j], sizeof(char), 1, fout);
            }
        }
        //Close input and output file
        fclose(fout);
        fclose(fin);
        
        return 0;
    }

  2. #2
    Officially An Architect brewbuck's Avatar
    Join Date
    Mar 2007
    Location
    Portland, OR
    Posts
    7,396
    You have cases for the top row, bottom row, left column, and right column... but no case for all the pixels in the middle.

    HINT: Allocate your 2D array to be size (width + 2) x (height + 2). That way you can load your image into the center of this buffer and the padding around the image will allow you to get rid of all those special cases.
    Code:
    //try
    //{
    	if (a) do { f( b); } while(1);
    	else   do { f(!b); } while(1);
    //}

  3. #3
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Or even more generally, allocate an array of pixels with (image_height+2×filter_height-1) rows and (image_width+2×filter_width-1) columns, where filter_width and filter_height are the size of your filter kernel (3×3 above, it looks like).

    It is often easier to work with test programs that manipulate images, if you use the netpbm tools to convert to PBM (bitmap), PGM (grayscale), or PPM (full color) file format and back. The formats are trivial to write, and easy to read (trivial if you disallow comments; for full spec, you'll need a helper function that reads a decimal number but skips leading whitespaces and comments).

    A typical experiment in image filtering for me ends up reading a PBM image from standard input, modifying it, then writing the result in PBM format to standard output. I end up using a single command-line command to compile and run and use eog to display the filtered image:
    Code:
    gcc -Wall -O2 myfilter.c -o myfilter && pngtopnm example-image.png | ./myfilter | pnmtopng -compress 9 > result-image.png && eog result-image.png
    I don't use Windows, so in Windows you might split that into separate steps (maybe a batch file or a shell script), but at least you could use any PNG or JPEG or GIF image as your source image; just change the initial converter helper program name.

  4. #4
    Registered User Mcdom34's Avatar
    Join Date
    Jun 2012
    Location
    North Royalton, Ohio, United States
    Posts
    22
    Quote Originally Posted by brewbuck View Post
    You have cases for the top row, bottom row, left column, and right column... but no case for all the pixels in the middle.

    HINT: Allocate your 2D array to be size (width + 2) x (height + 2). That way you can load your image into the center of this buffer and the padding around the image will allow you to get rid of all those special cases.
    Wouldn't the code try and place pixel values in those padding regions then?

  5. #5
    Registered User Mcdom34's Avatar
    Join Date
    Jun 2012
    Location
    North Royalton, Ohio, United States
    Posts
    22
    I've included a case for the rest of the pixels in the image, but am still unable to get a readable blurred image. What am I doing wrong? :/

    Code:
    #include <stdio.h>
    #include <stdlib.h>
    
    
    int main()
    {
    	FILE *fin, *fout;
    	char path_in[64], path_out[64];
    	char **rev, px;
    	int sum, width, height, read, i, j;
    	
    	printf("Input file name: ");
    	scanf("%s", path_in);
    	printf("Output file name: ");
    	scanf("%s", path_out);
    	
    	printf("Width of image (in pixels): ");
    	scanf("%d", &width);
    	printf("Height of image (in pixels): ");
    	scanf("%d", &height);
    	
    	fin = fopen(path_in, "rb");
    	fout = fopen(path_out, "wb");
    	
    	rev = (unsigned char **)malloc(height * sizeof(unsigned char *));
    	for(i = 0; i < height; i++)
    		rev[i] = (unsigned char *)malloc(width * sizeof(unsigned char));
    	//Store pixel values from image in a width x height unsigned char matrix
    	for(i = 0; i < height; i++)
    	{
    		for(j = 0; j < width; j++)
    		{
    			read = fread(&px, sizeof(unsigned char), 1, fin);
    			rev[i][j] = px;
    		}
    	}
    	//Blur image using average of neighboring pixels
    	sum = 0;
    	for(i = 0; i < height; i++)
    	{
    		for(j = 0; j < width; j++)
    		{
    			//Top row of image
    			if(i == 0)
    			{
    				if(j == 0)
    					sum = (rev[i][j] + rev[i][j + 1] + 
    							rev[i + 1][j] + rev[i + 1][j + 1]) / 4;
    				else if(j == width - 1)
    					sum = (rev[i][j] + rev[i][j - 1] +
    							rev[i + 1][j] + rev[i + 1][j - 1]) / 4;
    				else
    					sum = (rev[i][j] + rev[i][j - 1] + rev[i][j + 1] +
    							rev[i + 1][j] + rev[i + 1][j - 1] + rev[i + 1][j + 1]) / 6;
    			}
    			//Bottom row of image
    			else if(i == height - 1)
    			{
    				if(j == 0)
    					sum = (rev[i][j] + rev[i][j + 1] + 
    							rev[i - 1][j] + rev[i - 1][j + 1]) / 4;
    				else if(j == width - 1)
    					sum = (rev[i][j] + rev[i][j - 1] +
    							rev[i - 1][j] + rev[i - 1][j - 1]) / 4;
    				else
    					sum = (rev[i][j] + rev[i][j - 1] + rev[i][j + 1] +
    							rev[i - 1][j] + rev[i - 1][j - 1] + rev[i - 1][j + 1]) / 6;
    			}
    			//Left side of image (excluding top or bottom row)
    			else if(j == 0)
    				sum = (rev[i][j] + rev[i - 1][j] + rev[i + 1][j] +
    						rev[i][j + 1] + rev[i - 1][j + 1] + rev[i + 1][j + 1]) / 6;
    			//Right side of image (excluding top or bottom row)
    			else if(j == width - 1)
    				sum = (rev[i][j] + rev[i - 1][j] + rev[i + 1][j] + 
    						rev[i][j - 1] + rev[i - 1][j - 1] + rev[i + 1][j - 1]) / 6;
    			//Pixels not on a border of the image
    			else
    				sum = (rev[i][j] + rev[i][j - 1] + rev[i][j + 1] + rev[i - 1][j] + rev[i + 1][j] + 
    						rev[i - 1][j - 1] + rev[i - 1][j + 1] + rev[i + 1][j - 1] + rev[i + 1][j + 1]) / 9;
    			rev[i][j] = (unsigned char)sum;
    		}
    	}
    	//Write each pixel in rev to output file
    	for(i = 0; i < height; i++)
    	{
    		for(j = 0; j < width; j++)
    		{
    			if(j < width && i < height)
    				fwrite(&rev[i][j], sizeof(unsigned char), 1, fout);
    		}
    	}
    	//Close input and output file
    	fclose(fout);
    	fclose(fin);
    	
    	return 0;
    }
    Sorry, I'm a little bit frustrated...

  6. #6
    Ticked and off
    Join Date
    Oct 2011
    Location
    La-la land
    Posts
    1,728
    Quote Originally Posted by Mcdom34 View Post
    What am I doing wrong?
    Well, you asked. Immediately pokes my eye:
    • Using scanf() for file names. They can contain spaces, you know, and they might be longer than the buffer you've reserved for them.
      I'd just put the parameters on the command line (argv[]), to avoid the prompts altogether.
    • Not checking scanf() return values. It returns the number of successful conversions. If it returns less than that, or EOF, some of the parameters were not assigned to.
    • sizeof (char) == sizeof (unsigned char) == 1. Not an error to use, but looks weird.
    • Using a single buffer, instead of separate buffers for the source and target (filtered, result) images. That means your filter calculates the results based on a mix of source and already filtered pixels. I'd use a struct to describe each image instead, to save the amount of code I'd need to write.


    Personally, I'd use an image structure and accessor functions, something like the following:
    Code:
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    
    typedef unsigned char  pixel;
    typedef struct {
        int width;
        int height;
        size_t stride;
        pixel *data;
    } image;
    
    int create_image(image *const img, const int width, const int height)
    {
        size_t stride = width;
        pixel *data;
    
        if (!img || width < 1 || height < 1)
            return errno = EINVAL;
    
        data = malloc(sizeof (image) + stride * (size_t)height * sizeof (pixel));
        if (data == NULL)
            return errno = ENOMEM;
    
        img->width = width;
        img->height = height;
        img->stride = stride;
        img->data = data;
    
        return 0;
    }
    
    int getpixel(const image *const img, const int x, const int y, pixel *const p)
    {
        if (!img || x < 0 || y < 0 || x >= img->width || y >= img->height)
            return 0;
        *p = img->data[(size_t)y * img->stride + (size_t)x];
        return 1;
    }
    
    int setpixel(image *const img, const int x, const int y, const pixel p)
    {
        if (!img || x < 0 || y < 0 || x >= img->width || y >= img->height)
            return 0;
        img->data[(size_t)y * img->stride + (size_t)x] = p;
        return 1;
    }
    Both getpixel() and setpixel() return 1 if the coordinates were inside the image, 0 otherwise. Note that the final parameter to getpixel() is a pointer to a result pixel, whereas for setpixel() it is the pixel itself.

    To calculate each filtered pixel, you'll need to use a temporary variable to hold the filtered pixel value, at higher range and preferably precision than the source/target (so, maybe float or double type), as well as the filter weights. For example, one part of applying a filter kernel to an image to obtain a filtered pixel could be
    Code:
        double pixel_sum = 0.0;  /* Could also be a fixed value */
        double weight_sum = 0.0;  /* Could also be nonzero to "blend" to that fixed value */
        pixel temp;
    
        if (getpixel(source, x-1, y-1, &temp)) {
            const double w = 0.25; /* -1,-1 has weight 0.25 */
            pixel_sum += w * temp;
            weight_sum += w;
        }
    but I'd put the weights in an array (forming the filter kernel, obviously), and have a double loop over the filter kernel using essentially the if condition above.

    After all the weights in the filter kernel have been checked, you clamp the result (in case the weights might cause an overflow), and set the pixel in the target image:
    Code:
        if (weight_sum != 0.0) {
            pixel_sum /= weight_sum;
            if (pixel_sum < 0.5)
                putpixel(target, x, y, (pixel)0);
            else
            if (pixel_sum < 255.5)
                putpixel(target, x, y, (pixel)(0.5 + pixel_sum));
            else
                putpixel(target, x, y, (pixel)255);
        } else
            putpixel(target, x, y, (pixel)UNFILTERED_PIXEL);
    This assumes 8-bit pixels. The UNFILTERED_PIXEL value is there in case the weights cancel each other. For example, if you had a filter that had only nonzero weights in one quarter, or both positive and negative weights, the total weight could become zero. In that case, you'll want to use a fixed pixel value.

    Speed and efficiency should not be a factor here, clarity and ease of understanding is. There are ways to speed filtering up, and use not much memory, but it is an advanced topic and involves quite complicated code and techniques. Yet, even "slow" computers are fast enough to apply quite complicated filters in very large files, using the above unoptimized approach, without the user having to wait for it. (We humans have reaction time, and if something happens in a fraction of a second, and we only need it now and then, not continuously, we tend to "feel" it is instant.)

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Quadratic and Gaussian Blur
    By TIMBERings in forum C++ Programming
    Replies: 0
    Last Post: 09-19-2010, 06:15 PM
  2. Replies: 12
    Last Post: 11-20-2007, 09:35 AM
  3. A little trouble implementing a blur filter
    By omishompi in forum C++ Programming
    Replies: 2
    Last Post: 04-16-2006, 08:57 AM
  4. writing a gaussian blur filter
    By Unregistered in forum C++ Programming
    Replies: 3
    Last Post: 06-04-2002, 08:02 AM
  5. Makes Me So Blur!!!
    By Mitchell in forum Windows Programming
    Replies: 1
    Last Post: 10-22-2001, 06:44 AM

Tags for this Thread