001/**
002 * Copyright (c) 2011, The University of Southampton and the individual contributors.
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 *
008 *   *  Redistributions of source code must retain the above copyright notice,
009 *      this list of conditions and the following disclaimer.
010 *
011 *   *  Redistributions in binary form must reproduce the above copyright notice,
012 *      this list of conditions and the following disclaimer in the documentation
013 *      and/or other materials provided with the distribution.
014 *
015 *   *  Neither the name of the University of Southampton nor the names of its
016 *      contributors may be used to endorse or promote products derived from this
017 *      software without specific prior written permission.
018 *
019 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
020 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
021 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
022 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
023 * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
024 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
025 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
026 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
027 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
028 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029 */
030package org.openimaj.image.processing.restoration.inpainting;
031
032import java.util.Set;
033
034import org.openimaj.citation.annotation.Reference;
035import org.openimaj.citation.annotation.ReferenceType;
036import org.openimaj.image.FImage;
037import org.openimaj.image.Image;
038import org.openimaj.image.MBFImage;
039import org.openimaj.image.pixel.Pixel;
040import org.openimaj.image.processing.morphology.StructuringElement;
041import org.openimaj.image.processor.SinglebandImageProcessor;
042
043/**
044 * Implementation of Alexandru Telea's FMM-based inpainting algorithm. The
045 * {@link AbstractFMMInpainter} is extended with a method to inpaint pixels
046 * based on the neighbours and explicitly taking into account the image
047 * gradients in the neighbourhood in order to preserve sharp details and smooth
048 * zones.
049 * 
050 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk)
051 * 
052 * @param <IMAGE>
053 *            The type of image that this processor can process
054 */
055@Reference(
056                type = ReferenceType.Article,
057                author = { "Telea, Alexandru" },
058                title = "An Image Inpainting Technique Based on the Fast Marching Method.",
059                year = "2004",
060                journal = "J. Graphics, GPU, & Game Tools",
061                pages = { "23", "34" },
062                url = "http://dblp.uni-trier.de/db/journals/jgtools/jgtools9.html#Telea04",
063                number = "1",
064                volume = "9",
065                customData = {
066                                "biburl", "http://www.bibsonomy.org/bibtex/2b0bf54e265d011a8e1fe256e6fcf556b/dblp",
067                                "doi", "http://dx.doi.org/10.1080/10867651.2004.10487596",
068                                "keywords", "dblp"
069                })
070public class TeleaInpainting<IMAGE extends Image<?, IMAGE> & SinglebandImageProcessor.Processable<Float, FImage, IMAGE>>
071                extends
072                AbstractFMMInpainter<IMAGE>
073{
074        protected Set<Pixel> region;
075
076        /**
077         * Construct the inpainting operator with the given radius.
078         * 
079         * @param radius
080         *            the radius for selecting how many pixels are used to make
081         *            estimates.
082         */
083        public TeleaInpainting(int radius) {
084                region = StructuringElement.disk(radius).positive;
085        }
086
087        @Override
088        protected void inpaint(int x, int y, IMAGE image) {
089                if (image instanceof FImage)
090                        inpaint(x, y, (FImage) image);
091                else if (image instanceof MBFImage)
092                        inpaint(x, y, (MBFImage) image);
093                else
094                        throw new UnsupportedOperationException("Image type not supported!");
095        }
096
097        protected void inpaint(int x, int y, FImage input) {
098                final int width = input.getWidth();
099                final int height = input.getHeight();
100                final float gradx_u = gradX(timeMap.pixels, x, y);
101                final float grady_u = gradY(timeMap.pixels, x, y);
102
103                float accum = 0;
104                float norm = 0;
105
106                for (final Pixel p : region) {
107                        final int xx = p.x + x;
108                        final int yy = p.y + y;
109
110                        if (xx <= 1 || xx >= width - 1 || yy <= 1 || yy >= height - 1)
111                                continue;
112                        if (flag[yy][xx] != KNOWN)
113                                continue;
114
115                        final int rx = x - xx;
116                        final int ry = y - yy;
117
118                        // geometric distance.
119                        final float geometricDistance = (float) (1. / ((rx * rx + ry * ry) * Math.sqrt((rx * rx + ry * ry))));
120
121                        // levelset distance.
122                        final float levelsetDistance = (float) (1. / (1 + Math.abs(timeMap.pixels[yy][xx] - timeMap.pixels[y][x])));
123
124                        // Dot product of final displacement and gradient vectors.
125                        float direction = Math.abs(rx * gradx_u + ry * grady_u);
126                        if (direction < 0.000001f)
127                                direction = 0.000001f;
128
129                        final float weight = geometricDistance * levelsetDistance * direction;
130
131                        accum += weight * input.pixels[yy][xx];
132                        norm += weight;
133                }
134
135                input.pixels[y][x] = accum / norm;
136        }
137
138        protected void inpaint(int x, int y, MBFImage input) {
139                final int width = input.getWidth();
140                final int height = input.getHeight();
141                final float gradx_u = gradX(timeMap.pixels, x, y);
142                final float grady_u = gradY(timeMap.pixels, x, y);
143
144                final int nbands = input.numBands();
145                final float accum[] = new float[nbands];
146                float norm = 0;
147
148                for (final Pixel p : region) {
149                        final int xx = p.x + x;
150                        final int yy = p.y + y;
151
152                        if (xx <= 1 || xx >= width - 1 || yy <= 1 || yy >= height - 1)
153                                continue;
154                        if (flag[yy][xx] != KNOWN)
155                                continue;
156
157                        final int rx = x - xx;
158                        final int ry = y - yy;
159
160                        // geometric distance.
161                        final float geometricDistance = (float) (1. / ((rx * rx + ry * ry) * Math.sqrt((rx * rx + ry * ry))));
162
163                        // levelset distance.
164                        final float levelsetDistance = (float) (1. / (1 + Math.abs(timeMap.pixels[yy][xx] - timeMap.pixels[y][x])));
165
166                        // Dot product of final displacement and gradient vectors.
167                        float direction = Math.abs(rx * gradx_u + ry * grady_u);
168                        if (direction < 0.000001f)
169                                direction = 0.000001f;
170
171                        final float weight = geometricDistance * levelsetDistance * direction;
172
173                        for (int i = 0; i < nbands; i++)
174                                accum[i] += weight * input.getBand(i).pixels[yy][xx];
175                        norm += weight;
176                }
177
178                for (int i = 0; i < nbands; i++)
179                        input.getBand(i).pixels[y][x] = accum[i] / norm;
180        }
181
182        private float gradX(float[][] img, int x, int y) {
183                float grad;
184
185                if (flag[y][x + 1] != UNKNOWN) {
186                        if (flag[y][x - 1] != UNKNOWN)
187                                grad = (img[y][x + 1] - img[y][x - 1]) * 0.5f;
188                        else
189                                grad = (img[y][x + 1] - img[y][x]);
190                } else {
191                        if (flag[y][x - 1] != UNKNOWN)
192                                grad = (img[y][x] - img[y][x - 1]);
193                        else
194                                grad = 0;
195                }
196
197                return grad;
198        }
199
200        private float gradY(float[][] img, int x, int y) {
201                float grad;
202
203                if (flag[y + 1][x] != UNKNOWN) {
204                        if (flag[y - 1][x] != UNKNOWN)
205                                grad = (img[y + 1][x] - img[y - 1][x]) * 0.5f;
206                        else
207                                grad = (img[y + 1][x] - img[y][x]);
208                } else {
209                        if (flag[y - 1][x] != UNKNOWN)
210                                grad = (img[y][x] - img[y - 1][x]);
211                        else
212                                grad = 0;
213                }
214
215                return grad;
216        }
217}