/* * Copyright 2005, 2009 Cosmin Basca. * e-mail: cosmin.basca@gmail.com * * This is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * * Please see COPYING for the complete licence. */ import java.awt.BorderLayout; import java.awt.Color; import java.awt.Dimension; import java.awt.FlowLayout; import java.awt.Graphics2D; import java.awt.GridLayout; import java.awt.MediaTracker; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.geom.Ellipse2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.image.RenderedImage; import java.awt.image.renderable.ParameterBlock; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.logging.FileHandler; import java.util.logging.Logger; import javax.imageio.ImageIO; import javax.media.jai.Histogram; import javax.media.jai.JAI; import javax.media.jai.PlanarImage; import javax.media.jai.RenderedOp; import javax.media.jai.operator.MedianFilterDescriptor; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JTabbedPane; import javax.swing.JTextArea; import javax.swing.JToolBar; import javax.swing.SwingWorker; import javax.swing.UIManager; import com.sun.media.jai.codec.PNGEncodeParam; import com.sun.media.jai.codec.TIFFEncodeParam; import com.sun.org.apache.xml.internal.utils.StopParseException; import robo.gui.BufferedImageDisplayerPanel; import robo.gui.HoughEllipseConfigurationPanel; import robo.gui.icons.Icons; import robo.vision.EllipseDescriptor; import robo.vision.JAIOperatorRegister; import robo.vision.widgets.VisionUtils; public class EllipseDetector extends JFrame implements ActionListener { private Logger log = Logger.getLogger(EllipseDetector.class.getName()); private static final long serialVersionUID = -8237018495582668882L; private RenderedImage mask; private BufferedImageDisplayerPanel originalImage; private BufferedImageDisplayerPanel edgeDetector; private BufferedImageDisplayerPanel adaptiveThreshold; private BufferedImageDisplayerPanel ellipseDetection; private HoughEllipseConfigurationPanel ellipseCfg; private JToolBar toolbar; private JButton open; private JButton detect; private JButton exit; private JButton about; private JTabbedPane tabs; private JTextArea console; private JProgressBar progress; private PlanarImage source; private String CMD_OPEN = "CMD_OPEN"; private String CMD_EXIT = "CMD_EXIT"; private String CMD_DETECT = "CMD_DETECT"; private String CMD_ABOUT = "CMD_ABOUT"; private double border = 3.0; private double x = border; private double y = border; private class EllipseDetectionData { public BufferedImage greyScale; public BufferedImage medianFilter; public BufferedImage sobel; public BufferedImage edge; public BufferedImage binaryThreshold; public BufferedImage detectedEllipses; public List<EllipseDescriptor> ellipses; double iterativeThreshold; double maxEntropyThreshold; double maxVarianceThreshold; double minErrorThreshold; double minFuzzinessThreshold; double entropy; double mean; public String info() { String inf = ""; inf += "------------------------------------------------------------------------------\n"; inf += "IterativeThreshold \t: "+iterativeThreshold+"\n"; inf += "MaxEntropyThreshold \t: "+maxEntropyThreshold+"\n"; inf += "MaxVarianceThreshold \t: "+maxVarianceThreshold+"\n"; inf += "MinErrorThreshold \t: "+minErrorThreshold+"\n"; inf += "MinFuzzinessThreshold \t: "+minFuzzinessThreshold+"\n"; inf += "Entropy \t\t: "+entropy+"\n"; inf += "Mean \t\t: "+mean+"\n"; for(EllipseDescriptor ellipse:ellipses) { inf += "------------------------------------------------------------------------------\n"; inf += ellipse.toString()+"\n"; } return inf; } } public EllipseDetector() { this.setTitle("Randomized Hough Ellipse Detector"); // this.setLayout(new GridLayout(2,2)); this.setLayout(new BorderLayout()); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(800, 600); this.setVisible(true); this.toolbar = new JToolBar(); this.open = new JButton(); this.open.setActionCommand(CMD_OPEN); this.open.addActionListener(this); this.open.setIcon(Icons.iconOpen); this.detect = new JButton(); this.detect.setActionCommand(CMD_DETECT); this.detect.addActionListener(this); this.detect.setIcon(Icons.iconDetect); this.about = new JButton(); this.about.setActionCommand(CMD_ABOUT); this.about.addActionListener(this); this.about.setIcon(Icons.iconAbout); this.exit = new JButton(); this.exit.setActionCommand(CMD_EXIT); this.exit.addActionListener(this); this.exit.setIcon(Icons.iconExit); this.toolbar.add(open); this.toolbar.addSeparator(); this.toolbar.add(detect); this.toolbar.add(about); this.toolbar.add(exit); this.add(this.toolbar, BorderLayout.NORTH); JPanel topBox = new JPanel(new GridLayout(1, 2)); Dimension formatSize = new Dimension(200, 200); if (this.source != null) { formatSize = new Dimension(this.source.getWidth(), this.source .getHeight()); } ParameterBlock pb = new ParameterBlock(); pb.add(59); // minA - big radius range pb.add(71); // maxA pb.add(59); // minB - small radius range pb.add(71); // maxB pb.add(100); // the quality of the detected ellipse pb.add(5000); // threshold ellipseCfg = new HoughEllipseConfigurationPanel(formatSize); ellipseCfg.setParameters(pb); originalImage = new BufferedImageDisplayerPanel(); originalImage.setPreferredSize(formatSize); topBox.add(ellipseCfg); topBox.add(originalImage); this.add(topBox, BorderLayout.SOUTH); JPanel results = new JPanel(new GridLayout(2, 2)); progress = new JProgressBar(); JPanel progrepanel = new JPanel(new FlowLayout()); progrepanel.add(progress); results.add(progrepanel); edgeDetector = new BufferedImageDisplayerPanel(); edgeDetector.setPreferredSize(formatSize); results.add(edgeDetector); adaptiveThreshold = new BufferedImageDisplayerPanel(); adaptiveThreshold.setPreferredSize(formatSize); results.add(adaptiveThreshold); ellipseDetection = new BufferedImageDisplayerPanel(); ellipseDetection.setPreferredSize(formatSize); results.add(ellipseDetection); console = new JTextArea(); console.setEditable(false); console.setLineWrap(true); tabs = new JTabbedPane(); tabs.addTab("Processing steps", results); tabs.addTab("Processing results", new JScrollPane(console)); this.add(tabs, BorderLayout.CENTER); this.pack(); createMask(formatSize); } private void createMask(Dimension formatSize) { double w = formatSize.width - 2 * border; double h = formatSize.height - 2 * border; mask = new BufferedImage(formatSize.width, formatSize.height, BufferedImage.TYPE_INT_RGB); Graphics2D graphics = (Graphics2D) ((BufferedImage) mask).getGraphics(); graphics.setColor(Color.white); graphics.fill(new Rectangle2D.Double(x, y, w, h)); graphics.dispose(); } private BufferedImage getImage(PlanarImage image) { BufferedImage buffer = new BufferedImage(image.getWidth(), image .getHeight(), BufferedImage.TYPE_INT_RGB); Graphics2D graphics = (Graphics2D) buffer.getGraphics(); graphics.drawImage(image.getAsBufferedImage(), 0, 0, null); graphics.dispose(); return buffer; } public void detectAllEllipses() { class EllipseFinder extends SwingWorker<EllipseDetectionData, Object> { @Override public EllipseDetectionData doInBackground() { long startTime = System.currentTimeMillis(); EllipseDetectionData data = detectEllipses(); long time = System.currentTimeMillis() - startTime; System.out.println("DETECTION TOOK ---> "+time+" seconds"); return data; } @Override protected void done() { try { progress.setIndeterminate(false); EllipseDetectionData data = get(); EllipseDetector.this.adaptiveThreshold.setImage(data.binaryThreshold); EllipseDetector.this.edgeDetector.setImage(data.sobel); EllipseDetector.this.ellipseDetection.setImage(data.detectedEllipses); EllipseDetector.this.console.setText(data.info()); } catch (Exception ignore) { } } } (new EllipseFinder()).execute(); } public EllipseDetectionData detectEllipses() { EllipseDetectionData data = new EllipseDetectionData(); // snapshot - rendered image as argument List<EllipseDescriptor> ellipses = new ArrayList<EllipseDescriptor>(); if (this.source == null) return data; PlanarImage img = this.source; ParameterBlock pb = new ParameterBlock(); float scale = 1.0F; // ****************************************** // * GREYSCALE *** // ****************************************** img = VisionUtils.convertColorToGray(img, 0); data.greyScale = getImage(img); // ****************************************** // * MEDIAN FILTERING *** // ****************************************** pb = new ParameterBlock(); pb.addSource(img); pb.add(MedianFilterDescriptor.MEDIAN_MASK_X); pb.add(3); img = (PlanarImage) JAI.create("MedianFilter", pb); data.medianFilter = getImage(img); // ****************************************** // * SCALE *** // ****************************************** /*pb = new ParameterBlock(); pb.addSource(img); pb.add(scale); pb.add(scale); img = (PlanarImage) JAI.create("Scale", pb);*/ // ****************************************** // * EDGE DETECT SOBEL *** // ****************************************** pb = new ParameterBlock(); pb.addSource(img); pb.add(robo.vision.Kernel.SOBEL_H); pb.add(robo.vision.Kernel.SOBEL_V); img = (PlanarImage) JAI.create("GradientMagnitude", pb); data.sobel = getImage(img); // ****************************************** // * DEL BORDER *** // ****************************************** pb = new ParameterBlock(); pb.addSource(img); pb.addSource(mask); img = (PlanarImage) JAI.create("And", pb); data.edge = getImage(img); // ****************************************** // * ADAPTIVE TRESHOLD MEDIAN *** // ****************************************** /* * pb = new ParameterBlock(); pb.addSource(img); * pb.setParameters(thresholdBlock.getParameters()); * * img =(PlanarImage)JAI.create("AdaptiveThreshold", pb); // */ // /* int[] bins = { 256 }; double[] low = { 0.0D }; double[] high = { 256.0D }; pb = new ParameterBlock(); pb.addSource(img); pb.add(null); pb.add(1); pb.add(1); pb.add(bins); pb.add(low); pb.add(high); RenderedOp op = JAI.create("histogram", pb); Histogram histogram = (Histogram) op.getProperty("histogram"); // histogram = histogram.getSmoothed(true,20); double t1 = histogram.getIterativeThreshold()[0]; double t2 = histogram.getMaxEntropyThreshold()[0]; double t3 = histogram.getMaxVarianceThreshold()[0]; double t4 = histogram.getMinErrorThreshold()[0]; double t5 = histogram.getMinFuzzinessThreshold()[0]; double t6 = histogram.getEntropy()[0]; double t7 = histogram.getMean()[0]; data.iterativeThreshold = t1; data.maxEntropyThreshold = t2; data.maxVarianceThreshold = t3; data.minErrorThreshold = t4; data.minFuzzinessThreshold = t5; data.entropy = t6; data.mean = t7; /*System.out.println("==========================================================="); System.out.println(t1); System.out.println(t2); System.out.println(t3); System.out.println(t4); System.out.println(t5); System.out.println(t6); System.out.println(t7); System.out.println("==========================================================="); */ // -------------------------------- // PALY WITH THESE PARAMS // can select other threshold from tN above // the threshold here selects the amount of pixels to use // in the detection process later on, so choose one that is adequate // -------------------------------- pb = new ParameterBlock(); pb.addSource(img); pb.add(new double[] { t3 }); pb.add(new double[] { 255 }); img = (PlanarImage) JAI.create("BinaryThreshold", pb); data.binaryThreshold = getImage(img); // */ // ****************************************** // * HOUGH TRANSF *** // ****************************************** ellipses = this.findEllipsesAt(img, scale, data); data.ellipses = ellipses; return data; } @SuppressWarnings("unchecked") private List<EllipseDescriptor> findEllipsesAt(PlanarImage src, float scale, EllipseDetectionData data) { List<EllipseDescriptor> ellipses = null; ParameterBlock pb = ellipseCfg.getParameters(); System.out.println("RHTE Settings : "+pb.toString()); pb.addSource(src); PlanarImage eht = (PlanarImage) JAI.create("HoughEllipses", pb); data.detectedEllipses = getImage(eht); ellipses = (List<EllipseDescriptor>) eht .getProperty(EllipseDescriptor.DETECTED_ELLIPSES); return ellipses; } public static void show(BufferedImage img) { JFrame frame = new JFrame(); JLabel label = new JLabel(new ImageIcon(img)); frame.getContentPane().add(label); frame.pack(); frame.setVisible(true); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } public static RenderedImage getMask(PlanarImage source) { Dimension size = new Dimension(source.getWidth(), source.getHeight()); double border = 3.0; double w = size.width - 2 * border; double h = size.height - 2 * border; double x = border; double y = border; RenderedImage mask = new BufferedImage(size.width, size.height,BufferedImage.TYPE_INT_RGB); Graphics2D graphics = (Graphics2D) ((BufferedImage) mask).getGraphics(); graphics.setColor(Color.white); graphics.fill(new Rectangle2D.Double(x, y, w, h)); graphics.dispose(); return mask; } // fast method static @SuppressWarnings("unchecked") public static long[] detectEllipses(String sourceFile, ParameterBlock ellipseParams, boolean debug, boolean save) { long startTime = System.currentTimeMillis(); PlanarImage source = (PlanarImage) JAI.create("fileload", new File(sourceFile).getAbsolutePath()); RenderedImage mask = getMask(source); List<EllipseDescriptor> ellipses = new ArrayList<EllipseDescriptor>(); if (source == null) return new long[0]; PlanarImage img = source; ParameterBlock pb = new ParameterBlock(); // ****************************************** // * GREYSCALE *** // ****************************************** img = VisionUtils.convertColorToGray(img, 0); // ****************************************** // * MEDIAN FILTERING *** // ****************************************** pb = new ParameterBlock(); pb.addSource(img); pb.add(MedianFilterDescriptor.MEDIAN_MASK_X); pb.add(3); img = (PlanarImage) JAI.create("MedianFilter", pb); // ****************************************** // * EDGE DETECT SOBEL *** // ****************************************** pb = new ParameterBlock(); pb.addSource(img); pb.add(robo.vision.Kernel.SOBEL_H); pb.add(robo.vision.Kernel.SOBEL_V); img = (PlanarImage) JAI.create("GradientMagnitude", pb); // ****************************************** // * DEL BORDER *** // ****************************************** pb = new ParameterBlock(); pb.addSource(img); pb.addSource(mask); img = (PlanarImage) JAI.create("And", pb); // ****************************************** // * ADAPTIVE TRESHOLD MEDIAN *** // ****************************************** int[] bins = { 256 }; double[] low = { 0.0D }; double[] high = { 256.0D }; pb = new ParameterBlock(); pb.addSource(img); pb.add(null); pb.add(1); pb.add(1); pb.add(bins); pb.add(low); pb.add(high); RenderedOp op = JAI.create("histogram", pb); Histogram histogram = (Histogram) op.getProperty("histogram"); double t3 = histogram.getMaxVarianceThreshold()[0]; pb = new ParameterBlock(); pb.addSource(img); pb.add(new double[] { t3 }); pb.add(new double[] { 255 }); img = (PlanarImage) JAI.create("BinaryThreshold", pb); // ****************************************** // * HOUGH TRANSF *** // ****************************************** long startRHTETime = System.currentTimeMillis(); pb = ellipseParams; pb.addSource(img); PlanarImage eht = (PlanarImage) JAI.create("HoughEllipses", pb); BufferedImage bi = eht.getAsBufferedImage(); ellipses = (List<EllipseDescriptor>) eht.getProperty(EllipseDescriptor.DETECTED_ELLIPSES); long endTime = System.currentTimeMillis(); if (debug) { System.out.println("T0 : "+String.valueOf(startRHTETime-startTime)+" PREPROCESSING"); System.out.println("T1 : "+String.valueOf(endTime - startRHTETime)+" ELLIPSE DETECTION"); } if (save) { BufferedImage out = new BufferedImage(bi.getWidth(),bi.getHeight(),BufferedImage.TYPE_INT_ARGB); out.getGraphics().drawImage(bi,0,0,null); //for (EllipseDescriptor ellipse:ellipses) { // drawEllipse(out, ellipse); //} try { ImageIO.write(out, "PNG", new File(sourceFile.split("\\.")[0]+"_out.png")); } catch(Exception e) {System.out.println("ERR"+e);} } long data[] = new long[2]; data[0] = ellipses.size(); data[1] = endTime - startRHTETime; return data; } public static void drawEllipse(BufferedImage out,EllipseDescriptor ellipse) { Graphics2D g = (Graphics2D)out.getGraphics(); double cx = ellipse.getCenter().getX(); double cy = ellipse.getCenter().getY(); double a = ellipse.getHalfMajorAxis(); double b = ellipse.getHalfMinorAxis(); double theta = ellipse.getAlfa(); Ellipse2D.Double e = new Ellipse2D.Double(cx-a,cy-b,a*2,b*2); g.rotate(theta, cx, cy); g.draw(e); g.setColor(Color.green); } /** * @param args */ public static void main(String[] args) { JAIOperatorRegister.registerOperators(); if (args.length == 0) { new EllipseDetector(); } else { //Logger log = Logger.getLogger(EllipseDetector.class.getName()); try { //FileHandler handler = new FileHandler("results.log"); //log.addHandler(handler); for(int i=0; i<args.length; i+= 9) { //System.out.println("=================================================================="); System.out.println(""); String imageFile = args[i]; String minA = args[i+1]; String maxA = args[i+2]; String minB = args[i+3]; String maxB = args[i+4]; String q = args[i+5]; String cnt = args[i+6]; String maxP = args[i+7]; int max_test = Integer.valueOf(args[i+8]); boolean save = true; boolean debug = true; if (max_test > 1) { save = false; debug = false; } ParameterBlock pb = new ParameterBlock(); pb.add(Integer.valueOf(minA)); pb.add(Integer.valueOf(maxA)); pb.add(Integer.valueOf(minB)); pb.add(Integer.valueOf(maxB)); pb.add(Integer.valueOf(q)); pb.add(Integer.valueOf(cnt)); pb.add(Float.valueOf(maxP)); pb.add(Boolean.valueOf(debug)); double detected[] = new double[max_test]; double time[] = new double[max_test]; int total = 0; for (int j=0; j<max_test; j++) { //0 - ellipses, 1 - time to detect long data[] = EllipseDetector.detectEllipses(imageFile, pb, debug, save); detected[j] = data[0]; time[j] = data[1]; if (detected[j] == 1.0) total += detected[j]; } double ratio = (100.0*(double)total)/(double)max_test; double mean_detection = MEAN(detected); double stdev_detection = STDEV(detected); double mean_time = MEAN(time); double stdev_time = STDEV(time); System.out.println("ACCURACY : "+ratio); //System.out.println("MEAN DET : "+mean_detection); System.out.println("STDEV DET : "+stdev_detection); System.out.println("MEAN TIME : "+mean_time); System.out.println("STDEV TIME : "+stdev_time); } } catch (Exception e) { System.out.println("Exception "+e); } } } public static double MEAN( double[] data ) { // sd is sqrt of sum of (values-mean) squared divided by n - 1 // Calculate the mean double mean = 0; final int n = data.length; if ( n < 2 ) { return Double.NaN; } for ( int i=0; i<n; i++ ) { mean += data[i]; } mean /= n; return mean; } public static double STDEV ( double[] data ) { // sd is sqrt of sum of (values-mean) squared divided by n - 1 // Calculate the mean double mean = 0; final int n = data.length; if ( n < 2 ) { return Double.NaN; } for ( int i=0; i<n; i++ ) { mean += data[i]; } mean /= n; // calculate the sum of squares double sum = 0; for ( int i=0; i<n; i++ ) { final double v = data[i] - mean; sum += v * v; } return Math.sqrt( sum / ( n - 1 ) ); } protected void openImage(File src) { this.source = (PlanarImage) JAI.create("fileload", src .getAbsolutePath()); } @Override public void actionPerformed(ActionEvent e) { String cmd = e.getActionCommand(); if (cmd == CMD_ABOUT) { JOptionPane .showMessageDialog( this, "Copyright (c) 2005, 2009 Cosmin Basca \n cosmin.basca@gmail.com", "About RHED - Randomized HOUGH Ellipse Detector", JOptionPane.INFORMATION_MESSAGE); } else if (cmd == CMD_EXIT) { System.exit(0); } else if (cmd == CMD_DETECT) { edgeDetector.clearImage(); adaptiveThreshold.clearImage(); ellipseDetection.clearImage(); progress.setIndeterminate(true); this.detectAllEllipses(); } else if (cmd == CMD_OPEN) { JFileChooser fc = new JFileChooser(new File(System .getProperty("user.dir"))); fc.showOpenDialog(this); File file = fc.getSelectedFile(); if (file != null) { this.openImage(file); Dimension size = new Dimension(source.getWidth(), source.getHeight()); originalImage.setPreferredSize(size); originalImage.setMaximumSize(size); originalImage.setMinimumSize(size); edgeDetector.setPreferredSize(size); edgeDetector.setMaximumSize(size); edgeDetector.setMinimumSize(size); adaptiveThreshold.setPreferredSize(size); adaptiveThreshold.setMaximumSize(size); adaptiveThreshold.setMinimumSize(size); ellipseDetection.setPreferredSize(size); ellipseDetection.setMaximumSize(size); ellipseDetection.setMinimumSize(size); this.originalImage.setImage(this.source); this.createMask(size); this.pack(); } } } }