图像的统计特性
图像的基本统计分析量如下:
1.熵
一个 X 值域为{x1, ..., xn}的随机变量的熵值 H 定义为:,即熵的公式可以表示为:
上式我们取集合X为图像灰度值构成的集合,这样我们就可以得到图像灰度的熵值
2.灰度平均值,灰度中值已经灰度方差都能很容易得到
3.直方图的计算
我们来看一个灰度图像,让表示灰度
出现的次数,这样图像中灰度为
的像素的出现概率是
是图像中所有不同的灰度值,
是图像中所有的像素数,
实际上是图像的直方图,归一化到
。
4.图像的质量评价标准(其实就是误差估计)
在编写程序之前,我们应该了解图片格式以及相应文件数据结构,java语言默认支持jpg,png和gif三种图片格式(没有考证),如果我们要处理bmp图片格式的,我们必须对bmp图片解码提取出图片的特征以及每个像素的数据,然后在java类库的方法表示出图像,才能进行进一步的处理,换句话说就是将图片(这里指bmp)内存储像素转换到java语言的存储图片的像素。下面附上java语言读取转换bmp图片的程序
//BMPReader.java, from Mark Wutka //Revised by Xie-Hua Sun import java.awt.*; import java.io.*; import java.awt.image.*; /** *This class provides a public static method that takes an InputStream to a Windows * .bmp file and converts it into an ImageProducer via a MemoryImageSource. *You can fetch a .bmp throough a URL with the following code: *URL url = new URL(<wherever your URL is>) *Image img= createImage(BMPReader.getBMPImage(url.openStream())); */ public class BMPReader extends Object { //Constants indication how the data is stored public static final int BI_RGB = 0; public static final int BI_RLE8 = 1; public static final int BI_RLE4 = 2; public static MemoryImageSource getBMPImage(FileInputStream stream) throws IOException { //DataInputStream allows you to read in 16 and 32 bit numbers DataInputStream in=new DataInputStream(stream); //Verify that the header starts with 'BM' if(in.read() != 'B') throw new IOException("Not a .BMP file!"); if(in.read() != 'M') throw new IOException("Not a .BMP file!"); //Get the total file size int fileSize = intelInt(in.readInt()); //Skip the 2 16-bit reserved words in.readUnsignedShort(); in.readUnsignedShort(); int bitmapOffset = intelInt(in.readInt()); int bitmapInfoSize = intelInt(in.readInt()); int width = intelInt(in.readInt()); int height = intelInt(in.readInt()); //Skip the 16-bit bitplane size in.readUnsignedShort(); int bitCount = intelShort(in.readUnsignedShort()); int compressionType = intelInt(in.readInt()); int imageSize = intelInt(in.readInt()); //Skip pixels per meter in.readInt(); in.readInt(); int colorsUsed = intelInt(in.readInt()); int colorsImportant = intelInt(in.readInt()); if(colorsUsed == 0) colorsUsed = 1<<bitCount; int colorTable[] = new int[colorsUsed]; //Read the bitmap's color table for(int i = 0; i < colorsUsed; i++) colorTable[i] = (intelInt(in.readInt())&0xffffff)+0xff000000; //Create space for the pixels int pixels[] = new int[width*height]; //Read the pixels from the stream based on the compression type if(compressionType == BI_RGB) if(bitCount == 24) readRGB24(width,height,pixels,in); else readRGB(width,height,colorTable,bitCount,pixels,in); else if(compressionType == BI_RLE8) readRLE(width,height,colorTable,bitCount,pixels,in,imageSize,8); else if(compressionType == BI_RLE4) readRLE(width,height,colorTable,bitCount,pixels,in,imageSize,4); //Create a memory image source from the pixels System.out.println(pixels[0]+" "+pixels[1]+" "+pixels[pixels.length-2]); return new MemoryImageSource(width,height,pixels,0,width); } /* *Reads in pixels in 24-bit format. There is no color table, and the pixels are *stored in 3-byte pairs. Oddly, all windows bitmaps are stored upside - the *bottom line is stored first. **/ protected static void readRGB24(int width,int height,int pixels[], DataInputStream in) throws IOException { //start storing at the bottom of the array for(int h = height-1; h >= 0; h--) { int pos = h*width; for(int w = 0; w < width; w++) { //Read in the red, green and blue components int red = in.read(); int green = in.read(); int blue = in.read(); //Turn the red,green and blue values into an RGB color with an alpha value //of 255 (fully opaque) pixels[pos++] = 0xff000000+(red<<16)+(green<<8)+blue; } } } //readRGB reads in pixels values that are stored uncompressed. The bits represent //indices into the color table protected static void readRGB(int width,int height,int colorTable[], int bitCount, int pixels[], DataInputStream in) throws IOException { //How many pixels can be stored in a byte? int pixelsPerByte = 8/bitCount; //A bit mask containing the number of bits in a pixel int bitMask = (1<<bitCount)-1; //The shift values that will move each pixel to the far right int bitShifts[] = new int[pixelsPerByte]; for(int i = 0; i < pixelsPerByte; i++) bitShifts[i] = 8-((i+1)*bitCount); int whichBit = 0; //Read in the first byte int currByte = in.read(); //Start at the bottom of the pixel array and work up for(int h = height-1;h >= 0; h--) { int pos = h*width; for(int w = 0; w < width; w++) { //Get the next pixel from the current byte pixels[pos] = colorTable[(currByte>>bitShifts[whichBit])&bitMask]; pos++; whichBit++; //If the current bit position is past the number of pixels in //a byte, you advance to the next byte if(whichBit >= pixelsPerByte) { whichBit = 0; currByte = in.read(); } } } } //readRLE reads run-length encoded data in either RLE4 or RLE8 format protected static void readRLE(int width,int height,int colorTable[], int bitCount,int pixels[],DataInputStream in, int imageSize,int pixelSize) throws IOException { int x = 0; int y = height-1; //You already know how many bytes are in the image, so only go through that many for(int i = 0; i < imageSize; i++) { //RLE encoding is defined by two bytes int byte1 = in.read(); int byte2 = in.read(); i += 2; //If byte0==0, this is an escape code if(byte1 == 0) { //If escaped, byte2==0 means you are at end of line if(byte2 == 0) { x = 0; y--; //If escaped, byte2==1 means end of bitmap } else if(byte2 == 1) { return; //if escaped, byte2==2 adjusts the current x and y by //an offset stored in the next two words } else if(byte2 == 2) { int xoff = (char)intelShort(in.readUnsignedShort()); i += 2; int yoff = (char)intelShort(in.readUnsignedShort()); i += 2; x += xoff; y -= yoff; //If escaped, any other value for byte 2 is the number of bytes //that you should read as pixel values (these pixels are not //run-length encoded) } else { int whichBit = 0; //Read in the next byte int currByte = in.read(); i++; for(int j = 0; j < byte2; j++) { if(pixelSize == 4) { //The pixels are 4-bits,so half the time you shift the current byte //to the right as the pixel value if(whichBit == 0){ pixels[y*width+x] = colorTable[(currByte>>4)&0xf]; } else { //The rest of the time, you mask out the upper 4 bits, save the //pixel value, then read in the next byte pixels[y*width+x] = colorTable[currByte&0xf]; currByte = in.read(); i++; } } else { pixels[y*width+x] = colorTable[currByte]; currByte = in.read(); i++; } x++; if(x >= width) { x = 0; y--; } } //The pixels must be word-aligned, so if you read an unevel number of // bytes, read and ignore a byte to get aligned again if((byte2&1) == 1) { in.read(); i++; } } //If the first byte was not 0, it is the number of pixels that //are encoded by byte 2 } else { for(int j = 0;j < byte1; j++) { if(pixelSize == 4) { //If j is odd, use the upper 4 bits if((j&1) == 0) pixels[y*width+x] = colorTable[(byte2>>4)&0xf]; else pixels[y*width+x+1] = colorTable[byte2&0xf]; } else pixels[y*width+x+1] = colorTable[byte2]; x++; if(x >= width) { x = 0; y--; } } } } } //intelShort converts a 16-bit number stored in intel byte order into //the local host format protected static int intelShort(int i) { return((i>>8)&0xff)+((i<<8)&0xff00); } //intelInt converts a 32-bit number stored in intel byte order into //the local host format 转换成小端存储的机器整数 protected static int intelInt(int i) { return((i&0xff)<<24)+((i&0xff00)<<8)+((i&0xff0000)>>8)+((i>>24)&0xff); } }
程序中提供了一个静态方法getBMPImage将bmp图片的数据转换生成MemoryImageSource对象,这样就可以用来构造java的Image对象。查看相应文档,同样可以编写出其他图片格式的读取程序。然后将getBMPImage返回的MemoryImageSource对象传给createImage函数就生成了一个Image(jpg,png,gif可以直接构造Image),下面就进行统计直方图的处理:1.将刚才得到的Image对象传给grabber函数得到对应的像素数组
public int[] grabber(Image im, int iw, int ih) { int [] pix = new int[iw * ih]; try { PixelGrabber pg = new PixelGrabber(im, 0, 0, iw, ih, pix, 0, iw); pg.grabPixels(); System.out.println(pix[0]+" "+pix[1]); } catch (InterruptedException e) { e.printStackTrace(); } return pix; }
2.根据1.中返回的像素数组统计不同灰度值的频率,如果对于一张本身就是灰度图像(8位灰度图像)来说,他的像素值就是它的灰度值(这就是我们程序采用的方法,也就是说我们处理的黑白图的灰度),如果是一张彩色图像,则它的灰度值需要经过函数映射来得到。灰度图像是由纯黑和纯白来过渡得到的,在黑色中加入白色就得到灰色,纯黑和纯白按不同的比例来混合就得到不同的灰度值。
public int[] getHist(int[] pix, int iw, int ih) { int[] hist = new int[256]; for(int i = 0; i < iw*ih; i++) { int grey = pix[i]&0xff; hist[grey]++; } return hist; }
3.将灰度值频率显示出来
//显示直方图 public void draw(Graphics g, int[] h, int max) { g.clearRect(270, 0, 530, 350); g.drawLine(270, 306, 525, 306); //x轴 g.drawLine(270, 50, 270, 306); //y轴 for(int i = 0; i < 256; i++) g.drawLine(270+i, 306, 270+i, 306-h[i]); g.drawString(""+max, 275, 60); g.drawString("直方图", 370, 320); }
这是效果图:
其实灰度直方图反应了图片灰度值的一个分布情况,只是一个统计特性,并没有具体像素,但是我们可以从中得到图像的明亮程度,可以根据直方图知道我们照的照片的曝光程度,如果照片阴暗在低值的频率很大,过分曝光则在高灰度的频率很大。