MRCAL(1)
π MRCAL(1) β mrcal: camera projection, calibration toolkit π
okay bestie, buckle UP, because we're talking about mrcal-reproject-image π
β¨ NAME β¨
mrcal-reproject-image β remaps a captured image into another camera model π¦
like... it takes a picture that one camera saw and shows you what a different camera model would have seen. no cap.
π SYNOPSIS π
to "undistort" images, i.e. reproject to a pinhole projection π
### To "undistort" images to reproject to a pinhole projection
$ mrcal-reproject-image --to-pinhole
camera0.cameramodel
image*.jpg
Wrote image0-pinhole.jpg
Wrote image1-pinhole.jpg
...
to reproject images from one lens model to another π§
### To reproject images from one lens model to another
$ mrcal-reproject-image
camera0.cameramodel camera1.cameramodel
image*.jpg
Wrote image0-reprojected.jpg
Wrote image1-reprojected.jpg
Wrote image2-reprojected.jpg
...
to reproject two sets of images to a common pinhole projection π
### To reproject two sets of images to a common pinhole projection
$ mrcal-reproject-image --to-pinhole
camera0.cameramodel camera1.cameramodel
'image*-cam0.jpg' 'image*-cam1.jpg'
Wrote image0-reprojected.jpg
Wrote image1-reprojected.jpg
Wrote image2-reprojected.jpg
...
to "manually" stereo-rectify a pair of images πΈ
### To "manually" stereo-rectify a pair of images
$ mrcal-stereo \
--az-fov-deg 80 \
--el-fov-deg 50 \
--outdir /tmp \
left.cameramodel \
right.cameramodel
Wrote '/tmp/rectified0.cameramodel'
Wrote '/tmp/rectified1.cameramodel'
$ mrcal-reproject-image \
--outdir /tmp \
/tmp/left.cameramodel \
/tmp/rectified0.cameramodel \
left.jpg
Wrote /tmp/left-reprojected.jpg
$ mrcal-reproject-image \
--outdir /tmp \
/tmp/right.cameramodel \
/tmp/rectified1.cameramodel \
right.jpg
Wrote /tmp/right-reprojected.jpg
$ mrcal-stereo \
--already-rectified \
--outdir /tmp \
/tmp/rectified[01].cameramodel \
/tmp/left-reprojected.jpg \
/tmp/right-reprojected.jpg
# This is the same as using mrcal-stereo to do all the work:
$ mrcal-stereo \
--az-fov-deg 80 \
--el-fov-deg 50 \
--outdir /tmp \
left.cameramodel \
right.cameramodel \
left.jpg \
right.jpg
π DESCRIPTION π
okay so picture this: you've got image(s) of a scene, captured by one camera model. this tool takes those, and spits out image(s) of the same scene, as it would appear if captured by a different model β and it's lowkey accounting for both the different lens parameters and the geometries. it's giving mrcal-reproject-points energy, except it acts on a whole image instead of a discrete set of points. main character behavior fr fr π
there are several modes of operation, and which one you get depends on:
- how many camera models you hand over,
- whether
--to-pinholeis given, - and whether
--plane-n,--plane-dare given.
π¦ mode 1: undistort to a pinhole
to "undistort" (aka remap to a pinhole projection) a set of images captured using a particular camera model, you invoke it like this:
mrcal-reproject-image
--to-pinhole
model0.cameramodel image*.jpg
each of the given images gets reprojected and written to disk as "image....-reprojected.jpg". and the pinhole model used for the reprojection? that gets written to standard output. periodt. β¨
π§ mode 2: remap from model0 to model1
to remap images of a scene captured by model0 to images of the same scene captured by model1, do this:
mrcal-reproject-image
model0.cameramodel model1.cameramodel image*.jpg
each of the given images gets reprojected and written to disk as "image....-reprojected.jpg". and this time nothing goes to standard output. ngl, silence. π€«
by default, the full relative extrinsics between the two models are used in the reprojection. the unprojection distance (given with --distance) is infinity by default, so only the relative rotation is used by default. if you want to ignore the extrinsics entirely, pass --intrinsics-only.
π a very common use case here is validating the relative intrinsics and extrinsics in two models. if you have a pair of models and a pair of observed images, you can compute the reprojection and compare the reprojection-to-model1against images that were actually captured bymodel1. if the intrinsics and extrinsics were correct, then the two images would line up exactly for relevant objects (far-away observations with the default--distance, ground plane with--plane-n, etc).
computing this reprojection map is often very slow. π but if your use case is comparing two sets of captured images, there's a next invocation method that's much faster. keep reading bestie.
π mode 3: two sets β one common pinhole
to remap images of a scene captured by model0 and images of the same scene captured by model1 to a common pinhole projection, do this:
mrcal-reproject-image
--to-pinhole
model0.cameramodel model1.cameramodel 'image*-cam0.jpg' 'image*-cam1.jpg'
a pinhole model is constructed that has the same extrinsics as model1, and both sets of images are reprojected to this model. it's similar to the previous mode, but since we're projecting to a pinhole model, this computes much faster. ποΈ the generated pinhole model is written to standard output.
πΈ mode 4: reproject to arbitrary planes
finally, instead of reprojecting to match up images of objects at infinity, you can reproject to match up images of arbitrary planes. that's a command like this:
mrcal-reproject-image
--to-pinhole
--plane-n 1.1 2.2 3.3
--plane-d 4.4
model0.cameramodel model1.cameramodel 'image*-cam0.jpg' 'image*-cam1.jpg'
and if the models were already pinhole-projected, this does the same thing as
mrcal-reproject-image
--plane-n 1.1 2.2 3.3
--plane-d 4.4
model0.cameramodel model1.cameramodel 'image*-cam0.jpg'
so what's happening? this maps observations of a given plane in camera0 coordinates to where this plane would be observed in camera1 coordinates. and bestie, this requires both models to be passed-in. and ALL the intrinsics, extrinsics and the plane representation are used. if all of these are correct, the observations of this plane would line up exactly in the remapped-camera0 image and the camera1 image. π₯
the plane is represented in camera0 coordinates by a normal vector given by --plane-n, and the distance to the normal given by plane-d. the plane is all points p such that inner(p,planen) = planed. planen does not need to be normalized. this mode does not require --to-pinhole, but it makes the computations run much faster, as before. β‘
π the generated pinhole model
if --to-pinhole, then we generate a pinhole model, and that gets written to standard output. by default, the focal length of this pinhole model is the same as that of the input model. the "zoom" level of this pinhole model can be adjusted by passing --scale-focal SCALE, or more precisely by passing --fit. --fit takes an argument that is one of:
"corners": make sure all of the corners of the original image remain in-bounds of the pinhole projection"centers-horizontal": make sure the extreme left-center and right-center points in the original image remain in-bounds of the pinhole projection"centers-vertical": make sure the extreme top-center and bottom-center points in the original image remain in-bounds of the pinhole projection- a list of pixel coordinates
x0,y0,x1,y1,x2,y2,.... the focal-length will be chosen to fit all of the given points
by default, the resolution of the generated pinhole model is the same as the resolution of the input model. this can be adjusted by passing --scale-image. for instance, passing "--scale-image 0.5" will generate a pinhole model and images that are half the size of the input images, in both the width and height. π
π where do the images go
the output image(s) are written into the same directory as the input image(s), with annotations in the filename. and bestie, this tool will refuse to overwrite any existing files unless --force is given. respect the boundaries. π
π doing this in bulk
it is often desired to apply transformations to lots of images in bulk. to make this go faster, this tool supports the -j JOBS option. this works just like in Make: the work will be parallelized among JOBS simultaneous processes. unlike make though, the JOBS value must be specified. no auto-detect here, you gotta commit. β
β¨ OPTIONS β¨
okay bestie, here's the part where you actually get to drive π two flavors of arguments: the ones you just drop in there (positional), and the ones with dashes (optional). let's go.
πΈ POSITIONAL ARGUMENTS πΈ
model-fromβ the camera model for the FROM image(s), aka the "where we're coming from" model. and if you pass-here? bestie we read from standard input. iconic behavior tbh πmodel-to-and-image-globsβ optionally the camera model for the TO image. and then right after it (or right aftermodel-from, if you skipped that) come the from/to image globs. the details live in themrcal-reproject-imagedocumentation, so go read that, iykyk π
π¦ OPTIONAL ARGUMENTS π¦
the real ones. the flags. the moment we've all been waiting for β
-h,--helpβ shows this help message and dips. short, sweet, slays β¨--to-pinholeβ if you give this, we reproject the images to a pinhole model that gets generated off theMODEL-FROMplus--fit,--scale-focal, and--scale-image. and the generated pinhole model? written straight to the standard output. no cap π₯--intrinsics-onlyβ so if you gave two camera models, then by default the full relative transformation gets used in the reprojection. but if you only want the intrinsics? ONLY? then pass this. that's it, that's the flag π--distance DISTANCEβ okay so the fundamental operation of this whole tool is unprojecting points from one camera and reprojecting them into the other. the distance used for that unprojection is set by this argument. if you omit it, infinity is used, which is equivalent to only using the rotation component of the relative transformation between the cameras. and heads up: this option only makes sense without--intrinsics-onlyand without--plane-n/--plane-d. don't mix, it's giving chaos π--fit FITβ if we generate a target pinhole model (i.e. if--to-pinholeis given), we get to choose the focal length of the target model. this is a "zoom" operation, periodt. by default we just use whatever valuemodel-fromhas. OR we scale it by the value given in--scale-focal. OR we use--fitto scale the focal length intelligently π§ β¨. the--fitargument could be one of("corners", "centers-horizontal", "centers-vertical"), or it could be given as a list of pointsx0,y0,x1,y1,x2,y2,.... the focal length scale then gets chosen to zoom in as far as possible while still fitting all of these points. obsessed π--scale-focal SCALE_FOCALβ if we generate a target pinhole model (if--to-pinholeis given) then we can choose the focal length of the target model. this is a "zoom" operation. by default just use whatever valuemodel-fromhas. or we scale it by the value given in--scale-focal. or we use--fitto scale the focal length intelligently. pick your fighter π--scale-image SCALE_IMAGEβ if we generate a target pinhole model (if--to-pinholeis given) then we can choose the dimensions of the output image. by default we use the dimensions ofmodel-from. if--scale-imageis given, we use this value to scale the imager dimensions ofmodel-from. and bestie, listen: this parameter changes the RESOLUTION of the output, unlike--scale-focal, which ZOOMS the output. two different vibes, don't confuse them π--plane-n PLANE_N PLANE_N PLANE_Nβ we're reprojecting a plane π©οΈ the normal vector to this plane is given here, in from-camera coordinates. the normal does not need to be normalized; any scaling gets compensated inplaned. the plane is all pointspsuch thatinner(p,planen) = planed--plane-d PLANE_Dβ we're reprojecting a plane. the distance-along-the-normal to the plane, in from-camera coordinates, is given here. the plane is all pointspsuch thatinner(p,planen) = planed--outdir OUTDIRβ the directory to write the output images into. if you omit it, we write the output images to the same directory as the input images. keeping it cozy π--valid-intrinsics-regionβ if given, we annotate the images with the FROM model's valid-intrinsics region. little artistic touch π--mask-valid-intrinsics-regionβ if given, we draw everything outside the FROM model's valid-intrinsics region as black. so the unreliable regions aren't even drawn. gone. not invited to the party π--force,-fβ by default, existing files are not overwritten. pass--forceto overwrite them without complaint. it will simply not argue with you π--jobs,-j JOBSβ parallelize the processingJOBS-ways. this is likeMake, except you're required to explicitly specify a job count. no default, no guessing, just tell it π«Ά
π REPOSITORY π
the sacred link, the source of truth:
https://www.github.com/dkogan/mrcal
π AUTHOR π
Dima Kogan, <dima@secretsauce.net> β the mastermind, the main character, the one who wrote all of 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
and that's the whole page, bestie π this ismrcalversion2.5.2-4build1, dated2026-03-20, living its life asMRCAL(1). go forth and reproject slayfully β¨π¦π
π¦ 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