/*
 * Copyright (c) 2011-2016, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package boofcv.demonstrations.sfm.d2;

import boofcv.abst.feature.tracker.PointTracker;
import boofcv.abst.sfm.AccessPointTracks;
import boofcv.abst.sfm.d2.ImageMotion2D;
import boofcv.abst.sfm.d2.PlToGrayMotion2D;
import boofcv.alg.sfm.d2.StitchingFromMotion2D;
import boofcv.factory.sfm.FactoryMotion2D;
import boofcv.gui.VideoProcessAppBase;
import boofcv.gui.VisualizeApp;
import boofcv.io.image.ConvertBufferedImage;
import boofcv.io.image.SimpleImageSequence;
import boofcv.struct.image.ImageBase;
import boofcv.struct.image.ImageType;
import georegression.struct.InvertibleTransform;
import georegression.struct.homography.Homography2D_F64;
import georegression.struct.point.Point2D_F64;

import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

/**
 * Parent class for applications which estimate image motion based upon fit parameters to a model on extracted
 * point features.  Only gray scale images are processed, but the output can be in gray scale or color.
 *
 * @author Peter Abeles
 */
public abstract class VideoStitchBaseApp
		extends VideoProcessAppBase implements VisualizeApp
{
	// size of input image
	int inputWidth,inputHeight;

	// size of the image being stitched into
	int stitchWidth;
	int stitchHeight;

	// show a rectangle around the view be shown
	boolean showImageView;

	int borderTolerance = 30;

	// data type which is being fit
	IT fitModel;

	// tracks feature in the video stream
	protected PointTracker tracker;

	BufferedImage stitchOut;

	StitchingFromMotion2D alg;

	StitchingFromMotion2D.Corners corners;

	// number of times stitching has failed and it was reset
	int totalResets;

	private final static int maxIterations = 100;
	private final static int pruneThreshold = 10;

	boolean hasProcessedImage = false;

	protected Motion2DPanel gui;
	protected ImageMotionInfoPanel infoPanel = new ImageMotionInfoPanel();

	// stabilization parameters
	protected int absoluteMinimumTracks;
	protected double respawnTrackFraction;
	protected double respawnCoverageFraction;
	protected double maxJumpFraction;
	protected double inlierThreshold;

	public VideoStitchBaseApp(int numAlgFamilies,
							  Class imageType,
							  boolean color ,
							  Motion2DPanel gui) {
		super(numAlgFamilies, color ? ImageType.pl(3, imageType) : ImageType.single(imageType));

		this.gui = gui;
		gui.addMouseListener(this);
		setMainGUI(gui);

		infoPanel.setMaximumSize(infoPanel.getPreferredSize());
		add(infoPanel, BorderLayout.WEST);
	}

	protected void setStitchImageSize( int width , int height ) {
		this.stitchWidth = width;
		this.stitchHeight = height;

		stitchOut = new BufferedImage(stitchWidth, stitchHeight,BufferedImage.TYPE_INT_RGB);
	}

	protected StitchingFromMotion2D createAlgorithm( PointTracker tracker ) {

		if( imageType.getFamily() == ImageType.Family.PLANAR) {
			Class imageType = this.imageType.getImageClass();

			ImageMotion2D motion = FactoryMotion2D.createMotion2D(maxIterations,inlierThreshold,2,absoluteMinimumTracks,
					respawnTrackFraction,respawnCoverageFraction,false,tracker,fitModel);

			ImageMotion2D motion2DColor = new PlToGrayMotion2D(motion,imageType);

			return FactoryMotion2D.createVideoStitchMS(maxJumpFraction,motion2DColor,imageType);
		} else {
			ImageMotion2D motion = FactoryMotion2D.createMotion2D(maxIterations,inlierThreshold,2,absoluteMinimumTracks,
					respawnTrackFraction,respawnCoverageFraction,false,tracker,fitModel);

			return FactoryMotion2D.createVideoStitch(maxJumpFraction,motion, imageType.getImageClass());
		}
	}

	@Override
	public void loadConfigurationFile(String fileName) {}

	@Override
	public boolean getHasProcessedImage() {
		return alg != null;
	}

	@Override
	protected void process(SimpleImageSequence sequence) {
		if( !sequence.hasNext() )
			return;
		// stop the image processing code
		stopWorker();

		this.sequence = sequence;
		sequence.setLoop(true);

		// save the input image dimension
		I input = sequence.next();
		inputWidth = input.width;
		inputHeight = input.height;

		// start everything up and resume processing
		doRefreshAll();
	}

	@Override
	protected void updateAlg(I frame, BufferedImage buffImage) {
		if( alg == null )
			return;

		if( !alg.process(frame) ) {
			alg.reset();
			totalResets++;
		}

		hasProcessedImage = true;
	}

	@Override
	protected void updateAlgGUI(I frame, BufferedImage imageGUI, final double fps) {
		if( !hasProcessedImage )
			return;

		corners = alg.getImageCorners(frame.width,frame.height,null);
		ConvertBufferedImage.convertTo(alg.getStitchedImage(), stitchOut,true);

		if( checkLocation(corners) ) {
			// the change will only be visible in the next update
			alg.setOriginToCurrent();
		}

		AccessPointTracks access = (AccessPointTracks)alg.getMotion();

		List tracks = access.getAllTracks();
		List inliers = new ArrayList();

		for( int i = 0; i < tracks.size(); i++ ) {
			if( access.isInlier(i) )
				inliers.add( tracks.get(i) );
		}

		final int numInliers = inliers.size();
		final int numFeatures = tracks.size();

		showImageView = infoPanel.getShowView();

		gui.setImages(imageGUI, stitchOut);
		gui.setShowImageView(infoPanel.getShowView());
		gui.setCorners(corners);

		// toggle on and off showing the active tracks
		if( infoPanel.getShowInliers())
			gui.setInliers(inliers);
		else
			gui.setInliers(null);
		if( infoPanel.getShowAll())
			gui.setAllTracks(tracks);
		else
			gui.setAllTracks(null);

		Homography2D_F64 H = alg.getWorldToCurr(null).invert(null);
		gui.setCurrToWorld(H);

		// update GUI
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				// update GUI
				infoPanel.setFPS(fps);
				infoPanel.setNumInliers(numInliers);
				infoPanel.setNumTracks(numFeatures);
				infoPanel.setKeyFrames(totalResets);
				infoPanel.repaint();

				gui.repaint();
			}
		});
	}

	@Override
	public void refreshAll(Object[] cookies) {
		stopWorker();

		tracker = (PointTracker)cookies[0];
		fitModel = (IT)cookies[1];

		startEverything();
	}

	@Override
	public void setActiveAlgorithm(int indexFamily, String name, Object cookie) {
		if( sequence == null )
			return;

		stopWorker();

		switch( indexFamily ) {
			case 0:
				tracker = (PointTracker)cookie;
				break;

			case 1:
				fitModel = (IT)cookie;
				break;
		}

		// restart the video
		sequence.reset();

		startEverything();
	}

	protected void startEverything() {
		// make sure there is nothing left over from before
		tracker.dropAllTracks();
		alg = createAlgorithm(tracker);
		init(inputWidth,inputHeight);
		totalResets = 0;

		startWorkerThread();
	}

	protected abstract void init( int inputWidth , int inputHeight );

	/**
	 * Checks the location of the stitched region and decides if its location should be reset
	 */
	protected abstract boolean checkLocation( StitchingFromMotion2D.Corners corners );
}

 

Ads help maintain this website.