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-2016 Wilhelm Burger, Mark J. Burge. All rights reserved. 007 * Visit http://imagingbook.com for additional details. 008 *******************************************************************************/ 009 010package imagingbook.pub.threshold.adaptive; 011 012import ij.plugin.filter.GaussianBlur; 013import ij.plugin.filter.RankFilters; 014import ij.process.ByteProcessor; 015import ij.process.FloatProcessor; 016import imagingbook.pub.threshold.BackgroundMode; 017 018/** 019 * This version of Niblack's thresholder uses a circular support region, implemented 020 * with IJ's rank-filter methods. 021 */ 022public abstract class NiblackThresholder extends AdaptiveThresholder { 023 024 public static class Parameters { 025 public int radius = 15; 026 public double kappa = 0.30; 027 public int dMin = 5; 028 public BackgroundMode bgMode = BackgroundMode.DARK; 029 } 030 031 private final Parameters params; 032 protected FloatProcessor Imean; 033 protected FloatProcessor Isigma; 034 035 private NiblackThresholder () { 036 super(); 037 this.params = new Parameters(); 038 } 039 040 private NiblackThresholder (Parameters params) { 041 super(); 042 this.params = params; 043 } 044 045 protected abstract void makeMeanAndVariance(ByteProcessor I, Parameters params); 046 047 @Override 048 public ByteProcessor getThreshold(ByteProcessor I) { 049 int w = I.getWidth(); 050 int h = I.getHeight(); 051 makeMeanAndVariance(I, params); 052 ByteProcessor Q = new ByteProcessor(w, h); 053 final double kappa = params.kappa; 054 final int dMin = params.dMin; 055 final boolean darkBg = (params.bgMode == BackgroundMode.DARK); 056 057 for (int v = 0; v < h; v++) { 058 for (int u = 0; u < w; u++) { 059 double sigma = Isigma.getf(u, v); 060 double mu = Imean.getf(u, v); 061 double diff = kappa * sigma + dMin; 062 int q = (int) Math.rint((darkBg) ? mu + diff : mu - diff); 063 if (q < 0) q = 0; 064 if (q > 255) q = 255; 065 Q.set(u, v, q); 066 } 067 } 068 return Q; 069 } 070 071 // ----------------------------------------------------------------------- 072 073 public static class Box extends NiblackThresholder { 074 075 public Box() { 076 super(); 077 } 078 079 public Box(Parameters params) { 080 super(params); 081 } 082 083 @Override 084 protected void makeMeanAndVariance(ByteProcessor I, Parameters params) { 085 int width = I.getWidth(); 086 int height = I.getHeight(); 087 Imean = new FloatProcessor(width, height); 088 Isigma = new FloatProcessor(width, height); 089 final int radius = params.radius; 090 final int n = (radius + 1 + radius) * (radius + 1 + radius); 091 092 for (int v = 0; v < height; v++) { 093 for (int u = 0; u < width; u++) { 094 long A = 0; // sum of image values in support region 095 long B = 0; // sum of squared image values in support region 096 for (int j = -radius; j <= radius; j++) { 097 for (int i = -radius; i <= radius; i++) { 098 int p = getPaddedPixel(I, u + i, v + j); // this is slow! 099 A = A + p; 100 B = B + p * p; 101 } 102 } 103 Imean.setf(u, v, (float) A / n); 104 Isigma.setf(u, v, (float) Math.sqrt((B - (double) (A * A) / n) / n)); 105 } 106 } 107 } 108 109 } 110 111 // ----------------------------------------------------------------------- 112 113 public static class Disk extends NiblackThresholder { 114 115 public Disk() { 116 super(); 117 } 118 119 public Disk(Parameters params) { 120 super(params); 121 } 122 123 @Override 124 protected void makeMeanAndVariance(ByteProcessor I, Parameters params) { 125 FloatProcessor mean = (FloatProcessor) I.convertToFloat(); 126 FloatProcessor var = (FloatProcessor) mean.duplicate(); 127 128 RankFilters rf = new RankFilters(); 129 rf.rank(mean, params.radius, RankFilters.MEAN); 130 Imean = mean; 131 132 rf.rank(var, params.radius, RankFilters.VARIANCE); 133 var.sqrt(); 134 Isigma = var; 135 } 136 } 137 138 // ----------------------------------------------------------------------- 139 140 public static class Gauss extends NiblackThresholder { 141 142 public Gauss() { 143 super(); 144 } 145 146 public Gauss(Parameters params) { 147 super(params); 148 } 149 150 @Override 151 protected void makeMeanAndVariance(ByteProcessor I, Parameters params) { 152 // //uses ImageJ's GaussianBlur 153 // local variance over square of size (size + 1 + size)^2 154 int width = I.getWidth(); 155 int height = I.getHeight(); 156 157 Imean = new FloatProcessor(width,height); 158 Isigma = new FloatProcessor(width,height); 159 160 FloatProcessor A = (FloatProcessor) I.convertToFloatProcessor(); 161 FloatProcessor B = (FloatProcessor) I.convertToFloatProcessor(); 162 B.sqr(); 163 164 GaussianBlur gb = new GaussianBlur(); 165 double sigma = params.radius * 0.6; // sigma of Gaussian filter should be approx. 0.6 of the disk's radius 166 gb.blurFloat(A, sigma, sigma, 0.002); 167 gb.blurFloat(B, sigma, sigma, 0.002); 168 169 for (int v = 0; v < height; v++) { 170 for (int u = 0; u < width; u++) { 171 float a = A.getf(u, v); 172 float b = B.getf(u, v); 173 float sigmaG = (float) Math.sqrt(b - a*a); 174 Imean.setf(u, v, a); 175 Isigma.setf(u, v, sigmaG); 176 } 177 } 178 } 179 } 180 181}