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

boofcv.alg.geo.bundle.BundleAdjustmentMetricResidualFunction 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.alg.geo.bundle;

import boofcv.abst.geo.bundle.BundleAdjustmentSchur;
import boofcv.abst.geo.bundle.SceneObservations;
import boofcv.abst.geo.bundle.SceneStructureCommon;
import boofcv.abst.geo.bundle.SceneStructureMetric;
import boofcv.struct.geo.PointIndex2D_F64;
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 org.ddogleg.struct.DogArray;

import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * 

* Computes observations errors/residuals for metric bundle adjustment as implemented using * {@link org.ddogleg.optimization.UnconstrainedLeastSquares}. Parameterization is done using * the format in {@link CodecSceneStructureMetric}. *

* *

* cost(P) = (1/(m*n))*∑ij ||xj - (1/z)*[Ri|Ti]*Xj||2 *

* * @author Peter Abeles * @see SceneStructureMetric * @see SceneObservations */ @SuppressWarnings({"NullAway.Init"}) public class BundleAdjustmentMetricResidualFunction implements BundleAdjustmentSchur.FunctionResiduals { private SceneStructureMetric structure; private SceneObservations observations; // feature location in world coordinates private final Point3D_F64 worldPt = new Point3D_F64(); private final Point4D_F64 worldPt4 = new Point4D_F64(); // number of parameters being optimised private int numParameters; // number of observations. 2 for each point in each view private int numObservations; // local variable which stores the predicted location of the feature in the camera frame private final Point3D_F64 cameraPt = new Point3D_F64(); // Storage for rendered output private final Point2D_F64 predictedPixel = new Point2D_F64(); private final PointIndex2D_F64 observedPixel = new PointIndex2D_F64(); // Used to write the "unknown" parameters into the scene private final CodecSceneStructureMetric codec = new CodecSceneStructureMetric(); // Recycled world to view transforms private final DogArray storageSe3 = new DogArray<>(Se3_F64::new); // Look up workspace by view ID when relative view private final Map mapWorldToView = new HashMap<>(); // Storage for 3D points in Cartesian and homogenous coordinates private final Point3D_F64 p3 = new Point3D_F64(); private final Point4D_F64 p4 = new Point4D_F64(); /** * Specifies the scenes structure and observed feature locations */ @Override public void configure( SceneStructureMetric structure, SceneObservations observations ) { this.structure = structure; this.observations = observations; numObservations = observations.getObservationCount(); numParameters = structure.getParameterCount(); structure.assignIDsToRigidPoints(); // declare storage and create a look up table for world to view for all relative views mapWorldToView.clear(); storageSe3.reset(); for (int viewIdx = 0; viewIdx < structure.views.size; viewIdx++) { SceneStructureMetric.View v = structure.views.get(viewIdx); if (v.parent == null) continue; Se3_F64 world_to_view = storageSe3.grow(); mapWorldToView.put(v, world_to_view); } } @Override public int getNumOfInputsN() { return numParameters; } @Override public int getNumOfOutputsM() { return numObservations*2; } @Override public void process( double[] input, double[] output ) { // write the current parameters into the scene's structure codec.decode(input, structure); // Project the general scene now if (structure.isHomogenous()) project4(output); else project3(output); } /** * Computes the residuals and skips decoding the current parameters. Useful when you are using this to * compute the residuals and not optimizing */ public void process( double[] output ) { if (structure.isHomogenous()) project4(output); else project3(output); } /** * projection from 3D coordinates */ private void project3( double[] output ) { int observationIndex = 0; for (int viewIndex = 0; viewIndex < structure.views.size; viewIndex++) { SceneStructureMetric.View view = structure.views.get(viewIndex); SceneStructureCommon.Camera camera = structure.cameras.get(view.camera); Se3_F64 world_to_view = lookupWorldToView(view); //=========== Project General Points in this View { SceneObservations.View obsView = observations.views.get(viewIndex); if (obsView.cameraState != null) camera.model.setCameraState(obsView.cameraState); for (int i = 0; i < obsView.size(); i++) { obsView.getPixel(i, observedPixel); SceneStructureCommon.Point worldPt = structure.points.data[observedPixel.index]; worldPt.get(p3); SePointOps_F64.transform(world_to_view, p3, cameraPt); camera.model.project(cameraPt.x, cameraPt.y, cameraPt.z, predictedPixel); int outputIndex = observationIndex*2; output[outputIndex] = predictedPixel.x - observedPixel.p.x; output[outputIndex + 1] = predictedPixel.y - observedPixel.p.y; observationIndex++; } } //=========== Project Rigid Object Points in this View if (observations.hasRigid()) { SceneObservations.View obsView = observations.viewsRigid.get(viewIndex); if (obsView.cameraState != null) camera.model.setCameraState(obsView.cameraState); for (int i = 0; i < obsView.size(); i++) { obsView.getPixel(i, observedPixel); // Use lookup table to figure out which rigid object it belongs to int rigidIndex = structure.lookupRigid[observedPixel.index]; SceneStructureMetric.Rigid rigid = structure.rigids.get(rigidIndex); // Compute the point's index on the rigid object int pointIndex = observedPixel.index - rigid.indexFirst; // Load the 3D location of point on the rigid body SceneStructureCommon.Point objectPt = rigid.points[pointIndex]; objectPt.get(p3); // Transform to world frame and from world to camera SePointOps_F64.transform(rigid.object_to_world, p3, worldPt); SePointOps_F64.transform(world_to_view, worldPt, cameraPt); // Project and compute residual camera.model.project(cameraPt.x, cameraPt.y, cameraPt.z, predictedPixel); int outputIndex = observationIndex*2; output[outputIndex] = predictedPixel.x - observedPixel.p.x; output[outputIndex + 1] = predictedPixel.y - observedPixel.p.y; observationIndex++; } } } } /** * projection from homogenous coordinates */ private void project4( double[] output ) { int observationIndex = 0; for (int viewIndex = 0; viewIndex < structure.views.size; viewIndex++) { SceneStructureMetric.View view = structure.views.get(viewIndex); SceneStructureCommon.Camera camera = structure.cameras.get(view.camera); Se3_F64 world_to_view = lookupWorldToView(view); //=========== Project General Points in this View { SceneObservations.View obsView = observations.views.get(viewIndex); if (obsView.cameraState != null) camera.model.setCameraState(obsView.cameraState); for (int i = 0; i < obsView.size(); i++) { obsView.getPixel(i, observedPixel); SceneStructureCommon.Point worldPt = structure.points.data[observedPixel.index]; worldPt.get(p4); // TODO Explain why this is correct. The last row is omitted when converted to 3D SePointOps_F64.transformV(world_to_view, p4, cameraPt); camera.model.project(cameraPt.x, cameraPt.y, cameraPt.z, predictedPixel); int outputIndex = observationIndex*2; output[outputIndex] = predictedPixel.x - observedPixel.p.x; output[outputIndex + 1] = predictedPixel.y - observedPixel.p.y; observationIndex++; } } //=========== Project Rigid Object Points in this View if (observations.hasRigid()) { SceneObservations.View obsView = observations.viewsRigid.get(viewIndex); if (obsView.cameraState != null) camera.model.setCameraState(obsView.cameraState); for (int i = 0; i < obsView.size(); i++) { obsView.getPixel(i, observedPixel); // Use lookup table to figure out which rigid object it belongs to int rigidIndex = structure.lookupRigid[observedPixel.index]; SceneStructureMetric.Rigid rigid = structure.rigids.get(rigidIndex); // Compute the point's index on the rigid object int pointIndex = observedPixel.index - rigid.indexFirst; // Load the 3D location of point on the rigid body SceneStructureCommon.Point objectPt = rigid.points[pointIndex]; objectPt.get(p4); // Transform to world frame and from world to camera SePointOps_F64.transform(rigid.object_to_world, p4, worldPt4); SePointOps_F64.transformV(world_to_view, worldPt4, cameraPt); camera.model.project(cameraPt.x, cameraPt.y, cameraPt.z, predictedPixel); int outputIndex = observationIndex*2; output[outputIndex] = predictedPixel.x - observedPixel.p.x; output[outputIndex + 1] = predictedPixel.y - observedPixel.p.y; observationIndex++; } } } } /** * Returns a transform from the world_to_view. If relative then the parent's world to view is look up and used * to compute this view's transform and the results are saved. */ protected Se3_F64 lookupWorldToView( SceneStructureMetric.View v ) { Se3_F64 parent_to_view = structure.getParentToView(v); if (v.parent == null) return parent_to_view; Se3_F64 world_to_view = Objects.requireNonNull(mapWorldToView.get(v)); SceneStructureMetric.View parentView = v.parent; // See if the parent is relative to the global frame if (parentView.parent == null) { Se3_F64 world_to_parent = Objects.requireNonNull(structure.getParentToView(v.parent)); world_to_parent.concat(parent_to_view, world_to_view); } else { // Since the parent must have a lower index it's transform is already known Se3_F64 world_to_parent = Objects.requireNonNull(mapWorldToView.get(v.parent)); world_to_parent.concat(parent_to_view, world_to_view); } return world_to_view; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy