3D visionExplainer

Euler angle order conversion: rotation matrix between ZYZ and XYZ

Any Euler angle order conversion is safe through the rotation matrix: build it from the source axes, then decompose it in the target order, ZYZ to XYZ.

LumaSpec Vision9 min read Share on LinkedIn

On this page 9 sections
  1. 01Why do Euler angles from another system give a flipped robot pose?
  2. 02How do you convert ZYZ angles to another order safely?
  3. 03How do you build the rotation matrix from ZYZ angles?
  4. 04How do you read fixed-axis XYZ angles out of the matrix?
  5. 05What does the full conversion look like with real numbers?
  6. 06How do you verify the conversion before the robot moves?
  7. 07When does the matrix route still give a wrong pose?
  8. 08What should you check before you ship the conversion?
  9. 09Questions

A pose that reads perfectly in the sensor software can still flip a robot pick onto its side. The cause is usually not a wrong angle but a wrong angle order: the calibration result is listed in one Euler convention, the consumer expects another, and the same three numbers spell a different orientation in each. The fix is mechanical and always the same. Build the 3×3 rotation matrix from the source convention, then decompose that single matrix in the target convention. Below you get the builder and decomposer formulas for a ZYZ source and a fixed-axis XYZ target, a full worked conversion with every digit of arithmetic shown, a round-trip test with a tolerance you can put in an assert, and the four mistakes that survive even a correct matrix.

Why do Euler angles from another system give a flipped robot pose?

The angles are right and the order is wrong: one orientation has a different angle triple in every axis convention, so a ZYZ listing pasted into code that reads XYZ rotates about the wrong second axis and lands the tool tilted or mirrored.

Euler angles are a spelling of an orientation; the 3×3 matrix is the orientation itself. The Euler angles reference counts twelve valid rotation sequences before intrinsic-versus-extrinsic readings are even added, and each sequence reads a different triple from one and the same matrix. Hand-eye calibration is where the spellings collide: the sensor-side result comes out in the calibration library's order, while the robot poses you logged during dataset acquisition come in the controller's order. Nothing in either file warns you, because every number in both looks plausible.

If the calibration itself is still ahead of you, remember that the matrix you are about to convert is only as good as the poses that built it. An undersized calibration board makes it ill-conditioned; the hand-eye calibration target size guide shows how big the board must be for the working distance.

How do you convert ZYZ angles to another order safely?

Never map formula onto formula: build the 3×3 rotation matrix from the source convention, then decompose that one matrix into the target convention, because a single matrix has exactly one angle triple per convention and nothing from the source order can leak into the result.

That is two functions, written once and reused for every pair:

  1. to_matrix(order, angles) — multiply the three elementary rotations in the order and axis sense the source document states.
  2. from_matrix(order, R) — an atan2 set derived from that same product's elements.

Any conversion is the two in series, so adding a convention means two small functions rather than a new derivation. When a pose arrives as a rotation vector or a quaternion instead of angles, convert that representation to a matrix first; the rotation vector to rotation matrix guide covers that half, translation units included.

How do you build the rotation matrix from ZYZ angles?

Take ZYZ to mean R = Rz(φ)·Ry(θ)·Rz(ψ) — rotate about the base z by φ, then about the once-rotated y by θ, then about the twice-rotated z by ψ — and multiply the elementary matrices in exactly that written order.

Work in radians, column vectors, right-handed frames; ZYZ is a proper Euler set, first and last axis the same.

R = Rz(φ)·Ry(θ)·Rz(ψ) R00 = cφcθcψ − sφsψ R01 = −cφcθsψ − sφcψ R02 = cφsθ R10 = sφcθcψ + cφsψ R11 = −sφcθsψ + cφcψ R12 = sφsθ R20 = −sθcψ R21 = sθsψ R22 = cθ

c and s are the cosine and sine of the subscripted angle. If the source document instead defines extrinsic z-y-z — rotations about the fixed axes in the order z, then y, then z — the same three matrices multiply in a different arrangement and the triple feeding them changes with it. The word on the label is not the definition; the written product is.

How do you read fixed-axis XYZ angles out of the matrix?

