The Pinhole Model: Why a Hole Makes a Picture, and Where the Prediction Fails

Lesson 1 of the Image Formation unit. A pinhole projects a scene point to a pixel by similar triangles, and that is a prediction you can check. Worked on one board corner from OpenCV's calibration set: the model puts it at (523.98, 77.94) and the detector found it at (513.77, 86.53), 13.34 px apart. Across the view the error is 0.29 px in the middle third of the frame and 5.75 px in the outer third, which is the shape of the problem the rest of the unit solves.

Luis Condados · · Updated October 4, 2026 · 8 min read Beginner
How far the straight-line model misses, against how far from the optical axis the point sits. Flat in the middle of the frame, steep at the edges.
How far the straight-line model misses, against how far from the optical axis the point sits. Flat in the middle of the frame, steep at the edges.

The pinhole model says where any point in front of a camera lands in the picture, and that is what lets software draw an object into a photograph so that it looks as if it is standing there. Below, a cube is drawn onto a chessboard in each of thirteen photographs.

A person holding a chessboard at different angles; on each photo a green-based cube stands on the board, leaning with it
For each photograph the board’s position is estimated from its corners, and the cube’s eight corners are pushed through the pinhole model, with the lens’s distortion added. Nothing about the cube is drawn by hand. Calibration images from the OpenCV samples, Apache-2.0.

Where the same projection is at work:

  • AR apps and filters that place a virtual object on a table or a pair of glasses on a face.
  • Sports broadcasts, where lines and graphics drawn onto the playing field follow the camera as it pans and zooms.
  • Robots and drones that turn a point in the image back into a direction in the world.

A pinhole camera sends every point along a straight ray, so where it lands depends only on its sideways offset divided by its distance. On one of OpenCV’s calibration photos that prediction is within 0.29 px of the corners near the middle of the frame and off by 5.75 px near its edges, which is the lens bending the rays.

Where we are

Lesson 1 of image formation, and steps 2 and 4 of its pipeline: a point’s ray, and the pixel it lands on. Nothing on the track comes before it. The unit’s job is to explain how a three-dimensional scene becomes a grid of numbers, and the first question is why any of it is sharp.

A hole is a filter on rays

Point a bare sensor at a room and every photosite receives light from everywhere. Each cell sums the whole room, so the result is a uniform grey with no picture in it. Put a barrier with a small hole in front, and each point on the sensor can only be reached from one direction. That restriction is what makes an image: the hole turns “light from the whole room” into “light from one direction per pixel” [1].

Once each pixel corresponds to one ray, the geometry is a pair of similar triangles. A point sitting XX to the side at depth ZZ projects onto the image plane at distance ff behind the hole, and the two triangles share an angle:

xf=XZ,yf=YZ.\frac{x}{f} = \frac{X}{Z}, \qquad \frac{y}{f} = \frac{Y}{Z}.

Sensors are indexed in pixels rather than millimetres, and the optical axis does not necessarily meet the sensor at its middle. Writing the focal length in pixels as fx,fyf_x, f_y and the point where the axis crosses the sensor as (cx,cy)(c_x, c_y):

u=cx+fxXZ,v=cy+fyYZ.u = c_x + f_x\frac{X}{Z}, \qquad v = c_y + f_y\frac{Y}{Z}.

Two scalar equations, one division each. Every camera model in computer vision is built on top of these [2], and the rest of this unit is about the ways a real camera departs from them.

optical axis (Z)image planethe holeP = (X, Z)xfXZthe shared angle
The two triangles meet at the hole and share an angle, so their sides are in the same ratio: x / f = X / Z. Drawing the image plane in front of the hole rather than behind it avoids the upside-down image and changes nothing else.

Checking it on a real corner

The unit’s camera comes from 13 photographs of a printed chessboard [4], which give fx=536.07f_x = 536.07, fy=536.02f_y = 536.02 and a principal point at (342.37, 235.54). Note that the principal point is 22 px right of the frame’s centre. The optical axis and the middle of the sensor are different places, and assuming otherwise would start every prediction up to 22 px off before anything else went wrong.

Calibration also recovers where the board was in each shot. Take that as given for now, since recovering pose is the subject of PnP, and look only at what happens after: a corner whose position in the camera’s own frame is known, projected by the two equations above.

Averaged over all 54 corners in that photograph, the pinhole equations miss by 2.55 px. Splitting the corners by how far they sit from the principal point shows what that average is hiding:

CornersMean distance from the principal pointMean error
Innermost third56 px0.29 px
Outermost third171 px5.75 px

Near the middle of the frame the model is right to about a quarter of a pixel. Out at the edges it is twenty times worse. Whatever is wrong grows with distance from the centre, which is a strong hint about its cause and the subject of lesson 3.

Written out, the prediction is two lines of arithmetic:

# (X, Y, Z) is the corner in the camera's own frame; fx, fy, cx, cy from calibration.
u = cx + fx * X / Z
v = cy + fy * Y / Z
// (X, Y, Z) is the corner in the camera's own frame; fx, fy, cx, cy from calibration.
const double u = cx + fx * X / Z;
const double v = cy + fy * Y / Z;

What perspective does to a scene

Because the division is by ZZ, two things follow that the human eye takes for granted. An object twice as far away covers half as many pixels, so size on the sensor carries distance. And lines that are parallel in the world stop being parallel in the image unless they are parallel to the sensor, because each point along them is divided by a different ZZ.

The same division is why one photograph cannot tell you how big anything is: doubling both an object’s size and its distance leaves X/ZX/Z unchanged, so a small near object and a large far one produce identical pixels.

Now you try

Move the ball and the camera and watch the projected position follow the two equations. Then switch to the second panel, which holds X/ZX/Z fixed while changing both: two very different scenes, one photograph.

Try it: the red-ball scene in 3-D
Scene drag to orbit · scroll to zoom
What the camera sees the render (640×480)

How close is this to OpenCV?

The two equations are one line each, and cv2.projectPoints is OpenCV’s version of the same step, with or without the lens’s bend. The cells use the worked corner as printed above.

The pinhole equations by hand
import numpy as np

# the calibrated camera, from the unit's 13 photos
fx, fy = 536.0735, 536.0164
cx, cy = 342.3705, 235.5369

# one board corner in the camera's own frame
X, Y, Z = 4.68658, -4.06741, 13.83412

u = cx + fx * X / Z
v = cy + fy * Y / Z
print(f"pinhole: ({u:.2f}, {v:.2f})")
print("measured: (513.77, 86.53)")
Output
pinhole: (523.98, 77.94)
measured: (513.77, 86.53)
Against cv2.projectPoints
import cv2

K = np.array([[fx, 0, cx], [0, fy, cy], [0, 0, 1]])
dist = np.array([-0.26509, -0.04674, 0.00183,
                 -0.00031, 0.25231])
pt = np.array([[X, Y, Z]])
zero = np.zeros(3)

# no distortion: the same two equations
p, _ = cv2.projectPoints(pt, zero, zero, K, None)
print("cv2, pinhole only:", p.ravel().round(2))
# with the lens's measured bend
q, _ = cv2.projectPoints(pt, zero, zero, K, dist)
print("cv2, with distortion:", q.ravel().round(2))
for name, a in (("pinhole", p), ("with bend", q)):
    e = np.hypot(*(a.ravel() - [513.77, 86.53]))
    print(f"{name:9} misses the corner by {e:.2f} px")
Output
cv2, pinhole only: [523.98  77.94]
cv2, with distortion: [514.05  86.72]
pinhole   misses the corner by 13.34 px
with bend misses the corner by 0.34 px

With no distortion terms OpenCV returns the same pixel to the hundredth. Give it the five coefficients lesson 3 measures and the miss falls from 13.34 px to 0.34 px.

In the wild

The pinhole model’s central claim is that parallel lines in the world meet at a single point in the image. That is testable on any photograph with architecture in it, and it does not need a calibration target. How to compute that point from two lines is one cross product, which is the subject of homogeneous coordinates.

A temple cloister, shot straight down its axis. Detect line segments with OpenCV’s own Hough transform [3], keep the 60 longest that are neither vertical nor horizontal (those are the receding ones, since a building’s own uprights meet only at infinity), and find the point that is closest to all of them at once.

