All Downloads are FREE. Search and download functionalities are using the official Maven repository.

boofcv.alg.sfm.d3.VisOdomMonoOverheadMotion2D Maven / Gradle / Ivy

/*
 * Copyright (c) 2011-2017, 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.alg.sfm.d3;

import boofcv.abst.sfm.d2.ImageMotion2D;
import boofcv.alg.misc.GImageMiscOps;
import boofcv.alg.sfm.overhead.CreateSyntheticOverheadView;
import boofcv.alg.sfm.overhead.OverheadView;
import boofcv.alg.sfm.overhead.SelectOverheadParameters;
import boofcv.factory.sfm.FactorySfmMisc;
import boofcv.struct.calib.CameraPinholeRadial;
import boofcv.struct.image.ImageBase;
import boofcv.struct.image.ImageType;
import georegression.struct.se.Se2_F64;
import georegression.struct.se.Se3_F64;
import org.ejml.data.DMatrixRMaj;

/**
 * Estimates the motion of a monocular camera using the known transform between the camera and the ground plane.  The
 * camera's image is converted into an orthogonal overhead view.  Features are tracked inside the overhead image and
 * the 2D rigid body motion found.  The output can be either the 2D motion or the 3D motion of the camera. There is
 * no scale ambiguity since the transform from the plane to camera is known.
 *
 * The advantage of tracking inside the overhead view instead of the camera image is that the overhead view lacks
 * perspective distortion and is crops the image which does not contain the plane.  Features without perspective
 * distortion are easier to track and many false positives are removed by removing many features not on the plane.
 *
 * The plane which is being viewed is defined by the 'planeToCamera' transform.  In the plane's reference frame the
 * plane lies along the x-z axis and contains point (0,0,0).  See {@link CreateSyntheticOverheadView} for more
 * information about the ground plane coordinates and overhead image..
 *
 * @author Peter Abeles
 */
public class VisOdomMonoOverheadMotion2D>
{
	// creates the overhead image
	private CreateSyntheticOverheadView createOverhead;
	// estimates 2D motion inside the overhead image
	private ImageMotion2D motion2D;

	// storage for overhead image
	private OverheadView overhead;

	// selects a reasonable overhead map
	private SelectOverheadParameters selectOverhead;

	// transform from the plane to the camera
	private Se3_F64 planeToCamera;

	// storage for intermediate results
	private Se2_F64 worldToCurr2D = new Se2_F64();
	private Se3_F64 worldToCurrCam3D = new Se3_F64();
	private Se3_F64 worldToCurr3D = new Se3_F64();
	private Se2_F64 temp = new Se2_F64();

	// adjust for offset
	private Se2_F64 origToMap = new Se2_F64();
	private Se2_F64 mapToOrigin = new Se2_F64();

	/**
	 * Configures motion estimation algorithm.
	 *
	 * @param cellSize Size of cells in plane in world units
	 * @param maxCellsPerPixel Specifies minimum resolution of a region in overhead image. A pixel in the camera
	 *                         can't overlap more than this number of map cells.   Higher values allow lower
	 *                         resolution regions.  Try 4.
	 * @param mapHeightFraction Reduce the map height by this fraction to avoid excessive unusable image space.  Set to
	 *                          1.0 to maximize the viewing area and any value less than one to crop it.
	 * @param motion2D Estimates motion inside the overhead image.
	 */
	public VisOdomMonoOverheadMotion2D(double cellSize,
									   double maxCellsPerPixel,
									   double mapHeightFraction ,
									   ImageMotion2D motion2D , ImageType imageType )
	{
		selectOverhead = new SelectOverheadParameters(cellSize,maxCellsPerPixel,mapHeightFraction);
		this.motion2D = motion2D;

		createOverhead = FactorySfmMisc.createOverhead(imageType);

		overhead = new OverheadView<>(imageType.createImage(1, 1), 0, 0, cellSize);
	}

	/**
	 * Camera the camera's intrinsic and extrinsic parameters.  Can be called at any time.
	 * @param intrinsic Intrinsic camera parameters
	 * @param planeToCamera Transform from the plane to camera.
	 */
	public void configureCamera(CameraPinholeRadial intrinsic ,
								Se3_F64 planeToCamera ) {
		this.planeToCamera = planeToCamera;

		if( !selectOverhead.process(intrinsic,planeToCamera) )
			throw new IllegalArgumentException("Can't find a reasonable overhead map.  Can the camera view the plane?");

		overhead.centerX = selectOverhead.getCenterX();
		overhead.centerY = selectOverhead.getCenterY();

		createOverhead.configure(intrinsic,planeToCamera,overhead.centerX,overhead.centerY,overhead.cellSize,
				selectOverhead.getOverheadWidth(),selectOverhead.getOverheadHeight());

		// used to counter act offset in overhead image
		origToMap.set(overhead.centerX,overhead.centerY,0);
		mapToOrigin.set(-overhead.centerX,-overhead.centerY,0);

		// fill it so there aren't any artifacts in the left over
		overhead.image.reshape(selectOverhead.getOverheadWidth(), selectOverhead.getOverheadHeight());
		GImageMiscOps.fill(overhead.image,0);
	}

	/**
	 * Resets the algorithm into its initial state
	 */
	public void reset() {
		motion2D.reset();
	}

	/**
	 * Estimates the motion which the camera undergoes relative to the first frame processed.
	 *
	 * @param image Most recent camera image.
	 * @return true if motion was estimated or false if a fault occurred.  Should reset after a fault.
	 */
	public boolean process( T image ) {
		createOverhead.process(image, overhead.image);

		if( !motion2D.process(overhead.image) ) {
			return false;
		}

		worldToCurr2D.set(motion2D.getFirstToCurrent());
		worldToCurr2D.T.x *= overhead.cellSize;
		worldToCurr2D.T.y *= overhead.cellSize;

		// the true origin is offset from the overhead image which the transform is computed inside of
		origToMap.concat(worldToCurr2D,temp);
		temp.concat(mapToOrigin,worldToCurr2D);

		return true;
	}

	/**
	 * 2D motion.
	 *
	 * @return from world to current frame.
	 */
	public Se2_F64 getWorldToCurr2D() {
		return worldToCurr2D;
	}

	/**
	 * 3D motion.
	 *
	 * @return from world to current frame.
	 */
	public Se3_F64 getWorldToCurr3D() {
		// 2D to 3D coordinates
		worldToCurr3D.getT().set(-worldToCurr2D.T.y,0,worldToCurr2D.T.x);
		DMatrixRMaj R = worldToCurr3D.getR();

		// set rotation around Y axis.
		// Transpose the 2D transform since the rotation are pointing in opposite directions
		R.unsafe_set(0, 0, worldToCurr2D.c);
		R.unsafe_set(0, 2, -worldToCurr2D.s);
		R.unsafe_set(1, 1, 1);
		R.unsafe_set(2, 0, worldToCurr2D.s);
		R.unsafe_set(2, 2, worldToCurr2D.c);

		worldToCurr3D.concat(planeToCamera,worldToCurrCam3D);

		return worldToCurrCam3D;
	}

	/**
	 * Overhead image view
	 */
	public OverheadView getOverhead() {
		return overhead;
	}

	/**
	 * 2D motion algorithm
	 */
	public ImageMotion2D getMotion2D() {
		return motion2D;
	}


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy