001/******************************************************************************* 002 * This software is provided as a supplement to the authors' textbooks on digital 003 * image processing published by Springer-Verlag in various languages and editions. 004 * Permission to use and distribute this software is granted under the BSD 2-Clause 005 * "Simplified" License (see http://opensource.org/licenses/BSD-2-Clause). 006 * Copyright (c) 2006-2023 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit https://imagingbook.com for additional details. 008 ******************************************************************************/ 009package Ch12_Ransac_Hough; 010 011import ij.IJ; 012import ij.ImagePlus; 013import ij.gui.GenericDialog; 014import ij.plugin.filter.PlugInFilter; 015import ij.process.ByteProcessor; 016import ij.process.FloatProcessor; 017import ij.process.ImageProcessor; 018import imagingbook.common.hough.HoughLine; 019import imagingbook.common.hough.HoughTransformLines; 020import imagingbook.common.ij.DialogUtils; 021import imagingbook.common.ij.IjUtils; 022import imagingbook.common.ij.overlay.ColoredStroke; 023import imagingbook.common.ij.overlay.ShapeOverlayAdapter; 024import imagingbook.core.jdoc.JavaDocHelp; 025import imagingbook.sampleimages.GeneralSampleImage; 026 027import java.awt.Color; 028import java.awt.geom.Path2D; 029 030import static imagingbook.common.ij.IjUtils.noCurrentImage; 031 032/** 033 * <p> 034 * This ImageJ plugin demonstrates the use of the {@link HoughTransformLines} class for detecting straight lines in 035 * images (see Sec. 12.2 of [1] for details). It expects a binary input image with background = 0 and foreground 036 * (contour) pixels with values > 0. A vector overlay is used to display the detected lines. If no image is currently 037 * open, the plugin optionally loads a suitable sample image. 038 * </p> 039 * <p> 040 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing – An Algorithmic Introduction</em>, 3rd ed, Springer 041 * (2022). 042 * </p> 043 * 044 * @author WB 045 * @version 2022/10/03 046 */ 047public class Hough_Line_Detect implements PlugInFilter, JavaDocHelp { 048 049 private static int MaxLines = 5; // number of strongest lines to be found 050 private static int MinPointsOnLine = 50; // min. number of points on each line 051 052 private static boolean ShowAccumulator = true; 053 private static boolean ShowExtendedAccumulator = false; 054 private static boolean ShowAccumulatorPeaks = false; 055 private static boolean ListStrongestLines = false; 056 private static boolean ShowLines = true; 057 private static boolean InvertOriginal = false; 058 059 private static double LineWidth = 0.5; 060 private static Color LineColor = Color.magenta; 061 062 private static boolean ShowReferencePoint = true; 063 private static Color ReferencePointColor = Color.green; 064 065 private ImagePlus im; 066 067 /** 068 * Constructor, asks to open a predefined sample image if no other image is currently open. 069 */ 070 public Hough_Line_Detect() { 071 if (noCurrentImage()) { 072 DialogUtils.askForSampleImage(GeneralSampleImage.NoisyLines); 073 } 074 } 075 076 // ------------------------------------------------------------------------- 077 078 @Override 079 public int setup(String arg, ImagePlus im) { 080 this.im = im; 081 return DOES_8G + NO_CHANGES; 082 } 083 084 @Override 085 public void run(ImageProcessor ip) { 086 if (!IjUtils.isBinary(ip)) { 087 IJ.showMessage("Binary (edge) image required!"); 088 return; 089 } 090 091 HoughTransformLines.Parameters params = new HoughTransformLines.Parameters(); 092 093 if (!showDialog(params)) //dialog canceled or error 094 return; 095 096 // compute the Hough Transform and retrieve the strongest lines: 097 HoughTransformLines ht = new HoughTransformLines((ByteProcessor)ip, params); 098 HoughLine[] lines = ht.getLines(MinPointsOnLine, MaxLines); 099 100 if (lines.length == 0) { 101 IJ.log("No lines detected - check the input image and parameters!"); 102// return; 103 } 104 105 if (ShowAccumulator){ 106 FloatProcessor accIp = ht.getAccumulatorImage(); 107 (new ImagePlus("Accumulator for " + im.getTitle(), accIp)).show(); 108 } 109 110 if (ShowExtendedAccumulator){ 111 FloatProcessor accEx = ht.getAccumulatorImageExtended(); 112 (new ImagePlus("Extended accumulator for " + im.getTitle(), accEx)).show(); 113 } 114 115 if (ShowAccumulatorPeaks) { 116 FloatProcessor maxIp = ht.getAccumulatorMaxImage(); 117 (new ImagePlus("Accumulator peaks for " + im.getTitle(), maxIp)).show(); 118 } 119 120 if (ListStrongestLines) { 121 for (int i = 0; i < lines.length; i++) { 122 IJ.log(i + ": " + lines[i].toString()); 123 } 124 } 125 126 if (ShowLines) { 127 ImageProcessor lineIp = ip.duplicate(); 128 if (InvertOriginal) lineIp.invert(); 129 double lineLength = Math.hypot(ip.getWidth(), ip.getHeight()); 130 131 ShapeOverlayAdapter ola = new ShapeOverlayAdapter(); 132 ola.setStroke(new ColoredStroke(LineWidth, LineColor)); 133 for (HoughLine hl : lines) { 134 ola.addShape(hl.getShape(lineLength)); 135 } 136 137 if (ShowReferencePoint) { 138 ola.setStroke(new ColoredStroke(0.5, ReferencePointColor)); 139 ola.addShape(markPoint(ht.getXref(), ht.getYref())); 140 } 141 142 ImagePlus lim = new ImagePlus(im.getShortTitle()+"-lines", lineIp); 143 lim.setOverlay(ola.getOverlay()); 144 lim.show(); 145 } 146 } 147 148 // ----------------------------------------------------------------- 149 150 private boolean showDialog(HoughTransformLines.Parameters params) { 151 GenericDialog gd = new GenericDialog("Hough Transform (lines)"); 152 gd.addHelp(getJavaDocUrl()); 153 154 gd.addNumericField("Axial steps", params.nAng, 0); 155 gd.addNumericField("Radial steps", params.nRad, 0); 156 gd.addNumericField("Max. number of lines to show", MaxLines, 0); 157 gd.addNumericField("Min. number of points per line", MinPointsOnLine, 0); 158 gd.addCheckbox("Show accumulator", ShowAccumulator); 159 gd.addCheckbox("Show extended accumulator", ShowExtendedAccumulator); 160 gd.addCheckbox("Show accumulator peaks", ShowAccumulatorPeaks); 161 gd.addCheckbox("List strongest lines", ListStrongestLines); 162 gd.addCheckbox("Show lines", ShowLines); 163 gd.addNumericField("Line width", LineWidth, 1); 164 gd.addCheckbox("Show reference point", ShowReferencePoint); 165 166 gd.showDialog(); 167 if(gd.wasCanceled()) 168 return false; 169 170 params.nAng = (int) gd.getNextNumber(); 171 params.nRad = (int) gd.getNextNumber(); 172 MaxLines = (int) gd.getNextNumber(); 173 MinPointsOnLine = (int) gd.getNextNumber(); 174 ShowAccumulator = gd.getNextBoolean(); 175 ShowExtendedAccumulator = gd.getNextBoolean(); 176 ShowAccumulatorPeaks = gd.getNextBoolean(); 177 ListStrongestLines = gd.getNextBoolean(); 178 ShowLines = gd.getNextBoolean(); 179 LineWidth = gd.getNextNumber(); 180 ShowReferencePoint = gd.getNextBoolean(); 181 182 if(gd.invalidNumber()) { 183 IJ.showMessage("Error", "Invalid input number"); 184 return false; 185 } 186 return true; 187 } 188 189 private Path2D markPoint(double xc, double yc) { 190 double markerSize = 2.0; 191 Path2D path = new Path2D.Double(); 192 path.moveTo(xc - markerSize, yc); 193 path.lineTo(xc + markerSize, yc); 194 path.moveTo(xc, yc - markerSize); 195 path.lineTo(xc, yc + markerSize); 196 return path; 197 } 198 199}