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 */ 030/** 031 * 032 */ 033package org.openimaj.image.processing.transform; 034 035import java.util.Collection; 036 037import org.openimaj.image.DisplayUtilities; 038import org.openimaj.image.FImage; 039import org.openimaj.image.MBFImage; 040import org.openimaj.image.analysis.algorithm.HoughLines; 041import org.openimaj.image.colour.RGBColour; 042import org.openimaj.image.processing.edges.CannyEdgeDetector; 043import org.openimaj.image.processing.threshold.OtsuThreshold; 044import org.openimaj.image.processor.ImageProcessor; 045import org.openimaj.image.renderer.MBFImageRenderer; 046import org.openimaj.math.geometry.line.Line2d; 047 048import Jama.Matrix; 049 050/** 051 * Uses the Hough transform (for lines) to attempt to find the skew of the image 052 * and unskews it using a basic skew transform. 053 * 054 * @see "http://javaanpr.sourceforge.net/anpr.pdf" 055 * 056 * @author David Dupplaw (dpd@ecs.soton.ac.uk) 057 * @created 12 Aug 2011 058 * 059 */ 060public class SkewCorrector implements ImageProcessor<FImage> 061{ 062 private static final boolean DEBUG = true; 063 064 /** 065 * Accuracy is a multiplier for the number of degrees in one bin of the 066 * HoughLines transform 067 */ 068 private int accuracy = 1; 069 070 /** 071 * {@inheritDoc} 072 * 073 * @see org.openimaj.image.processor.ImageProcessor#processImage(org.openimaj.image.Image) 074 */ 075 @Override 076 public void processImage(final FImage image) 077 { 078 final CannyEdgeDetector cad = new CannyEdgeDetector(); 079 final FImage edgeImage = image.process(cad).inverse(); 080 081 // Detect Lines in the image 082 final HoughLines hl = new HoughLines(360 * this.accuracy); 083 edgeImage.analyseWith(hl); 084 085 if (SkewCorrector.DEBUG) 086 this.debugLines(edgeImage, Matrix.identity(3, 3), 087 "Detection of Horizontal Lines", 088 hl.getBestLines(2)); 089 090 // --------------------------------------------------------------- 091 // First rotate the image such that the prevailing lines 092 // are horizontal. 093 // --------------------------------------------------------------- 094 // Find the prevailing angle 095 double rotationAngle = hl.calculatePrevailingAngle(); 096 097 FImage rotImg = null; 098 FImage outImg = null; 099 if (rotationAngle == Double.MIN_VALUE) 100 { 101 System.out.println("WARNING: Detection of rotation angle failed."); 102 rotImg = edgeImage.clone(); 103 outImg = image.clone(); 104 } 105 else 106 { 107 rotationAngle -= 90; 108 rotationAngle %= 360; 109 110 if (SkewCorrector.DEBUG) 111 System.out.println("Rotational angle: " + rotationAngle); 112 113 rotationAngle *= 0.0174532925; 114 115 // Rotate so that horizontal lines are horizontal 116 final Matrix rotationMatrix = new Matrix(new double[][] { 117 { Math.cos(-rotationAngle), -Math.sin(-rotationAngle), 0 }, 118 { Math.sin(-rotationAngle), Math.cos(-rotationAngle), 0 }, 119 { 0, 0, 1 } 120 }); 121 122 // We use a projection processor as we need our 123 // background pixels to be white. 124 rotImg = ProjectionProcessor.project(edgeImage, rotationMatrix, 1f). 125 process(new OtsuThreshold()); 126 127 // We need to return a proper image (not the edge image), so we 128 // process that here too. 129 outImg = ProjectionProcessor.project(image, rotationMatrix, 0f); 130 } 131 132 if (SkewCorrector.DEBUG) 133 DisplayUtilities.display(outImg, "Rotated Image"); 134 135 // --------------------------------------------------------------- 136 // Now attempt to make the verticals vertical by shearing 137 // --------------------------------------------------------------- 138 // Re-process with the Hough lines 139 rotImg.analyseWith(hl); 140 141 final float shearAngleRange = 20; 142 143 if (SkewCorrector.DEBUG) 144 this.debugLines(rotImg, Matrix.identity(3, 3), "Detection of Vertical Lines", 145 hl.getBestLines(2, -shearAngleRange, shearAngleRange)); 146 147 // Get the prevailing angle around vertical 148 double shearAngle = hl.calculatePrevailingAngle(-shearAngleRange, shearAngleRange); 149 150 if (shearAngle == Double.MIN_VALUE) 151 { 152 System.out.println("WARNING: Detection of shear angle failed."); 153 } 154 else 155 { 156 shearAngle %= 360; 157 158 if (SkewCorrector.DEBUG) 159 System.out.println("Shear angle = " + shearAngle); 160 161 shearAngle *= 0.0174532925; 162 163 // Create a shear matrix 164 final Matrix shearMatrix = new Matrix(new double[][] { 165 { 1, Math.tan(shearAngle), 0 }, 166 { 0, 1, 0 }, 167 { 0, 0, 1 } 168 }); 169 170 // Process the image to unshear it. 171 // FImage unshearedImage = rotImg.transform( shearMatrix ); 172 outImg = outImg.transform(shearMatrix); 173 } 174 175 if (SkewCorrector.DEBUG) 176 DisplayUtilities.display(outImg, "Final Image"); 177 178 image.internalAssign(outImg); 179 } 180 181 /** 182 * Helper function to display the image with lines 183 * 184 * @param i 185 * @param hl 186 * @param tf 187 * @param title 188 * @param lines 189 */ 190 private void debugLines(final FImage i, final Matrix tf, final String title, 191 final Collection<Line2d> lines) 192 { 193 // Create an image showing where the lines are 194 final MBFImage output = new MBFImage(i.getWidth(), 195 i.getHeight(), 3); 196 final MBFImageRenderer r = output.createRenderer(); // RenderHints.ANTI_ALIASED 197 // ); 198 r.drawImage(i, 0, 0); 199 200 for (final Line2d l : lines) 201 { 202 final Line2d l2 = l.transform(tf).lineWithinSquare(output.getBounds()); 203 204 // l2 can be null if it doesn't intersect with the image 205 if (l2 != null) 206 { 207 System.out.println(l2); 208 r.drawLine(l2, 2, RGBColour.RED); 209 } 210 } 211 212 DisplayUtilities.display(output, title); 213 } 214 215 /** 216 * Set the accuracy of the skew corrector. The value here is a multiplier 217 * for the number of degrees that are in a single bin of the Hough Transform 218 * for lines. The default is 1 which means that the Hough Transform can 219 * detect 360 degrees. If the accuracy is set to 2, the Hough Transform can 220 * detect 720 distinct directional angles (accuracy is half a degree). 221 * 222 * @param accuracy 223 * The accuracy of the skew corrector 224 */ 225 public void setAccuracy(final int accuracy) 226 { 227 this.accuracy = accuracy; 228 } 229}