Write the target convention's own product first — for fixed-axis roll-pitch-yaw it is R = Rz(γ)·Ry(β)·Rx(α), with yaw about the base z applied last — and its nine elements then hand you the angles through atan2.

In that product the third row opens with exactly −sin β, the rest of the last column carries sin α·cos β and cos α·cos β, and the rest of the first column carries sin γ·cos β and cos γ·cos β. That gives:

β = atan2(−R20, sqrt(R00² + R10²)) α = atan2(R21, R22) γ = atan2(R10, R00)

atan2 keeps the quadrant, so roll and yaw cover their full ±180° while pitch stays inside ±90°. A different target order changes the atan2 set, not the method: multiply the target's elementary matrices, find the element that isolates one sine, and read its neighbours. Transplanting the three lines onto a convention whose product you have not written out is how flipped poses get manufactured.

One guard covers gimbal lock: when sqrt(R00² + R10²) returns zero, pitch sits at ±90° and only the combination α∓γ is defined. Set γ = 0 and take α = atan2(R01, R02); the round trip below settles the sign at β = −90°. The pose stays exact — only its spelling becomes one of the valid many.

What does the full conversion look like with real numbers?

Convert a ZYZ listing of φ = 30°, θ = 45°, ψ = 60° and the same orientation comes out as roll α = 40.9°, pitch β = 20.7°, yaw γ = 97.8°, as the arithmetic and the round trip below show.

Build the matrix with c30 = 0.8660, s30 = 0.5000, c45 = s45 = 0.7071, c60 = 0.5000 and s60 = 0.8660:

R00 = 0.8660·0.7071·0.5000 − 0.5000·0.8660 = 0.3062 − 0.4330 = −0.1268 R10 = 0.5000·0.7071·0.5000 + 0.8660·0.8660 = 0.1768 + 0.7500 = 0.9268 R20 = −0.7071·0.5000 = −0.3536

The remaining elements come off the same lines, which gives:

x column y column z column
row 1 −0.1268 −0.7803 0.6124
row 2 0.9268 0.1268 0.3536
row 3 −0.3536 0.6124 0.7071

A sanity check before decomposing: each row and each column squares to 1.0000 within the rounding, as a rotation matrix must.

Now decompose:

sqrt(R00² + R10²) = sqrt(0.0161 + 0.8589) = sqrt(0.8750) = 0.9354 β = atan2(0.3536, 0.9354) = 20.7° α = atan2(0.6124, 0.7071) = 40.9° γ = atan2(0.9268, −0.1268) = 97.8°

The yaw lands in the second quadrant because atan2(0.9268, −0.1268) measures from the −x side — exactly what a plain ratio cannot see. Round the trip: rebuild Rz(97.8°)·Ry(20.7°)·Rx(40.9°) and every element lands within ±0.001 of the table, the residue of four-decimal rounding rather than a convention fault.

How do you verify the conversion before the robot moves?

Rebuild the matrix from the converted angles and compare element by element: in double precision, with angles under a full turn, every element should agree to better than 1×10⁻⁶, and a larger gap means a wrong order or a transpose rather than floating point.

Three more checks, cheapest first:

  • Determinant. A proper rotation has det(R) = +1 within rounding. A determinant near −1 means a reflection entered the chain, and a mirrored pose passes every angle check while picking the mirrored part.
  • Picture. Render before you move: draw the tool or camera frame straight from the matrix, the way visualizing a camera pose frustum from an extrinsic matrix shows, and hold it against the physical cell.
  • Hardware. Run a visibly asymmetric part through the real chain and command one slow move; the converted pose is exactly what the pick correction consumes when it applies X, Y and angle in one shot.

This is routine work in a robot hand-eye cell, where the sensor side is a laser profiler such as the MV-DP4180-03P and the two listings converted above are its calibration output and the pendant's pose log.

3D sensorsHikrobot MV-DP4180-03P 3D Laser Profile SensorMV-DP4180-03P
Type
Laser profiler
$12,389.00 · quotedView and quote

The builder and decomposer run on the cell's vision controller next to the inspection jobs; a controller such as the MV-VC5525-256G70-NN keeps the cameras, the calibration and this conversion on one machine.

Vision controllersHikrobot MV-VC5525-256G70-NN VC5000 Vision Controller, 12th-I5, 32GB+256G SSD, Supported PCIEMV-VC5525-256G70-NN
LAN ports
5+ LAN ports
USB 3 ports
3-4 USB 3 ports
PoE
PoE camera ports
Light outputs
8 built-in channels
$4,479.00 · quotedView and quoteBuild a system with it

When does the matrix route still give a wrong pose?

Four mismatches survive a mathematically perfect matrix: a transpose read as an inverse, degrees fed to radian code, a passive convention on one side, and a pose sitting in a gimbal-lock neighborhood.

  • Transpose. For rotations Rᵀ = R⁻¹, so a source that stores world-to-sensor where your code assumes sensor-to-world needs one transpose — and transposing also reverses composition order, which the extrinsic transform order article traces through a chained pose.
  • Units. 45° is 0.7854 rad; one mixed unit corrupts every off-diagonal element at once, so convert at the input boundary, once.
  • Passive versus active. Some libraries tabulate the matrix that maps coordinates rather than the one that moves the part; the two are transposes. If the rebuilt picture points the tool the wrong way while every element verifies, this is the usual culprit.
  • Singular poses. Near pitch ±90°, or θ near 0° and 180° in ZYZ, the triple is valid but jumps between programs. Compare such poses by matrices, never by subtracting angles.

What should you check before you ship the conversion?

Ship it after a five-line check: both conventions written down, the round trip asserted, the determinant guarded, the singular branch handled, and one asymmetric part moved slowly through the real chain.

  • Both conventions on paper: axes, order, intrinsic or extrinsic, radians or degrees, column or row vectors — taken from the two documents, not from memory.
  • max(abs(R − R_rebuilt)) < 1e-6 asserted inside the conversion function, with the worked example, (30°, 45°, 60°) to (40.9°, 20.7°, 97.8°), as the unit test.
  • det(R) near +1 enforced, so a reflection cannot pass silently.
  • The near-singular branch present and exercised once at pitch = 90°.
  • One physical pass with an asymmetric part before the first production pick.

To choose the hardware around this code, start from the 3D sensors and vision controllers categories or go straight into the build-a-system configurator; more conversion and calibration reading sits on the 3D topic page.

Questions

Do Euler angles need to be in radians before building the rotation matrix?

Yes, unless the trig call has an explicit degree mode, because every formula here assumes radians. One degree is π/180 ≈ 0.017453 rad, so the worked example's inputs are 30° = 0.5236 rad, 45° = 0.7854 rad and 60° = 1.0472 rad. Convert once at the input boundary, keep radians through the matrix math, and format degrees only for display.

Why does the same orientation appear as two different angle triples?

Euler listings are not unique. In ZYZ, the triple (φ + 180°, −θ, ψ + 180°) builds the identical matrix as (φ, θ, ψ), and every convention has such twins. So never compare poses by subtracting angles: rebuild both matrices and take the largest element difference — two triples can differ by 180° in two axes and still describe the same physical orientation.

Can two Euler conventions be converted directly without a matrix?

A closed-form pair exists for any ordered pair of conventions, but each pairing needs its own derivation, singular cases and tests. The matrix route reuses one builder and one decomposer for every pairing, so a new convention is two small functions. In double precision the extra nine multiplies sit far below timing budgets at trigger rates.

What tolerance should the round-trip assert use?

With 64-bit arithmetic and angles below a full turn, a matrix rebuilt from its own angles matches to better than 1×10⁻¹². After rounding entries to four decimals, agreement sits near 1×10⁻⁴, so assert at 1×10⁻³ per element, two orders looser than the rounding. A break that big indicates an order or transpose fault, not precision.

Does the translation vector need conversion too?

No. The three translation numbers are coordinates and carry no axis order, so the Euler conversion leaves them alone. Check two things instead: the length unit, millimeters versus meters, and the frame the vector is expressed in, because a tool-frame translation pasted into a base-frame slot flips signs and swaps components on its own.