Reading Accurately

OCR does not fail cleanly. A barcode either decodes or does not; text always comes back with something, and a misread looks exactly like a correct read until somebody checks it.

Five settings decide whether what comes back can be trusted. They matter roughly in this order.

1. Crop to the text

input/crop is the most effective setting here, and the one most often left at its default.

A full 1920x1080 frame containing a 200x60 label gives the engine an enormous amount of background to find text in - and it finds some: edges of the part, shadows, the conveyor. Those become low-scoring words that pad the reading, and they push the layout analysis toward "this is a complicated page" when it is one line of print.

Crop tight around where the text will be. It is faster, and it removes a whole class of wrong answers instead of filtering them afterwards.

A crop that does not fit the image is refused, not trimmed to fit. A region silently shrunk would show up much later as words that are simply missing.

2. If you cannot crop, merge the colour channels

input/preprocess exists for one failure, and it is a nasty one: a colour frame in which a photograph or other colourful scenery shares the image with the text can come back with no words at all. Not garbled - empty, while status-code reports success. That is indistinguishable from an image with no text in it, so it does not look like a setting to change; it looks like the camera is pointed at the wrong thing.

Measured on one such frame - a 300x168 capture, a parrot photograph on the left, a title on a dark panel on the right:

preprocess Read

NONE (default)

nothing at all

GRAYSCALE

Title Of The Presentation

BINARIZE_OTSU

Title Of The Presentation

No layout and no engine rescues the first row - all 24 combinations of the two return nothing. What defeats the engine is how it reduces a colour image before reading it, not the size of the text: the same frame flattened to grey reads perfectly, and so does the colour frame cropped to the panel.

Crop first. Cropping fixes this case in colour, costs nothing, and helps for all the reasons above. Reach for preprocess when the text and the scenery cannot be separated by a rectangle - a label on a patterned part, or a screen photographed whole.

GRAYSCALE is the one to use. BINARIZE_OTSU additionally forces every pixel to black or white, and is worth trying only if GRAYSCALE did not help: it discards the smooth edges the engine reads character shapes from, and on the frame above it reads Title 01 The where GRAYSCALE reads Title OF The.

Neither setting invents text. On a frame with no text in it - the photograph alone, or a flat grey field - all three settings return nothing, so turning this on cannot manufacture a reading.

3. Restrict the character set

input/characters/mode picks a set, allow adds to it, and deny removes from it.

The classic OCR errors are all substitutions between character classes: O/0, I/1/l, S/5, B/8, Z/2. A part number that is only ever digits, read with mode = DIGITS, cannot produce any of them. This is nearly free accuracy and costs one setting.

Marking Setting

Numeric part or batch number

mode = DIGITS

Upper-case alphanumeric code

mode = UPPERCASE_LETTERS, allow = 0123456789

Date code with separators

mode = DIGITS, allow = /-.

Anything with a fixed alphabet

mode = CUSTOM, allow = the alphabet

Free text, human readable

mode = ALL_SYMBOLS (the default)

An empty character set is refused. mode = CUSTOM with an empty allow, or a deny that removes everything the mode selected, would be passed to the engine as no filtering at all - the exact opposite of what was configured, and invisible in the result. Both are rejected with status code -2, naming the node.

4. Match the layout to what you are looking at

input/layout tells the engine how much structure to look for. The default is SINGLE_BLOCK, because a cropped label is one block of text.

Value Use for

SINGLE_BLOCK

A cropped label - the default

SINGLE_LINE

One line of print, nothing else in the crop

SINGLE_WORD

One part number, one code

SINGLE_CHAR

One character, e.g. a cavity or station mark

RAW_LINE

One line, with no attempt to find structure at all

AUTO

Several blocks in unknown positions

SPARSE

Text scattered across the frame with no layout

AUTO_OSD, SPARSE_OSD

As above, and work out the orientation as well

SINGLE_COLUMN, SINGLE_BLOCK_VERT, CIRCLE_WORD

The shapes their names describe

Narrower is better when you know what you are looking at: telling the engine "this is one word" stops it inventing a second one.

The _OSD variants cost more than they look. They load a second model and spend time deciding which way up the text is. In an inspection cell the part arrives the same way up every time - a fact the mechanics already guarantee - so paying for it on every reading is waste. Use them only when the orientation genuinely varies.

5. Set a confidence threshold

input/min-score is the confidence a word must reach to appear in the word list. It defaults to 0, which reports everything, including guesses the engine has almost no faith in.

That default is deliberate - a fresh installation should show you what it can see rather than silently hide most of it - but it is not a production setting.

To choose one:

  1. Run a few known-good parts with min-score at 0.

  2. Look at mean_score and the individual word scores in the result.

  3. A correct reading on a decent image scores well above 0.8; the spurious words a loose crop picks up usually sit below 0.5.

  4. Set the threshold in the gap, closer to the correct readings than to the noise.

min_score is reported back in the result, so a reader looking at a short word list can tell a strict threshold from a bad image.

What this does not fix

  • Poor contrast, glare, or a part out of focus. No setting recovers text the camera did not capture. Fix the lighting.

  • Dot-matrix and laser-etched marking. These defeat the LSTM engine more often than printed text; engine = LEGACY sometimes does better and is worth trying first.

  • A part that moved. The reader has no idea where the label should be. If it drifts outside the crop the reading is empty rather than wrong - which is the better failure, and another reason to crop.