3D visionHow-to

Plane orientation from four known 3D points on a flat plate

Get plate orientation from four 3D points: fit the plane, convert the normal to tilt angles, and set your accuracy from mark spread and depth noise.

LumaSpec Vision9 min read Share on LinkedIn

On this page 9 sections
  1. 01How do you find a plate's orientation from four known 3D points?
  2. 02What are the steps from depth image to tilt angle?
  3. 03How do you convert the fitted normal into tilt angles?
  4. 04What does the arithmetic give for a 200 x 150 mm plate at 800 mm?
  5. 05How accurate will the angle be?
  6. 06Do you still need a pose solver when you have XYZ?
  7. 07How should you place the marks and mount the camera?
  8. 08Which checks prove the angle before you trust it?
  9. 09Questions

You have a flat plate, four marks on it, and a depth camera that reports each mark's XYZ. The tilt you need for a gripper approach, an alignment check or a fixture verification is three formulas away: a cross product for the plane normal, two arctangents for the angles. This guide walks the full path — valid depth at the marks, ordering, the normal, signed angles — with the arithmetic worked out for a 200 x 150 mm plate at 800 mm. It also gives the error budget, so you know whether ±1 mm depth noise supports a 0.1° result or only a 1° one, plus the checks that separate a real tilt from a bad mark detection.

How do you find a plate's orientation from four known 3D points?

Join the four points into two edge vectors, take their cross product, and normalise it: that unit vector is the plate's normal, and its tilt angles follow from two arctangents — about 2.9° and 5.7° in the worked example below.

Three non-collinear points already fix a plane in space; the fourth point exists to be checked. Because a depth camera hands you metric XYZ per pixel, there is no iteration and no camera-pose model: the normal is two subtractions, one cross product and one division. Keep everything in the camera's own frame — x right, y down, z forward along the optical axis — and the angles mean the same thing on every run.

What are the steps from depth image to tilt angle?

Seven steps take you from a live depth frame to a signed tilt angle, and on a static plate the computation itself is a few dozen floating-point operations once the marks are found.

  1. Mark four points on the plate face: printed dots, drilled corners, anything the camera finds repeatably. An asymmetric layout removes doubt about which mark is which.
  2. Detect each mark's pixel position in the frame.
  3. Read depth at each pixel and reject zero or flagged readings — a hole in the depth map is not a plate at 0 mm.
  4. Deproject the four pixels to XYZ with the intrinsics of the stream you read: the depth stream's own, or the colour stream's when depth is aligned to colour. Which set applies is the aligned depth point cloud intrinsics question.
  5. Order the points a, b, c, d going around the plate; fit the plane through a, b and d, and keep c as the check point.
  6. Fit the normal: u = b - a, v = d - a, n = (u × v)/|u × v|, sign flipped so n.z > 0.
  7. Convert: θy = atan2(n.x, n.z) and θx = atan2(-n.y, n.z), in degrees.

How do you convert the fitted normal into tilt angles?

Two arctangent operations convert the unit normal into component tilts, and the arccosine of the z-component gives the total tilt from face-on; with n = (-0.05, 0.10, 0.9938) they evaluate to -2.9°, -5.7° and 6.4°.

u = b - a v = d - a n = (u × v) / |u × v| sign flipped so n.z > 0 θy = atan2(n.x, n.z) tilt about the image's vertical axis θx = atan2(-n.y, n.z) tilt about the image's horizontal axis α = acos(n.z) total tilt from face-on

With x right, y down and z forward, a negative θy means the +x edge of the plate sits deeper in the scene than the -x edge, and a negative θx means the image's lower edge sits nearer to the camera. The component tilts do not add: they combine as the square root of the sum of their squares — √(2.9² + 5.7²) = 6.4°, matching the arccosine.

What does the arithmetic give for a 200 x 150 mm plate at 800 mm?

A plate whose corners sit at (-100, -75, 800), (100, -75, 810), (100, 75, 795) and (-100, 75, 785) mm yields a normal of (-0.05, 0.10, 0.9938), which is 2.9° of yaw, 5.7° of roll and 6.4° total from face-on.

The whole computation, in millimetres:

  • u = b - a = (200, 0, 10)
  • v = d - a = (0, 150, -15)
  • u × v = (0·(-15) - 10·150, 10·0 - 200·(-15), 200·150 - 0·0) = (-1500, 3000, 30000)
  • |u × v| = √(1500² + 3000² + 30000²) = √911250000 ≈ 30187
  • n = (-0.0497, 0.0994, 0.9938), z already positive
  • θy = atan2(-0.0497, 0.9938) = -2.86°, reported as -2.9°
  • θx = atan2(-0.0994, 0.9938) = -5.71°, reported as -5.7°
  • α = acos(0.9938) = 6.39°, reported as 6.4°

The physical reading: the right-hand edge lies 10 mm deeper than the left, and the bottom edge lies 15 mm nearer than the top. The check point earns its keep as the residual: n·(c - a) = (-0.0497)(200) + (0.0994)(150) + (0.9938)(-5) = -9.94 + 14.91 - 4.97 = 0.0 mm, because these corners were built planar. On measured corners expect this residual inside about 1 mm; above 3 mm, suspect a mark before you suspect the plate.

How accurate will the angle be?

Tilt error scales with depth noise divided by mark spread, so ±1 mm depth repeatability over a 200 mm spread gives about 0.4° per shot, dropping to roughly 0.1° when you average 30 frames, because noise falls with the square root of the count.

