图像的采样
采样是把空域上或时域上连续的图像(模拟图像)转换成离散采样点(像素)集合(数字图像)的操作。
采样越细,像素越小,越能精细地表现图像。不同采样间距的效果如下:
a.采样间隔16 b.采样间隔32 c.采样间隔64
图1
算法源代码1(java):
/** * 对图像进行采样 * * @param pix * 保存图片像素 * @param iw * 二维像素矩阵的宽 * @param ih * 二维像素矩阵的高 * @param grey * 采样间距 * @return */ private static int[] sample(int[] pix, int iw, int ih, int grey) { // 对图像进行采样 ColorModel cm = ColorModel.getRGBdefault(); int d = (int) (256 / grey); // 采样间隔 int dd = d * d; for (int i = 0; i < ih; i = i + d) { for (int j = 0; j < iw; j = j + d) { int r = 0, g = 0, b = 0; for (int k = 0; k < d; k++) for (int l = 0; l < d; l++) { r = r + cm.getRed(pix[(i + k) * iw + (j + l)]); g = g + cm.getGreen(pix[(i + k) * iw + (j + l)]); b = b + cm.getBlue(pix[(i + k) * iw + (j + l)]); } r = (int) (r / dd); g = (int) (g / dd); b = (int) (b / dd); for (int k = 0; k < d; k++) for (int l = 0; l < d; l++) // pix[(i+k)*iw+(j+l)] = 255<<24|r<<16|g<<8|b; pix[(i + k) * iw + (j + l)] = new Color(r, g, b) .getRGB(); } } return pix; }
图像的量化
量化是把像素的灰度(浓淡)变换成离散的整数值的操作。最简单的量化是用黑(0)白(255)两个数值(即2级)来表示,成为二值图像。
量化越细致,灰度级数(浓淡层次)表现越丰富。计算机中一般用8bit(256级)来量化,这意味着像素的灰度(浓淡)是0—255之间的数值。化级数的效果图如下:
a.量化级数2 b.量化级数8 c.量化级数64
图2
算法源代码2(java):
/** * 对图像进行量化 * @param srcPath 原图像文件路径 * @param distPath 目标图像文件路径 * @param grey 量化的级数 */ public static void quantize(String srcPath, String distPath, int grey) { OutputStream out = null; try { BufferedImage img = ImageIO.read(new File(srcPath)); int imgType = img.getType(); int w = img.getWidth(); int h = img.getHeight(); int pix[] = new int[w*h]; img.getRGB(0, 0, w, h, pix, 0, w); int greyScope = 256/grey; int r,g,b,temp; r=b=g=temp=0; ColorModel cm=ColorModel.getRGBdefault(); for(int i=0; i<w*h; i++) { r = cm.getRed(pix[i]); temp = r/greyScope; r = temp*greyScope; g = cm.getGreen(pix[i]); temp = g/greyScope; g = temp*greyScope; b = cm.getBlue(pix[i]); temp = b/greyScope; b = temp*greyScope; pix[i] = new Color(r, g, b).getRGB(); } out = new FileOutputStream(distPath); BufferedImage imgOut = new BufferedImage(w, h, imgType); imgOut.setRGB(0, 0, w, h, pix, 0, w); ImageIO.write(imgOut, "jpg", out); System.out.println("test"); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } finally { try { out.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
图像直方图
直方图的定义
灰度直方图(histogram)是灰度级分布的函数,它表示图象中具有每种灰度级的象素的个数,反映图象中每种灰度出现的频率。灰度直方图的横坐标是灰度级,纵坐标是该灰度级出现的频率,是图象的最基本的统计特征。
生成图像灰度直方图的一般步骤:
1、统计各个灰度值的像素个数;
2、根据统计表画出直方图
如下图
图3
图4
算法源代码3:见下面算法源代码4中的drawHistogram()方法
灰度直方图的性质
1、只反映该图像中不同灰度值出现的次数(或频率),而不能反映某一灰度值像素所在的位置;
2、任何一张图像能唯一地确定一个与它对应的直方图,而一个直方图可以有多个不同的图像;
3、如果一张图片被剪裁成多张图片,各个子图的直方图之和就是这个全图的直方图。
直方图的用途
直方图有很多的用途,比如阀值分割,图像增强,还常常用于医疗影像。
图像的阀值(二值)处理
阀值处理的定义
图像的阀值处理是将图像的像素灰度值在某个定值(设为t)以上的点赋值为白色(或黑色),在这个定值t以下的点赋值为黑色(或白色)的处理过程。用公式表示成:
由于图像的阀值处理得到的是只有两个灰度值的二值图像,所以也将阀值处理称作二值化处理,得到的图像叫二值图像。如下图
图像阀值处理的步骤
1、 画出图像的灰度直方图;
2、 根据图像的灰度直方图确定阀值t,如图5的阀值为65;
3、 将像素灰度值小于等于t的点赋值为白色(或黑色),大于t的点赋值为黑色(或白色);效果如图6:
图5
图6
算法源代码4(java):
package cn.edu.jxau.luoweifu; import java.awt.BorderLayout; import java.awt.Canvas; import java.awt.Color; import java.awt.Container; import java.awt.Dimension; import java.awt.FileDialog; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.io.File; import java.io.IOException; import java.util.Date; import javax.imageio.ImageIO; import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JDialog; import javax.swing.JFrame; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JSlider; import javax.swing.border.LineBorder; import javax.swing.event.ChangeEvent; import javax.swing.event.ChangeListener; /** * 设置头像 * @author 罗伟富 * */ public class Histogram extends JFrame implements ChangeListener, ActionListener{ /** * 头像界面的宽度 */ public static final int WIDTH = 680; /** * 头像界面的高度 */ public static final int HEIGHT = 400; /** * 显示图片版面的宽度 */ public static final int PWIDTH = 350; /** * 显示图片版面的高度 */ public static final int PHEIGHT = 300; /** * 直方图版面的宽度 */ public static final int H_WIDTH = 350; /** * 直方图片版面的高度 */ public static final int H_HEIGHT = 300; /** * 滑块的最大值 */ public static final int JS_MAXIMUM = 100; public static final String FRAMTITLE = "图像的灰度直方图"; private String imgSrc; Image img; JSlider jsliderH, jsliderV; //水平和垂直滑块 JPanel uP, picP, uplodP, histP; JButton openFile, histogram, threshold; MyCanvas canvas; Canvas histCanvas; int imgW = PWIDTH, imgH = PHEIGHT; int xcentre = PWIDTH/2, ycentre = PHEIGHT/2; private int dx1 = xcentre-imgW/2, dy1 = ycentre-imgH/2, dx2 = xcentre + imgW/2, dy2 = ycentre + imgH/2; private int sx1 = 0, sy1 = 0, sx2, sy2; private float shx = 0, shy = 0; /** * 构造函数 */ public Histogram() { setTitle(FRAMTITLE); launchDialog(); } /** * 返回canvas * @return */ public Canvas getCanvas() { return canvas; } /** * 界面设计 */ private void launchDialog() { //初始化图片对象 imgSrc = "F:\\image processing\\baboom2_gray.jpg"; img = Toolkit.getDefaultToolkit().getImage(imgSrc); //初始化组件 canvas = new MyCanvas(); jsliderH = new JSlider(); jsliderH.setMaximum(JS_MAXIMUM); jsliderH.setValue(JS_MAXIMUM/2); jsliderH.setMinimum(1); jsliderH.setOrientation(JSlider.HORIZONTAL); jsliderH.addChangeListener(this); jsliderV = new JSlider(); jsliderV.setMaximum(JS_MAXIMUM); jsliderV.setValue(JS_MAXIMUM/2); jsliderV.setMinimum(1); jsliderV.setOrientation(JSlider.VERTICAL); jsliderV.addChangeListener(this); picP = new JPanel(); picP.setPreferredSize(new Dimension(PWIDTH, PHEIGHT)); //picP.setBackground(Color.green); uP = new JPanel(); uplodP = new JPanel(); openFile = new JButton("打开图片"); histogram = new JButton("显示直方图"); threshold = new JButton("显示二值图像"); openFile.addActionListener(this); histogram.addActionListener(this); threshold.addActionListener(this); //添加组件 picP.setLayout(new BorderLayout()); picP.add(canvas, BorderLayout.CENTER); uP.setLayout(new BorderLayout()); uP.add(picP, BorderLayout.CENTER); uP.add(jsliderH, BorderLayout.SOUTH); uP.add(jsliderV, BorderLayout.EAST); histCanvas = new Canvas(); histP = new JPanel(new BorderLayout()); histP.add(histCanvas); histP.setPreferredSize(new Dimension(H_WIDTH, H_HEIGHT)); histP.setBorder(new LineBorder(Color.blue)); //System.out.println("w:" + histP.getWidth() + " h:" + histP.getHeight() + " " + histP.HEIGHT); uplodP.setLayout(new FlowLayout()); uplodP.add(openFile); uplodP.add(histogram); uplodP.add(threshold); Container c = getContentPane(); c.setLayout(new BoxLayout(c, BoxLayout.Y_AXIS)); //c.add(uP); JPanel p = new JPanel(); p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); p.add(uP); p.add(histP); c.add(p); c.add(uplodP); setSize(WIDTH, HEIGHT); setResizable(false); setLocationRelativeTo(null); setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); setVisible(true); } public void drawHistogram() { try { BufferedImage bfImg = ImageIO.read(new File(imgSrc)); int w = bfImg.getWidth(); int h = bfImg.getHeight(); int pix[] = new int[w*h]; int hist[] = new int[256]; /*for(int i=0; i<hist.length; i++) { hist[i] = 0; }*/ int imgType = bfImg.getType(); int temp; bfImg.getRGB(0, 0, w, h, pix, 0, w); ColorModel cm = ColorModel.getRGBdefault(); for(int i=0; i<pix.length; i++) { /*for(int j=0; j<hist.length; j++) { if(j == cm.getRed(pix[i])) { hist[j] ++; } }*/ temp = cm.getRed(pix[i]); hist[temp] ++; } //System.out.println(hist.length); int max = 0; for(int i=0; i<hist.length; i++) { if(hist[i] > max) { max = hist[i]; } } for(int i=0; i<hist.length; i++) { hist[i] = (int)(hist[i]/(float)max * 250); /*System.out.print(hist[i] + "\t"); if(i%10 == 0) { System.out.println(); }*/ } //histCanvas.setHistPix(hist); //histCanvas.repaint(); Graphics g = histCanvas.getGraphics(); Color c = g.getColor(); g.setColor(Color.red); g.drawLine(10, H_HEIGHT-10, H_WIDTH-30, H_HEIGHT-10); g.drawLine(H_WIDTH-35, H_HEIGHT-15, H_WIDTH-30, H_HEIGHT-10); g.drawLine(H_WIDTH-35, H_HEIGHT-5, H_WIDTH-30, H_HEIGHT-10); g.drawString("灰度级", H_WIDTH-80, H_HEIGHT); g.drawLine(10, H_HEIGHT-10, 10, 10); g.drawLine(5, 15, 10, 10); g.drawLine(15, 15, 10, 10); g.drawString("像素个数", 15, 15); g.setColor(Color.black); for(int i=0; i<hist.length; i++) { g.drawLine(10+i, H_HEIGHT-10, 10+i, H_HEIGHT-10-hist[i]); if(i%30 == 0) { g.drawString(i+"", 10+i, H_HEIGHT); } } g.setColor(c); } catch (IOException e) { e.printStackTrace(); } } public void threshold(int threshold) { try { BufferedImage bfImg = ImageIO.read(new File(imgSrc)); int w = bfImg.getWidth(); int h = bfImg.getHeight(); int pix[] = new int[w*h]; int imgType = bfImg.getType(); bfImg.getRGB(0, 0, w, h, pix, 0, w); int max = 0; ColorModel cm = ColorModel.getRGBdefault(); for(int i=0; i<pix.length; i++) { if(cm.getRed(pix[i]) <= threshold) { pix[i] = new Color(255,255,255).getRGB(); } else { pix[i] = new Color(0, 0, 0).getRGB(); } } bfImg.setRGB(0, 0, w, h, pix, 0, w); //histCanvas.setHistPix(hist); //histCanvas.repaint(); Graphics g = histCanvas.getGraphics(); g.clearRect(0, 0, H_WIDTH, H_HEIGHT); Color c = g.getColor(); g.drawImage(bfImg, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, this); g.setColor(c); } catch (IOException e) { e.printStackTrace(); } } /** * 事件监听响应 */ public void actionPerformed(ActionEvent e) { if(e.getSource() == openFile) { FileDialog openFileDialog = new FileDialog(this, "打开图片"); openFileDialog.setMode(FileDialog.LOAD); //设置此对话框为从文件加载内容 openFileDialog.setFile("*.jpg;*.jpeg;*.gif;*.png;*.bmp;*.tif;"); //设置可打开文件的类型为:.txt,.java openFileDialog.setVisible(true); String fileName = openFileDialog.getFile(); String directory = openFileDialog.getDirectory(); if(null != fileName) { imgSrc = directory + fileName; img = Toolkit.getDefaultToolkit().getImage(imgSrc); histCanvas.repaint(); } else { JOptionPane.showMessageDialog(this, "您已经取消选择了,请重新选择!"); } } else if(e.getSource() == histogram) { System.out.println(new Date()); drawHistogram(); System.out.println(new Date()); } else if(e.getSource() == threshold) { threshold(65); } } /** * 滑动条滑动响应事件 */ public void stateChanged(ChangeEvent e) { if(e.getSource() == jsliderH) { float valueH = jsliderH.getValue(); imgW = (int)(2*PWIDTH*(valueH/JS_MAXIMUM)); if(imgW < PWIDTH/4) { imgW = PWIDTH/4; } dx1 = xcentre-imgW/2; dy1 = ycentre-imgH/2; dx2 = xcentre + imgW/2; dy2 = ycentre + imgH/2; canvas.repaint(); } else if(e.getSource() == jsliderV) { float valueV = jsliderV.getValue(); imgH = (int)(2*PHEIGHT*(valueV/JS_MAXIMUM)); if(imgH < PHEIGHT/4) { imgH = PHEIGHT/4; } dx1 = xcentre-imgW/2; dy1 = ycentre-imgH/2; dx2 = xcentre + imgW/2; dy2 = ycentre + imgH/2; canvas.repaint(); } } public static void main(String[] args) { new Histogram(); } /** * 用于画图像的Canvas */ class MyCanvas extends Canvas { public MyCanvas() { } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; //g.drawImage(img, xcentre-imgW/2, ycentre-imgH/2, imgW, imgH, this); sx2 = img.getWidth(this); sy2 = img.getHeight(this); g2.shear(shx, shy); g2.drawImage(img, dx1, dy1, dx2, dy2, sx1, sy1, sx2, sy2, this);//Color.green, } } } /*class HistCanvas extends Canvas { int histPix[] = new int[0]; public void setHistPix(int[] pix) { histPix = pix; } public void paint(Graphics g) { Color c = g.getColor(); g.setColor(Color.red); g.drawLine(10, H_HEIGHT-10, H_WIDTH-30, H_HEIGHT-10); g.drawLine(H_WIDTH-35, H_HEIGHT-15, H_WIDTH-30, H_HEIGHT-10); g.drawLine(H_WIDTH-35, H_HEIGHT-5, H_WIDTH-30, H_HEIGHT-10); g.drawString("灰度级", H_WIDTH-80, H_HEIGHT); g.drawLine(10, H_HEIGHT-10, 10, 10); g.drawLine(5, 15, 10, 10); g.drawLine(15, 15, 10, 10); g.drawString("像素个数", 15, 15); g.setColor(Color.black); for(int i=0; i<histPix.length; i++) { g.drawLine(10+i, H_HEIGHT-10, 10+i, H_HEIGHT-10-histPix[i]); if(i%30 == 0) { g.drawString(i+"", 10+i, H_HEIGHT); } } g.setColor(c); } }*/