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 ******************************************************************************/
009
010package imagingbook.common.edges;
011
012import ij.plugin.filter.Convolver;
013import ij.process.ColorProcessor;
014import ij.process.FloatProcessor;
015import imagingbook.common.ij.IjUtils;
016import imagingbook.common.image.PixelPack;
017import imagingbook.common.math.Matrix;
018import imagingbook.common.util.ParameterBundle;
019
020import static imagingbook.common.math.Arithmetic.sqr;
021import static java.lang.Math.sqrt;
022
023/**
024 * <p>
025 * Multi-Gradient ("DiZenzo/Cumani-style") color edge detector. Applicable to color images ({@link ColorProcessor})
026 * only. See Sec. 16.2 (Alg. 16.2) of [1] for additional details.
027 * </p>
028 * <p>
029 * [1] W. Burger, M.J. Burge, <em>Digital Image Processing &ndash; An Algorithmic Introduction</em>, 3rd ed, Springer
030 * (2022).
031 * </p>
032 *
033 * @author WB
034 * @version 2022/09/11
035 */
036public class MultiGradientEdgeDetector implements EdgeDetector {
037        
038        /**
039         * Parameters for {@link MultiGradientEdgeDetector} (currently unused, no parameters to set).
040         */
041        public static class Parameters implements ParameterBundle<MultiGradientEdgeDetector> {
042        }
043        
044        @SuppressWarnings("unused")
045        private final Parameters params;
046        private final int M;    // image width
047        private final int N;    // image height
048        private final FloatProcessor E_mag;     // edge magnitude map
049        private final FloatProcessor E_ort;     // edge orientation map
050
051        // Sobel-kernels for x/y-derivatives:
052    private static final float[][] HxS = Matrix.multiply(1.0f/8, new float[][] {
053                        {-1, 0, 1},
054                    {-2, 0, 2},
055                    {-1, 0, 1}});
056
057    private static final float[][] HyS = Matrix.multiply(1.0f/8, new float[][] {
058                        {-1, -2, -1},
059                        { 0,  0,  0},
060                        { 1,  2,  1}});
061    
062        public MultiGradientEdgeDetector(ColorProcessor cp) {
063                this(cp, new Parameters());
064        }
065        
066        public MultiGradientEdgeDetector(ColorProcessor cp, Parameters params) {
067                this.params = params;
068                this.M = cp.getWidth();
069                this.N = cp.getHeight();
070                this.E_mag = new FloatProcessor(M, N);
071                this.E_ort = new FloatProcessor(M, N);
072                findEdges(cp);
073        }
074
075        private void findEdges(ColorProcessor cp) {
076                FloatProcessor[] I = new PixelPack(cp).getFloatProcessors();
077                
078                // calculate image derivatives in x/y for all color channels:
079            FloatProcessor[] Ix = new FloatProcessor[3];
080            FloatProcessor[] Iy = new FloatProcessor[3];
081            Convolver conv = new Convolver();
082                conv.setNormalize(false);
083                for (int k = 0; k < 3; k++) {
084                        Ix[k] = I[k];
085                        Iy[k] = (FloatProcessor) Ix[k].duplicate();
086                        // TODO: convert to generic filter?
087                        IjUtils.convolve(Ix[k], HxS);
088                        IjUtils.convolve(Iy[k], HyS);
089                }
090                
091                // calculate color edge magnitude and orientation:
092                for (int v = 0; v < N; v++) {
093                        for (int u = 0; u < M; u++) {
094                                double rx = Ix[0].getf(u, v), ry = Iy[0].getf(u, v);
095                                double gx = Ix[1].getf(u, v), gy = Iy[1].getf(u, v);
096                                double bx = Ix[2].getf(u, v), by = Iy[2].getf(u, v);
097                                
098                                double A = rx*rx + gx*gx + bx*bx;
099                                double B = ry*ry + gy*gy + by*by;
100                                double C = rx*ry + gx*gy + bx*by;
101                                
102                                double lambda0 = 0.5 * (A + B + sqrt(sqr(A - B) + 4 * sqr(C)));
103                                double theta0 =  0.5 * Math.atan2(2 * C, A - B);
104
105                                E_mag.setf(u, v, (float) sqrt(lambda0));
106                                E_ort.setf(u, v, (float) theta0);
107                        }
108                }
109        }
110        
111        @Override
112        public FloatProcessor getEdgeMagnitude() {
113                return E_mag;
114        }
115
116        @Override
117        public FloatProcessor getEdgeOrientation() {
118                return E_ort;
119        }
120        
121}