Small Background
This article is to help readers understand how to process images with Java. During my course work in Image Processing(8810) under Dr. Hamid R. Arabnia, I have developed this class that helps one to work on images, apply filters and other such operations. It lets you work with images at pixel level and is implemented well enough. Though my assignments were implemented in .NET for which I used Bitmap class, I wrote this utility class to help out my friends and surprisingly it did work very well. Well, cut the crap and lets go into some theory.
Practical Image
An image could be considered as a 2D array of Pixel. A pixel has a position (x,y) and intensity values (R,G,B). You have a color image and wish to covert the image into a gray scale image, then you extract these R,G,B values and then the gray level of that pixel in the simplest form is (R+G+B)/3 and replace each R,G,B with this average. Note that in image processing terminology, a gray scale image is different from a Black and white image. A Black and White image is also known as a Binary Image where the intensity of each pixel is either 0(Black) or 1(actually 255, white). A Gray Scale image has all R,G,B values equal.
Java Image Processing
There are several options in Java to develop image processing applications. The very well known library is the Java Advanced Imaging(JAI) Api. It is a very sophisticated API and does great work, but again, to understand JAI properly, one should really have a strong Image Processing fundamentals. Simpler options include the ImageIO API(again by Sun), ImageJ library and one very good library is the Hiof's Image Processing API. It can be found here. It is a very decent library and relatively old but does great job. And I have to market my own class. I do not call it a library but it has excellent methods that helps you work with raw pixel data. I call this library as ImageUtility and is available upon request. The following are the methods that are available in the ImageUtility:
1.LoadImage(String path) loads the image and returns the BufferedImage object
2.GetImageMatrix(BufferedImage img) returns the image passed as 2d array of integers. A pixel at (0,0) is accessed as returnedArray[0][0] and the value is between 0 to 255.
3.GetHistogram(int[][] matrix) - returns the int[] histogram of the passed matrix. A histogram is the distribution of intensities. For example, histogram[255] gives you the number of pixels whose intensity is White and histogram[0] gives the number of pixels whose intensity is Black.
4.GetImageFromMatrix(int[][] matrix) a function reverse to GetImageMatrix() method. Pass your matrix and get its bufferedImage object.
5.SaveImageToDisk(BufferedImage,String saveFileAs,String type) saves the image to the disk at the specified path(saveFileAs) as a image of type("jpg","png","gif")
6.GetBufferedImage(Image) Image is the super class of BufferedImage and yet is not straight forward to get a BufferedImage from an Image. This function does that exactly and trust me it is very useful.
7.GetHistogramImage(int[] histogram) returns the histogram as an Image, a graph that is more visual than an integer array.
8.ApplyFilter(int[][] filter,int[][] matrix) I should say this is the heart of the library. A Kernel/Filter is a 2D Array that does magic on an image. You can sharpen the image, do noise reduction(smoothing) and different other operations with this amazing filter. Consider the filter as a square piece of paper and your image as bigger sheet of paper. First put the smaller paper on the image paper such that both their (0,0) are on top of each other. Also both the horizontal and vertical borders of each should be aligned. I hope this gives and idea. Now you multiple each pixel positions and add the total value. For example, if you have a 3x3 kernel, first of all, you do a
1: val = k[0][0]*i[0][0]+k[0][1]*i[0][1]+k[0][1]*i[0][1] +
2: k[1][0]*i[1][0]+k[1][1]*i[1][1]+k[1][1]*i[1][1] +
3: k[2][0]*i[2][0]+k[2][1]*i[2][1]+k[2][1]*i[2][1]
where k is the kernel array and i[][] is the image matrix.
Now you put the value at i[1][1] (the center of image where kernel is applied). Once the first position is applied, you just move the kernel by one pixel such that k[0][0] is now at i[0][1]. That way you move the kernel all the way till the row ends. Then you move by one pixel down and reapply the same way. That way you cover the entire image. This infact is a time consuming operation and thus it should be now obvious on why image processing suites such as Photoshop,Paint.NET and GiMP are CPU intesive.
Understanding kernels is very important. Most frequent Image processing operations could be done with Kernels. I recommend you take a look at some Image Processing book to get a list of some filters, I dont remember them now.
9. GetFilesList(dir) Just an out of field method. Given a directory path, it returns the list of files in that directory.
10.SaveImageListToDisk(ArrayList list,folderPath) Saves the images in the arraylist in the specified folder.
Well now it is time for me to explain some code. Some of you might not be interested in my code and might just want to know how to load images from disk, how to extract pixel information from the image, etc. The subsequent sections explains you this.
Reading an Image from Disk
We use ImageIO class to load the image. Shown below is the code that loads the image. It is exactly as in ImageUtility.LoadImage() method.
1: public static BufferedImage LoadImage(String path) throws IOException
2: {
3: //Load the image
4: BufferedImage img = ImageIO.read(new File(path));
5: //If it is not a gray scale image
6: if (img.getType() != BufferedImage.TYPE_BYTE_GRAY)
7: {
8: //Let us convert to Gray Scale.
9: ColorConvertOp conv = new ColorConvertOp(ColorSpace
10: .getInstance(ColorSpace.CS_GRAY), null);
11: conv.filter(img, img);
12: }
13: return img;
14: }
Saving the image to disk
Again we use ImageIO class. The following method is in my ImageUtility.java
1: public static void SaveImageToDisk(BufferedImage img, String filename,
2: String type) throws IOException
3: {
4: //very simple, use ImageIO.write()!!!!
5: ImageIO.write(img, type, new File(filename));
6: }
7:
8:
9:
10:
Extracting the image matrix, the raw pixel data
Get the WritableRaster using img.getRaster(). Then use getSample method from the raster to get the pixel intensity at the specified location(x,y). It is as simple as that.
1: public static int[][] GetImageMatrix(BufferedImage img)
2: {
3: int height = img.getHeight();
4: int width = img.getWidth();
5: int imageMatrix[][] = new int[height][width];
6: WritableRaster wr = img.getRaster();
7: for(int y = 0; y < height; y++)
8: for(int x = 0; x < width; x++)
9: imageMatrix[y][x] = (wr.getSample(x,y,0)
10: +wr.getSample(x,y,1)
11: +wr.getSample(x,y,2))/2;
12: return imageMatrix;
13: }
Getting image from the matrix passed
Again, pretty straight forward operation like the GetImageMatrix() method. Get the WritableRaster and use wr.setPixel(x,y,pixel) where pixel is an integer array where pixel[0] is R, pixel[1] is G, pixel[2] is B. Look at the method.
1: public static BufferedImage GetImageFromMatrix(int[][] matrix)
2: {
3: int height = matrix.length;
4: int width = matrix[0].length;
5: BufferedImage img = new BufferedImage(width, height,
6: BufferedImage.TYPE_BYTE_GRAY);
7: WritableRaster wr = img.getRaster();
8: for(int y = 0; y < height; y++)
9: for(int x = 0; x< width; x++)
10: {
11: int[] pixel = new int[3];
12: for(int i =0; i<3; i++) pixel[0] = matrix[y][x];
13: wr.setPixel(x,y,pixel);
14: }
15: return img;
16: }
How to apply filter?
The signature for the apply filter method is shown below. It returns the new matrix after the filter has been applied.
1: public static int[][] ApplyFilter(double[][] filter, int[][] matrix)
Typical Image Processing Method
We have looked at the most fundamental operations that could be used to do amazing IP operations in a simple fashion. A Typical image processing operation could look something like this.
1:
2: void Sharpen(String imagePath,String outputPath,int[][] sharpenFilter)
3: {
4: BufferedImage actualImage = ImageUtility.LoadImage(imagePath);
5: int[][] actualImage_matrix = ImageUtility.GetImageMatrix(actualImage);
6: int[][] sharpenedImage_matrix = ImageUtility.ApplyFilter(sharpenFilter,actualImage_matrix);
7: BufferedImage sharpenedImage = ImageUtility.GetImageFromMatrix(sharpenedImage_matrix);
8: ImageUtility.SaveImageToDisk(sharpenedImage,outputPath,"jpg");
9: }
The above image could actually be placed in the ImageUtility.java but sometimes you might want to repeatedly apply certain filters in a sequence. Anyway the above method gives the basic idea on how to do image processing operations. The sharpenFilter, like I said earlier, I dont remember, anyway it is just a 2D integer array. Actually a double[][] filter makes more sense. Anyway my idea behind this article is to give fundamental understanding on how to do image processing operations on images with ease. Not many internet resources has this simple a code to write an image processing java code. I hope this helps atleast a few people.
Email me at krishnabhargav@yahoo.com in case you need the complete ImageUtility or some guidance on Java Programming or Image Processing.