|
| 1 | +package it.polito.teaching.cv; |
| 2 | + |
| 3 | +import java.io.ByteArrayInputStream; |
| 4 | +import java.io.File; |
| 5 | +import java.util.ArrayList; |
| 6 | +import java.util.List; |
| 7 | + |
| 8 | +import org.opencv.core.Core; |
| 9 | +import org.opencv.core.CvType; |
| 10 | +import org.opencv.core.Mat; |
| 11 | +import org.opencv.core.MatOfByte; |
| 12 | +import org.opencv.core.Rect; |
| 13 | +import org.opencv.core.Scalar; |
| 14 | +import org.opencv.highgui.Highgui; |
| 15 | +import org.opencv.imgproc.Imgproc; |
| 16 | + |
| 17 | +import javafx.fxml.FXML; |
| 18 | +import javafx.scene.control.Button; |
| 19 | +import javafx.scene.image.Image; |
| 20 | +import javafx.scene.image.ImageView; |
| 21 | +import javafx.stage.FileChooser; |
| 22 | +import javafx.stage.Stage; |
| 23 | + |
| 24 | +/** |
| 25 | + * The controller associated to the only view of our application. The |
| 26 | + * application logic is implemented here. It handles the button for opening an |
| 27 | + * image and perform all the operation related to the Fourier transformation and |
| 28 | + * antitransformation. |
| 29 | + * |
| 30 | + * @author <a href="mailto:luigi.derussis@polito.it">Luigi De Russis</a> |
| 31 | + * @since 2013-12-11 |
| 32 | + * |
| 33 | + */ |
| 34 | +public class FourierController |
| 35 | +{ |
| 36 | + // images to show in the view |
| 37 | + @FXML |
| 38 | + private ImageView originalImage; |
| 39 | + @FXML |
| 40 | + private ImageView transformedImage; |
| 41 | + @FXML |
| 42 | + private ImageView antitransformedImage; |
| 43 | + // a FXML button for performing the transformation |
| 44 | + @FXML |
| 45 | + private Button transformButton; |
| 46 | + // a FXML button for performing the antitransformation |
| 47 | + @FXML |
| 48 | + private Button antitransformButton; |
| 49 | + |
| 50 | + // the main stage |
| 51 | + private Stage stage; |
| 52 | + // the JavaFX file chooser |
| 53 | + private FileChooser fileChooser; |
| 54 | + // support variables |
| 55 | + private Mat image; |
| 56 | + private List<Mat> planes; |
| 57 | + // the final complex image |
| 58 | + private Mat complexImage; |
| 59 | + |
| 60 | + /** |
| 61 | + * Init the needed variables |
| 62 | + */ |
| 63 | + protected void init() |
| 64 | + { |
| 65 | + this.fileChooser = new FileChooser(); |
| 66 | + this.image = new Mat(); |
| 67 | + this.planes = new ArrayList<>(); |
| 68 | + this.complexImage = new Mat(); |
| 69 | + } |
| 70 | + |
| 71 | + /** |
| 72 | + * Load an image from disk |
| 73 | + */ |
| 74 | + @FXML |
| 75 | + protected void loadImage() |
| 76 | + { |
| 77 | + // show the open dialog window |
| 78 | + File file = this.fileChooser.showOpenDialog(this.stage); |
| 79 | + if (file != null) |
| 80 | + { |
| 81 | + // read the image in gray scale |
| 82 | + this.image = Highgui.imread(file.getAbsolutePath(), Highgui.CV_LOAD_IMAGE_GRAYSCALE); |
| 83 | + // show the image |
| 84 | + this.originalImage.setImage(this.mat2Image(this.image)); |
| 85 | + // set a fixed width |
| 86 | + this.originalImage.setFitWidth(250); |
| 87 | + // preserve image ratio |
| 88 | + this.originalImage.setPreserveRatio(true); |
| 89 | + // update the UI |
| 90 | + this.transformButton.setDisable(false); |
| 91 | + |
| 92 | + // empty the image planes and the image views if it is not the first |
| 93 | + // loaded image |
| 94 | + if (!this.planes.isEmpty()) |
| 95 | + { |
| 96 | + this.planes.clear(); |
| 97 | + this.transformedImage.setImage(null); |
| 98 | + this.antitransformedImage.setImage(null); |
| 99 | + } |
| 100 | + |
| 101 | + } |
| 102 | + } |
| 103 | + |
| 104 | + /** |
| 105 | + * The action triggered by pushing the button for apply the dft to the |
| 106 | + * loaded image |
| 107 | + */ |
| 108 | + @FXML |
| 109 | + protected void transformImage() |
| 110 | + { |
| 111 | + // optimize the dimension of the loaded image |
| 112 | + Mat padded = this.optimizeImageDim(this.image); |
| 113 | + padded.convertTo(padded, CvType.CV_32F); |
| 114 | + // prepare the image planes to obtain the complex image |
| 115 | + this.planes.add(padded); |
| 116 | + this.planes.add(Mat.zeros(padded.size(), CvType.CV_32F)); |
| 117 | + // prepare a complex image for performing the dft |
| 118 | + Core.merge(this.planes, this.complexImage); |
| 119 | + |
| 120 | + // dft |
| 121 | + Core.dft(this.complexImage, this.complexImage); |
| 122 | + |
| 123 | + // optimize the image resulting from the dft operation |
| 124 | + Mat magnitude = this.createOptimizedMagnitude(this.complexImage); |
| 125 | + |
| 126 | + // show the result of the transformation as an image |
| 127 | + this.transformedImage.setImage(this.mat2Image(magnitude)); |
| 128 | + // set a fixed width |
| 129 | + this.transformedImage.setFitWidth(250); |
| 130 | + // preserve image ratio |
| 131 | + this.transformedImage.setPreserveRatio(true); |
| 132 | + |
| 133 | + // enable the button for performing the antitransformation |
| 134 | + this.antitransformButton.setDisable(false); |
| 135 | + // disable the button for applying the dft |
| 136 | + this.transformButton.setDisable(true); |
| 137 | + } |
| 138 | + |
| 139 | + /** |
| 140 | + * The action triggered by pushing the button for apply the inverse dft to |
| 141 | + * the loaded image |
| 142 | + */ |
| 143 | + @FXML |
| 144 | + protected void antitransformImage() |
| 145 | + { |
| 146 | + Core.idft(this.complexImage, this.complexImage); |
| 147 | + |
| 148 | + Mat restoredImage = new Mat(); |
| 149 | + Core.split(this.complexImage, this.planes); |
| 150 | + Core.normalize(this.planes.get(0), restoredImage, 0, 255, Core.NORM_MINMAX); |
| 151 | + |
| 152 | + this.antitransformedImage.setImage(this.mat2Image(restoredImage)); |
| 153 | + // set a fixed width |
| 154 | + this.antitransformedImage.setFitWidth(250); |
| 155 | + // preserve image ratio |
| 156 | + this.antitransformedImage.setPreserveRatio(true); |
| 157 | + |
| 158 | + // disable the button for performing the antitransformation |
| 159 | + this.antitransformButton.setDisable(true); |
| 160 | + } |
| 161 | + |
| 162 | + /** |
| 163 | + * Optimize the image dimensions |
| 164 | + * |
| 165 | + * @param image |
| 166 | + * the {@link Mat} to optimize |
| 167 | + * @return the image whose dimensions have been optimized |
| 168 | + */ |
| 169 | + private Mat optimizeImageDim(Mat image) |
| 170 | + { |
| 171 | + // init |
| 172 | + Mat padded = new Mat(); |
| 173 | + // get the optimal rows size for dft |
| 174 | + int addPixelRows = Core.getOptimalDFTSize(image.rows()); |
| 175 | + // get the optimal cols size for dft |
| 176 | + int addPixelCols = Core.getOptimalDFTSize(image.cols()); |
| 177 | + // apply the optimal cols and rows size to the image |
| 178 | + Imgproc.copyMakeBorder(image, padded, 0, addPixelRows - image.rows(), 0, addPixelCols - image.cols(), |
| 179 | + Imgproc.BORDER_CONSTANT, Scalar.all(0)); |
| 180 | + |
| 181 | + return padded; |
| 182 | + } |
| 183 | + |
| 184 | + /** |
| 185 | + * Optimize the magnitude of the complex image obtained from the DFT, to |
| 186 | + * improve its visualization |
| 187 | + * |
| 188 | + * @param complexImage |
| 189 | + * the complex image obtained from the DFT |
| 190 | + * @return the optimized image |
| 191 | + */ |
| 192 | + private Mat createOptimizedMagnitude(Mat complexImage) |
| 193 | + { |
| 194 | + // init |
| 195 | + List<Mat> newPlanes = new ArrayList<>(); |
| 196 | + Mat mag = new Mat(); |
| 197 | + // split the comples image in two planes |
| 198 | + Core.split(complexImage, newPlanes); |
| 199 | + // compute the magnitude |
| 200 | + Core.magnitude(newPlanes.get(0), newPlanes.get(1), mag); |
| 201 | + |
| 202 | + // move to a logarithmic scale |
| 203 | + Core.add(mag, Scalar.all(1), mag); |
| 204 | + Core.log(mag, mag); |
| 205 | + // optionally reorder the 4 quadrants of the magnitude image |
| 206 | + this.shiftDFT(mag); |
| 207 | + // normalize the magnitude image for the visualization since both JavaFX |
| 208 | + // and OpenCV need images with value between 0 and 255 |
| 209 | + Core.normalize(mag, mag, 0, 255, Core.NORM_MINMAX); |
| 210 | + |
| 211 | + // you can also write on disk the resulting image... |
| 212 | + // Highgui.imwrite("../magnitude.png", mag); |
| 213 | + |
| 214 | + return mag; |
| 215 | + } |
| 216 | + |
| 217 | + /** |
| 218 | + * Reorder the 4 quadrants of the image representing the magnitude, after |
| 219 | + * the DFT |
| 220 | + * |
| 221 | + * @param image |
| 222 | + * the {@link Mat} object whose quadrants are to reorder |
| 223 | + */ |
| 224 | + private void shiftDFT(Mat image) |
| 225 | + { |
| 226 | + image = image.submat(new Rect(0, 0, image.cols() & -2, image.rows() & -2)); |
| 227 | + int cx = image.cols() / 2; |
| 228 | + int cy = image.rows() / 2; |
| 229 | + |
| 230 | + Mat q0 = new Mat(image, new Rect(0, 0, cx, cy)); |
| 231 | + Mat q1 = new Mat(image, new Rect(cx, 0, cx, cy)); |
| 232 | + Mat q2 = new Mat(image, new Rect(0, cy, cx, cy)); |
| 233 | + Mat q3 = new Mat(image, new Rect(cx, cy, cx, cy)); |
| 234 | + |
| 235 | + Mat tmp = new Mat(); |
| 236 | + q0.copyTo(tmp); |
| 237 | + q3.copyTo(q0); |
| 238 | + tmp.copyTo(q3); |
| 239 | + |
| 240 | + q1.copyTo(tmp); |
| 241 | + q2.copyTo(q1); |
| 242 | + tmp.copyTo(q2); |
| 243 | + } |
| 244 | + |
| 245 | + /** |
| 246 | + * Set the current stage (needed for the FileChooser modal window) |
| 247 | + * |
| 248 | + * @param stage |
| 249 | + * the stage |
| 250 | + */ |
| 251 | + public void setStage(Stage stage) |
| 252 | + { |
| 253 | + this.stage = stage; |
| 254 | + } |
| 255 | + |
| 256 | + /** |
| 257 | + * Convert a Mat object (OpenCV) in the corresponding Image for JavaFX |
| 258 | + * |
| 259 | + * @param frame |
| 260 | + * the {@link Mat} representing the current frame |
| 261 | + * @return the {@link Image} to show |
| 262 | + */ |
| 263 | + private Image mat2Image(Mat frame) |
| 264 | + { |
| 265 | + // create a temporary buffer |
| 266 | + MatOfByte buffer = new MatOfByte(); |
| 267 | + // encode the frame in the buffer, according to the PNG format |
| 268 | + Highgui.imencode(".png", frame, buffer); |
| 269 | + // build and return an Image created from the image encoded in the |
| 270 | + // buffer |
| 271 | + return new Image(new ByteArrayInputStream(buffer.toArray())); |
| 272 | + } |
| 273 | +} |
0 commit comments