Small text on a laminated card is one of the hardest OCR jobs: the letters are a few pixels wide, the font kerns them until they touch, and the ROI comes back as one blob instead of a word. The usual reflex — retrain the character set file, or demand a higher-resolution camera — fixes the wrong half of the problem. Segmentation happens before classification, so a merged glyph never reaches the classifier as two letters. This guide works the issue in the order that costs least: measure the pixels you actually have, split the strokes you can split, train the pairs you cannot, and only then ask for different hardware. Every threshold below is a number you can check on your own images today.
Why does my OCR see two touching letters as one character?
Your reader merges two letters into one glyph because segmentation runs on connected foreground blobs before any character classification, so once binarization closes the 2-3 pixel gap between strokes, no character set file can split them. The pipeline order is image, threshold, segmentation, classification: a word reaches the classifier only after the segmenter has cut it into pieces, and whatever it cuts wrong, the classifier must live with.
Three things close the gap:
- Anti-aliased edges: the printed gap is not white but gray, often 100-200 of 255, and a mid-range threshold turns it foreground.
- Glare from a laminated surface floods the counters and the gap with light.
- Kerning: the font prints letters less than one stroke width apart, so at low pixel counts the gap is only 1-2 px wide.
Test it now: crop the failing word, zoom to 800 percent, and lay a line profile across the gap. Count the pixels whose gray level sits between the stroke and the paper. If the gap never reaches near-paper brightness, your threshold is the merger — fix the light or the threshold before touching anything else. The same inspection tells you whether several fields in one frame are being grouped, which is a layout problem rather than a letter problem, covered in OCR on a fixed code reader: multiple text strings in one image.
How many pixels per letter does segmentation need before it can work?
Commit to 25-30 pixels of cap height for the best read rates, treat 8 × 8 pixels as the absolute floor for alphanumeric characters, and treat anything under about 10 pixels tall as unreadable by preprocessing alone. Production OCR engines publish 8 × 8 pixels as the minimum for large characters and about 2 × 2 for small marks such as periods; real field results land far higher, around 25-30 pixels of height.
Worked example, with the arithmetic shown:
- ROI across the surname: 64 mm, covered by 1600 sensor pixels.
- Pixel size on object: 64 mm / 1600 px = 0.04 mm = 40 µm.
- Cap height on the card: 1.2 mm → 1.2 / 0.04 = 30 px. That is the top of the usable range, so resolution is not the problem.
- Stroke width 0.15 mm → 0.15 / 0.04 ≈ 4 px. Printed gap between letters 0.12 mm → 0.12 / 0.04 = 3 px. A 3 px gap that binarizes to zero is the problem.
Decision by the number: under 10 px tall, stop preprocessing and halve the field of view or change the camera; between 10 and 25 px, morphology plus pair training carries you; at 25 px and above, your remaining failures are threshold and segmentation settings, not pixels. Run your own numbers through the calculator:
A fixed code reader with room to spare for the whole card:
- Interface
- Fast Ethernet
- Resolution
- 5 MP
- Sensor format
- 1/1.7"
- Shutter
- Global
The LS-CRD-0028 reads 2560 × 2048 pixels at 60 codes/s with an 8 mm lens. The same field-of-view arithmetic, worked for parts instead of text, is in Blister inspection working distance and field of view formula.
Can morphology split touching letters without changing the camera?
Yes: a morphological opening with a vertical structuring element about two-thirds the letter height breaks the thin bridge between horizontally touching letters while keeping vertical strokes intact, and you apply it after binarization, before segmentation. Opening is erosion followed by dilation with the same element. With a 1-px-wide, 20-px-tall element on 30-px letters, a pixel survives erosion only if a full 20-px vertical line fits inside the foreground: vertical strokes survive, a 2-4 px horizontal bridge does not, and the dilation restores the survivors to their original width.
Rules that keep it safe:
- Element height: start at two-thirds of cap height and shrink until thin letters stop breaking; serif fonts need a shorter element.
- Element width: 1 px. Wider elements eat stroke width you cannot spare.
- Apply it to the binary image, never the gray image, and only after the threshold is right — morphology cannot recover a gap the threshold already destroyed.
The same mechanics for digits, with kernel sizing worked through, are in How to separate touching digits before OCR.
How do I segment letters that have different widths?
Drop fixed-pitch segmentation entirely and use connected components with a height filter plus a vertical-projection valley check, splitting any component wider than roughly 1.5 times the mean letter width at its shallowest valley. Proportional fonts and Cyrillic capitals run from narrow (І, Г) to wide (Ш, Ы, M), so a pitch tool that expects equal cells cuts wide letters in half and glues narrow ones to their neighbours.
Do not feed it an average width. An average describes neither the narrow nor the wide tail, and both tails are where the errors live. Instead: binarize and open as above, label the connected components, then for each component taller than half the modal height compute the vertical projection — foreground pixels per column. A component wider than 1.5 × the mean component width is a merge candidate: cut it at the projection valley, the column with the fewest foreground pixels between two maxima. This catches the pair that survived opening and costs nothing on clean words.
How do I handle two-part letters like Ы?
For letters printed as two unconnected parts, run a morphological closing with a horizontal element sized to the internal gap so the parts merge into one blob, then delete noise components shorter than half the letter height. Ы prints as two stems, and i and j carry dots, so a connected-component segmenter sees two or three objects where the font means one. Closing — dilation then erosion — with a horizontal element bridges small gaps without moving the outer edges.
The catch is sizing: the element must be wider than the gap inside Ы but narrower than the smallest gap between letters, or you merge neighbours instead. Measure both on 20 real crops. If the internal gap of Ы is 2 px and the tightest inter-letter gap is 4 px, a 3 px element separates the two cases cleanly. If the distributions overlap — internal gap and letter gap the same width — skip the closing and let pair training own those letters, because no kernel can tell the two gaps apart at that size. Leftover specks shorter than half the modal height get deleted, and the dot above i joins the stem below it by the same overlap rule in reverse.
Should I train stuck letter pairs as single characters?
Train as single-character entries only the pairs that still merge after morphology — typically 5-20 pairs in a real document font — and let each entry output two letters, because a 33-letter alphabet gives 33 × 33 = 1,089 ordered pairs, far too many to train blind. The method itself is sound: a trained glyph shape that maps to a two-character string needs no notion that two letters were joined. The scaling is what kills the brute-force version, so make the count data-driven:
- Run the finished pipeline over 30 production images.
- Log every component that failed to read or read as one letter.
- Keep the pairs that appear in at least 3 of the 30 images — those are the font's real merge habits.
- Train each kept pair as one entry that outputs both letters.
Expect a short list: in a Cyrillic document font the habitual mergers are the pairs where a round right side meets a straight left side, and everything else separates once threshold and opening are right. Capturing clean crops per character follows the workflow in Training a custom OCR font for curved inner-cap text.
How do I sharpen the image when the camera must stay?
You can still cut the field of view with a closer working distance or a tighter ROI, refocus on the text plane, and add diffuse or polarized light, because character height in pixels scales linearly with magnification — halve the view and letters double. None of these touches the camera body:
- Tighter view. Pixels per letter = sensor pixels × (letter height / field of view). At 1600 px across 64 mm you get 30 px caps; the same lens and camera across 32 mm of text give 60 px. If the whole card must stay in frame, this lever is spent — move on.
- Focus. Refocus with the card in place, not a target at the flange. A tilted or bowed laminated card puts one corner outside depth of field; square the card to the optical axis or accept a smaller aperture.
- Aperture. Stopping down buys depth of field but diffraction sets a floor: the spot diameter is roughly 2.44 × wavelength × f-number, about 11 µm at f/8 for 550 nm light. Past that point strokes go soft before they gain depth, so trade aperture last.
- Light. Laminate glare is what merges letters: diffuse the source with a dome over the field, or cross polarizers on the light and the lens.
- Exposure. If the card moves at 100 mm/s under a 40 µm pixel, half a pixel of blur means exposure ≤ 0.5 × 40 µm / 100 mm/s = 0.2 ms. A 10 ms exposure at that speed smears 1 mm — 25 px — and no algorithm recovers it.
Built as one unit, the LS-SMC-0063 pairs a 1.6 MP sensor, 60 fps and a 16 mm lens with polarized white light. For more pixels, the MV-ID5120RM-08M-RBN is a 12 MP code reader with a GigE interface. Two parts that bracket the choice:
- Interface
- GigE
- Resolution
- 12 MP
- Interface
- GigE
- Resolution
- Up to 3 MP
The configurator matches a camera, lens, light, light controller and cables to each other, step by step, and lists only parts that fit what you already picked.
In what order should I apply these fixes?
Work in this order: measure pixels per letter, then fix focus and lighting, then binarize and open, then split wide components, then retrain the character set on the final preprocessed images — each step is only worth doing if the previous one passes its number. Both fixed code readers and smart cameras expose the same knobs, so the order holds on any platform:
- Measure. Letter height in pixels from the formula above. Under 10 px: change the view or the camera; nothing downstream helps.
- Light and focus. Glare gone, card square to the axis, strokes dark against paper.
- Threshold. Line profile across the tightest gap; the gap must reach near-paper gray levels.
- Open vertically. Element at two-thirds cap height, 1 px wide; shrink until thin letters survive.
- Close horizontally for two-part letters. Only if the internal gap measured narrower than the letter gaps.
- Split wide components at projection valleys and delete components under half the modal height.
- Retrain on preprocessed crops, then add the pair entries that still merge.
- Prove it. 200 consecutive cards, no manual reads, 99.5 percent field read rate or better before you call it fixed.
If characters vanish after a decode that clearly saw them, the filter stage is eating them; the diagnosis is in OCR missing characters: diagnose text filtering errors. When the honest conclusion is "more pixels", the camera finder and the code reading topic page are the next stops, and the contact page reaches our applications team for a read-rate review of your crops.
Questions
Can a character set file alone split letters that touch?
No. The character set is the classifier's dictionary and runs after segmentation, so a merged blob never reaches it as two letters. Worse, retraining on merged crops teaches the engine that the joined shape is one valid character, which then substitutes on clean prints. Fix the threshold and morphology first, then retrain on the cleaned images.
How many crops do I need per trained letter pair?
Collect 10-20 clean crops of each pair you decide to train, taken from real cards through the finished pipeline. Fewer samples than that and you train printer quirks that vanish with the next ribbon or laminate batch, and the pair entry starts misfiring on good prints. Re-prove the whole set on 200 consecutive cards afterwards.
What gray-level difference do strokes need for a clean split?
Aim for at least 100 of 255 gray levels between stroke and paper, measured with a line profile through the tightest gap. Below about 50 levels the binarization threshold becomes the thing that decides whether letters touch, and read rates swing with every reprint. Raise light intensity, remove glare with polarization, or shorten exposure before touching morphology.
Is 8 × 8 pixels enough for wide letters like Ш or two-part letters like Ы?
No, 8 × 8 is the floor for plain alphanumerics. Wide letters need the same height but roughly double the width to keep both outer strokes resolved, and two-part letters need the internal gap to stay at 2 pixels or more after binarization. Plan on 12-15 pixels of cap height whenever Ш, Щ, Ы or M appear in the alphabet; at 8 pixels those glyphs fail first.
Does a polarizer cost me anything on a laminated card?
It costs light: two crossed polarizers pass well under half the illumination, so expect to lengthen exposure, open the aperture, or fit a stronger illuminator. The trade is usually worth it, because specular glare off the laminate is what fills letter gaps with white, and removing it at the source beats every downstream fix. Keep the motion exposure budget in mind when you add them.


