1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
use std::{collections::BTreeMap, sync::Arc};
use tracing::error;
use flydra_types::{RawCamName, TrackingParams};
use mvg::PointWorldFrameWithSumReprojError;
use crate::{
safe_u8, set_of_subsets, tracking_core::HypothesisTest, CamAndDist, HypothesisTestResult,
MyFloat,
};
const HTEST_MAX_N_CAMS: u8 = 3;
type CamComboKey = RawCamName;
type CamComboList = Vec<Vec<RawCamName>>;
#[derive(Clone)]
pub(crate) struct NewObjectTestFull3D {
cam_combinations_by_size: BTreeMap<u8, CamComboList>,
recon: flydra_mvg::FlydraMultiCameraSystem<MyFloat>,
params: Arc<TrackingParams>,
}
impl NewObjectTestFull3D {
pub(crate) fn new(
recon: flydra_mvg::FlydraMultiCameraSystem<MyFloat>,
params: Arc<TrackingParams>,
) -> Self {
{
let mut cam_combinations_by_size = BTreeMap::new();
{
let mut useful_cams = BTreeMap::new();
for raw_cam_name in recon.cam_names() {
let name = RawCamName::new(raw_cam_name.to_string());
let k: CamComboKey = name;
useful_cams.insert(k, ());
}
let cam_combinations_btree = set_of_subsets(&useful_cams);
let cam_combinations: CamComboList = cam_combinations_btree
.into_iter()
.map(|v| v.into_iter().collect())
.collect();
for cc in cam_combinations.iter() {
let size = safe_u8(cc.len());
if (2..=HTEST_MAX_N_CAMS).contains(&size) {
let size_entry = &mut cam_combinations_by_size
.entry(size)
.or_insert_with(Vec::new);
size_entry.push(cc.clone());
}
}
}
Self {
cam_combinations_by_size,
params,
recon,
}
}
}
}
impl HypothesisTest for NewObjectTestFull3D {
/// Use hypothesis testing algorithm to find best 3D point.
///
/// Finds combination of cameras which uses the most number of cameras
/// while minimizing mean reprojection error. Algorithm used accepts
/// any camera combination with reprojection error less than the
/// some acceptable distance.
///
/// Returns at most a single object.
///
/// We can safely make the assumption that all incoming data is from the same
/// framenumber and timestamp.
fn hypothesis_test(
&self,
good_points: &BTreeMap<RawCamName, mvg::DistortedPixel<MyFloat>>,
) -> Option<HypothesisTestResult> {
// TODO: convert this to use undistorted points and then remove
// orig_distorted, also from the structure it is in.
let hypothesis_test_params =
self.params.hypothesis_test_params.as_ref().expect(
"calling NewObjectTestFull3D:hypothesis_test() without hypothesis_test_params",
);
let minimum_number_of_cameras = hypothesis_test_params.minimum_number_of_cameras;
let hypothesis_test_max_acceptable_error =
hypothesis_test_params.hypothesis_test_max_acceptable_error;
let mut best_overall: Option<(
PointWorldFrameWithSumReprojError<MyFloat>,
Vec<CamComboKey>,
)> = None;
for n_cams in 2..(HTEST_MAX_N_CAMS + 1) {
if n_cams < minimum_number_of_cameras {
continue;
}
// Calculate the least reprojection error starting with all
// possible combinations of 2 cameras, then start increasing
// the number of cameras used. For each number of cameras,
// determine if there exists a combination with an acceptable
// reprojection error.
let combos = match self.cam_combinations_by_size.get(&n_cams) {
Some(combos) => combos,
None => {
// We are probably here because there are only 2 cameras connected
// and thus self.cam_combinations_by_size will have only an entry
// for the key '2', but in this loop n_cams can be more.
continue;
}
};
let mut best_solution_so_far: Option<(
PointWorldFrameWithSumReprojError<MyFloat>,
Vec<CamComboKey>,
)> = None;
for cams_used in combos.iter() {
let mut missing_cam_data = false;
let mut points = Vec::with_capacity(cams_used.len());
for cam_name in cams_used {
if let Some(pt) = good_points.get(cam_name) {
let new_pt = pt.clone();
points.push((cam_name.as_str().to_string(), new_pt));
} else {
missing_cam_data = true;
break;
}
}
if missing_cam_data {
continue;
}
let data = match self.recon.find3d_and_cum_reproj_dist_distorted(&points) {
Ok(data) => data,
Err(err) => {
if let mvg::MvgError::SvdFailed = err {
error!("failed SVD in find3d with points {:?}", points);
continue;
}
error!("failed find3d {}", err);
return None;
}
};
best_solution_so_far = match best_solution_so_far {
Some((bssf, best_cams_so_far)) => {
if data.cum_reproj_dist < bssf.cum_reproj_dist {
// This new solution is better, keep it.
Some((data, cams_used.clone()))
} else {
// The previous best remains best.
Some((bssf, best_cams_so_far))
}
}
None => {
// No previous solutions, keep this one.
Some((data, cams_used.clone()))
}
};
}
if let Some((bssf, best_cams_so_far)) = best_solution_so_far {
if bssf.mean_reproj_dist > hypothesis_test_max_acceptable_error {
// Not possible for fitting N+1 points have less error than with N points,
// so abort early.
break;
} else {
// We are iterating from least to most cameras. Anything within
// our acceptable distance with more cameras is here favored over
// any solution (with lower mean reprojection error) from more cams.
best_overall = Some((bssf, best_cams_so_far));
}
}
}
best_overall.map(|(bssf, cams_used)| {
// Build CamAndDist struct for each camera.
debug_assert!(cams_used.len() == bssf.reproj_dists.len());
let cams_and_reproj_dist = cams_used
.iter()
.zip(bssf.reproj_dists.iter())
.map(|(ros_cam_name, reproj_dist)| CamAndDist {
raw_cam_name: ros_cam_name.clone(),
reproj_dist: *reproj_dist,
})
.collect();
HypothesisTestResult {
coords: bssf.point.coords,
cams_and_reproj_dist,
}
})
}
}