MRCAL(1)
β¨ NAME β¨
mrcal-convert-lensmodel β the glow-up tool that takes a camera model in one lens model and converts it into another lens model. Same camera, different outfit π
This is part of the mrcal toolkit (camera projection + calibration things, yk the whole vibe) π
πΈ SYNOPSIS πΈ
$ mrcal-convert-lensmodel
--viz LENSMODEL_OPENCV4 left.cameramodel
... lots of output as the solve runs ...
RMS error of the solution: 3.40256580058 pixels.
Wrote 'left-LENSMODEL_OPENCV4.cameramodel'
... a plot pops up showing the differences ...
π¦ DESCRIPTION π¦
okay so bestie, here's the tea β
You hand this tool one camera model, and it hands you back another camera model that describes the same camera, except it's expressed with a different lens model. Like, all lens models are out here solving the same problem, but their different representations don't map onto each other perfectly (they're not clones, they're cousins π). So this tool goes and finds the best-fitting parameters of the target lens model for you. Two whole methods are implemented:
- If the cameramodel file you gave contains
optimization_inputs, then we literally have all the data that was originally used to compute this model, and we can just re-run the original optimization with the new lens model. This is the default behavior, and it's the preferred choice, no cap. Downside: it only works with models that were computed by mrcal originally. We re-run the full original solve β even if it had multiple cameras in it β unless--monocularis given. With that option, we re-solve only the subset of the images observed by the one requested camera. - We can sample a grid of points on the imager, unproject them to observation vectors in the camera coordinate system, and then fit a new camera model that reprojects those vectors as closely to the original pixel coordinates as possible. This one works on models that did <em>not</em> come from mrcal. Pick this mode by passing
--sampled.
Since camera models (that's lens parameters AND geometry, both of them, don't forget) get computed off real pixel observations, the confidence of the projections is all over the place β it varies wildly across the imager and across observation distances. Method 1 uses the original data, so it implicitly respects those uncertainties: the uncertain areas in the original model stay uncertain in the new model too. Method 2 though? It has no idea which parts of the imager and space are trustworthy, so the results just suffer π it's giving guessing.
As always, the intrinsics have some baked-in geometry information. Both methods optimize intrinsics AND extrinsics, and output cameramodels with updated versions of both. If --sampled: we can ask that only the intrinsics be optimized by passing --intrinsics-only.
Also, if --sampled and <em>not</em> --intrinsics-only: we fit the extrinsics off 3D points, not just observation directions. The distance from the camera to the points is set by --distance. This one takes a comma-separated list of distances to use. And it's STRONGLY recommended that you ask for two different distances:
- A "near" distance: where we expect the intrinsics to have the most accuracy. At the range of the chessboards, usually.
- A "far" distance: at "infinity". A few km is good usually.
The reason for all this is that --sampled solves at a single distance aren't sufficiently constrained. If you ask for a single far distance β like "--distance 1000" for instance β you can easily get an extrinsics shift of 100m. That's aphysical, bestie!! Changing the intrinsics could shift the camera origin by a few mm, but not 100m. Conceptually we want to perform a rotation-only extrinsics solve, but that isn't yet implemented. Passing both a near and a far distance appears to constrain the extrinsics well in practice. The computed extrinsics transform is printed on the console, with a warning if an aphysical shift was computed. So do pay attention to the console output π
Sampled solves are sometimes sensitive to the optimization seed. To control for this, pass --num-trials to evaluate the solve multiple times from different random seeds, and to pick the best one. These solves are usually quick, so there's no harm in passing something like "--num-trials 10" π
We also gotta consider that the model we're trying to fit may not fit the original model in <em>all</em> parts of the imager. Usually this comes up when converting wide-angle cameramodels to use a leaner model: a decent fit will be possible at the center, with more and more divergence as we move towards the edges. We handle this with the --where and --radius options, so you can select the area of the imager that's used for the fit: observations outside the selected area get thrown out. This region is centered on the point given by --where (or at the center of the imager, if --where is omitted). The radius of this region is given by --radius. If '--radius 0' then we use ALL the data. A radius<0 can be used to set the size of the no-data margin at the corners; in this case I'll use
r = sqrt(width^2 + height^2)/2. - abs(radius)
There's a balance to strike here, fr fr. A larger radius means we'll try to fit as well as we can in a larger area. That might mean we won't fit well anywhere, but we also won't do terribly anywhere either. A smaller area means we give up on the outer regions entirely (resulting in very poor fits there), but we'll be able to fit much better in the areas that remain. Generally empirical testing is required to find a good compromise: pass --viz to see the resulting differences β¨
β οΈ Note that--radiusand--whereapply <em>only</em> if we're optimizing sampled reprojections; if we're using the original optimization inputs, those options are illegal. Don't do it, bestie.
The output is written to a file on disk, with the same filename as the input model, but with the new lensmodel added to the filename. Iconic π
β¨ OPTIONS β¨
ok bestie, this is the part where it gets juicy π every knob and dial this thing has, laid out for you. no skipping, no summarizing β we go through all of it, periodt.
π POSITIONAL ARGUMENTS π
toβ the target lens model. this is the glow-up destination πmodelβ your input camera model. if you skip it, or pass-, we read from standard input and write to standard output π«Ά
π OPTIONAL ARGUMENTS π
-h,--helpβ shows this help message and dips out. iconic behavior π--sampledβ so instead of solving the actual original calibration problem using the new lens model, you use sampled imager points. lowkey this produces biased results π but it's a lifesaver when the originaloptimization_inputsjust aren't available anymore. it's giving "make it work with what you've got" energy.--gridn GRIDN GRIDNβ only relevant if you passed--sampled. it's how densely we sample the imager. by default we use a30x20grid β¨--distance DISTANCEβ required if you're doing--sampledand not--intrinsics-only. lemme explain: a sampled solve fits the intrinsics and extrinsics so the reprojections of a grid of observed pixels line up. the points being projected get set a particular distance (that's this argument!) from the camera. set it to the distance that's expected to be most confident for the given cameramodel. heads up: points at infinity aren't supported yet, so just specify a high distance instead π. you can fit multiple distances at the same time by passing them here as a comma-separated, whitespace-less list. if you give multiple distances, we fit the model using ALL of them, but--vizonly displays the difference for the FIRST distance you gave. see the description above. and if you're not using--sampled, this is used for the visualization only.--intrinsics-onlyβ used if--sampled. by default I optimize the intrinsics and extrinsics to find the closest reprojection. but if for whatever reason we already know the camera coordinate system was right, or we need to keep the original extrinsics, pass--intrinsics-only. the resulting extrinsics will be the same, but the fit won't be as good π¬. in many cases, optimizing extrinsics is required to get a usable fit, so--intrinsics-onlymay not be an option if accurate results are required. no cap.--where WHERE WHEREβ works with or without--sampled. I use a subset of the imager to compute the fit, and the active region is a circle centered on this point. if you omit it, we focus on the center of the imager π―--radius RADIUSβ works with or without--sampled. I use a subset of the imager to compute the fit, and the active region is a circle with a radius given by this parameter. ifradius == 0, I'll use the whole imager for the fit. ifradius < 0, this parameter specifies the width of the region at the corners that I should ignore: I will usesqrt(width^2 + height^2)/2. - abs(radius). this is valid ONLY if we're focusing at the center of the imager β οΈ. by default I ignore a large-ish chunk area at the corners.--monocularβ used if not--sampled. by default we re-solve the FULL calibration problem that was used to originally obtain the input model, even if it contained multiple cameras. if--monocular, we re-solve only a subset of the original problem, using ONLY the observations made by THIS camera π. exclusive with--all.--allβ used if not--sampled. by default we re-solve the FULL calibration problem that was used to originally obtain the input model, even if it contained multiple cameras. and by default, we only output the result for the ONE given model. if--all, we output the result for ALL the models in the solve. exclusive with--monocular.--vizβ visualize the differences between the input and output models π. if we have--distance, the FIRST given distance is used to display the fit error.--cbmax CBMAXβ maximum range of the colorbar.--title TITLEβ used if--viz. title string for the diff plot. overrides the default title. exclusive with--extratitle.--extratitle EXTRATITLEβ used if--viz. additional string for the plot to append to the default title. exclusive with--title.--hardcopy HARDCOPYβ used if--viz. write the diff output to disk instead of making an interactive plot πΎ--terminal TERMINALβ used if--viz. gnuplotlib terminal. the default is good almost always, so most people don't need this option fr fr.--set SETβ used if--viz. extrasetdirectives to gnuplotlib. can be given multiple times π--unset UNSETβ used if--viz. extraunsetdirectives to gnuplotlib. can be given multiple times π--force,-fβ by default, existing models on disk are not overwritten. pass--forceto overwrite them without complaint π--outdir OUTDIRβ the directory to write the output into. if omitted, we use the directory of the input model. ignored if we're reading stdin (model is omitted or-).--num-trials NUM_TRIALSβ if given, run the solve more than once. useful in case random initialization produces noticeably different results π². by default we run just one trial.
π REPOSITORY π
<https://www.github.com/dkogan/mrcal>
π AUTHOR π
Dima Kogan, "<dima@secretsauce.net>" β absolute legend for making this π
π LICENSE AND COPYRIGHT π
Copyright (c) 2017-2023 California Institute of Technology ("Caltech"). U.S. Government sponsorship acknowledged. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
aaaand that's a wrap bestie π this whole thing is from mrcal version 2.5.2-4build1, dated 2026-03-20, and it's page MRCAL(1). slay π
β¨
π¦ where this page comes from
- ubuntu resolute: mrcal 2.5.2-4build1 Β· Apache-2.0
- ubuntu stonking: mrcal 2.5.2-4build1 Β· Apache-2.0
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms