图像的统计特性
图像的基本统计分析量如下:
1.熵
一个 X 值域为{x1, ..., xn}的随机变量的熵值 H 定义为:,即熵的公式可以表示为:
上式我们取集合X为图像灰度值构成的集合,这样我们就可以得到图像灰度的熵值
2.灰度平均值,灰度中值已经灰度方差都能很容易得到
3.直方图的计算
我们来看一个灰度图像,让表示灰度
出现的次数,这样图像中灰度为
的像素的出现概率是
![p_x(i) = \frac{n_i}{n}, i\in {0,..., L - 1}](http://upload.wikimedia.org/wikipedia/zh/math/1/f/4/1f4dc118da909b84580ca1c96c32b26d.png)
是图像中所有不同的灰度值,
是图像中所有的像素数,
实际上是图像的直方图,归一化到
。
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);
- }
这是效果图:
其实灰度直方图反应了图片灰度值的一个分布情况,只是一个统计特性,并没有具体像素,但是我们可以从中得到图像的明亮程度,可以根据直方图知道我们照的照片的曝光程度,如果照片阴暗在低值的频率很大,过分曝光则在高灰度的频率很大。
以下内容为补充内容
信息是一个随机变量,它是指某一信源发出某一消息所含有的信息量。所发出 的消息不同,它们所含有的信息量也就不同。任何一个消息的自信息量都代表不了 信源所包含的平均自信息量。不能作为整个信源的信息测度,因此定义自信息量的
数学期望为信源的平均自信息量:
信息熵的意义:信源的信息熵H是从整个信源的统计特性来考虑的。它是从平均意 义上来表征信源的总体特性的。对于某特定的信源,其信息熵只有一个。不同的信 源因统计特性不同,其熵也不同。
3.学习图像熵基本概念,能够求出图像一维熵和二维熵。
图像熵是一种特征的统计形式,它反映了图像中平均信息量的多少。图像的一 维熵表示图像中灰度分布的聚集特征所包含的信息量,令P
i 表示图像中灰度值为i的像素所占的比例,则定义灰度图像的一元灰度熵为:
图像的一维熵可以表示图像灰度分布的聚集特征,却不能反映图像灰度分布的空间 特征,为了表征这种空间特征,可以在一维熵的基础上引入能够反映灰度分布空间 特征的特征量来组成图像的二维熵。选择图像的邻域灰度均值作为灰度分布的空间
特征量,与图像的像素灰度组成特征二元组,记为( i, j ),其中i 表示像素的灰度值 (0 <= i <= 255),j 表示邻域灰度(0 <= j <= 255),
上式能反应某像素位置上的灰度值与其周围像素灰度分布的综合特征,其中f(i, j) 为特征二元组(i, j)出现的频数,N 为图像的尺度,定义离散的图像二维熵为:
构造的图像二维熵可以在图像所包含信息量的前提下,突出反映图像中像素位置的 灰度信息和像素邻域内灰度分布的综合特征