C++ School Assignment? Project Due Tomorrow? Chat LIVE With A Programming Expert!

Welcome to Dream.In.Code
Become a C++ Expert!

Join 307,004 C++ Programmers for FREE! Get instant access to thousands of C++ experts, tutorials, code snippets, and more! There are 1,955 people online right now. Registration is fast and FREE... Join Now!




Image Processing Tutorial

 
Reply to this topicStart new topic

> Image Processing Tutorial, Very basic image processing

cwginac
Group Icon



post 15 Dec, 2008 - 05:09 AM
Post #1


Basic Image Processing

Processing images may seem like a daunting task. With thousands of pixels to keep track of, they can be downright scary to the occasional programmer. But when you stop and think about it, they break down to a basic data structure that you know how to use.

Before we get to any code, let’s break these images down. To start with, let’s use a grayscale picture (it’ll be easier). Each of the pixels in a grayscale picture has one value, the gray value. If you think about pixels as numbers instead of a region of color, what do you get? A 2D array. This 2D array is stored in basically a text file. All you have to do is read in the array into an array you create.

Now that you know that images are basically 2D arrays, you’ll need to know how to create an image object in c++.

First you’ll need to create a user defined class. If you don’t know how to do this, you can Google it or look at the Tutorial here on DIC (http://www.dreamincode.net/forums/showtopic9870.htm , written by Born2Code).

In the class you’ll want to have the 2D array. You’ll also want variables holding the number of rows and columns and the max gray value. These will help when you create the object. In the rest of the class you’ll want functions that you’re going to use to process the image, such as rotating and enlarging.

Here is the class that we will be working with:
CODE

#ifndef IMAGE_H
#define IMAGE_H

class Image
{
      public:
             Image();                             //constructor
             Image(int numRows, int numCols, int grayLevels);    //constructor
             ~Image();                        //destructor
             Image(const Image& oldImage);                //constructor
             void operator=(const Image&);                //overloaded assignment operator
             void setImageInfo(int numRows, int numCols, int maxVal);
             void getImageInfo(int &numRows, int &numCols, int &maxVal);
             int getPixelVal(int row, int col);
             void setPixelVal(int row, int col, int value);
             bool inBounds(int row, int col);                //checks to see if a pixel is in bounds
             void getSubImage(int upperLeftRow, int upperLeftCol,
                int lowerRightRow, int lowerRightCol, Image& oldImage);
             void enlargeImage(int value, Image& oldImage);
             void shrinkImage(int value, Image& oldImage);
             void reflectImage(bool flag, Image& oldImage);
             void translateImage(int value, Image& oldImage);
             void rotateImage(int theta, Image& oldImage);
             Image operator+(const Image &oldImage);        //overloaded + operator
             Image operator-(const Image& oldImage);        //overloaded - operator
             void negateImage(Image& oldImage);
      private:
              int N;                             // number of rows
              int M;                             // number of columns
              int Q;                             // number of gray levels
              int **pixelVal;                        //2D array
};

#endif



(By the way, we will be working with 3 files: main.cpp, image.h, image.cpp. They will be attached as .txt files.)

Let’s start with Image(int numRows, int numCols, int grayLevels). Very rarely will you create an Image without knowing the rows, columns and the max gray level, so I won’t go into Image(). Don’t worry it’ll be in the code.

The constructor Image(int numRows, int numCols, int grayLevels) will create an Image with dimensions numRows x numCols with the maximum gray level of grayLevels. The code is below:

CODE

Image::Image(int numRows, int numCols, int grayLevels)
/* Creates an Image of numRows x numCols and creates the arrays for it*/
{    
    
    N = numRows;
    M = numCols;
    Q = grayLevels;
    
    pixelVal = new int *[N];
    for(int i = 0; i < N; i++)
    {
        pixelVal[i] = new int [M];
        for(int j = 0; j < M; j++)
            pixelVal[i][j] = 0;
    }
}

(This code goes into image.cpp)

This sets the private data members of the class and creates the array. As I said before, we are using an array of pointers that point to integer arrays. First we create that array of pointers. Each pointer is a row of the array. Then in each of those rows, we put enough memory to hold the cells of that row. After you create the arrays, you can use the [][] operators. Then we set the image to a blank black image (you can also insert Q to make a white image).

After we create the object, now we can modify and insert the data from the file.

The type of Image that we will be using in this example is the .PGM file format. For more info on this format, go to http://tinyurl.com/pgmfile. I’ll sum it all up real quick. The file holds all you need to know to create the image. The type of image, also called the magic number (will be P5); the file name (we won’t care about this line); the number of rows and columns; and the data (2D array).

To read this into the image we will write a function to do this. I put this into main.cpp, but you can put this into the class very easily with little modification. The code is below:

CODE

int readImage(char fname[], Image& image)
{
    int i, j;
    int N, M, Q;
    unsigned char *charImage;
    char header [100], *ptr;

    ifstream ifp;
    ifp.open(fname, ios::in | ios::binary);

    if (!ifp)     //error checking
    {
        cout << "Can't read image: " << fname << endl;
        exit(1);
    }

// read header

    ifp.getline(header,100,'\n');        //magic number
    if ( (header[0]!=80) || (header[1]!=53) )      //if not P5
    {  
        cout << "Image " << fname << " is not PGM" << endl;
        exit(1);
    }

    ifp.getline(header,100,'\n');
    while(header[0]=='#')        //file name line in file starts with #
        ifp.getline(header,100,'\n');

    M=strtol(header,&ptr,0);    //number of colums
    N=atoi(ptr);            //number of rows

    ifp.getline(header,100,'\n');
    Q=strtol(header,&ptr,0);    //max gray value

    charImage = (unsigned char *) new unsigned char [M*N];    //creates 2D array

    ifp.read( reinterpret_cast<char *>(charImage), (M*N)*sizeof(unsigned char));  //reads in 2D array

    if (ifp.fail())
    {
        cout << "Image " << fname << " has wrong size" << endl;
        exit(1);
    }

    ifp.close();

// Convert the unsigned characters to integers

    int val;

    for(i=0; i<N; i++)
        for(j=0; j<M; j++)
        {
            val = (int)charImage[i*M+j];
            image.setPixelVal(i, j, val);    
        }

    delete [] charImage;

    return (1);
}


I have inserted comments in the code so the code is explained right when you first read it. Using these same concepts you also need a function to write to the file, which is included in the main.txt (main.cpp file) that is attached.

However, there are limitations on this function. The major thing is that you pass the image already at the size it should be at. To fix that, you have a function that reads just the header on the image file. This goes into main.cpp also:

CODE

int readImageHeader(char fname[], int& N, int& M, int& Q, bool& type)
{
    int i, j;
    unsigned char *charImage;
    char header [100], *ptr;
    
    ifstream ifp;
    ifp.open(fname, ios::in | ios::binary);

    if (!ifp)
    {
        cout << "Can't read image: " << fname << endl;
        exit(1);
    }

// read header

//*************************
//this section will not be used in this tutorial, this is used for programs that can handle color images

    type = false;   // PGM

    ifp.getline(header,100,'\n');
    if ( (header[0] == 80) && (header[1]== 53) )
    {  
      type = false;
    }
    else if ( (header[0] == 80) && (header[1] == 54) )
    {      
      type = true;
    }
    else
    {
        cout << "Image " << fname << " is not PGM or PPM" << endl;
        exit(1);
    }

//*****************************

    ifp.getline(header,100,'\n');
    while(header[0]=='#')
        ifp.getline(header,100,'\n');

    M=strtol(header,&ptr,0);
    N=atoi(ptr);

    ifp.getline(header,100,'\n');

    Q=strtol(header,&ptr,0);

    ifp.close();

    return(1);
}


For this program, bool type does nothing. This will set the number of rows, cols, and max grayscale value that you need to create the image. At this point you have written all the functions you need to create the object. The code below puts it all together. This is in int main().

CODE

int main(int argc, char *argv[])
{
    int M, N, Q; // rows, cols, grayscale
    int val;
    bool type;

    // read image header
    readImageHeader(argv[1], N, M, Q, type);

    // allocate memory for the image array
    Image image(N, M, Q);

    // read image
    readImage(argv[1], image);
}


If you’re not quite sure what int argc, char *argv[] is, Google it. They refer to parameters passed to the program.

Congratulations, you did it! You read in the file, but now is the fun part: manipulation.

Since the data in the Image is a 2D array, much of the manipulation is simple and easy to figure out. I won’t go into most of the functions, and they will be attached anyways. But try to write your own code for them, it’s a lot of fun to see the code your write do stuff!

But anyways, I will go into detail about one: rotateImage(int theta, Image& oldImage).

There are a lot of things you will have to think about with rotating the Image. You need a little math to help you out. There are formulas that you will need to use to get it right.

CODE

r’ = r cos(theta) – c sin(theta)
c’ = r sin(theta) – c cos(theta)


But to rotate it around the center you will need to use:

CODE

r’ = r0 + (r – r0)cos(theta) – (c – c0)sin(theta)
c’ = c0 + (r - r0)sin(theta) + (c – c0)cos(theta)


Note: r0 and c0 are the center points.

Below is the actual code to do this operation (goes into image.cpp):

CODE

void Image::rotateImage(int theta, Image& oldImage)
/*based on users input and rotates it around the center of the image.
{
    int r0, c0;
    int r1, c1;
    int rows, cols;
    rows = oldImage.N;
    cols = oldImage.M;
    Image tempImage(rows, cols, oldImage.Q);
    
    float rads = (theta * 3.14159265)/180.0; //converts the degree given by user into radians
    
    //find midpoints
    r0 = rows / 2;
    c0 = cols / 2;
    
    // goes through the array of the oldImage, uses the formulas to find where the pixel should go
   //  then puts the old pixel value into the new pixel position on the tempImage
    for(int r = 0; r < rows; r++)
    {
        for(int c = 0; c < cols; c++)
        {
            r1 = (int) (r0 + ((r - r0) * cos(rads)) - ((c - c0) * sin(rads)));
            c1 = (int) (c0 + ((r - r0) * sin(rads)) + ((c - c0) * cos(rads)));
            
            if(inBounds(r1,c1))  // makes sure the new pixel location is in bounds,
            {
                tempImage.pixelVal[r1][c1] = oldImage.pixelVal[r][c];
            }
        }
    }
    
    for(int i = 0; i < rows; i++)
    {
        for(int j = 0; j < cols; j++)
        {
            if(tempImage.pixelVal[i][j] == 0)
                tempImage.pixelVal[i][j] = tempImage.pixelVal[i][j+1];
        }
    }
    oldImage = tempImage;
}


Now many of you might be wondering what the last for loop is for. See the formulas present an interesting problem. You would find that due to the integer truncation, some of the pixels on the new image would not be touched, leaving little holes (you should remove that for loop and see for yourself). To counter act that, I have put in the last for loop. It goes through the array and if the pixel is black has a neighbor of the right side, it will assume the color of the neighbor.

That wraps up this tutorial. Attached are the original files used for the project and the rest of the code. I have elected to not talk about a lot of the functions here. They are in the attached files. I believe that if you understand that the 2D array is easy to manipulate, you can do all of the functions easily. Sorry, the files don’t have all the comments that I put in here.

Thanks, and have fun manipulating those images!




Attached File(s)
Attached File  image.cpp.txt ( 8.45k ) Number of downloads: 319
Attached File  image.h.txt ( 1.46k ) Number of downloads: 246
Attached File  main.cpp.txt ( 11.71k ) Number of downloads: 257
Go to the top of the page
+Quote Post


Register to Make This Ad Go Away!

mals48323
*



post 25 Jan, 2009 - 08:55 AM
Post #2
It would have been super if you also included strategies for displaying the image.
Go to the top of the page
+Quote Post

cwginac
Group Icon



post 22 Feb, 2009 - 09:01 PM
Post #3
Sorry, I haven't really ever played with showing the images within the code. You can use ifranview to see the images. It's a little harder, but at least it works. If any of you have a good way of doing this, it would be appricated.
Go to the top of the page
+Quote Post

Predictor
*



post 28 Mar, 2009 - 04:46 AM
Post #4
This reminds me of why I do this kind of work in MATLAB. biggrin.gif

"Life is too short for DO loops." -MATLAB


-Will Dwinnell
Data Mining in MATLAB

Go to the top of the page
+Quote Post

Master.
*



post 11 Sep, 2009 - 11:47 AM
Post #5
thanks alot ,
can you accompany your first post with a screenshot too?
tanx in advance,
by the way would you contribute more on this kinda stuff ? or guide me where i can find more of these awesome tuts for beginners?
Go to the top of the page
+Quote Post


Reply to this topicStart new topic
1 User(s) are reading this topic (1 Guests and 0 Anonymous Users)
0 Members:

 


Lo-Fi Version Time is now: 11/21/09 06:48AM

Live C++ Help!

Be Social

Dream.In.Code RSS Feed Dream.In.Code LinkedIn Group Follow Us On Twitter Fan Us On Facebook

C++ Tutorials

Reference Sheets

C++ Snippets

DIC Chatroom

Bye Bye Ads

Monthly Drawing

Thumb Drive

Top Contributors

Top 10 Kudos This Month