A tilt is a slope: a depth difference over a distance. Two independent depth readings, each carrying σz, give a slope error of √2·σz/L radians, and √2 × 57.3 converts radians to degrees:

ε ≈ 81 × σz / L degrees, with σz and L in mm over N averaged frames: ε / √N

±1 mm over 200 mm: 81/200 = 0.41°. The same noise over a 400 mm spread: 0.20°. Marks clustered into a 50 mm patch: 1.6°. Averaging 30 frames divides by √30 = 5.48, so 0.41° becomes 0.07° — call it 0.1°. The lateral pixel coordinates contribute almost nothing at these scales; depth noise dominates. Take σz as your depth repeatability at the working distance: around ±1 mm is a fair figure for a structured-light camera near a metre, and for stereo depth it grows roughly with distance squared, so halving the working distance cuts it about fourfold. Spread the marks before you shop for a better sensor.

Do you still need a pose solver when you have XYZ?

No: pose routines such as solvePnP exist for the case where you only have pixel coordinates, but once the camera returns XYZ the problem is a linear plane fit with no iteration and no planar ambiguity.

A PnP solver models the camera and searches for the pose that best reprojects your points. On a near-face-on planar target that search is ill-conditioned: two quite different rotations project to almost the same image, and the estimate can flip between them from frame to frame. The cross product has one answer, always. The solver earns its keep when depth is unavailable: if your four points come from a single 2D image of a known layout, the ground plane pose from one image method recovers the plane's pose from pixels alone. Most depth cameras in the 3D sensors category hand you XYZ per pixel, which is why the plane fit is the shorter road; the depth camera finder filters them by type.

How should you place the marks and mount the camera?

Put the four marks as close to the plate corners as the part allows, keep no three of them collinear, and bolt the camera to a rigid stand, because vibration between shots reads as tilt.

The corners are the accuracy: corner-to-corner marks on this plate give L = 200 mm and 0.4° single-shot error, while the same four marks clustered into a 50 mm patch give 1.6°. Asymmetric spacing also makes the ordering unambiguous, so step 1 never guesses. Rigid mounting holds the working distance, and with it σz, constant between shifts; pick the distance with the 3D camera working distance and positioning guide, then choose from the camera stands range or go straight to the stand configurator. The LS-MNT-0040 holds a camera at a fixed 700 mm height on a 400 x 450 mm base.

A projected structured-light camera returns the dense XYZ cloud you pick these marks from:

3D sensorsHikrobot MV-DPS200P-02 Projected structured-light 3D cameraMV-DPS200P-02
Type
Stereo / structured light
$8,829.00 · quotedView and quote

A stand keeps that geometry from drifting between shifts:

Camera standsMachine vision camera stand, professional slide-table type, height 700 mm, 400 x 450 mm base (LS-MNT-0040)LS-MNT-0040
Type
Camera stands
$629.00View partBuild a system with it

Which checks prove the angle before you trust it?

Four checks catch nearly every bad result: unit-length normal, sub-millimetre residual on the check point, correct sign on a known tip, and repeatability matching your error budget — a reading that fails any of them is a detection or deprojection fault, not a tilted plate.

  • Normal length. After normalisation, |n| = 1.000 ± 0.001; a value like 0.83 means the division never happened.
  • Residual. |n·(c - a)| under about 1 mm on a machined face; above 3 mm, re-detect the marks before touching the mechanism.
  • Sign. Tip the plate on a 2 mm shim under one edge across a 200 mm span — atan(2/200) = 0.57° — and confirm the matching angle moves the same way each time. This one test kills the mirrored-normal fault for good.
  • Repeatability. Capture 30 frames on a static plate; the standard deviation should sit near the 0.1° your error budget predicts.
  • Cross-checks. On a fixed rig, compare against the camera's own IMU inclination reading; if residuals stay high on a known-flat plate, measure the depth camera's flat plane distortion at the marks' image positions.

More depth-geometry guides sit on the 3D vision topic page. Send the plate drawing and the angle tolerance through the quote form and we will size the sensor, the stand and the error budget with you.

Questions

Is three points enough instead of four?

Three non-collinear points define the plane exactly, so three marks can run the method; the fourth buys a free residual check, |n·(c - a)|, that costs one dot product. With five or more marks, a least-squares plane fit uses them all and averages the random error down by roughly the square root of the count.

Which way should the normal point?

Either, but fix one convention for the whole cell and keep it: flip the computed normal's sign whenever its z-component is negative, as step 6 does. Reversing the normal flips the sign of both tilt angles while leaving their magnitudes unchanged — the classic cause of a gripper approaching the plate from the wrong side.

How do I express the angle in the robot's frame instead of the camera's?

Rotate the unit normal into the robot frame with your calibrated camera-to-robot transform first, then apply the same atan2 formulas to the rotated components. Do not add camera-frame angles to robot axis angles. That transform comes from hand-eye or extrinsic calibration, and its own accuracy sets the floor on the angles you report to the controller.

Can I measure the tilt while the plate is moving?

Yes, provided the plate holds still within one exposure. Motion during the exposure smears each mark and biases its centroid, which reads as tilt. Use a short exposure with a strobed or high-output light, trigger per part, and expect depth on a moving surface to be noisier than the static figures in this guide.

What if depth is missing at some of the marks?

Dark or shiny faces drop depth readings. Give each mark a matte finish or a printed dot, and reject any mark whose depth reads zero or is flagged invalid rather than interpolating it. If a mark fails every frame, move it a few millimetres: a valid reading on the plate face is all the plane fit needs.