图像处理之简单综合实例(大米计数)
一位网友给我发了几张灰度图像,说是他们单位的工业相机拍摄的,画质非常的清楚,他们
单位是农业科研单位,特别想知道种子的数量,他想知道的是每次工业相机拍摄种子图片中
有多少颗粒种子,想到了用图像处理的办法解决他们的问题,看了他给我照片,以大米种子
为例。实现了一个简单的算法流程,可以得到种子的数目。
大致算法分为以下三个步骤:
1. 将灰度图像二值化,二值化方法可以参考以前的文章,求取像素平均值,灰度直方图都
可以
2. 去掉二值化以后的图像中干扰噪声。
3. 得到种子数目,用彩色标记出来。
源图像如下:
程序处理中间结果及最终效果如下:
二值化处理参见以前的文章 - http://blog.csdn.net/jia20003/article/details/7392325
大米计数与噪声块消去算法基于连通组件标记算法,源代码如下:
package com.gloomyfish.rice.analysis; import java.awt.image.BufferedImage; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import com.gloomyfish.face.detection.AbstractBufferedImageOp; import com.gloomyfish.face.detection.FastConnectedComponentLabelAlg; public class FindRiceFilter extends AbstractBufferedImageOp { private int sumRice; public int getSumRice() { return this.sumRice; } @Override public BufferedImage filter(BufferedImage src, BufferedImage dest) { int width = src.getWidth(); int height = src.getHeight(); if ( dest == null ) dest = createCompatibleDestImage( src, null ); int[] inPixels = new int[width*height]; int[] outPixels = new int[width*height]; getRGB(src, 0, 0, width, height, inPixels ); FastConnectedComponentLabelAlg fccAlg = new FastConnectedComponentLabelAlg(); fccAlg.setBgColor(0); int[] outData = fccAlg.doLabel(inPixels, width, height); // labels statistic HashMap<Integer, Integer> labelMap = new HashMap<Integer, Integer>(); for(int d=0; d<outData.length; d++) { if(outData[d] != 0) { if(labelMap.containsKey(outData[d])) { Integer count = labelMap.get(outData[d]); count+=1; labelMap.put(outData[d], count); } else { labelMap.put(outData[d], 1); } } } // try to find the max connected component Integer[] keys = labelMap.keySet().toArray(new Integer[0]); Arrays.sort(keys); int threshold = 10; ArrayList<Integer> listKeys = new ArrayList<Integer>(); for(Integer key : keys) { if(labelMap.get(key) <=threshold){ listKeys.add(key); } System.out.println( "Number of " + key + " = " + labelMap.get(key)); } sumRice = keys.length - listKeys.size(); // calculate means of pixel int index = 0; for(int row=0; row<height; row++) { int ta = 0, tr = 0, tg = 0, tb = 0; for(int col=0; col<width; col++) { index = row * width + col; ta = (inPixels[index] >> 24) & 0xff; tr = (inPixels[index] >> 16) & 0xff; tg = (inPixels[index] >> 8) & 0xff; tb = inPixels[index] & 0xff; if(outData[index] != 0 && validRice(outData[index], listKeys)) { tr = tg = tb = 255; } else { tr = tg = tb = 0; } outPixels[index] = (ta << 24) | (tr << 16) | (tg << 8) | tb; } } setRGB( dest, 0, 0, width, height, outPixels ); return dest; } private boolean validRice(int i, ArrayList<Integer> listKeys) { for(Integer key : listKeys) { if(key == i) { return false; } } return true; } }大米着色处理很简单,只是简单RGB固定着色,源码如下:
package com.gloomyfish.rice.analysis; import java.awt.image.BufferedImage; import com.gloomyfish.face.detection.AbstractBufferedImageOp; public class ColorfulRiceFilter extends AbstractBufferedImageOp { @Override public BufferedImage filter(BufferedImage src, BufferedImage dest) { int width = src.getWidth(); int height = src.getHeight(); if ( dest == null ) dest = createCompatibleDestImage( src, null ); int[] inPixels = new int[width*height]; int[] outPixels = new int[width*height]; getRGB(src, 0, 0, width, height, inPixels ); int index = 0, srcrgb; for(int row=0; row<height; row++) { int ta = 255, tr = 0, tg = 0, tb = 0; for(int col=0; col<width; col++) { index = row * width + col; // ta = (inPixels[index] >> 24) & 0xff; // tr = (inPixels[index] >> 16) & 0xff; // tg = (inPixels[index] >> 8) & 0xff; // tb = inPixels[index] & 0xff; srcrgb = inPixels[index] & 0x000000ff; if(srcrgb > 0 && row < 140) { tr = 0; tg = 255; tb = 0; } else if(srcrgb > 0 && row >= 140 && row <=280) { tr = 0; tg = 0; tb = 255; } else if(srcrgb > 0 && row >=280) { tr = 255; tg = 0; tb = 0; } else { tr = tg = tb = 0; } outPixels[index] = (ta << 24) | (tr << 16) | (tg << 8) | tb; } } setRGB( dest, 0, 0, width, height, outPixels ); return dest; } }测试程序UI代码如下:
package com.gloomyfish.rice.analysis; import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.MediaTracker; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import javax.imageio.ImageIO; import javax.swing.JButton; import javax.swing.JComponent; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JPanel; public class MainFrame extends JComponent implements ActionListener { /** * */ private static final long serialVersionUID = 1518574788794973574L; public final static String BROWSE_CMD = "Browse..."; public final static String NOISE_CMD = "Remove Noise"; public final static String FUN_CMD = "Colorful Rice"; private BufferedImage rawImg; private BufferedImage resultImage; private MediaTracker tracker; private Dimension mySize; // JButtons private JButton browseBtn; private JButton noiseBtn; private JButton colorfulBtn; // rice number.... private int riceNum = -1; public MainFrame() { JPanel btnPanel = new JPanel(); btnPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); browseBtn = new JButton("Browse..."); noiseBtn = new JButton("Remove Noise"); colorfulBtn = new JButton("Colorful Rice"); browseBtn.setToolTipText("Please select image file..."); noiseBtn.setToolTipText("find connected region and draw red rectangle"); colorfulBtn.setToolTipText("Remove the minor noise region pixels..."); // buttons btnPanel.add(browseBtn); btnPanel.add(noiseBtn); btnPanel.add(colorfulBtn); // setup listener... browseBtn.addActionListener(this); noiseBtn.addActionListener(this); colorfulBtn.addActionListener(this); browseBtn.setEnabled(true); noiseBtn.setEnabled(true); colorfulBtn.setEnabled(true); // minX = minY = 10000; // maxX = maxY = -1; mySize = new Dimension(500, 300); JFrame demoUI = new JFrame("Rice Detection Demo"); demoUI.getContentPane().setLayout(new BorderLayout()); demoUI.getContentPane().add(this, BorderLayout.CENTER); demoUI.getContentPane().add(btnPanel, BorderLayout.SOUTH); demoUI.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); demoUI.pack(); demoUI.setVisible(true); } public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; if(rawImg != null) { Image scaledImage = rawImg.getScaledInstance(200, 200, Image.SCALE_FAST); g2.drawImage(scaledImage, 0, 0, 200, 200, null); } if(resultImage != null) { Image scaledImage = resultImage.getScaledInstance(200, 200, Image.SCALE_FAST); g2.drawImage(scaledImage, 210, 0, 200, 200, null); } g2.setPaint(Color.RED); if(riceNum > 0) { g2.drawString("Number of Rice : " + riceNum, 100, 300); } else { g2.drawString("Number of Rice : Unknown", 100, 300); } } public Dimension getPreferredSize() { return mySize; } public Dimension getMinimumSize() { return mySize; } public Dimension getMaximumSize() { return mySize; } public static void main(String[] args) { new MainFrame(); } @Override public void actionPerformed(ActionEvent e) { if(BROWSE_CMD.equals(e.getActionCommand())) { JFileChooser chooser = new JFileChooser(); chooser.showOpenDialog(null); File f = chooser.getSelectedFile(); BufferedImage bImage = null; if(f == null) return; try { bImage = ImageIO.read(f); } catch (IOException e1) { e1.printStackTrace(); } tracker = new MediaTracker(this); tracker.addImage(bImage, 1); // blocked 10 seconds to load the image data try { if (!tracker.waitForID(1, 10000)) { System.out.println("Load error."); System.exit(1); }// end if } catch (InterruptedException ine) { ine.printStackTrace(); System.exit(1); } // end catch BinaryFilter bfilter = new BinaryFilter(); rawImg = bfilter.filter(bImage, null); repaint(); } else if(NOISE_CMD.equals(e.getActionCommand())) { FindRiceFilter frFilter = new FindRiceFilter(); resultImage = frFilter.filter(rawImg, null); riceNum = frFilter.getSumRice(); repaint(); } else if(FUN_CMD.equals(e.getActionCommand())) { ColorfulRiceFilter cFilter = new ColorfulRiceFilter(); resultImage = cFilter.filter(resultImage, null); repaint(); } else { // do nothing... } } }关于连通组件标记算法,我实现一个优化过的快速版本,可以参见
http://blog.csdn.net/jia20003/article/details/7596443