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.RankFilters; 013import ij.process.ByteProcessor; 014import ij.process.FloatProcessor; 015import imagingbook.pub.threshold.BackgroundMode; 016import imagingbook.pub.threshold.adaptive.AdaptiveThresholder; 017 018/** 019 * Adaptive thresholder as proposed in J. Sauvola and M. Pietikäinen, 020 * "Adaptive document image binarization", Pattern Recognition 33(2), 021 * 1135-1143 (2000). 022 */ 023public class SauvolaThresholder extends AdaptiveThresholder { 024 025 public static class Parameters { 026 public int radius = 15; 027 public double kappa = 0.5; 028 public double sigmaMax = 128; 029 public BackgroundMode bgMode = BackgroundMode.DARK; 030 } 031 032 private FloatProcessor Imean; 033 private FloatProcessor Isigma; 034 private final Parameters params; 035 036 public SauvolaThresholder() { 037 super(); 038 this.params = new Parameters(); 039 } 040 041 public SauvolaThresholder(Parameters params) { 042 super(); 043 this.params = params; 044 } 045 046 @Override 047 public ByteProcessor getThreshold(ByteProcessor I) { 048 FloatProcessor mean = (FloatProcessor) I.convertToFloat(); 049 FloatProcessor var = (FloatProcessor) mean.duplicate(); 050 051 RankFilters rf = new RankFilters(); 052 rf.rank(mean, params.radius, RankFilters.MEAN); 053 Imean = mean; 054 055 rf.rank(var, params.radius, RankFilters.VARIANCE); 056 var.sqrt(); 057 Isigma = var; 058 059 int width = I.getWidth(); 060 int height = I.getHeight(); 061 final double kappa = params.kappa; 062 final double sigmaMax = params.sigmaMax; 063 final boolean darkBg = (params.bgMode == BackgroundMode.DARK); 064 065 ByteProcessor Q = new ByteProcessor(width, height); 066 for (int v = 0; v < height; v++) { 067 for (int u = 0; u < width; u++) { 068 final double sigma = Isigma.getf(u, v); 069 final double mu = Imean.getf(u, v); 070 final double diff = kappa * (sigma / sigmaMax - 1); 071 int q = (int) Math.rint((darkBg) ? mu * (1 - diff) : mu * (1 + diff)); 072 if (q < 0) 073 q = 0; 074 if (q > 255) 075 q = 255; 076 Q.set(u, v, q); 077 } 078 } 079 return Q; 080 } 081 082}