Wide-baseline stereo on circles grid, enormous stereo RMS

Thanks so much for providing this test code! I’ve used it as the basis of my own asymmetric circle grid stereo calibration code. Intrinsic camera parameter determination functions well (0.3 and 0.5 rms error), but when I try to achieve stereo calibration, things go horribly awry (rms of 708,000). I think this is because the left and image pairs are offset from one another by about 72°. However, apparently, fisheye lenses were not used. One constraint I have is that the data were collected before my time in the lab, and the test subjects for which the calibration is being done are not presently available. Moreover, the cameras themselves are not readily available, making even collecting new calibration images challenging.

Thus, I first wonder if you were able to get stereo calibration to work with an asymmetric circular grid pattern. If so, a set of images I could test my code on would be well-appreciated… Or more example source!

Interestingly, if I call cv2.cornerSubPix() after having called cv2.findCirclesGrid(), the rms error drops from 708,000 to 136. I tried including cornerSubPix() because I kept reading that it may not be necessary when using circular grids. I added the following code snippet:

criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 50, 0.001)

print('Using pattern type asymmetric_circles')

ret_l, corners_l = cv2.findCirclesGrid(grayL, pattern_size, flags=cv2.CALIB_CB_ASYMMETRIC_GRID)
ret_r, corners_r = cv2.findCirclesGrid(grayR, pattern_size, flags=cv2.CALIB_CB_ASYMMETRIC_GRID)

if ret_l and ret_r:
    corners_l = cv2.cornerSubPix(grayL, corners_l, (5, 5), (-1, -1), criteria)
    corners_r = cv2.cornerSubPix(grayR, corners_r, (5, 5), (-1, -1), criteria)

that’s an angle, not a distance. are you saying the cameras are “going cross-eyed”?

I think the literature counts that as “wide baseline”. that situation has unique challenges.

With OpenCV 5.0 they released algorithms for multi-camera calibration. Perhaps you should look into that. If you are working with AI, you may have to tell it to use docs.opencv.org/5.0/ explicitly rather than using Google search. Google somehow deindexed the entire docs domain.

One typical issue with calibration is that the patterns are recognized upside-down, or whatever causes the points on the pattern to be enumerated backwards. This can happen very easily if the canonical orientation is close to some notion of “zero degrees” or wrap-around.

cornerSubPix() is not for circles. Circles have no corners/saddle point in their center. Not just is it “not necessary”, it CANNOT WORK on circles. If you plot the picture along with the points after cornerSubPix you should see the points being clearly off-center.

Thanks for the feedback!

are you saying the cameras are “going cross-eyed”?

I suppose they were. I don’t actually know the cameras they were oriented. I just inherited the resultant videos. To get one of the images to best match the other, I need to rotate it about 72°. I have seen the documentation for the multi-camera calibration under the now released OpenCV 5.0., but I wanted to be sure what I’m working with is a good foundation before going that route (also, when I first looked it up, it hadn’t been released yet). The set-up actually used four cameras, but two of them work at a lower resolution (1280x720, rather than 1920x1080) and frame rate (500, rather than 1000). But yes, I absolutely do intend to explore that code soon.

I’ve also read of that occurring, but I don’t see how it would, since the pattern coloration and arrow ordering demonstrate the start and enumeration order. I can double check that it is properly order in all of the calibration image pairs.

Concerning cornerSubPix(), the following did come from geeksforgeeks.org (https://www.geeksforgeeks.org/machine-learning/calibratecamera-opencv-in-python/), but notice the comment at the end of this source code blurb.

# If true is returned, 
# then 3D and 2D vector points are updated and corner is drawn on image
if ret == True:
    obj_points.append(obj3d)

    corners2 = cv.cornerSubPix(gray, corners, (11, 11), (-1, -1), criteria)
    # In case of circular grids, 
    # the cornerSubPix() is not always needed, so alternative method is:
    # corners2 = corners
    img_points.append(corners2)

That said, it appears that the actual improvement came from enforcing a criteria (of (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 50, 0.001), rather than (None, 0, 0)), which, apart from within cornerSubPix(), is finally applied in cv2.stereoCalibrate(). However, in this case, calling cornerSubPix() produced absolutely no ill effect, as can be seen in the attached image, which was displayed and saved after having called cornerSubPix(). Moreover, the reprojection error is exactly the same in either case: 136.07265517186306. [I now realize it doesn’t strictly make sense to call a corner enhancing function on a circular pattern, but then, neither does calling drawChessboardCorners(). I imagined perhaps each result of the iterative process was reversed.]

(I know the pattern’s tiny! I didn’t collect these…)

please just show some pictures. human prose is too inaccurate.

That is a SEO junk site. Never trust it. Always assume they are maliciously trying to mislead you. Correctness, accuracy, truth don’t factor into what they post. Their only goal is to rank at the top in Google results, for the advertising money.

That comment in that code proves they are a profoundly incompetent lot. I’d have much stronger words to describe them.

what do you mean? broken termcrits (the second expression) must cause everything to fail. sensible termcrits are required.

please don’t think like that. the scientific term for that is “magical thinking”, the same fallacy that underpins homeopathy and cargoculting. do not just do things because they have “no ill effect”. in that instance, “no ill effect” was its failure to do its job, and you are lucky it failed gracefully (it returned the input points). if it had found something in the image to interpret as a corner, which would likely have been the perimeter of a circle, you’d get completely ruined points instead. you decide if your code should rely on luck to not fail catastrophically. and you must understand what a thing does and how it does that, how it can fail, when to use it (appropriate use) and when not (inappropriate use).

it is. and that fact makes the entire picture useless for intrinsic or extrinsic calibration. it shows no “foreshortening”, but that is the property that is crucial to calibration.

Thanks for the great feedback!

I understand what you’re saying. But it is confusing that we’re discussing corners at all when applying calibration to circular grids. This makes it difficult to determine what points are necessarily corners (or should be), and what points aren’t. Perhaps wrapper functions would be useful…

I don’t exactly know what I mean yet. I’m learning as I go along. I thought that if I had no criteria, then there would be no max iterations and no cutoff. (It appears that the documentation for OpenCV 5 is much cleaner. However, for previous versions, it’s been very difficult for me to follow. There are several different ones, and I often don’t know what’s been superseded or precisely where to go to learn from competent sources.)

Very poor wording on my part. How about, ‘no observable ill effects’? Also, I didn’t mean in general; I only meant in that usage case. That is, from having called cornerSubPix()in this scenario, I couldn’t determine that anything had gone awry. Also, because the results improved dramatically, I initially concluded that that function call was responsible for the improvement. I would say that I was extremely unlucky that the function failed gracefully. It would have been much better for it to have failed catastrophically, because then I could have detected the error myself, rather than imagining that there was something funny going on in the function (because really, corners ought to be corners).

I suppose that in the absence of known documentation, I could have looked at the source code. But I’m still not used to source code being open. I’m used to opaque APIs, where the documentation tells you what you need to know. So, I sought out documentation.

Thanks again! However, I wouldn’t know this straight off—or anyway, didn’t. I thought that it might also depend on the initial resolution. I also obtained intrinsic results that were about 0.57 for the left and 0.32 for the right camera, which, being well below 1, I considered fine for preliminary investigation. I also expected that the other two cameras ought to help considerably because, for one thing, they didn’t view the image (however tiny) from the same orientation, so their amounts of foreshortening are likely to be different.

Additionally, concerning foreshortening, I wasn’t sure it was necessary at all because of this former error: findCirclesGrid can’t detect some asymmetric grids, as in, what was the code doing before this bug was fixed? So, I thought perhaps the distance from the cameras (as estimated by circle size) was being used to obtain such information. Also, in Calibration Patterns Explained, calib․io claims that both the elliptical and projected circle center need to be accounted for, and that OpenCV does neither. Is this still true in OpenCV 5? If so, how much work would it take to address? (I may not know anything now, but I’ve felt the pain, and might want to take a good crack at that later, if the need remains.)

As for the lack of video resolution, I thought that this would be problematic, albeit not necessarily for calibration. Instead, I thought that I’d have no hope of determining the head versus body position accurately, unless I labeled each frame of interest manually. Thus, I came here from automatic animal pose estimation, so that I might extract 3D coordinates and use those to help determine if that is indeed the case, and that software does not support circle grids.

Finally, I’m unable to attach multiple images because I’m a new user: “An error occurred: Sorry, new users can only put one embedded media item in a post.” So, here’s the right counterpart of the left image I posted earlier, to demonstrate what I mean by circa 72° rotation:

Re: “no ill effect” and the thinking I suspected, my communication style tends to “ramp up” if I get a whiff of certain things. That is to get ahead and in front of certain patterns, even before there is evidence of it, because of my own impatience and the sadly high probability of the evidence coming later. I meant no offense.

I am 100% with you that the docs have been lacking. v5.0 docs are said to have received some improvements. I don’t know yet what those are. v5.0 being so new, I haven’t worked with these docs yet, can’t judge them. Regardless, the docs deserve more improvement, of the “manual work and thought” kind, not so much the styling and presentation kind.

Points and corners, and how that relates to circles, it’s complicated. Usually you can take “corners” as a synonym for “points” and vice versa. There are very few instances where corners actually means corners, and cornerSubPix is one such situation. Circles as a calibration grid is an uncommon flavor of “points”. Corners are typically “saddle points” in the image, i.e. checkerboard corners. These can be located at greatest accuracy (as done by cornerSubPix).

The issue with circles in 3D is that under perspective projection to 2D, the apparent center of the projected circle, which is an ellipse, deviates from the projected center of the 3D circle. Note the order and meaning of the words. This is a geometric fact and makes circle grids actually terrible for calibration. The more out-of-plane the circle presents, the greater this difference, but for calibration you WANT out-of-plane rotation, for the perspective foreshortening. Circles’ only real advantage is that in some situations, circles are easier to make than checkerboard corners. Another reason is that sometimes your camera is so bad that you’re already struggling to see circle-shaped blobs, and can’t resolve corners at all. That has been the case with a thermal infrared camera I got to use many years ago.

So no, AFAIK opencv does not deal with this. It might, but I’m unaware of this. Dealing with this is difficult because compensation requires knowing the out-of-plane rotation. I doubt it can be solved in closed form. it probably requires iteration, at least a few iterations.

Note that even calib.io only claims “more” accuracy for their own solution, but not that they’re getting unbiased results.

Circles grids are not made for accuracy. I’ve seen some companies recommend circles grids and sell circles grids for their own systems. IDK what they were thinking. Perhaps not much.

Some geometric considerations can probably lead to unbiased results for circles, but I would simply not bother and just go with saddle points.

If you can do your calibration with software that supports circles grids, you should be able to transfer the calibration to other systems. It might require some calculations to adapt the matrix and distortion coefficients.

I have no idea how foreshortening should relate to that bug or to finding the grid. Looks like it “simply” failed to enumerate the pattern’s points correctly, and failed entirely in some cases where “recovering the grid” was made sufficiently more difficult by perspective.

By the way, I’ve been meaning to contribute a much more generic, robust, and flexible “grid recovery” algorithm. That would make “charuco” boards entirely moot. the only reason someone came up with “charuco” boards is that the usual checkerboard detection code FAILS if the pattern is incomplete. that is a failing of the detection code, not a failing of the good old (and robust) checkerboard pattern.

Thanks for the second picture. That’s approximately the situation I imagined.

The pattern is enumerated the same in both views. I have no real hypothesis as to why repro error should be that high. The solver must have converged to a wildly implausible solution. Most likely that is due to the patterns not covering the entire view, thus yielding distortion coefficients that satisfy the patterns, but show great distortion towards the edges of the views.

Can you share the camera matrix and distortion coefficients?

None taken. I fear I may sometimes do likewise, but without ever noticing. At any rate, I don’t mind a little butt-kicking to gain good answers. Totally worth it! Sometimes, I don’t know how else I would obtain so much information.

I just installed OpenCV 5 via conda (miniforge), so I’ll give it a go and have a look at its documentation, which does seem to be a lot cleaner. [Unfortunately however, AFAIK, none of the pre-compiled opencv libraries available via conda include the contrib packages, and definitely not CUDA support. I thought of building OpenCV 5, but was only able to retrieve the CPU-only source (although I didn’t attempt to retrieve the source via conda). Lack of CUDA isn’t important to me now, especially since the new engine is still CPU-only, but I may need some contrib packages.]

Once you’d explicitly pointed it out, that made a lot of sense, because I had looked up Harris Corner Detection, and one can see that cornerSubPix() doesn’t have a parameter to specify that it’s receiving a circle, so it couldn’t possibly satisfy opposing criteria. I just hadn’t put the two together.

Yes… I’d read about application of asymmetric circle grids to thermal cameras, but I’d forgotten how terrible their resolution used to be, so, although I didn’t collect the data or the corresponding calibration, I certainly wasn’t thinking of not being able to resolve corners at all as when this grid class should be used. I was only thinking of much more precise animal pose recognition, but I guess in the case of thermal cameras (etc.), you use whatever you can get.

I do not understand bias in this context. Where should I go to read more about it? How can you know that your biased result is more accurate, since the bias itself may be inaccurate?

I have too…

This. OpenCV should be trumpeting this point, especially since there is so much known fallacious information concerning it out there. Even as support for circle grids is expanded, it should be clearly stated that this grid form is to be avoided, except in the rare thermal case you mentioned above (or if, for some reason, you can only obtain a very limited number of calibration images). At least until or unless the corrections you mention may apply are also provided or otherwise easily available.

If I had other data, I might not bother either (or would only do so to gain greater insight). But so far, I don’t.

Well, if I can, it would be from OpenCV. For my use case, you already outperform MATLAB, and I don’t want to pony up buckage for opaque software that might provide better results, which I nonetheless expect would still be insufficient. However, I don’t understand what you mean by transferring the calibration to other systems. What do you mean by “other systems”? I thought that as long as the relative positions of the cameras remained consistent, and so did the cameras themselves and their lenses, then the same extrinsic and intrinsic calibration applies, only that more or less refined parameters could be obtained from a different kind of grid, or from more challenging collection circumstances, in the case of partial occlusion.

Isn’t foreshortening a form of perspective?

Any idea of when that contribution might make its debut? I’m for supporting as many grids to the highest degree as feasible, because some data can be hard to obtain, and then you’ve only got the calibration grid it was collected with.

They certainly don’t cover the entire view…

Here are the camera matrices and distortion coefficients:

Left camera intrinsic parameters:
[[57711.39149507121, 0.0, 969.4391598343224], [0.0, 18084.305855652063, 517.4350617464842], [0.0, 0.0, 1.0]]

Left camera distortion coefficients:
[38.26993895418094, -6972.758752951257, 1.0781937674228974, -0.0954462285924587, -37.8133092773903]

Right camera intrinsic parameters:
[[16228.966150748682, 0.0, 957.1090488270004], [0.0, 15085.707001085935, 537.1123617941882], [0.0, 0.0, 1.0]]

Right camera distortion coefficients:
[3.4618162172099005, 222.28266616111136, 0.08763855453306493, -0.25452855984845424, 0.6366740321843442]

Hmm. It is somewhat disturbing that the focal length is so different between the two camera, given that I think they were both using the same (unknown) fixed camera lens…

When cv2.stereoCalibrate()is called with flags=cv2.CALIB_USE_INTRINSIC_GUESS, rather than cv2.CALIB_FIX_INTRINSIC, the resultant camera matrices and distortion coefficients are the following:

Left camera intrinsic parameters (possibly updated):
[[43713.95149911353, 0.0, -302.44767813632194], [0.0, 10086.411839574377, 113.29474964910439], [0.0, 0.0, 1.0]]

Left camera distortion coefficients (possibly updated):
[309.93970861885214, -9791.178443208812, -0.5149032842527181, -9.139777025342118, -11239.073080795077]

Right camera intrinsic parameters (possibly updated):
[[44086.414339342926, 0.0, -475.78297501653327], [0.0, 17768.421615711402, 124.13615595948114], [0.0, 0.0, 1.0]]

Right camera distortion coefficients (possibly updated):
[-5.228133532870598, 162.34703439719462, 1.7975605314388026, 0.6821279979630578, -429.706811944283]

Moreover, the reprojection error dropped from 136.073 to 69.470.

In this context, the difference between the center of the ellipse (projected circle), and the 3D circle’s projected center, always deviates in a particular direction that directly relates to which way the circle faces, relative to the camera’s image plane. I’ll call that “bias”. if the calib pattern is parallel to the camera’s sensor plane, all the circles project straight, and the centers coincide. no bias. if the pattern faces away from the camera in any particular direction, then all the ellipse centers deviate from the projected circle centers the same way. if you were to take these ellipse centers as truth, then your estimation would be biased. the individual biases wouldn’t “cancel out”.

“bias” is a general term for something that is “off the mark” in some way. when speaking of probability distributions, or populations, then it’s a term you can use to describe when the mean (a population statistic, not just individual samples) deviates from some value that you expect it to have. let’s say we’re in some factory and they are filling bottles with water. if their machine fills bottles, and we take a statistic over a day’s production, and that happens to be distributed normally with µ=1.467 L, with whatever σ (it’s mostly irrelevant), then it would be fair to say the machine is biased negatively because it’s differing from the 1.50 L mark by (2.2%) more than someone deems acceptable (say 1%).

IIRC, at least at some point in the past, the calibration results from the MATLAB calibration toolbox were using a lens model that was normalized differently from how OpenCV does it. either the one or the other expressed the distortion coefficients independent of sensor resolution, while the other, respectively, had a distortion model formulated to take straight image space coordinates. or something like that. can easy to convert, if the distortion models are clear.

other example: if one program had a distortion model, that means the model takes true points, and distorts them into “lens-distorted points”. if another program had an undistortion model, its equation would do the opposite. since those models might be polynomials or something similar, they are costly to invert, and may not even be invertible in the general case, only if the lens is sufficiently “tame”. then, converting between those models takes some effort and numerical iteration.

that is contingent on my being bored enough and un-stressed enough to have a clear head for this kind of stuff. unless some company decides that they want me to work on it, I estimate the probability to be infinitesimal.

the idea is really not that complex. it’s just geometry, more or less. pick a point, pick its nearest neighbor, assume that you can walk the grid by taking the p_1-p_0 vector and adding it to p_1, and then look for the nearest point to that, and you should have found p_2. next step, maybe go one order higher with the differences, so you can predict curvature (or perspective distortion).
and that’s grid recovery along one dimension.
for the second dimension, either assume a rectangular grid, give p_1-p_0 a 90 degree rotation (trivial in 2D), add it, look for the nearest point, and do the same thing along that direction. or, from p_0, pick whatever remaining points are about as close as p_1 was, and walk them. this could result in the recovery of triangular/hexagonal grids. if some assumption of the grid type can be made (say, it’s square), that would take much guesswork out of it and make it more robust.
with some logic, this should be able to jump over missing points, or walk around them. It’s really just “flood fill/region growing”, except you aren’t on a square grid, but you have to discover it as you go.
this idea could, given local guidance (not looking for points, but the centers of “modules”/squares), even recover warped QR codes and similar things.

the two focal lengths of the left camera matrix being so much different (57.7e3, 18e3) worries me greatly. that would imply a non-square image sensor, but the image does not show that.

at least it seems to have figured out the cx, cy = 969, 517, which are plausible for a FullHD camera.

the distortion coefficients are absolutely wild. usually, the later coefficients go wild a little, that’s not much of a problem, but your k1, k2 are already way out of any reasonable range. the images you presented show no noticeable lens distortion, so any DCs should be very much smaller than 1.

the right camera’s matrix looks better, but still the foci shouldn’t differ by 7.5%. they should differ by something less than 1%, less than 0.1% even. the DCs are still too wild.

if those cameras are supposed to be of the same brand and model, their calibrations should be much more similar to each other than these two are.

USE_INTRINSIC_GUESS does not constrain the calibration, but it lets you start from a guess. giving that can help make convergence go faster, or hit the right local optimum, if you know what it should be, from theoretical considerations.

FIXING the intrinsics only makes sense if you already did intrinsic calibrations for each camera individually, and you think those are better than what the stereo calibration could calculate.

repro errors on that order mean the data is bad. repro error, for good calib data, should be below 1, meaning it is good enough to explain pictures to an accuracy of better than one pixel. some systems are so high resolution, and/or have such wide field of view, that the repro error may rise above one pixel, but it shouldn’t rise above, say, 3-10 pixels, and if it did, I would recommend thinking about whether the distortion model is too simple to model the lens, or whether the calibration data is somehow flawed.

my opinion: bad calibration data. and that is not due to circles, but due to insufficiently covering the view.

to be able to confidently assert that, I would have to see the entire data set.

what I’ve seen so far is one pair of pictures, and I wouldn’t want to judge from N=1, but if that pair is representative…

bad calibration data is usually not salvageable.

sometimes it might be, if there are image pairs that could be called “outliers”, and all other pairs are fine, then you could kick such a bad pair out of the set.

Good point! I didn’t consider fy because I assumed the pixels are square (they are). I preferred the left camera matrix because a) I thought its fx focal length made more sense; b) I don’t understand distortion coefficients yet; and c) when I tried only supplying stereoCalibrate() with one pre-solved camera matrix and set of distortion coefficients (from cameraCalibrate()) (the corresponding parameters for the other camera having been zeroed out), I got a lower rms when the left camera matrix and distortion coefficients were provided. The two cameras are definitely the same make and model.

I’ll look into the distortion model—especially now that I have finally found reliable documentation (LLMs invading search is often really not so helpful; they’re could be good for parsing questions, but not providing reliable answers)—but I’m almost certain the real problem is bad calibration data due to insufficient view coverage.

AFAIK, because I’m a new forum member, I’m still limited to attaching a single image per post; if that can be alleviated, I can post the others. But either way, I can assure you that a large portion of the field of view is not covered in the calibration videos. However, here is something else that might also play a role:

So far, I’ve used the same images for single camera calibration as for stereo calibration. However, this seems overly restrictive, in that sometimes one camera may have a complete view of the entire grid, while the other does not. This would be fine for use in calibrating the one camera, and could allow for greater coverage, but would fail for stereo calibration. Later today, I’ll add another folder where images can be placed for single camera calibration, alongside those used for stereo calibration.

Additionally, there were four cameras used to collect the data. If the totality of represented frames from them all constitute sufficient coverage (I don’t know if they do or not; I just chose the pair with highest resolution because I knew I’d need it for improved pose estimation), would using calibrateMultiview() be able to salvage the calibration?

Finally, what is the advantage of having resolution below half a pixel? (I can imagine roundoff error affecting calculations with >0.5 pixel resolution.) Does it come into play when one camera has higher resolution than another? Could one, for example, aim with greater precision than their actual vision by means of such resolution?

don’t do that, unless you mean to understand the standard model more deeply. my suggestion was a hypothetical for situations other than yours. the standard distortion model is fine. your issue is that the calibration data was gathered wrong, and perhaps the “experimental setup” might be flawed.

yes, the requirement of overlap for a stereo calibration excludes images that are useful for intrinsic calibration. that is also why stereo calibration functions support using individual intrinsic calibration results during stereo calib.

that’s a new function coming with v5.0. it should be worth checking out. I have never used that before and don’t know its requirements.

you ask questions that take a lot to answer.

first, resolution defines what a pixel is. it is not expressed in pixels. I’ll assume you meant accuracy, or maybe reprojection error.
since you are dealing with stereo vision, accuracy matters a lot. an appreciable amount of effort goes into squeezing accuracy from the data.
you haven’t said “disparity map” yet, so I can’t assume or go into that math. a “pixel” is only a thing because our images are discrete things (image sensors discretize an area of light into pixels). if they weren’t discrete things, we’d instead talk about angles or tangent values. the more precisely you can express angles, e.g. of “sight rays”, one from each eye, that intersect the same physical point, the more precisely you can calculate the distance to that point. the closer the point is to your eyes/cameras, the more stable is that result. the further away, the more inaccurate it gets. equal triangles and all that.
algorithms to match one image to another, for the purposes of stereo vision, operate by matching blocks, i.e. small image patches. that allows matching at subpixel resolution. OpenCV’s stereo matcher algorithms typically express their disparity values with 4 bits of subpixel resolution, or 1/16 of a pixel.
two cameras having differing resolutions don’t matter all that much. if you go the “stereo vision with disparity map” path, at some point the images get “rectified”, which applies camera matrix and lens distortion model in such a way that you get two new pictures that show map “epipolar lines” to be parallel (and horizontal). that’s a requirement for block matching. that step also equalizes the resolutions of both cameras, so either one image gets blown up, or the other’s “excess” resolution gets thrown away.

