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.edgepreservingfilters; 011 012import imagingbook.lib.filters.GenericFilter; 013import imagingbook.lib.image.ImageAccessor; 014 015/** 016 * This class implements a Kuwahara-type filter, similar to the filter suggested in 017 * Tomita and Tsuji (1977). It structures the filter region into five overlapping, 018 * square subregions (including a center region) of size (r+1) x (r+1). 019 * See algorithm 5.2 in Utics Vol. 3. 020 * 021 * @author W. Burger 022 * @version 2013/05/30 023 */ 024public class KuwaharaFilter extends GenericFilter { 025 026 public static class Parameters { 027 /** Radius of the filter (should be even) */ 028 public int radius = 2; 029 /** Threshold on sigma to avoid banding in flat regions */ 030 public double tsigma = 5.0; 031 } 032 033 private Parameters params; 034 035 private int n; // fixed subregion size 036 private int dm; // = d- 037 private int dp; // = d+ 038 039 // these are used in calculation by several methods: 040 private float Smin; // min. variance 041 private float Amin; 042 private float AminR; 043 private float AminG; 044 private float AminB; 045 046 // constructor using default settings 047 public KuwaharaFilter() { 048 this.params = new Parameters(); 049 initialize(); 050 } 051 052 public KuwaharaFilter(Parameters params) { 053 this.params = params; 054 initialize(); 055 } 056 057 public KuwaharaFilter(int r, double tsigma) { 058 this.params = new Parameters(); 059 this.params.radius = r; 060 this.params.tsigma = tsigma; 061 initialize(); 062 } 063 064 void initialize() { 065 int r = params.radius; 066 n = (r + 1) * (r + 1); // size of complete filter 067 dm = (r/2) - r; // d- = top/left center coordinate 068 dp = dm + r; // d+ = bottom/right center coordinate 069 } 070 071 static int checkRadius(int radius) { 072 assert radius >= 1 : "filter radius must be >= 1"; 073 return radius; 074 } 075 076 // ------------------------------------------------------ 077 078 /* 079 * This method is used for all scalar-values images. 080 */ 081 public float filterPixel(ImageAccessor.Scalar ia, int u, int v) { 082 Smin = Float.MAX_VALUE; 083 evalSubregionGray(ia, u, v); // a centered subregion (not in original Kuwahara) 084 Smin = Smin - (float)params.tsigma * n; // tS * n because we use variance scaled by n 085 evalSubregionGray(ia, u + dm, v + dm); 086 evalSubregionGray(ia, u + dm, v + dp); 087 evalSubregionGray(ia, u + dp, v + dm); 088 evalSubregionGray(ia, u + dp, v + dp); 089 return Amin; 090 } 091 092 /* 093 * sets the member variables Smin, Amin 094 */ 095 void evalSubregionGray(ImageAccessor.Scalar ia, int u, int v) { 096 float S1 = 0; 097 float S2 = 0; 098 for (int j = dm; j <= dp; j++) { 099 for (int i = dm; i <= dp; i++) { 100 float a = ia.getVal(u+i, v+j); 101 S1 = S1 + a; 102 S2 = S2 + a * a; 103 } 104 } 105// double s = (sum2 - sum1*sum1/nr)/nr; // actual sigma^2 106 float s = S2 - S1*S1/n; // s = n * sigma^2 107 if (s < Smin) { 108 Smin = s; 109 Amin = S1 / n; // mean 110 } 111 } 112 113 // ------------------------------------------------------ 114 115 final float[] rgb = {0,0,0}; 116 117 public float[] filterPixel(ImageAccessor.Rgb ia, int u, int v) { 118 Smin = Float.MAX_VALUE; 119 evalSubregion(ia, u, v); // centered subregion - different to original Kuwahara! 120 Smin = Smin - (3 * (float)params.tsigma * n); // tS * n because we use variance scaled by n 121 evalSubregion(ia, u+dm, v+dm); 122 evalSubregion(ia, u+dm, v+dp); 123 evalSubregion(ia, u+dp, v+dm); 124 evalSubregion(ia, u+dp, v+dp); 125 rgb[0] = (int) Math.rint(AminR); 126 rgb[1] = (int) Math.rint(AminG); 127 rgb[2] = (int) Math.rint(AminB); 128 return rgb; 129 } 130 131 void evalSubregion(ImageAccessor.Rgb ia, int u, int v) { 132 // evaluate the subregion centered at (u,v) 133 //final int[] cpix = {0,0,0}; 134 int S1R = 0; int S2R = 0; 135 int S1G = 0; int S2G = 0; 136 int S1B = 0; int S2B = 0; 137 for (int j = dm; j <= dp; j++) { 138 for (int i = dm; i <= dp; i++) { 139 final float[] cpix = ia.getPix(u + i, v + j); 140 int red = (int) cpix[0]; 141 int grn = (int) cpix[1]; 142 int blu = (int) cpix[2]; 143 S1R = S1R + red; 144 S1G = S1G + grn; 145 S1B = S1B + blu; 146 S2R = S2R + red * red; 147 S2G = S2G + grn * grn; 148 S2B = S2B + blu * blu; 149 } 150 } 151 // calculate the variance for this subregion: 152 float nf = n; 153 float SR = S2R - S1R * S1R / nf; 154 float SG = S2G - S1G * S1G / nf; 155 float SB = S2B - S1B * S1B / nf; 156 // total variance (scaled by nr): 157 float Srgb = SR + SG + SB; 158 if (Srgb < Smin) { 159 Smin = Srgb; 160 AminR = S1R / n; 161 AminG = S1G / n; 162 AminB = S1B / n; 163 } 164 } 165}