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.demos.image;
031
032import java.io.File;
033import java.io.IOException;
034
035import javax.swing.JFrame;
036import javax.swing.JLabel;
037
038import org.openimaj.demos.Demo;
039import org.openimaj.feature.local.list.LocalFeatureList;
040import org.openimaj.feature.local.matcher.FastBasicKeypointMatcher;
041import org.openimaj.feature.local.matcher.consistent.ConsistentLocalFeatureMatcher2d;
042import org.openimaj.image.DisplayUtilities;
043import org.openimaj.image.FImage;
044import org.openimaj.image.ImageUtilities;
045import org.openimaj.image.MBFImage;
046import org.openimaj.image.colour.Transforms;
047import org.openimaj.image.feature.local.engine.DoGSIFTEngine;
048import org.openimaj.image.feature.local.keypoints.Keypoint;
049import org.openimaj.image.processing.resize.ResizeProcessor;
050import org.openimaj.image.processing.transform.ProjectionProcessor;
051import org.openimaj.math.geometry.point.Point2d;
052import org.openimaj.math.geometry.transforms.HomographyModel;
053import org.openimaj.math.geometry.transforms.residuals.SingleImageTransferResidual2d;
054import org.openimaj.math.model.fit.RANSAC;
055
056/**
057 * Demonstrates using the SIFT keypoint matching and automatic homography
058 * transform calculation to project 3 photos into a basic stitched panorama.
059 * 
060 * @author Sina Samangooei (ss@ecs.soton.ac.uk)
061 * 
062 * @created 15 Feb 2012
063 */
064@Demo(
065                author = "Sina Samangooei",
066                description = "Demonstrates using the SIFT keypoint matching and automatic "
067                                + "homography transform calculation to project 3 photos into a basic stitched "
068                                + "panorama.",
069                keywords = { "image", "photo", "mosaic", "stitch", "panorama" },
070                title = "Simple Photo Mosaic",
071                icon = "/org/openimaj/demos/icons/image/mosaic-icon.png",
072                screenshot = "/org/openimaj/demos/screens/image/mosaic-screen.png",
073                vmArguments = "-Xmx1G")
074public class SimpleMosaic
075{
076        /**
077         * Default main
078         * 
079         * @param args
080         *            Command-line arguments
081         * @throws IOException
082         */
083        public static void main(String args[]) throws IOException
084        {
085                final JLabel l = new JLabel();
086                l.setHorizontalAlignment(JLabel.CENTER);
087                final JFrame f = new JFrame("Mosaic Progress");
088                f.getContentPane().add(l);
089                f.setSize(500, 80);
090                f.setVisible(true);
091
092                new Thread(new Runnable()
093                {
094                        @Override
095                        public void run()
096                        {
097                                try
098                                {
099                                        // Set up all the various processors
100                                        l.setText("Setting up processors");
101                                        final ResizeProcessor rp = new ResizeProcessor(800, 600);
102                                        final DoGSIFTEngine engine = new DoGSIFTEngine();
103
104                                        final ConsistentLocalFeatureMatcher2d<Keypoint> matcher =
105                                                        new ConsistentLocalFeatureMatcher2d<Keypoint>(
106                                                                        new FastBasicKeypointMatcher<Keypoint>(8));
107                                        final HomographyModel model = new HomographyModel();
108                                        final RANSAC<Point2d, Point2d, HomographyModel> modelFitting = new RANSAC<Point2d, Point2d, HomographyModel>(
109                                                        model, new SingleImageTransferResidual2d<HomographyModel>(), 8.0, 1600,
110                                                        new RANSAC.BestFitStoppingCondition(),
111                                                        true);
112                                        matcher.setFittingModel(modelFitting);
113
114                                        // Load in the first (middle) image and calculate the SIFT
115                                        // features
116                                        final MBFImage imageMiddle = ImageUtilities.readMBF(SimpleMosaic.class
117                                                        .getResource("/org/openimaj/demos/image/mosaic/trento-view-1.jpg"));
118                                        imageMiddle.processInplace(rp);
119                                        final FImage workingImageMiddle = Transforms.calculateIntensityNTSC(imageMiddle);
120                                        l.setText("Calculating features on middle image");
121                                        final LocalFeatureList<Keypoint> middleKP = engine.findFeatures(workingImageMiddle);
122                                        matcher.setModelFeatures(middleKP);
123
124                                        // Calculate the projection for the first image
125                                        final ProjectionProcessor<Float[], MBFImage> ptp =
126                                                        new ProjectionProcessor<Float[], MBFImage>();
127                                        imageMiddle.accumulateWith(ptp);
128
129                                        // Load in the right-hand image and calculate its features
130                                        final MBFImage imageRight = ImageUtilities.readMBF(SimpleMosaic.class
131                                                        .getResource("/org/openimaj/demos/image/mosaic/trento-view-0.jpg"));
132                                        imageRight.processInplace(rp);
133                                        final FImage workingImageRight = Transforms.calculateIntensityNTSC(imageRight);
134                                        l.setText("Calculating features on right image");
135                                        final LocalFeatureList<Keypoint> rightKP = engine.findFeatures(workingImageRight);
136
137                                        // Find matches with the middle image
138                                        l.setText("Finding matches with middle image and right image");
139                                        matcher.findMatches(rightKP);
140                                        ptp.setMatrix(model.getTransform());
141                                        l.setText("Projecting right image");
142                                        imageRight.accumulateWith(ptp);
143
144                                        // Load in the left-hand image and calculate its features
145                                        final MBFImage imageLeft = ImageUtilities.readMBF(SimpleMosaic.class
146                                                        .getResource("/org/openimaj/demos/image/mosaic/trento-view-2.jpg"));
147                                        imageLeft.processInplace(rp);
148                                        final FImage workingImageLeft = Transforms.calculateIntensityNTSC(imageLeft);
149                                        l.setText("Calculating features on left image");
150                                        final LocalFeatureList<Keypoint> leftKP = engine.findFeatures(workingImageLeft);
151
152                                        // Find the matches with the middle image
153                                        l.setText("Finding matches with middle image and left image");
154                                        matcher.findMatches(leftKP);
155                                        ptp.setMatrix(model.getTransform());
156                                        l.setText("Projecting left image");
157                                        imageLeft.accumulateWith(ptp);
158
159                                        l.setText("Projecting final image");
160                                        final MBFImage projected = ptp.performBlendedProjection(
161                                                        (-imageMiddle.getWidth()),
162                                                        imageMiddle.getWidth() + imageMiddle.getWidth(),
163                                                        -imageMiddle.getHeight() / 2, 3 * imageMiddle.getHeight() / 2,
164                                                        (Float[]) null);
165
166                                        // Display the result
167                                        f.setVisible(false);
168                                        DisplayUtilities.display(projected.process(rp));
169
170                                        // Write the result
171                                        ImageUtilities.write(projected, "png", new File(
172                                                        "/Users/jsh2/Desktop/mosaic.png"));
173                                }
174                                catch (final IOException e)
175                                {
176                                        e.printStackTrace();
177                                }
178                        }
179                }).start();
180        }
181}