Code readingHow-to

Read randomly rotated digits with OCR: deskew before you train

OCR on randomly rotated digits: find two fiducial circles, compute the rotation with atan2, clean the crop, train upright only and gate on confidence.

LumaSpec Vision11 min read Share on LinkedIn

On this page 11 sections
  1. 01What you need
  2. 02Put the rotation in geometry, not in training
  3. 03Give the digits enough pixels
  4. 04Find a rotation-invariant reference
  5. 05Worked example: level a 2-digit stamp from centers 200 px apart in x and 100 px in y
  6. 06Clean the digit window
  7. 07Train upright, gate on confidence
  8. 08What the pipeline returns
  9. 09When the usual answer is wrong
  10. 10Checklist before you trust it
  11. 11Questions

A two-digit mark lands at whatever angle the press, the operator or the upstream fixture leaves it. Training an OCR font on rotated copies multiplies the sample set fast and still misses the angles between buckets. The better split gives rotation to geometry and shape to the classifier: measure the part's angle from a rotation-invariant feature, level the crop, and train on upright digits only. This walkthrough is for anyone setting up station-level OCR on short numeric marks. You get the fiducial math, the threshold and hole-filling cleanup, the confidence-gated rotation sweep, and the checks that keep a passing average honest.

What you need

Four things, none of them exotic:

  • a camera that runs OCR on the device itself — see the smart cameras — so the rotation sweep in the runtime steps never waits on a host round trip;
  • two circular fiducials on the part, or any small feature pair that looks identical at every rotation angle;
  • your library's threshold, closing, fill-holes and rotation operators, plus the confidence score each read returns;
  • a labeled sample set that includes both the bright prints and the dark ones the line produces.

Any of these carries the whole pipeline on the device. The MV-SC3050XC-16M-ATN-H is a 5 MP color vision sensor with a global shutter and an M12 mount. The MV-SC5060XM-08M-WBN pairs a 6 MP mono sensor with an 8 mm M12 lens over GigE; mono suits a dark stamp on light metal, and its rolling shutter costs nothing here because the part sits still during capture. The MV-SC6050C-08M-XBN adds its own multi-color light source to a 5 MP color sensor and an 8 mm lens, which helps when the fiducials and the digits want opposite contrast.

Smart cameras & vision sensorsHikrobot MV-SC3050XC-16M-ATN-H 5 MP color vision sensorMV-SC3050XC-16M-ATN-H
Interface
Fast Ethernet
Resolution
5 MP
$1,809.00View part
Smart cameras & vision sensorsHikrobot MV-SC5060XM-08M-WBN SC5000X series Smart Camera, 6 MP, Mono, 8 mm focal length, M12-mountMV-SC5060XM-08M-WBN
Interface
GigE
Resolution
6-10 MP
$1,629.00View part
Smart cameras & vision sensorsHikrobot MV-SC6050C-08M-XBN SC6000 Series Smart Camera, 5 MP, Color, 8 mm focal length, M12-mount, Multi-color light sourceMV-SC6050C-08M-XBN
Interface
GigE
Resolution
5 MP
$3,449.00 · quotedView and quote

Put the rotation in geometry, not in training

Train the font once, upright. Every angle bucket you add to training multiplies the sample set without adding stroke information: 10° buckets turn one digit class into 36 variants, and the reads at bucket edges fail first. The part's rotation, meanwhile, is a plain number you can measure from two circles. So measure it, rotate the crop back, and hand the classifier a straight digit every time. That split is the whole decision: geometry owns rotation, the classifier owns shape, and neither does the other's job.

Give the digits enough pixels

Settle the pixel budget before you touch the font. Count on at least 20 px of digit height, nearer 30 when the set is full of 0, 6, 8 and 9, whose enclosed counters collapse first. A 2-digit window then runs roughly 40-60 px tall, and that is what the optics have to deliver: when the digits are small on the part, shorten the working distance or lengthen the focal length until the window fills its share of the frame. If similar digits still trade places at a healthy size, the fault is contrast or resolution, not rotation — OCR confusing similar characters: fix resolution and contrast walks that diagnosis. Budget your own digits with the calculator below.

Camera resolution for the smallest feature
Units
Pixels needed
–
Sensor size
– MP
Smallest sensor that fits
–
Pixel on the part
– mm
Pixels across the feature, that sensor
–

Pixels needed = field / feature × pixels per feature on each axis (field incl. margin). The lens must resolve the pixels too: Nyquist = 1000 / (2 × pixel µm) lp/mm.

Find cameras with enough pixels Open the full calculator

Find a rotation-invariant reference

A circle shows the same gray pattern at every in-plane angle, so the locator reports the same center whether the part sits at 0° or at 273°. One circle gives a center and no roll. Two circles give the line between their centers — the part's own x-axis — plus the distance between them, which doubles as a presence and scale check on every frame. Size the circles a few stroke widths across and keep them well clear of the digits, so neither the crop nor the fill step ever touches them. Locate on the raw gray image, before any thresholding: threshold first and the circle edges become whatever that threshold decided.

Worked example: level a 2-digit stamp from centers 200 px apart in x and 100 px in y

The inputs come from one frame at set-up of a typical two-circle part: the locator reports circle A at (412, 388) px and circle B at (612, 288) px, both as subpixel centers in pixel units, and the values recorded at commissioning are a 220 px baseline nominal with a ±5% tolerance. The rule: the fiducial axis angle is the arctangent of the rise over the run between the two centers, and the baseline is the straight-line distance between them.

theta = atan2(dy, dx) baseline = sqrt(dx^2 + dy^2) undo the angle: rotate the crop by -theta

The arithmetic:

  • dx = 612 - 412 = 200 px; dy = 288 - 388 = -100 px. Image y grows downward, so the negative dy means the axis climbs left to right.
  • theta = atan2(-100, 200) = -26.6°.
  • baseline = sqrt(200² + 100²) = sqrt(50,000) ≈ 224 px.
  • Scale check against the 220 px nominal: (224 - 220) / 220 = +1.8%, inside the ±5% tolerance, so this is the right part at the right stand-off.

Result: the fiducial axis sits 26.6° off level and the part passes the scale check. Decision: rotate the digit crop by -theta, which here means +26.6°, read it, and accept only when confidence clears your gate. Re-run the locator on the levelled crop and require the reported angle within ±0.5° of 0; a clean circle pair locates far tighter than that, so a wider residual means a mismatched fiducial, not noise. If the stamp itself can still twist inside the part frame, sweep ±10° in 2° steps around level — 11 reads — and keep the highest confidence that clears the gate.

Clean the digit window

Crop tight in fiducial coordinates: the digits sit at fixed offsets from the circle pair, so define the window once and it follows the part through any rotation. Threshold that crop only. Then fill holes: 0, 4, 6, 8 and 9 all carry enclosed counters that thresholding turns into background islands, and the fill-holes operator closes them from the inside. When a stroke gap leaks a counter to the outside, fill cannot see it, so close first — dilate then erode with an element just larger than the measured gap — and fill afterwards. Keep the window clear of the fiducial circles so filling never reaches them. If two digits kiss, split them before the classifier: How to separate touching digits before OCR. When characters vanish outright, trace the filter chain in OCR missing characters: diagnose text filtering errors before you blame the font.

Train upright, gate on confidence

  1. Train the font on upright samples only: about 30 per digit class, spanning the bright, mid and dark prints the line produces. Brightness spread, not angle, drove the sample count up in the build behind the numbers in the next section.
  2. Set the gate from measurements, not a feeling. Read the labeled set, and where the worst correct read scored 0.62 and the worst wrong one 0.41, the gate goes at 0.50. Most tools print a 0-1 score and some print 0-100; use whichever yours does.
  3. At runtime, locate both circles on the raw frame, take the angle and the baseline, and void the frame when the baseline misses nominal by more than ±5%.
  4. Rotate the digit crop by the negative of that angle, clean it, and read. Accept only strings of one or two characters drawn from your digit classes: longer means merged digits, shorter means a dropped stroke, and both reject.
  5. When confidence misses the gate, sweep ±10° in 2° steps around level and keep the best score above the gate. A hit inside the sweep counts only if it clears it.
  6. Confirm the levelling: the locator re-run on the levelled crop must report 0° ± 0.5°, or the read is void whatever its score.
  7. Save every reject with its angle, baseline and score. That folder is the next training round, and it improves the font without touching a single good part.

If scores drift between shifts, suspect light before font: uncontrolled ambient moves both your threshold and your scores. Ambient light variation in pick and place vision: control it shows how to pin the illumination down.

What the pipeline returns

Run end to end, this pipeline held over 98% correct reads across about 6,500 production images, with the cleanup, the morphology and the rotate-to-confidence step all in place. The same font underperformed before the cleanup went in; the classifier was never the weak link, the pixels were. Treat the gate as the arbiter rather than the average, for the same reason you score a Barcode read confidence: how to judge whether a decode is trustworthy: a read you cannot score is a read you cannot ship.

