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

boofcv.abst.geo.bundle.SceneStructureMetric Maven / Gradle / Ivy

Go to download

BoofCV is an open source Java library for real-time computer vision and robotics applications.

There is a newer version: 1.1.5
Show newest version
/*
 * Copyright (c) 2023, 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.abst.geo.bundle;

import boofcv.misc.BoofMiscOps;
import georegression.struct.point.Point2D_F64;
import georegression.struct.point.Point3D_F64;
import georegression.struct.point.Point4D_F64;
import georegression.struct.se.Se3_F64;
import georegression.transform.se.SePointOps_F64;
import lombok.Getter;
import org.ddogleg.struct.DogArray;
import org.jetbrains.annotations.Nullable;

import java.util.Objects;

import static boofcv.misc.BoofMiscOps.checkTrue;

/**
 * Specifies a metric (calibrated) scene for optimizing using {@link BundleAdjustment}.
 * It specifies the relationships between cameras, views, and points. A camera projects a 3D point onto the image plane.
 * Examples of specific models include pinhole and fisheye. A view is a set of observed features from a specific
 * camera image. Views have an associated camera and specify the pose of the camera when the scene was viewed. Points
 * describe the scene's 3D structure.
 *
 * Points belonging to the general scene and rigid objects have two different sets of ID's.
 *
 * @author Peter Abeles
 */
public class SceneStructureMetric extends SceneStructureCommon {

	/** List of views. A view is composed of a camera model and it's pose. */
	@Getter public final DogArray views = new DogArray<>(View::new, View::reset);

	/** List of motions for the views that specifies their spatial relationship */
	@Getter public final DogArray motions = new DogArray<>(Motion::new, Motion::reset);

	/** List of rigid objects. A rigid object is a group of 3D points that have a known relationship with each other. */
	@Getter public final DogArray rigids = new DogArray<>(Rigid::new);

	// Lookup table from rigid point to rigid object
	public int[] lookupRigid = new int[0];

	/**
	 * Configure bundle adjustment
	 *
	 * @param homogenous if true then homogeneous coordinates are used
	 */
	public SceneStructureMetric( boolean homogenous ) {
		super(homogenous);
	}

	/**
	 * Call this function first. Specifies number of each type of data which is available.
	 *
	 * @param totalCameras Number of cameras
	 * @param totalViews Number of views
	 * @param totalPoints Number of points
	 */
	public void initialize( int totalCameras, int totalViews, int totalPoints ) {
		initialize(totalCameras, totalViews, totalViews, totalPoints, 0);
	}

	/**
	 * Call this function first. Specifies number of each type of data which is available.
	 *
	 * @param totalCameras Number of cameras
	 * @param totalViews Number of views
	 * @param totalMotions Number of motions
	 * @param totalPoints Number of points
	 * @param totalRigid Number of rigid objects
	 */
	public void initialize( int totalCameras, int totalViews, int totalMotions, int totalPoints, int totalRigid ) {
		// Declare enough memory to store all the motions, but the way motions are constructed they are grown
		motions.resize(totalMotions).reset();

		// Reset first so that when it resizes it will call reset() on each object
		cameras.reset().resize(totalCameras);
		views.reset().resize(totalViews);
		points.reset().resize(totalPoints);
		rigids.reset().resize(totalRigid);

		// forget old assignments
		lookupRigid = new int[0];
	}

	/**
	 * Assigns an ID to all rigid points. Call this function after the structure of all the rigid objects has
	 * been specified. You must call this before you assign observations to rigid bodies,
	 */
	public void assignIDsToRigidPoints() {
		// return if it has already been assigned
		if (lookupRigid.length != 0)
			return;
		// Assign a unique ID to each point belonging to a rigid object
		// at the same time create a look up table that allows for the object that a point belongs to be quickly found
		lookupRigid = new int[getTotalRigidPoints()];
		int pointID = 0;
		for (int i = 0; i < rigids.size; i++) {
			Rigid r = rigids.data[i];
			r.indexFirst = pointID;
			for (int j = 0; j < r.points.length; j++, pointID++) {
				lookupRigid[pointID] = i;
			}
		}
	}

	/**
	 * Returns SE3 relationship to the specified view from its parent.
	 *
	 * @param view The target view
	 * @return SE3 transform from parent to view.
	 */
	public Se3_F64 getParentToView( View view ) {
		return motions.get(view.parent_to_view).parent_to_view;
	}

	/**
	 * Returns SE3 relationship to the specified view from its parent.
	 *
	 * @param viewIdx Index of target view
	 * @return SE3 transform from parent to view.
	 */
	public Se3_F64 getParentToView( int viewIdx ) {
		View v = views.get(viewIdx);
		return motions.get(v.parent_to_view).parent_to_view;
	}

	/**
	 * Computes and returns the transform from world to view. If the view is relative then the chain
	 * is followed
	 *
	 * @param view The target view
	 * @param world_to_view Storage for the found transform. Can be null.
	 * @param tmp Optional workspace to avoid creating new memory. Can be null;
	 * @return SE3 transform from parent to view.
	 */
	public Se3_F64 getWorldToView( View view,
								   @Nullable Se3_F64 world_to_view,
								   @Nullable Se3_F64 tmp ) {
		if (world_to_view == null)
			world_to_view = new Se3_F64();

		if (tmp == null)
			tmp = new Se3_F64();

		world_to_view.setTo(getParentToView(view));

		while (view.parent != null) {
			view = view.parent;
			Se3_F64 parent_to_view = getParentToView(view);

			// world_to_view is really view_to_child
			parent_to_view.concat(world_to_view, tmp);
			world_to_view.setTo(tmp);
		}

		return world_to_view;
	}

	/**
	 * Computes and returns the transform from view1 to view2
	 *
	 * @param view1 First view
	 * @param view2 Second view
	 * @return SE3 transform from view1 to view2.
	 */
	public Se3_F64 getViewToView( View view1, View view2 ) {
		var world_to_view1 = new Se3_F64();
		var world_to_view2 = new Se3_F64();
		var tmp = new Se3_F64();

		getWorldToView(view1, world_to_view1, tmp);
		getWorldToView(view2, world_to_view2, tmp);

		world_to_view1.invertConcat(world_to_view2, tmp);
		return tmp;
	}

	/**
	 * Computes and returns the transform from view1 to view2. See {@link #getViewToView(View, View)}.
	 */
	public Se3_F64 getViewToView( int view1, int view2 ) {
		return getViewToView(views.get(view1), views.get(view2));
	}

	/**
	 * Returns true if the scene contains rigid objects
	 */
	public boolean hasRigid() {
		return rigids.size > 0;
	}

	/**
	 * Specifies the spacial transform for a view and assumes the parent is the world frame.
	 *
	 * @param viewIndex Which view is being specified.
	 * @param cameraIndex Index of camera model
	 * @param known If these parameters are fixed or not
	 * @param world_to_view The transform from world to view reference frames. Internal copy is saved.
	 */
	public void setView( int viewIndex, int cameraIndex, boolean known, Se3_F64 world_to_view ) {
		views.get(viewIndex).camera = cameraIndex;
		views.get(viewIndex).parent_to_view = addMotion(known, world_to_view);
		views.get(viewIndex).parent = null;
	}

	/**
	 * Specifies the spacial transform for a view and assumes the parent is the world frame.
	 *
	 * @param viewIndex Which view is being specified.
	 * @param cameraIndex Index of camera model
	 * @param known If the parameters are known and not optimized or unknown and optimized
	 * @param parent_to_view The transform from parent to view reference frames. Internal copy is saved.
	 * @param parent ID / index of the parent this this view is relative to
	 */
	public void setView( int viewIndex, int cameraIndex, boolean known, Se3_F64 parent_to_view, int parent ) {
		checkTrue(parent < viewIndex, "Parent must be less than viewIndex");
		views.get(viewIndex).camera = cameraIndex;
		views.get(viewIndex).parent_to_view = addMotion(known, parent_to_view);
		views.get(viewIndex).parent = parent >= 0 ? views.get(parent) : null;
	}

	/**
	 * Specifies which motion is attached to a view
	 *
	 * @param viewIndex Which view is being specified.
	 * @param cameraIndex Index of camera model
	 * @param motionIndex The motion that describes the parent_to_view relationship
	 * @param parent ID / index of the parent this this view is relative to
	 */
	public void setView( int viewIndex, int cameraIndex, int motionIndex, int parent ) {
		checkTrue(parent < viewIndex, "Parent must be less than viewIndex");
		views.get(viewIndex).camera = cameraIndex;
		views.get(viewIndex).parent_to_view = motionIndex;
		views.get(viewIndex).parent = parent >= 0 ? views.get(parent) : null;
	}

	/**
	 * Specifies a new motion.
	 *
	 * @param known If the parameters are known and not optimized or unknown and optimized
	 * @param parentToView Specifies motion from parent to view reference frames
	 * @return Index or ID for the created motion
	 */
	public int addMotion( boolean known, Se3_F64 parentToView ) {
		int index = motions.size;
		Motion m = motions.grow();
		m.known = known;
		m.parent_to_view.setTo(parentToView);
		return index;
	}

	/**
	 * Declares the data structure for a rigid object. Location of points are set by accessing the object directly.
	 * Rigid objects are useful in known scenes with calibration targets.
	 *
	 * @param which Index of rigid object
	 * @param known If the parameters are known and not optimized or unknown and optimized
	 * @param objectToWorld Initial estimated location of rigid object
	 * @param totalPoints Total number of points attached to this rigid object
	 */
	public void setRigid( int which, boolean known, Se3_F64 objectToWorld, int totalPoints ) {
		Rigid r = rigids.data[which];
		r.init(totalPoints, pointSize);
		r.known = known;
		r.object_to_world.setTo(objectToWorld);
	}

	/**
	 * Specifies that the view uses the specified camera
	 *
	 * @param viewIndex index of view
	 * @param cameraIndex index of camera
	 */
	public void connectViewToCamera( int viewIndex, int cameraIndex ) {
		if (views.get(viewIndex).camera != -1)
			throw new RuntimeException("View has already been assigned a camera");
		views.get(viewIndex).camera = cameraIndex;
	}

	/**
	 * Returns the number of cameras with parameters that are not fixed
	 *
	 * @return non-fixed camera count
	 */
	public int getUnknownCameraCount() {
		int total = 0;
		for (int i = 0; i < cameras.size; i++) {
			if (!cameras.data[i].known) {
				total++;
			}
		}
		return total;
	}

	/**
	 * Returns the number of motions with parameters that are not fixed
	 *
	 * @return non-fixed view count
	 */
	public int getUnknownMotionCount() {
		int total = 0;
		for (int i = 0; i < motions.size; i++) {
			if (!motions.get(i).known) {
				total++;
			}
		}
		return total;
	}

	/**
	 * Returns the number of view with parameters that are not fixed
	 *
	 * @return non-fixed view count
	 */
	public int getUnknownRigidCount() {
		int total = 0;
		for (int i = 0; i < rigids.size; i++) {
			if (!rigids.data[i].known) {
				total++;
			}
		}
		return total;
	}

	/**
	 * Returns total number of points associated with rigid objects.
	 */
	public int getTotalRigidPoints() {
		int total = 0;
		for (int i = 0; i < rigids.size; i++) {
			total += rigids.data[i].points.length;
		}
		return total;
	}

	/**
	 * Projects the requested point onto the requested view and computes its predicted pixel coordinates.
	 *
	 * @param pointIdx (Input) index of point
	 * @param viewIdx (Input) index of view
	 * @param world_to_view (Output) Internal work space
	 * @param tmpSE (Output) Internal work space
	 * @param tmpX (Output) Internal work space
	 * @param pixel (Output) The projected pixel
	 * @return true if it's in front of the camera or false if it's behind
	 */
	public boolean projectToPixel( int pointIdx, int viewIdx,
								   @Nullable BundleCameraState cameraState,
								   Se3_F64 world_to_view, Se3_F64 tmpSE,
								   Point3D_F64 tmpX, Point2D_F64 pixel ) {

		// Get the coordinate transform
		View view = views.get(viewIdx);
		getWorldToView(view, world_to_view, tmpSE);

		// extract the coordinate for 3D and homogenous case
		Point p = points.get(pointIdx);
		double x, y, z, w;
		x = p.coordinate[0];
		y = p.coordinate[1];
		z = p.coordinate[2];
		w = homogenous ? p.coordinate[3] : 1.0;

		// Project the pixel while being careful of points at infinity
		BundleAdjustmentCamera camera = Objects.requireNonNull(getViewCamera(view).model);
		if (cameraState != null)
			camera.setCameraState(cameraState);
		SePointOps_F64.transformV(world_to_view, x, y, z, w, tmpX);
		camera.project(tmpX.x, tmpX.y, tmpX.z, pixel);

		// if the sign is positive then it's in front
		return z*w > 0.0;
	}

	/**
	 * Returns the camera for this particular view
	 */
	public Camera getViewCamera( View v ) {
		return cameras.get(v.camera);
	}

	/**
	 * Returns the camera for this particular view
	 */
	public Camera getViewCamera( int viewIdx ) {
		return cameras.get(views.get(viewIdx).camera);
	}

	/**
	 * Returns the total number of parameters which will be optimised
	 *
	 * @return number of parameters
	 */
	@Override
	public int getParameterCount() {
		return getUnknownMotionCount()*6 + getUnknownRigidCount()*6 + points.size*pointSize + getUnknownCameraParameterCount();
	}

	/**
	 * Returns the specified {@link Rigid}.
	 */
	public Rigid getRigid(int rigidIdx ) {
		return rigids.get(rigidIdx);
	}

	/**
	 * Checks to see if the passed in scene is identical to "this" scene. Floating point values are checked to within
	 * tolerance
	 *
	 * @return true if identical to within specified float tolerance and false if not
	 */
	public boolean isIdentical( SceneStructureMetric m, double tol ) {
		if (isHomogenous() != m.isHomogenous())
			return false;
		if (views.size != m.views.size)
			return false;
		if (motions.size != m.motions.size)
			return false;
		if (rigids.size != m.rigids.size)
			return false;
		if (cameras.size != m.cameras.size)
			return false;
		if (points.size != m.points.size)
			return false;

		for (int i = 0; i < views.size; i++) {
			if (!views.get(i).isIdentical(m.views.get(i)))
				return false;
		}

		for (int i = 0; i < motions.size; i++) {
			if (!motions.get(i).isIdentical(m.motions.get(i), tol))
				return false;
		}

		for (int i = 0; i < rigids.size; i++) {
			if (!rigids.get(i).isIdentical(m.rigids.get(i), tol))
				return false;
		}

		for (int i = 0; i < cameras.size; i++) {
			if (!cameras.get(i).isIdentical(m.cameras.get(i), tol))
				return false;
		}

		for (int i = 0; i < points.size; i++) {
			if (points.get(i).distance(m.points.get(i)) > tol)
				return false;
		}

		return true;
	}

	/**
	 * Describes a view from a camera at a particular point. References are provided to the data structures which
	 * provide the intrinsic and extrinsic parameters. A view contains no parameters that are optimized directly
	 * but instead describes the graph's structure.
	 */
	public static class View {
		/** Which motion specifies the transform from parent to this view's reference frame */
		public int parent_to_view = -1;
		/** The camera associated with this view */
		public int camera = -1;
		/** Points to the parent view or null if world is parent. Parent must be less than this view's ID. */
		public @Nullable View parent;

		public void reset() {
			parent_to_view = -1;
			camera = -1;
			parent = null;
		}

		public boolean isIdentical( View m ) {
			if (parent_to_view != m.parent_to_view)
				return false;
			if (camera != m.camera)
				return false;
			if (parent == null) {
				return m.parent == null;
			} else if (m.parent == null)
				return false;

			// NOTE: Can't check to see if the parent is the same because that requires knowing the view index
			return true;
		}
	}

	/**
	 * Describes the relationship between two views. Multiple pairs of views can reference the same motion. This
	 * is useful if a set of cameras are rigidly mounted, e.g. stereo cameras.
	 */
	public static class Motion {
		/** If the parameters are assumed to be known and should not be optimised. */
		public boolean known;
		/** Transform from parent view into this view */
		public final Se3_F64 parent_to_view = new Se3_F64();

		public void reset() {
			known = true;
			parent_to_view.reset();
		}

		public boolean isIdentical( Motion m, double tol ) {
			if (known != m.known)
				return false;
			if (parent_to_view.T.distance(m.parent_to_view.T) > tol)
				return false;
			for (int i = 0; i < 9; i++) {
				if (Math.abs(parent_to_view.R.data[i] - m.parent_to_view.R.data[i]) > tol)
					return false;
			}
			return true;
		}
	}

	/**
	 * A set of connected points that form a rigid structure. The 3D location of points
	 * in the rigid body's reference frame is constant.
	 */
	@SuppressWarnings({"NullAway.Init"})
	public static class Rigid {
		/** If the parameters are assumed to be known and should not be optimised. */
		public boolean known;

		/** Transform from world into the rigid object's frame */
		public final Se3_F64 object_to_world = new Se3_F64();

		/** Location of points in object's reference frame. The coordinate is always fixed. */
		public Point[] points = new Point[0];

		/** Index of the first point in the list */
		public int indexFirst;

		/**
		 * Resets and initialzies internal data structures. If possible previously declared data is reused
		 *
		 * @param numPoints Number of points in the rigid body
		 * @param dof DOF in each point
		 */
		public void init( int numPoints, int dof ) {
			known = false;
			object_to_world.reset();
			indexFirst = -1;

			if (points.length != numPoints)
				points = new SceneStructureCommon.Point[numPoints];

			if (points.length > 0 && (points[0] == null || points[0].coordinate.length != numPoints)) {
				for (int i = 0; i < numPoints; i++) {
					points[i] = new Point(dof);
				}
			} else {
				for (int i = 0; i < numPoints; i++) {
					points[i].reset();
				}
			}
		}

		/**
		 * Indicates that this point has been observed in this view and performs sanity checks.
		 *
		 * @deprecated Use {@link #connectPointToView(int, int, float, float, SceneObservations)} instead.
		 */
		@Deprecated
		public void connectPointToView( int pointIdx, int viewIdx ) {
			if (points[pointIdx].views.contains(viewIdx))
				throw new IllegalArgumentException("Tried to add the same view twice. viewIndex=" + viewIdx);
			points[pointIdx].views.add(viewIdx);
		}

		/**
		 * Connects the point to the view and adds the pixel observations. These steps can be easy to mess up, so
		 * it's recommended that you use this function.
		 *
		 * 

NOTE: Make sure you call {@link SceneStructureMetric#assignIDsToRigidPoints()} before calling this * function.

* * @param pointIdx Index of the feature on the rigid object * @param viewIdx Which view this observation is from * @param pixX The pixel-x observation * @param pixY The pixel-y observation * @param sceneObs Data structure storing the observations */ public void connectPointToView( int pointIdx, int viewIdx, float pixX, float pixY, SceneObservations sceneObs ) { BoofMiscOps.checkTrue(indexFirst >= 0, "Please call scene.assignIDsToRigidPoints() first"); sceneObs.getViewRigid(viewIdx).add(indexFirst + pointIdx, pixX, pixY); connectPointToView(pointIdx, viewIdx); } public void setPoint( int which, double x, double y, double z ) { points[which].set(x, y, z); } public void setPoint( int which, double x, double y, double z, double w ) { points[which].set(x, y, z, w); } public void getPoint( int which, Point3D_F64 p ) { points[which].get(p); } public void getPoint( int which, Point4D_F64 p ) { points[which].get(p); } public int getTotalPoints() { return points.length; } public boolean isIdentical( Rigid m, double tol ) { if (known != m.known) return false; if (indexFirst != m.indexFirst) return false; if (points != null) { if (m.points == null) return false; if (points.length != m.points.length) return false; for (int i = 0; i < points.length; i++) { if (points[i].distance(m.points[i]) > tol) return false; } } else return m.points == null; return true; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy