What a machine computes

A pixel is not a point

Where the sample sits inside a pixel is a convention, and getting it wrong shifts every mark by half a pixel in each axis. This site can say what that costs, because it recovers cameras from pictures — the answer is a principal point exactly 0.707 px from the truth with the focal length untouched, and the other half-pixel mistake does precisely the reverse.

Worth reading first: The divide is postponed, not avoided · Recovering the camera from the picture it drew.

The last step of the pipeline is arithmetic that looks like nothing. Normalised device coordinates run 1-1 to +1+1; the viewport is WW pixels wide; so

u=(xndc+1)W2u = \frac{(x_{\text{ndc}} + 1)\,W}{2}

and the point has landed on the picture. There is no geometry left in this step, no projection, no divide. It is a change of units.

It is also where two independent half-pixel mistakes live, and this site is in an unusual position to say what either one costs — because it does not merely draw pictures, it recovers cameras from them. A half pixel is not a quantity a picture can be inspected for. It is a quantity a recovery can name, and the two mistakes turn out to be named differently.

Where the sample sits inside the pixel, and what a recovery calls the differenceSampling at the pixel's corner instead of its centre translates every mark by half a pixel in each axis — 0.7071 px — and the site's own recovery reads that as a principal point 0.707 px from where it should be, with the focal length changed by 4.5e-13 px. The other half-pixel error, mapping the viewport onto W−1 pixels instead of W, does the opposite: it leaves the principal point alone and shortens the focal length by 1.30 px.one pixel is an areacentrescornersprincipal point moves0.707 pxfocal length changes by4.5e-13 pxan edge-versus-centre viewport1.303 pxa half-pixel convention is a principal-point error; an off-by-one viewport is a focal-length error8 vertices, all shifted by the same 0.7071 pxspread across marks 0.0e+0 px
Fig. 1 The grid, magnified, with the two candidate sample positions marked, and the recovery’s verdict beside it. Taking a pixel’s corner as its sample point translates every mark by 0.7071 px — half a pixel in each axis — and the recovery reads that as a principal point 0.7071 px from the truth with the focal length changed by nothing at all. The other error does the reverse.

What a pixel actually is

A pixel is not a point. It is a small square area of the picture, and everything about it that matters here follows from that being taken literally.

A picture WW pixels wide covers the interval from 0 to WW, and pixel ii occupies [i,i+1)[i, i+1). Its centre — the place a sample taken to represent it should be taken — is at i+12i + \tfrac12. Its corner is at ii.

Those two differ by half a pixel and both are perfectly reasonable-looking things to write. i is what a loop index supplies. i + 0.5 requires knowing why. So the mistake is not exotic: it is the default that arises from writing the obvious loop.

The first mistake: sampling at the corner

Take the correct pipeline and change one thing — treat the pixel’s corner as its sample point rather than its centre. Every mark in the picture moves by 12-\tfrac12 px in xx and 12-\tfrac12 px in yy, so by 1/2=0.7071\sqrt{1/2} = 0.7071 px along the diagonal.

Two facts follow and the gate asserts both.

Every mark moves by the same amount. The spread across the eight vertices of the test box is 0.0 px exactly — not small, zero — because the operation is a translation and a translation moves everything equally. That is worth checking rather than assuming, because a change that moved marks by different amounts would be a distortion and would need a completely different account.

And a recovery reads it as a principal point error. Run the site’s round trip on the shifted picture: three bundles of parallel edges, three vanishing points, and the focal length and principal point that follow. The focal length comes back at 898.755727 px against a true 898.755727 — identical to six decimal places, and in fact to the last bit. The principal point comes back 0.707107 px away.

That is not a coincidence and it is not an approximation. A translation of the image plane is exactly a translation of the principal point: the picture is a perfectly good projection, of a camera whose principal point is half a pixel from where the renderer believes it is.

A box drawn from a known camera, and the camera recovered from the drawingThree vanishing points found from the twelve drawn edges alone give back the focal length to 4e-15 relative.recovered principal pointused to drawrecoveredgapfocal length853.90853.904e-15principal x345.0345.02e-12angle44.0°44.0°correct from 20 cm, at 160 mm wide44° across
Fig. 2 The recovery doing the work. Three bundles of drawn parallel edges give three vanishing points; the orthocentre of their triangle is the principal point and the three dot products give the focal length. Nothing in this computation is shown the camera, which is why what it says about a half-pixel shift is evidence rather than restatement.

The second mistake: mapping onto W − 1 pixels

The second error is quieter and it is the more common one, because it comes from a genuine ambiguity about what “the width of the picture” means.

A picture WW pixels wide has pixel indices 00 to W1W-1. So the mapping from NDC to pixels is sometimes written to send 10-1 \mapsto 0 and +1W1+1 \mapsto W - 1, which puts the extreme device coordinates at the centres of the outermost pixels rather than at the outer edges of the picture.

That is not a translation. It is a scaling, by (W1)/W(W-1)/W, and at W=690W = 690 that is 0.99855 — a shortfall of 0.145%.

And a recovery names it differently. The focal length comes back short by exactly that factor: 1.30 px on a focal length of 899. The principal point does not move at all, because the scaling is about the picture’s centre, which is where the principal point already is.

So the two half-pixel mistakes are orthogonal in exactly the sense a calibration cares about:

principal point focal length
corner instead of centre moves 0.707 px unchanged
W1W-1 instead of WW unchanged short by 1.30 px

Two errors of the same size in pixels, one affecting each of the two things a camera recovery returns. Neither is visible in the picture.

The cost of assuming the principal pointThe two-vanishing-point route to a focal length needs a principal point supplied, and every textbook supplies the middle of the frame. On a shifted or cropped picture that is wrong, and f² = −(v₁ − p)·(v₂ − p) turns the error into a product of two large numbers: 1.6% at 150 px of shift. The three-point recovery does not assume it and has no such error.00.50011.50050100150how far the principal point really is from the middle of the frame (px)error in the focal length, from assuming it is not (%)unshifted: the assumption is truea 44° lens1.55% at 150 px of shift
Fig. 3 Why the two are worth separating. The lens field already measured what happens when a principal point is assumed rather than fitted: the error is absorbed into the focal length by a factor that depends on the field of view. So a pipeline making both half-pixel mistakes at once does not simply add them — the second is partly disguised by the first, and separating them requires knowing which is which.

Is 1.3 px a lot?

It depends entirely on what the picture is for, and this site is one of the few places where the question has both answers written down.

As a picture, it is nothing. A misplaced principal point of 0.7 px shifts everything by less than the width of a hairline. No viewer will ever see it, and no figure on this site would look different.

As a measurement, it is not nothing. Every essay in the metrology field reads a scene out of a picture, and every one of them is sensitive to the intrinsics. A height from a photograph is a cross-ratio along a vertical, which the principal point enters through the horizon. The plumb-line fit recovers a lens’s distortion by minimising the sag of lines that ought to be straight, and it is measuring sags of a few pixels — so a systematic half-pixel bias is a substantial fraction of the signal.

And it is a systematic error, which is the important word. Random error at half a pixel averages away over many measurements. A convention error does not: it is the same half pixel in the same direction in every picture the pipeline ever produces, and averaging a thousand of them gives the same wrong answer with a smaller standard error.

A 3.6 m object, measured off an uncorrected frameOn a pinhole picture the horizon-fraction recovery returns 3.6 m exactly. Through a lens with k₁ = -0.24 it returns 3.59 m — 0.16% out. A 2.2 m object at the same spot on the same lens comes back 0.14% out, because what costs is the radius the three marks span, not where in the frame they are.3.593.603.60-0.400-0.2000k₁ of the lens the photograph was taken withheight recovered from the photograph (m)the true 3.6 m3.59 m3.6 m tall, 11 m away, on a level camera0.16% out — against 0.14% for a 2.2 m object
Fig. 4 The lens field’s version of the same argument. An uncorrected systematic error in the imaging model produces a height measurement that is confidently wrong, and repeating the measurement does not help because the bias is in the model rather than in the reading. A half-pixel convention error is the same kind of thing, three orders of magnitude smaller and just as unaveraged.

Why the recovery is the right instrument for this

There is a more direct way to detect a half-pixel error: render a scene twice with the two conventions and subtract. That works, it takes one line, and it reports almost nothing — that the pictures differ by half a pixel, which was already known because the difference was introduced deliberately.

Recovering the camera reports something that was never put in: what kind of camera the wrong picture is a picture of. The answer — a real camera, with an intact focal length and a displaced principal point — is not obvious in advance, and it is the answer that decides what to do about it.

If a half-pixel convention error produced a picture that was not a projection of anything, the fix would be urgent and detectable. It produces a picture that is a perfect projection of a slightly different camera, which means:

  • no residual anywhere reports it — the drawn edges are exactly concurrent;
  • no consistency check reports it — the three independent focal estimates agree to 1e-16;
  • and any calibration performed on such pictures will recover the shifted principal point, faithfully, and be right about the camera it was actually shown.

That last one is the interesting case, and it is genuinely benign: a system that both renders and calibrates with the same wrong convention is internally consistent and its measurements are correct. The error only bites when a picture made under one convention is measured under another — which is exactly what happens whenever an image passes between two pieces of software.

The three vanishing points of one box, drawn to scale with the boxThe picture is the small rectangle. Two of the three vanishing points fall well outside it, which is why they are computed rather than located by eye.the pictureVP₁VP₂VP₃orthocentrefocal length from the triangle — 853.9 pxspread 1e-14% across three routes
Fig. 5 The three points the recovery reads, and the triangle they make. The principal point is the orthocentre of that triangle, so moving every drawn mark by half a pixel moves all three vertices equally and moves the orthocentre by the same half pixel — which is why the shift lands entirely on the principal point and leaves the focal length exactly alone.

The convention this site uses, and why it is stated

This site’s camera puts the principal point at (W/2,H/2)(W/2, H/2) by default and projects to continuous coordinates, and the pipeline maps NDC 0 to W/2W/2 — the boundary between the two middle pixels of an even-width picture, not the centre of any pixel.

That is the centre convention and it is the one that makes the matrix pipeline agree with the pinhole to 5.7×10145.7 \times 10^{-14} px. The corner convention is implemented too, because an essay measuring a wrong convention needs the wrong convention to exist, and a claim about what a mistake costs is worthless if the mistake was never actually made.

The general form of that is a habit this whole site runs on: an assertion that has never rejected anything proves nothing, so every gate here ends by feeding its machinery input it must refuse. The corner convention is that input, for this claim.

Why the two errors do not simply add

A pipeline could easily make both mistakes at once, and it is worth working out what a recovery would then say, because the answer is not the sum of the two rows in the table.

The corner convention translates; the W1W-1 convention scales about the picture’s centre. Applying a scale and then a translation gives a map whose fixed point is not the picture’s centre, so the composite is a scaling about a slightly different point — which a recovery reports as both a shortened focal length and a displaced principal point, in a proportion that depends on the order the two were applied in.

So the two errors are separable in principle and entangled in practice. A calibration performed on pictures from such a pipeline recovers a self-consistent camera; comparing that camera against the one the renderer believes it has gives a discrepancy in both parameters; and there is no way, from the discrepancy alone, to say which of the two mistakes was made or whether both were.

That matters because the natural debugging move — measure the discrepancy and correct it — works and teaches nothing. The camera is corrected, the pipeline still has two bugs in it, and the next picture rendered at a different resolution is wrong again, because one of the two errors scales with WW and the other does not.

That is the diagnostic. Render the same scene at two resolutions and recover the camera from each. A corner-convention error gives a principal-point offset of 0.707 px at both, in absolute terms. A W1W-1 error gives a focal-length shortfall of one part in WW — half as large, in relative terms, at twice the resolution. One is constant in pixels and one is constant in fractions, and comparing two resolutions separates them.

The cost of assuming the principal pointThe two-vanishing-point route to a focal length needs a principal point supplied, and every textbook supplies the middle of the frame. On a shifted or cropped picture that is wrong, and f² = −(v₁ − p)·(v₂ − p) turns the error into a product of two large numbers: 5.1% at 150 px of shift. The three-point recovery does not assume it and has no such error.024050100150how far the principal point really is from the middle of the frame (px)error in the focal length, from assuming it is not (%)unshifted: the assumption is truea 72° lens5.12% at 150 px of shift
Fig. 6 The entanglement, at a wide field of view where it is strongest. What a wrongly-assumed principal point does to the recovered focal length depends on the field of view, so two pipelines with the same pair of half-pixel bugs report different discrepancies at different lens settings — and a correction fitted at one setting is wrong at the next.

The half pixel and the pixel grid are different subjects

One boundary worth drawing, because the two get conflated and only one of them is this site’s.

Where a sample sits is geometry: it is a statement about which point of the continuous picture a stored value represents, and it has an exactly right answer that this essay computes.

What happens when a continuous picture is reduced to samples is not geometry. Aliasing, filtering, reconstruction and the whole sampling literature are a different subject with different mathematics, and this site takes no position on any of it. The fleet’s register of which site owns which argument records the boundary, and the test that separates the two is this: every argument here survives with every sample taken exactly, and a sampling argument does not.

The distinction is easy to hold onto by asking what the answer depends on. A half-pixel offset is decided by the coordinate convention alone and is the same for every scene. Everything in the sampling literature depends on what is being sampled.

Seven identical spheres across a 84° frameThe outer sphere images 27% wider than the central one. That is what a correct rectilinear projection does, and it vanishes if the picture is viewed from 9 cm.54 px69 px84° across27% wider at the edge
Fig. 7 An example of the distinction. What happens to a sphere’s drawn shape across the frame is projective — it is the stretch the viewing field measures, computable from the angle off axis and nothing else. What happens to its edge once the picture becomes a grid of samples is not, and the two questions have no arithmetic in common.

The general lesson, which is about units

Both mistakes in this essay are unit errors in the strict sense: a quantity measured from one origin used as though it were measured from another, and a quantity counting intervals used as though it counted endpoints.

That second one — the fencepost — is the more instructive, because it recurs everywhere and is genuinely ambiguous rather than merely careless. A picture 690 px wide has 690 pixels and 691 pixel boundaries, and both counts are the right answer to some question. NDC ±1\pm 1 maps to the boundaries; pixel indices 0 to 689 count the pixels. Choosing wrong is not a slip, it is answering the wrong one of two reasonable questions.

The reason it matters here rather than being a curiosity is the one this whole site is built on: a picture does not carry the conditions under which it is a picture of anything. It does not say what its focal length was, where its principal point is, or which of two pixel conventions its coordinates are in. All of that has to be stated separately — and when it is not, the picture goes on looking exactly right while the measurements made from it drift by an amount nobody can see.

Level, tilted, shiftedTilting the camera up to fit the building in makes the verticals converge by 4.55°. Shifting the lens up instead moves the principal point 95 px off centre, frames the same view, and leaves the verticals parallel to 0e+0° — because the picture plane never tilts.level — the top is cut off0.00° of spreadtilted 13°4.55° of spreadshifted 95 px0.00° of spreada shift moves every point by exactly the shift95.0 px, and no direction at all
Fig. 8 The largest version of the same fact, from the lens field. A shifted lens moves the principal point by a hundred pixels or more, deliberately, and the picture stays a perfectly ordinary picture with its verticals parallel. Half a pixel is the smallest member of that family and it behaves identically: the principal point is a property of the camera that the picture declines to report.
One projection, two routes: divide by depth, or multiply and divide laterThe same box through the site's pinhole and through a 4×4 projection matrix with the divide postponed until after clip space. The worst disagreement over all twelve edges is 5.7e-14 px, which is the noise floor of double precision rather than an approximation.x/z, y/z — the pinholeM·p, then divide by wworst disagreement 5.7e-14 px over 8 verticescorrect from 30 cm, at 160 mm wide30° across · near 0.1 m, far 1000 m
Fig. 9 And the agreement the whole field rests on, at a narrow field of view. Every claim in this essay is a claim about a departure from this — which is only worth measuring because the baseline is exact to five parts in a hundred trillion rather than to a pixel.

Shares its objects with

Essays that name at least two of the same things, and that neither author linked.

Named objects

A flat tag is an object no other essay names yet.

Camera calibrationCamera matrixDemonstrationFocal lengthPrincipal pointProjected not constructedQuantisationResidualSampling gridVanishing point