It wasn’t a fisheye lens, so, yes, I think that the standard distortion model is fine for it. (There’s a limited number of lenses it could be, but I’m not sure which of them it was; that information was not explicitly recorded. And since it’s a c-mount, the camera couldn’t record that information automatically.) But I don’t know much about it, either. I need to know enough to know when/if I’ve recorded garbage.

Agreed.

Yes. But I think I’m going to use one script, while collecting them separately from the stereo frames, so that in saving the directory structure with images in it, I can document what constitutes good images for determining intrinsic parameters and stereo calibration all at once.

I could also pair different of the four cameras together, to see if that helps. The biggest problem there is that I don’t have a good sense of how far some of the cameras were from one another. (They’re arranged in two stereo pairs, but I don’t know how “wide” the resultant rectangle was. I’ll assume it must have been similar to what it is today.) I can see that we’re going to need new data, but I already expected that. So, I think I’d better explore all the possibilities of the existing data to dismiss any doubt.

I mean the smallest size by which the visual system perceives visual information. Can one respond with greater accuracy and precision than they can actually visually perceive the world?

I may not be applying it entirely correctly yet, but I do already call stereoRectify(). However, since I’ve only used it with cameras of the same resolution, I never bothered checking newImageSize. Also, the images are so poorly rectified that the horizontal lines I draw along it never intersect the same part of both images, although reducing the rms usually gets them to align more closely.

But either way, the accuracy of the calculated distance to a given point is not reduced, right?

One last thing: Great emphasis is placed on reducing the rms. However, from this discussion, I see that doing so it not always meaningful. So, under what circumstances should the rms not be trusted? When the epipolar lines fail to be horizontal is one of them. Discordant focal lengths and distortion coefficients is another. Is there some value for which the rms is so low that these other issues can’t have arisen, or is it always wise to have a look at (and to understand) these other variables?