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

boofcv.alg.structure.InitializeCommonMetric 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.7
Show newest version
/*
 * Copyright (c) 2022, 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.structure;

import boofcv.abst.geo.bundle.SceneObservations;
import boofcv.abst.geo.bundle.SceneStructureMetric;
import boofcv.alg.geo.MetricCameras;
import boofcv.alg.structure.PairwiseImageGraph.Motion;
import boofcv.alg.structure.PairwiseImageGraph.View;
import boofcv.misc.BoofMiscOps;
import boofcv.struct.feature.AssociatedIndex;
import georegression.struct.point.Point2D_F64;
import lombok.Getter;
import lombok.Setter;
import org.ddogleg.fitting.modelset.ransac.Ransac;
import org.ddogleg.struct.DogArray;
import org.ddogleg.struct.DogArray_I32;
import org.ddogleg.struct.FastArray;
import org.ddogleg.struct.VerbosePrint;
import org.jetbrains.annotations.Nullable;

import java.io.PrintStream;
import java.util.Objects;
import java.util.Set;

import static boofcv.misc.BoofMiscOps.checkTrue;

/**
 * Given a set of views and a set of features which are visible in all views, estimate their metric structure.
 *
 * 
    *
  1. Select the three best 3 views
  2. *
  3. Association between all 3 views
  4. *
  5. Robust self calibration
  6. *
* * TODO handle 2-view case * TODO specify camera shape individually. One camera is used for all views right now * TODO Need to provide some way to external adjust RANSAC parameters here * TODO Handle planar scenes. * * @author Peter Abeles */ @SuppressWarnings({"NullAway.Init"}) public class InitializeCommonMetric implements VerbosePrint { /** Common algorithms for reconstructing the projective scene */ public @Getter @Setter PairwiseGraphUtils utils; /** Used to convert observations from 3-views into a metric scene */ protected final @Getter ThreeViewEstimateMetricScene pixelToMetric3 = new ThreeViewEstimateMetricScene(); /** * List of feature indexes for each view that are part of the inlier set. The seed view is at index 0. The other * indexes are in order of 'seedConnIdx'. */ protected @Getter final DogArray inlierIndexes = new DogArray<>(DogArray_I32::new, DogArray_I32::reset); // Retrieve a Pairwise.View given its index in the SBA structure protected @Getter final FastArray viewsByStructureIndex = new FastArray<>(View.class); //-------------- Internal workspace variables private final DogArray_I32 selectedTriple = new DogArray_I32(2); /** * lookup table from feature ID in seed view to structure index. There will only be 3D features for members * of the inlier set. */ protected final DogArray_I32 seedToStructure = new DogArray_I32(); // Indexes of observations in the seed view which are common to the selected adjacent views DogArray_I32 seedFeatsIdx = new DogArray_I32(); // Indicates if debugging information should be printed private @Nullable PrintStream verbose; public InitializeCommonMetric( ConfigProjectiveReconstruction configProjective ) { utils = new PairwiseGraphUtils(configProjective); } public InitializeCommonMetric() { this(new ConfigProjectiveReconstruction()); } /** * Computes a metric reconstruction. Reconstruction will be relative the 'seed' and only use features * listed in 'common'. The list of views is taken from seed and is specified in 'motions'. * * @param dbSimilar (Input) Data based used to look up information on each image * @param seed (Input) The seed view that will act as the origin * @param seedConnIdx (Input) Indexes of motions in the seed view to use when initializing * @param results (Output) Found metric reconstruction for seed and selected connected views * @return true is successful or false if it failed */ public boolean metricScene( LookUpSimilarImages dbSimilar, LookUpCameraInfo dbCams, View seed, DogArray_I32 seedConnIdx, MetricCameras results ) { results.reset(); checkTrue(seed.connections.size >= seedConnIdx.size, "Can't have more seed connection indexes than actual connections"); if (verbose != null) verbose.println("ENTER projectiveSceneN: seed='" + seed.id + "' common.size=" + seedFeatsIdx.size + " conn.size=" + seedConnIdx.size); if (seedConnIdx.size < 2) { if (verbose != null) verbose.println("2-views, a.k.a. stereo, is a special case and requires different" + " logic and isn't yet supported"); return false; } else if (seedConnIdx.size >= 3) { if (verbose != null) verbose.println("Can only handle the 3-view case for right now"); return false; } // initialize data structures utils.dbSimilar = dbSimilar; utils.dbCams = dbCams; viewsByStructureIndex.reset(); inlierIndexes.reset().resize(1 + seedConnIdx.size); // find the 3 view combination with the best score if (!selectInitialTriplet(seed, seedConnIdx, selectedTriple)) { if (verbose != null) verbose.println("FAILED: Select initial triplet"); return false; } // Find observations which are common between the selected views utils.findAllConnectedSeed(seed, selectedTriple, seedFeatsIdx); // Find features which are common between all three views utils.seed = seed; utils.viewB = utils.seed.connections.get(selectedTriple.data[0]).other(seed); utils.viewC = utils.seed.connections.get(selectedTriple.data[1]).other(seed); utils.createThreeViewLookUpTables(); utils.findFullyConnectedTriple(seedFeatsIdx); if (verbose != null) { verbose.println("Selected Triplet: seed='" + utils.seed.id + "' viewB='" + utils.viewB.id + "' viewC='" + utils.viewC.id + "' common.size=" + utils.commonIdx.size + " connections.size=" + seedConnIdx.size); } if (utils.commonIdx.isEmpty()) { if (verbose != null) verbose.println("FAILED: No common features found"); return false; } // Estimate the initial projective cameras using trifocal tensor utils.createTripleFromCommon(verbose); // TODO move out camera estimation from pairwise utils? if (!estimateMetricCamerasRobustly()) { if (verbose != null) verbose.println("FAILED: Create metric views from initial triplet"); return false; } // look up tables to trace the same feature across different data structures createStructureLookUpTables(seed); saveInlierObservationsConnectedViews(); viewsByStructureIndex.resize(3); viewsByStructureIndex.set(0, utils.seed); viewsByStructureIndex.set(1, utils.viewB); viewsByStructureIndex.set(2, utils.viewC); // sanity check for bugs viewsByStructureIndex.forIdx(( i, o ) -> BoofMiscOps.checkTrue(o != null)); // Save results for (int camIdx = 0; camIdx < pixelToMetric3.listPinhole.size; camIdx++) { results.intrinsics.grow().setTo(pixelToMetric3.listPinhole.get(camIdx)); } for (int viewIdx = 1; viewIdx < pixelToMetric3.listWorldToView.size; viewIdx++) { results.motion_1_to_k.grow().setTo(pixelToMetric3.listWorldToView.get(viewIdx)); } return true; } /** True if a single camera generated all views */ private boolean isSingleCamera() { int camA = utils.dbCams.viewToCamera(utils.seed.id); int camB = utils.dbCams.viewToCamera(utils.viewB.id); int camC = utils.dbCams.viewToCamera(utils.viewC.id); return camA == camB && camA == camC; } /** * Robustly estimates metric views with extrinsics known up to a scale factor */ public boolean estimateMetricCamerasRobustly() { pixelToMetric3.viewToCamera = isSingleCamera() ? new int[]{0, 0, 0} : new int[]{0, 1, 2}; pixelToMetric3.initialize(utils.priorCamA.width, utils.priorCamA.height); if (!pixelToMetric3.process(utils.matchesTriple.toList())) return false; utils.inliersThreeView.reset(); utils.inliersThreeView.addAll(pixelToMetric3.ransac.getMatchSet()); utils.inlierIdx.reset(); utils.inlierIdx.resize(utils.inliersThreeView.size); for (int i = 0; i < utils.inliersThreeView.size; i++) { utils.inlierIdx.data[i] = pixelToMetric3.ransac.getInputIndex(i); } return true; } /** * Create look up tables to go from seed feature index to structure feature index. * ransac inlier index to seed feature index * * Only points that are in the inlier set are part of the scene's structure. */ void createStructureLookUpTables( View viewA ) { final Ransac ransac = pixelToMetric3.ransac; final int numInliers = ransac.getMatchSet().size(); seedToStructure.resize(viewA.totalObservations); seedToStructure.fill(-1); // -1 indicates no match DogArray_I32 inlierToSeed = inlierIndexes.get(0); inlierToSeed.resize(numInliers); for (int i = 0; i < numInliers; i++) { int inputIdx = ransac.getInputIndex(i); // table to go from inlier list into seed feature index inlierToSeed.data[i] = utils.commonIdx.get(inputIdx); // seed feature index into the output structure index BoofMiscOps.checkTrue(seedToStructure.data[inlierToSeed.data[i]] == -1); seedToStructure.data[inlierToSeed.data[i]] = i; } } /** * Exhaustively look at all triplets that connect with the seed view * * @param edgeIdxs (input) List of edges in seed it will consider * @param selected (output) Indexes of the two selected edges going out of `seed` */ boolean selectInitialTriplet( View seed, DogArray_I32 edgeIdxs, DogArray_I32 selected ) { selected.resize(2); double bestScore = 0; // zero is used for invalid triples for (int i = 0; i < edgeIdxs.size; i++) { int edgeI = edgeIdxs.get(i); View viewB = seed.connections.get(edgeI).other(seed); for (int j = i + 1; j < edgeIdxs.size; j++) { int edgeJ = edgeIdxs.get(j); View viewC = seed.connections.get(edgeJ).other(seed); double s = scoreTripleView(seed, viewB, viewC); if (s <= bestScore) continue; bestScore = s; selected.data[0] = edgeI; selected.data[1] = edgeJ; } } return bestScore != 0; } /** * Evaluates how well this set of 3-views can be used to estimate the scene's 3D structure * * @return higher is better. zero means worthless */ double scoreTripleView( View seedA, View viewB, View viewC ) { Motion motionAB = Objects.requireNonNull(seedA.findMotion(viewB)); Motion motionAC = Objects.requireNonNull(seedA.findMotion(viewC)); Motion motionBC = viewB.findMotion(viewC); if (motionBC == null) return 0.0; double score = 0.0; score += motionAB.score3D; score += motionAC.score3D; score += motionBC.score3D; return score; } /** * Save which observations are in the inlier set to the connected views */ private void saveInlierObservationsConnectedViews() { DogArray_I32 inlierToSeed = inlierIndexes.get(0); // Save which observations are part the inlier set // Now add observations for edges connected to the seed for (int motionIdx = 0; motionIdx < selectedTriple.size(); motionIdx++) { SceneObservations.View obsView = utils.observations.getView(motionIdx + 1); Motion m = utils.seed.connections.get(selectedTriple.get(motionIdx)); View v = m.other(utils.seed); boolean seedIsSrc = m.src == utils.seed; utils.dbCams.lookupCalibration(utils.dbCams.viewToCamera(v.id), utils.priorCamB); utils.dbSimilar.lookupPixelFeats(v.id, utils.featsB); BoofMiscOps.offsetPixels(utils.featsB.toList(), -utils.priorCamB.cx, -utils.priorCamB.cy); // indicate which observation from this view contributed to which 3D feature DogArray_I32 connInlierIndexes = inlierIndexes.get(motionIdx + 1); connInlierIndexes.resize(inlierToSeed.size); for (int epipolarInlierIdx = 0; epipolarInlierIdx < m.inliers.size; epipolarInlierIdx++) { AssociatedIndex a = m.inliers.get(epipolarInlierIdx); // See if the feature is one of inliers computed from 3-view RANSAC int structId = seedToStructure.data[seedIsSrc ? a.src : a.dst]; if (structId < 0) continue; // get the observation in this view to that feature[structId] connInlierIndexes.set(structId, seedIsSrc ? a.dst : a.src); Point2D_F64 o = utils.featsB.get(seedIsSrc ? a.dst : a.src); obsView.add(structId, (float)o.x, (float)o.y); } } } /** * Returns the {@link View} given the index of the view in structure */ public View getPairwiseGraphViewByStructureIndex( int index ) { return viewsByStructureIndex.get(index); } @Override public void setVerbose( @Nullable PrintStream out, @Nullable Set configuration ) { this.verbose = BoofMiscOps.addPrefix(this, out); BoofMiscOps.verboseChildren(out, configuration, pixelToMetric3); } public SceneStructureMetric getStructure() { return pixelToMetric3.getStructure(); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy