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.transform; 031 032import java.util.ArrayList; 033import java.util.List; 034 035import org.openimaj.image.Image; 036import org.openimaj.image.pixel.Pixel; 037import org.openimaj.image.processor.ImageProcessor; 038import org.openimaj.image.renderer.ScanRasteriser; 039import org.openimaj.image.renderer.ScanRasteriser.ScanLineListener; 040import org.openimaj.math.geometry.point.Point2d; 041import org.openimaj.math.geometry.shape.Polygon; 042import org.openimaj.math.geometry.shape.Rectangle; 043import org.openimaj.math.geometry.shape.Shape; 044import org.openimaj.math.geometry.transforms.TransformUtilities; 045import org.openimaj.util.pair.Pair; 046 047import Jama.Matrix; 048 049/** 050 * Implementation of a piecewise warp. The warp can be piecewise affine, 051 * piecewise homographic or a mixture of the two. Basically this means you can 052 * warp images represented by triangles, quads or a mixture of the two. 053 * 054 * @author Jonathon Hare (jsh2@ecs.soton.ac.uk) 055 * 056 * @param <T> 057 * the pixel type 058 * @param <I> 059 * the actual image of the concrete subclass 060 */ 061public class PiecewiseMeshWarp<T, I extends Image<T, I>> implements ImageProcessor<I> { 062 List<Pair<Shape>> matchingRegions; 063 List<Matrix> transforms = new ArrayList<Matrix>(); 064 Rectangle bounds; 065 066 /** 067 * Construct the warp with a list of matching shapes (which must be either 068 * quads or triangles). The pairs map from the measured/observed space to 069 * the canonical space. 070 * 071 * @param matchingRegions 072 * the matching shapes 073 */ 074 public PiecewiseMeshWarp(List<Pair<Shape>> matchingRegions) { 075 this.matchingRegions = matchingRegions; 076 initTransforms(); 077 } 078 079 protected final Matrix getTransform(Point2d p) { 080 final int sz = matchingRegions.size(); 081 082 for (int i = 0; i < sz; i++) { 083 if (matchingRegions.get(i).secondObject().isInside(p)) { 084 return transforms.get(i); 085 } 086 } 087 return null; 088 } 089 090 /** 091 * Get the shape in the observation space for a point in the canonical 092 * space. 093 * 094 * @param p 095 * point in the canonical space. 096 * @return the matching shape in observed space 097 */ 098 public Shape getMatchingShape(Point2d p) { 099 for (int i = 0; i < matchingRegions.size(); i++) { 100 final Pair<Shape> matching = matchingRegions.get(i); 101 if (matching.secondObject().isInside(p)) { 102 return matching.firstObject(); 103 } 104 } 105 return null; 106 } 107 108 /** 109 * Get the shape pair index for a point in the canonical space. 110 * 111 * @param p 112 * the point in canonical space. 113 * @return the index of the matching point pair 114 */ 115 public int getMatchingShapeIndex(Point2d p) { 116 for (int i = 0; i < matchingRegions.size(); i++) { 117 final Pair<Shape> matching = matchingRegions.get(i); 118 if (matching.secondObject().isInside(p)) { 119 return i; 120 } 121 } 122 return -1; 123 } 124 125 protected void initTransforms() { 126 bounds = new Rectangle(Float.MAX_VALUE, Float.MAX_VALUE, 0, 0); 127 128 for (final Pair<Shape> shape : matchingRegions) { 129 final Polygon p1 = shape.firstObject().asPolygon(); 130 final Polygon p2 = shape.secondObject().asPolygon(); 131 132 bounds.x = (float) Math.min(bounds.x, p2.minX()); 133 bounds.y = (float) Math.min(bounds.y, p2.minY()); 134 bounds.width = (float) Math.max(bounds.width, p2.maxX()); 135 bounds.height = (float) Math.max(bounds.height, p2.maxY()); 136 137 if (p1.nVertices() == 3) { 138 transforms.add(getTransform3(polyMatchToPointsMatch(p2, p1))); 139 } else if (p1.nVertices() == 4) { 140 transforms.add(getTransform4(polyMatchToPointsMatch(p2, p1))); 141 } else { 142 throw new RuntimeException("Only polygons with 3 or 4 vertices are supported!"); 143 } 144 } 145 146 bounds.width -= bounds.x; 147 bounds.height -= bounds.y; 148 } 149 150 protected List<Pair<Point2d>> polyMatchToPointsMatch(Polygon pa, Polygon pb) { 151 final List<Pair<Point2d>> pts = new ArrayList<Pair<Point2d>>(); 152 for (int i = 0; i < pa.nVertices(); i++) { 153 final Point2d pta = pa.getVertices().get(i); 154 final Point2d ptb = pb.getVertices().get(i); 155 156 pts.add(new Pair<Point2d>(pta, ptb)); 157 } 158 return pts; 159 } 160 161 protected Matrix getTransform4(List<Pair<Point2d>> pts) { 162 return TransformUtilities.homographyMatrixNorm(pts); 163 } 164 165 protected Matrix getTransform3(List<Pair<Point2d>> pts) { 166 return TransformUtilities.affineMatrix(pts); 167 } 168 169 @Override 170 public void processImage(final I image) { 171 final int width = image.getWidth(); 172 final int height = image.getHeight(); 173 174 final I ret = image.newInstance(width, height); 175 176 final Scan scan = new Scan(width, height, image, ret); 177 178 for (int i = 0; i < matchingRegions.size(); i++) { 179 final Polygon from = matchingRegions.get(i).secondObject().asPolygon(); 180 scan.tf = transforms.get(i); 181 182 ScanRasteriser.scanFill(from.points, scan); 183 } 184 185 image.internalAssign(ret); 186 } 187 188 /** 189 * Transform the content of the input image into an output image of the 190 * given dimensions. 191 * 192 * @param image 193 * the input image 194 * @param width 195 * the output width 196 * @param height 197 * the output height 198 * @return the output image 199 */ 200 public I transform(final I image, int width, int height) { 201 final I ret = image.newInstance(width, height); 202 203 final Scan scan = new Scan(width, height, image, ret); 204 205 for (int i = 0; i < matchingRegions.size(); i++) { 206 final Polygon from = matchingRegions.get(i).secondObject().asPolygon(); 207 scan.tf = transforms.get(i); 208 209 ScanRasteriser.scanFill(from.points, scan); 210 } 211 212 return ret; 213 } 214 215 private class Scan implements ScanLineListener { 216 private final Pixel p = new Pixel(); 217 private final int xmin = (int) Math.max(0, bounds.x); 218 private final int ymin = (int) Math.max(0, bounds.y); 219 private final int xmax; 220 private final int ymax; 221 private final I image; 222 private final I ret; 223 Matrix tf; 224 225 Scan(int width, int height, I image, I ret) { 226 xmax = (int) Math.min(width, bounds.x + bounds.width); 227 ymax = (int) Math.min(height, bounds.y + bounds.height); 228 this.image = image; 229 this.ret = ret; 230 } 231 232 @Override 233 public void process(int x1, int x2, int y) { 234 if (y < ymin || y > ymax) 235 return; 236 237 final int startx = Math.max(xmin, Math.min(x1, x2)); 238 final int stopx = Math.min(xmax, Math.max(x1, x2)); 239 240 for (int x = startx; x <= stopx; x++) { 241 p.x = x; 242 p.y = y; 243 244 p.transformInplace(tf); 245 246 ret.setPixel(x, y, image.getPixelInterp(p.x, p.y)); 247 } 248 } 249 } 250}