Commit 09d21774 authored by Andrey Filippov's avatar Andrey Filippov

CLAUDE: Add nadir_undistort parameter — rectilinear nadir images for COLMAP PINHOLE

Add nadir_undistort (boolean, default true) to IntersceneMatchParameters
at all 6 required locations (field, dialog, read, save, load, clone).

When enabled, renderNadirSequence() undistorts the pX,pY coordinates of
each scene's nadir disparity map before calling renderGPUFromDSI(), so the
rendered NADIR-MERGED TIFF images are rectilinear (pinhole projection)
rather than retaining the sensor's native radial distortion.

Analysis of the Boson 640 calibration (lwir16-06-72-00.calib-tiff) showed:
- True distortion polynomial terms: < 1 px magnitude across the full image
- The dominant issue was d = K0 = 1.00997 (a ~1% focal-length constant offset)
  which, when modeled as PINHOLE with the nominal focal length, curves the
  ground plane into the 'Little Prince planet' sphere effect seen in Blender
- With nadir_undistort=true, COLMAP can use a pure PINHOLE model with the
  correct effective focal length (f * d = 1121.8 px instead of 1110.8 px)

The undistortPxPy() helper mirrors getWorldCoordinates() in GeometryCorrection:
  rD_mm = ||(pX-cx, pY-cy)|| * pixelSize_mm
  factor = getRByRDist(rD_mm / distortionRadius)
  pX_rect = (pX - cx) * factor + cx
Co-authored-by: 's avatarClaude <claude@elphel.com>
parent 11c8998d
......@@ -1147,6 +1147,7 @@ min_str_neib_fpn 0.35
public boolean nadir_um_en = false; // apply unsharp mask to nadir images for SIFT feature enhancement
public double nadir_um_sigma = 2.0; // UM Gaussian sigma (pixels) for nadir images
public double nadir_um_weight = 1.0; // UM weight for nadir images (1.0 = 100%)
public boolean nadir_undistort = true; // undistort pX,pY so nadir images are rectilinear (COLMAP PINHOLE-compatible)
public boolean fgnd_gen_optho = true; // generate orthogonal to the ground view
public boolean fgnd_gen_tilted = true; // generate tilted ground disparity view
public boolean fgnd_gen_scan = false; // generate "CT scan" views
......@@ -3389,6 +3390,8 @@ min_str_neib_fpn 0.35
"Nadir unsharp mask Gaussian sigma.");
gd.addNumericField("Nadir UM weight", this.nadir_um_weight, 5,7, "",
"Nadir unsharp mask weight (1.0=100%).");
gd.addCheckbox ("Undistort nadir images (rectilinear)", this.nadir_undistort,
"Correct pX,pY in nadir disparity maps so rendered images are rectilinear (no radial distortion) — COLMAP can use PINHOLE model.");
gd.addCheckbox ("Generate orthogonal to the ground view", this.fgnd_gen_optho,
"Generate relative to the ground plane view, minimizing anisotropic distortions.");
gd.addCheckbox ("Generate tilted-ground view", this.fgnd_gen_tilted,
......@@ -4829,6 +4832,7 @@ min_str_neib_fpn 0.35
this.nadir_um_en = gd.getNextBoolean();
this.nadir_um_sigma = gd.getNextNumber();
this.nadir_um_weight = gd.getNextNumber();
this.nadir_undistort = gd.getNextBoolean();
this.fgnd_gen_optho = gd.getNextBoolean();
this.fgnd_gen_tilted = gd.getNextBoolean();
this.fgnd_gen_scan = gd.getNextBoolean();
......@@ -6128,6 +6132,7 @@ min_str_neib_fpn 0.35
properties.setProperty(prefix+"nadir_um_en", this.nadir_um_en+""); // boolean
properties.setProperty(prefix+"nadir_um_sigma", this.nadir_um_sigma+""); // double
properties.setProperty(prefix+"nadir_um_weight", this.nadir_um_weight+""); // double
properties.setProperty(prefix+"nadir_undistort", this.nadir_undistort+""); // boolean
properties.setProperty(prefix+"fgnd_gen_optho", this.fgnd_gen_optho+""); // boolean
properties.setProperty(prefix+"fgnd_gen_tilted", this.fgnd_gen_tilted+""); // boolean
properties.setProperty(prefix+"fgnd_gen_scan", this.fgnd_gen_scan+""); // boolean
......@@ -7410,6 +7415,7 @@ min_str_neib_fpn 0.35
if (properties.getProperty(prefix+"nadir_um_en")!=null) this.nadir_um_en=Boolean.parseBoolean(properties.getProperty(prefix+"nadir_um_en"));
if (properties.getProperty(prefix+"nadir_um_sigma")!=null) this.nadir_um_sigma=Double.parseDouble(properties.getProperty(prefix+"nadir_um_sigma"));
if (properties.getProperty(prefix+"nadir_um_weight")!=null) this.nadir_um_weight=Double.parseDouble(properties.getProperty(prefix+"nadir_um_weight"));
if (properties.getProperty(prefix+"nadir_undistort")!=null) this.nadir_undistort=Boolean.parseBoolean(properties.getProperty(prefix+"nadir_undistort"));
if (properties.getProperty(prefix+"fgnd_gen_optho")!=null) this.fgnd_gen_optho=Boolean.parseBoolean(properties.getProperty(prefix+"fgnd_gen_optho"));
if (properties.getProperty(prefix+"fgnd_gen_tilted")!=null) this.fgnd_gen_tilted=Boolean.parseBoolean(properties.getProperty(prefix+"fgnd_gen_tilted"));
if (properties.getProperty(prefix+"fgnd_gen_scan")!=null) this.fgnd_gen_scan=Boolean.parseBoolean(properties.getProperty(prefix+"fgnd_gen_scan"));
......@@ -8673,6 +8679,7 @@ min_str_neib_fpn 0.35
imp.nadir_um_en = this.nadir_um_en;
imp.nadir_um_sigma = this.nadir_um_sigma;
imp.nadir_um_weight = this.nadir_um_weight;
imp.nadir_undistort = this.nadir_undistort;
imp.fgnd_gen_optho = this.fgnd_gen_optho;
imp.fgnd_gen_tilted = this.fgnd_gen_tilted;
imp.fgnd_gen_scan = this.fgnd_gen_scan;
......
......@@ -10951,6 +10951,10 @@ java.lang.NullPointerException
scene_ers_xyz_dt, // double [] ers_xyz_dt
scene_ers_atr_dt);// double [] ers_atr_dt
}
// Optionally undistort pX,pY so rendered images are rectilinear (COLMAP PINHOLE-compatible)
if (clt_parameters.imp.nadir_undistort) {
undistortPxPy(nadir_pXpYD[nscene], selected_scenes[nscene].getErsCorrection());
}
// Render from scene's own position: ZERO3 offset, scene is its own reference
ImagePlus imp_scene = QuadCLT.renderGPUFromDSI(
-1, // final int sensor_mask (-1 = merge all)
......@@ -10994,6 +10998,34 @@ java.lang.NullPointerException
return imp_result;
}
/**
* Undistort pixel coordinates in a pXpYD tile array in-place.
* Converts distorted sensor coordinates to rectilinear (pinhole) coordinates
* using the scene's radial distortion calibration (getRByRDist).
* With Boson 640, the correction is &lt;1 px but eliminates the systematic
* bowl effect in COLMAP reconstructions that use a PINHOLE camera model.
* @param pXpYD tile array: each non-null element is {pX, pY, disparity}
* @param gc GeometryCorrection providing distortion model and pixel geometry
*/
private static void undistortPxPy(double[][] pXpYD, ErsCorrection gc) {
if (pXpYD == null) return;
final double cx = 0.5 * gc.pixelCorrectionWidth;
final double cy = 0.5 * gc.pixelCorrectionHeight;
final double mmPerPix = 0.001 * gc.pixelSize; // µm → mm
final double distRad = gc.distortionRadius; // mm
for (double[] tile : pXpYD) {
if (tile == null) continue;
double pXc = tile[0] - cx;
double pYc = tile[1] - cy;
double rD_mm = Math.sqrt(pXc * pXc + pYc * pYc) * mmPerPix;
if (rD_mm > 0) {
double rND2R = gc.getRByRDist(rD_mm / distRad, false);
tile[0] = pXc * rND2R + cx;
tile[1] = pYc * rND2R + cy;
}
}
}
public static QuadCLT[] selectScenes(
QuadCLT[] quadCLTs,
QuadCLT ref_scene) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment