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}