segments detected631
long receding lines kept60, from 305 to 523 px
after one outlier pass53
vanishing point(872, 1076) px, inside the frame
median distance from a line to that point45.1 px
as a fraction of the frame diagonal1.6%
A temple cloister with 60 detected lines drawn over it, all converging on a marked point at the end of the corridor
The lines that were fitted, and the point they agree on. Nobody told the algorithm where the corridor ends. Source: “The Cloister Mandapam, in One point perspective” by Sindugab (Wikimedia Commons), CC0.

What it means: 53 lines from stone that was cut by hand, photographed on a phone, agree on one point to within 1.6% of the frame. The residual has ordinary causes: masonry that is not perfectly straight, a lens that is not perfectly rectilinear (the subject of lesson 3), and segments fitted to a few hundred pixels each. The projection this lesson derives from a hole in a box survives contact with a real building.

Where this breaks

The error is radial, and it is not small. The table above is the failure: 0.29 px in the middle of the frame against 5.75 px at the edges, with a worst corner 13.34 px out. Adding the lens terms that lesson 3 covers drops the same view’s mean error from 2.55 px to 0.170 px, with a worst corner of 0.404 px. More care in calibration would not have closed that gap: the pinhole equations are missing a term that only matters away from the axis.

Depth is gone and cannot be recovered from one image. The projection maps a three-dimensional world onto two dimensions, and the information lost is not retrievable by looking harder. Everything the geometry branch of this track does later, from stereo to structure from motion, exists to put it back.

And a pinhole sharp enough for this is too dark to use. The whole model above assumes each pixel sees exactly one ray, which requires a hole of essentially no size. A hole of no size passes no light, so the exposure time runs to minutes and anything that moves is a smear. Open the hole to get light back and each sensor point starts collecting a cone of rays instead of one, which is blur. That trade is the reason no camera you own is a pinhole camera.

Exercises

  1. Project a point. Write project(X, Y, Z), the pixel the pinhole equations give for a point in the camera’s frame, with fx, fy, cx, cy from the cells above.
Your turn
def project(X, Y, Z):
    return (0.0, 0.0)  # pixel (u, v), pinhole only

print(project(X, Y, Z))
  1. Predict, then run. Move the same corner twice as far from the camera. How far from the image centre does it land now?
  2. Where is the centre? How far is the optical centre (cx,cy)(c_x, c_y) from the middle of the 640×480 frame?
What you should see

These come from the “Lesson 1” cells of the unit notebook, image_formation.ipynb, and are the same on OpenCV 4.11 (this page) and 5.0.

  1. (523.98, 77.94).
  2. 120.2 px from the centre instead of 240.5: half, because the position is X/ZX/Z.
  3. 22.4 px across and 4.5 px up.

Check yourself

Four questions on this lesson. Pick an answer to see why it is right or wrong.

  1. Under the pinhole model, a corner moves twice as far from the camera. What happens to its distance from the image centre?

  2. Where are the pinhole equations least accurate on OpenCV’s calibration photo?

  3. On this camera, where does the optical axis meet the sensor?

  4. Why does a pinhole give a sharp picture?

Next

A lens collects a wide cone of light and bends it back to a point, buying the brightness a pinhole cannot have. It charges for it with a plane of focus, and lenses, focal length and depth of field is about what is in focus once you have one.

Run it: every code block on this page has a cell in the unit’s notebook: open it in Colab.

References

[1] Forsyth, D. A., & Ponce, J. (2012). Computer Vision: A Modern Approach (2nd ed.), ch. 1 “Geometric Camera Models”, §1.1.1 “Pinhole Perspective” (p. 4). Pearson.

[2] Hartley, R., & Zisserman, A. (2004). Multiple View Geometry in Computer Vision (2nd ed.), ch. 6 “Camera Models” (p. 153). Cambridge University Press.

[3] OpenCV Documentation (5.0). Image processing (imgproc): Feature detection, cv::HoughLinesP and cv::Canny. docs.opencv.org/5.0

[4] Zhang, Z. (2000). A flexible new technique for camera calibration. IEEE Transactions on Pattern Analysis and Machine Intelligence, 22(11), 1330–1334. doi:10.1109/34.888718