When the usual answer is wrong

Three cases break the recipe. First, no fiducials and no way to add them: a bare digit pair with no rotation reference forces a full-circle sweep — 72 reads per part at 5° steps — on every cycle. If you control the marking at all, print two small circles; they are the cheapest fix on this page.

Second, if you can change what is marked, mark a 2D code and stop fighting angles. A DataMatrix locates its own reference pattern at any rotation, and error correction absorbs damaged cells, so the decode is rotation-tolerant by design. Marks on bare metal then suit a polarized fixed code reader such as the MV-IDB005EXI-05NRP, specified with 1.3 MP resolution, a 4.63 mm focal length and a 70 mm focus distance.

Fixed code readersHikrobot MV-IDB005EXI-05NRP Fully isolated version, 1.3 MP resolution, 4.63 mm focal length, 70 mm focus distance, Polarized industrial code readerMV-IDB005EXI-05NRP
Interface
Fast Ethernet
Resolution
Up to 3 MP
Shutter
Global
Lens mount
M5.8
$719.00 · quotedView and quote

Third, text curving around the part is a curvature problem, not a rotation one. The annulus method in Read curved text on a part: OCR on an annulus without unwrapping and the cap workflow in Deskew curved cap text into one line for OCR cover that geometry. And if a reader station already exists on the line, check whether it can take plain-text reading — OCR on a fixed code reader: multiple text strings in one image — before you add a second device.

Checklist before you trust it

Run the list once at set-up and again after any retrain.

Set-up

  • Both circles findable and unoccluded at every rotation the fixture allows; the whole pipeline hangs on them.
  • Digit window defined in fiducial coordinates and clear of the circles.
  • At least 20 px of digit height at the sensor, 30 px for counter-heavy sets.

Training

  • Upright samples only, about 30 per class, bright to dark.
  • Gate measured between worst good and worst bad — 0.50 between 0.62 and 0.41 in the worked set.

Runtime

  • Baseline within ±5% of nominal, or the frame is void.
  • Levelled crop re-locates at 0° ± 0.5°, or the read is void.
  • Confidence at or above the gate; the sweep is a fallback, never a shortcut.
  • Every reject saved with angle, baseline and score.

Specing a station rather than rescuing one? The camera finder filters by resolution, shutter and mount, the camera station configurator walks the rest of the chain, and the code-reading topic page collects the remaining OCR material. When a station needs a second opinion, the contact page reaches the applications team.

Questions

What if the part has no fiducial circles?

Anchor on any asymmetric feature you can find reliably at every angle: a printed logo corner, the mark's border, or the part outline itself. If nothing exists and you cannot add it, the only fallback is sweeping the full circle, 72 reads per part at 5° steps, which is why two printed circles are almost always cheaper than the sweep they replace.

How do I set the confidence gate, and does it move over time?

Measure it: read your labeled set, take the lowest score among correct reads and the highest among wrong ones, and place the gate between. It moves with every retrain and with any lighting change, so re-measure after both. Log the score of every production read too: a slow downward drift across shifts usually means a dirty window, an aging lamp or slipping focus, and it shows in the scores long before read rates collapse.

Why do only some digits need hole filling?

Only digits with fully enclosed counters, 0, 4, 6, 8 and 9, trap background islands at threshold; 1, 2, 3, 5 and 7 have nothing to fill. Order matters as well: close before fill, because a stroke gap that leaks a counter to the outside hides it from the fill operator, and closing seals the leak first. Fill inside the digit window only, never the whole frame.

Does the same setup carry a third or fourth digit?

The fiducial frame and the font carry over unchanged; the string arithmetic does not. At 99% per-character accuracy, a 2-digit string reads fully correct about 98% of the time (0.99² ≈ 0.98) and a 4-digit string about 96% (0.99⁴ ≈ 0.96). Widen the window in fiducial coordinates, keep the gate where it is, and judge yield on the string-level number, not the character-level one.

Can a code reader do this job instead of a smart camera?

If you can change the marking, yes, and preferably to a DataMatrix, which finds its own reference pattern at any rotation and corrects damaged cells, so rotation stops mattering. Existing reader stations can often take plain-text reading too, so check before adding hardware. If the digits must stay bare for operator checks or paperwork, OCR on a smart camera remains the right tool.