Page 1 of 1

Image Processing Tutorial Very basic image processing Rate Topic: ****- 3 Votes

#1 cwginac  Icon User is offline

  • New D.I.C Head
  • member icon

Reputation: 2
  • View blog
  • Posts: 26
  • Joined: 13-December 08

Posted 15 December 2008 - 06:09 AM

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:
#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:

 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:

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:

 
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().

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.

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:

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):

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)



Is This A Good Question/Topic? 2
  • +

Replies To: Image Processing Tutorial

#2 mals48323  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 25-January 09

Posted 25 January 2009 - 09:55 AM

It would have been super if you also included strategies for displaying the image.
Was This Post Helpful? 0
  • +
  • -

#3 cwginac  Icon User is offline

  • New D.I.C Head
  • member icon

Reputation: 2
  • View blog
  • Posts: 26
  • Joined: 13-December 08

Posted 22 February 2009 - 10:01 PM

Sorry, I haven't really ever played with showing the images within the code. You can use irfanview 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.
Was This Post Helpful? 0
  • +
  • -

#4 Predictor  Icon User is offline

  • D.I.C Head

Reputation: 11
  • View blog
  • Posts: 90
  • Joined: 20-September 07

Posted 28 March 2009 - 05:46 AM

This reminds me of why I do this kind of work in MATLAB. :D

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


-Will Dwinnell
Data Mining in MATLAB
Was This Post Helpful? 0
  • +
  • -

#5 Master.  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 2
  • Joined: 11-September 09

Posted 11 September 2009 - 12:47 PM

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?
Was This Post Helpful? 0
  • +
  • -

#6 tpapastylianou  Icon User is offline

  • New D.I.C Head

Reputation: 2
  • View blog
  • Posts: 4
  • Joined: 22-January 10

Posted 22 January 2010 - 07:55 AM

Unfortunately, displaying the image is a bit more complicated than just coding a 'show' function as a one-liner. To display the image you need access to the graphical display of whatever operating system you're on, which is usually complicated stuff delving into direct hardware manipulation. This is usually taken care of under the hood by graphical user interface (GUI) libraries, such as QT, GTK, WxWidgets, or the Windows/Mac native stuff.

However, there's an image processing library for c/c++, called 'opencv', that I've worked with for a project, which also includes minimal GUI abilities, to display an image and handle basic mouse and keyboard events on that image window. It is a fairly simple library to learn and use, and there's excellent beginner tutorials both on their site and with the documentation provided with the library.

Having said that, opencv has its own image struct to handle image data. So if you wanted to use the Image class shown here to process the data, you'd still then have to 'translate' that data into the opencv native image struct every time you want to use an opencv function (such as, to display the image). Therefore, it is probably easier to just work on the native opencv image struct directly; one way of doing that would be to modify the class given above to inherit and make use of the opencv image struct and its associated ImgData, as opposed to a generic image data array.

If you were to use opencv, you'll be happy to hear that a lot of the image processing functions you might need are probably already in there. Of course, it's still useful to design things yourself as an exercise, but at the end of the day, opencv is a professional library and their version of that function is likely a lot better than what you'd come up with. There's still potential for learning though, as the reference index goes into nice detail (without being obfuscated) on what the function actually tries to do. I found that I answered a lot of my image processing questions, when I was learning, just by reading the explanation of what certain functions do (and then possibly supplementing my understanding of the core procedure and new terms with a simple internet search)

Some Links:
http://opencv.willowgarage.com/ <-- Main OpenCV site
http://opencv.willow.../FullOpenCVWiki <-- Main Wiki, lots of links to tutorials, FAQ, guides etc
http://opencv.willow...n/genindex.html <-- The main reference page with all the OpenCV functions. Priceless once you start programming in it.
http://sourceforge.n...vlibrary/files/ <-- Download links. Documentation downloads are separate, and they include examples and tutorials. Get those.
http://www.cs.iit.ed...ntro/index.html <-- A nice intro tutorial on the web which I remember gave me great tips when I was getting started.
plus, whatever else Google comes up with :)

OpenCV is certainly not the only image processing library out there, but it's a good one, and it's the one I've used and can personally recommend. Plus it's open source, so you can learn a lot by studying the code if you wanted to.

As for the 'doing this kind of work in MATLAB' comment, matlab is a completely different case scenario. If you just wanted to manipulate the image as a matrix to confirm stuff, and then report the results, then it's fine. But if your aim was, say, to design the next photoshop (heh) as a stand-alone application, you can't do that. At least not without expecting your target audience to have to buy matlab and learn it first, before they can even use your program. Not to mention all the extra image processing toolkits you may have used in the code, which can also be very expensive. Besides, programming in c++ is an entirely different beast altogether, which, personally, I found a lot more enjoyable to learn and use than matlab (but that's just me :) )

Sorry this was a bit long :)
Was This Post Helpful? 0
  • +
  • -

#7 Predictor  Icon User is offline

  • D.I.C Head

Reputation: 11
  • View blog
  • Posts: 90
  • Joined: 20-September 07

Posted 27 February 2010 - 09:03 AM

View Posttpapastylianou, on 22 January 2010 - 06:55 AM, said:

As for the 'doing this kind of work in MATLAB' comment, matlab is a completely different case scenario. If you just wanted to manipulate the image as a matrix to confirm stuff, and then report the results, then it's fine. But if your aim was, say, to design the next photoshop (heh) as a stand-alone application, you can't do that. At least not without expecting your target audience to have to buy matlab and learn it first, before they can even use your program. Not to mention all the extra image processing toolkits you may have used in the code, which can also be very expensive. Besides, programming in c++ is an entirely different beast altogether, which, personally, I found a lot more enjoyable to learn and use than matlab (but that's just me :) )

Sorry this was a bit long :)



You make several good points here, and let me say that I was poking in fun, and that it was not my intent to take away from your contribution here. For the actual image processing part, though, I am continually amazed at the size of procedural code (C, C++, etc.) needed versus vectorized code (MATLAB among others). Naturally, you are also right to point out that tool selection is at least in part a matter of taste.


-Will Dwinnell
Data Mining in MATLAB
Was This Post Helpful? 0
  • +
  • -

#8 Guest_Beginner*


Reputation:

Posted 18 March 2010 - 09:07 AM

thank so much for sharing. I have some problems.ifstream ifp;.Please help me.
Was This Post Helpful? 0

#9 mo7amed_mo7sen  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 23-June 10

Posted 23 June 2010 - 06:32 PM

could u help me please .. what can I do if I have already a .PGM pic and I need to convert it into a 2D array to work on it as an abstract data type and apply some graph theory functions ?
Was This Post Helpful? 0
  • +
  • -

#10 axistos  Icon User is offline

  • New D.I.C Head

Reputation: 0
  • View blog
  • Posts: 1
  • Joined: 30-November 12

Posted 30 November 2012 - 12:42 PM

Hello and thanks for this very good post!
Can you tell me what changes have to be done to do the same thing for a colorful image?
Any link or advice should be good enough...
Thanks!
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1