System designTroubleshooting

Remove dust noise from screw thread images: filter before threshold

Why the default median and graythresh leave dust on a screw silhouette, how to size the kernel and place the cut, with the pixel arithmetic worked out.

LumaSpec Vision11 min read Share on LinkedIn

On this page 9 sections
  1. 01Three causes, in order of probability
  2. 02Measure the dust and the pitch in pixels first
  3. 03Fix 1: size the median to the speck, cap it at half the pitch
  4. 04Fix 2: keep Gaussian away from contaminants
  5. 05Fix 3: place the cut from the screw peak, not from graythresh
  6. 06The corrected pipeline
  7. 07When the right fix is at the camera
  8. 08Checks before you trust the outline
  9. 09Questions

You binarize a screw silhouette, run a Sobel pass, and the dust and fur on the threads come through as speckles, notches and a fat, broken outline. The default pipeline — rgb2gray, crop, a 3×3 medfilt2, graythresh, then edge with 'Sobel' — fails here for three reasons you can put numbers on: the median kernel is smaller than the specks, Gaussian blur feeds the specks into the edge, and the default threshold cut lands inside the dust's gray band. This page gives you the pixel arithmetic to size the kernel, a rule for placing the cut from the histogram, and a corrected seven-step pipeline. It is for anyone cleaning silhouette images of machined parts for measurement, presence checks or robot guidance.

Three causes, in order of probability

Start with the diagnosis, because each cause leaves a different fingerprint in the image:

  1. The median kernel is smaller than the dust. medfilt2 with no size argument runs a 3×3 window, which only clears specks narrower than about 2 px. Dust grains and fur fibers tens of pixels wide pass through untouched, then binarize and get traced by the edge pass.
  2. The filter family is wrong for the noise. imgaussfilt with a sigma of 4 averages each bright speck into its neighbourhood instead of rejecting it, which lifts the gray level along the flank and rounds the crest.
  3. The threshold cut lands in the dust band. graythresh maximizes between-class variance over the whole histogram; on a dark-screw-on-bright-field image it cuts between the screw and the background — exactly the gray range where the dust sits — so a small brightness shift flips the dust from object to background and back.

One test separates them: run imhist on the cropped frame and measure the widest speck with a cursor. Note that the original code computed the histogram on the full frame before imcrop ever ran. The crop leaves a 709 × 332 frame (710−1 by 410−78), and the histogram belongs to that region, not the uncropped image.

Measure the dust and the pitch in pixels first

Every decision below is pixel arithmetic, so convert once:

scale = field width / sensor width in pixels speck width w = dust width / scale pitch p = thread pitch / scale kernel k ≥ 2w + 1, rounded up to an odd number kernel cap k ≤ p / 2

The two sides of that kernel rule have a physical basis. A median filter removes a feature only when it occupies less than half the window's pixels, so the kernel side must exceed about twice the speck's narrow dimension. The cap exists because a window wider than half the pitch spans two thread grooves and starts voting the crest corrugation away.

Worked example, arithmetic in full — a backlit M8 screw (1.25 mm coarse pitch) over a 48 mm field on a 4000-pixel sensor width:

  • Pixel scale: 48 mm ÷ 4000 px = 0.012 mm/px = 12 µm/px
  • Dust grain, 0.1 mm wide: 100 µm ÷ 12 µm/px ≈ 8 px
  • Thread pitch: 1.25 mm ÷ 0.012 mm/px ≈ 104 px
  • Kernel floor: 2 × 8 + 1 = 17; kernel cap: 104 ÷ 2 = 52 → a 17×17 median sits at 16 % of the pitch, with margin
  • Fur fiber, 0.3 mm wide: 300 µm ÷ 12 µm/px = 25 px → floor 2 × 25 + 1 = 51 against a cap of 52 — legal by 1 px, which is strained, and that case needs the threshold route below as well
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

Enter the field width and the sensor's pixel count and the calculator returns the pixel scale the same way; its button then opens the camera finder filtered to sensors with at least that pixel count.

Fix 1: size the median to the speck, cap it at half the pitch

Replace the default call with an explicit, measured window. Set w = 8 (measured on screen), compute k = 2 × w + 1 = 17, and call medfilt2(I2,[k k]) — the size comes from the measurement, not from trial and error.

For the fiber case the same formula gives 51×51 against the 52 cap. It will remove the fiber — the true pixels around it outvote it — but the margin is gone, so inspect the crest at magnification. If the corrugation softens, drop the grayscale filtering and rely on the threshold-and-fill route, which ignores dust size entirely.

Fix 2: keep Gaussian away from contaminants

imgaussfilt with sigma 4 makes this image worse, and the arithmetic shows why. A Gaussian concentrates most of its response within 3σ, so at σ = 4 a single bright grain spreads over a disc of radius 12 px, about 24 px across. The grain's brightness is averaged, not rejected: the flank gray rises, the cut walks outward, and the outline bulges precisely where the fur sits.

The two filters answer different questions. Gaussian is a weighted average — the right tool for smooth sensor grain noise. Median is an order statistic: any feature holding fewer than half the window's pixels simply loses the vote, which is also why a straight edge keeps its position. Dust and fur are impulsive outliers, closer to salt-and-pepper noise than to Gaussian grain, and published filter comparisons consistently find the median the stronger tool for that class. Blur with intent: for grain noise, spend exposure before gain first — the brightfield noise and exposure guide covers that order; for discrete contaminants, use the sized median. If the edges are soft before any filtering, that is a separate defect — see machine vision motion blur: diagnose soft edges.

Fix 3: place the cut from the screw peak, not from graythresh

graythresh returns Otsu's level normalized to 0-1; multiply by 255 for the gray level. On this histogram the two dominant classes are the dark screw and the bright background, and the maximum-variance cut falls in the valley between them — the same gray band the dust occupies. That is why one frame binarizes clean and the next sprouts holes: the dust cluster straddles the cut.

Read the peaks and place the cut yourself. In the worked example, suppose imhist on the crop shows a screw peak at 45, a dust cluster from 110 to 155, and a background peak at 225:

  • Low cut, the default choice: t = 110 − 20 = 90. Dust binarizes white, becoming holes inside the black screw — and dust sitting on the bright background vanishes too, since 110-155 lies above 90. Fill the holes and the silhouette is whole.
  • High cut: t = 155 + 20 = 175. Dust binarizes black and merges with the screw, which fills interior dust automatically but keeps every protruding fiber as a bulge.

The low cut wins for enclosed dust. Keep 20 gray levels of margin as a rule of thumb against lamp drift; the screw peak keeps 45 levels (90 − 45), so the part itself is never eaten. If the default imadjust stretch has merged the dust cluster into the background peak, skip the stretch and threshold the raw crop. Treat graythresh's value as a first guess and move it to the histogram-derived level — the mechanics of polarity and valleys are in edge detection without a vision library: threshold basics.

The corrected pipeline

  1. Crop first, then histogram: run imhist on the 709 × 332 crop so the peaks belong to the part, not the bench around it.
  2. Measure the widest speck w and the pitch p in pixels; compute k = 2w + 1 and round up to odd. Apply medfilt2(I2,[k k]) only if k ≤ p/2 — otherwise leave the grayscale alone.
  3. Threshold at t = dust_bottom − 20 from the histogram, passed to imbinarize as t/255 (90/255 ≈ 0.35 in the example), not at graythresh's default.
  4. Fill interior holes; this is what removes enclosed grains and fibers of any size.
  5. Delete foreground islands smaller than about 50 px² — roughly the grain's own area, π × 4² ≈ 50 for an 8-px grain.
  6. Take the perimeter of the filled mask as the outline: it is 1 px wide by construction. Skip edge(I4,'Sobel') on the binary — a 3×3 gradient on a step returns a band 2-3 px wide, not a line. If you need gradient edges for sub-pixel fitting, run the operator on the filtered grayscale instead.
  7. Re-run on at least 20 parts, including deliberately dusty ones, before locking the parameters.
Quantity Value Arithmetic
Pixel scale 12 µm/px 48 mm ÷ 4000 px
Grain 8 px 0.1 mm ÷ 0.012 mm/px
Kernel 17 × 17 2 × 8 + 1
Pitch 104 px 1.25 mm ÷ 0.012 mm/px
Kernel cap 52 104 ÷ 2
Fiber case 51 × 51, strained 2 × 25 + 1 vs 52
Peaks (example) 45 / 110-155 / 225 imhist on the crop
Cut 90 110 − 20
Island cutoff 50 px² π × 4²

When the right fix is at the camera

Filtering cannot beat physics: the ratio between speck width and pitch is set by the optics, and re-imaging moves both numbers together. What resolution does buy is quantization — a grain spanning 2-3 px cannot be measured or filtered reliably, while 6-8 px gives the kernel room between its floor and the cap. So, in order of payoff:

  • Blow off or brush parts before the station; dust removed mechanically never reaches the algorithm. For contaminants that stay, lighting does more than filtering — the dark contaminant detection guide pairs light geometry with blob checks.
  • Light it as a silhouette. Dark steel on a bright field gives the bimodal histogram this whole method relies on; the choice between bright and dark field follows the law of reflection, as set out in bright field vs dark field lighting.
  • Use a mono sensor. One gray value per pixel keeps the dust cluster separated from the screw peak — the same argument made in light color and monochrome camera contrast.

Cameras that give the worked example its headroom: the LS-CMR-0166 resolves 4000 × 3000 over USB3, and the MV-CS200-10UM pairs 20 MP with a mono, 1-inch sensor. Over the same 48 mm field, 4000 columns deliver the 12 µm/px used throughout this page. In a production cell, the LS-SMC-0063 runs the same median-threshold-count logic on the device, with its own polarized white light that suppresses glare specks before they reach the threshold.

Area scan cameras12 MP USB3 area scan camera, IMX226, 1/1.7" rolling shutter, 30 fps, 4000 × 3000 resolution (LS-CMR-0166)LS-CMR-0166
Sensor format
1/1.7"
Interface
USB3
$560.00View part
Area scan camerasHikrobot MV-CS200-10UM 20 MP Area Scan Camera, USB3.0, IMX183, 2nd Gen., MonoMV-CS200-10UM
Sensor format
1"
Lens mount
C-mount
$759.00View part
Smart cameras & vision sensors1.6 MP smart camera, 60 fps, 16 mm lens, white light (polarized, transparent light) (LS-SMC-0063)LS-SMC-0063
Interface
GigE
Resolution
Up to 3 MP
$1,110.00View part

Choosing the camera and optics for a thread station is its own exercise in pixels per millimetre — see screw thread inspection camera selection for robot handling, or start from the area scan cameras and smart cameras pages.

Checks before you trust the outline

Run down this list on the bench, not on a single frame:

  • Histogram: three separated peaks; the cut sits at least 20 gray levels from the dust cluster, and the screw peak keeps its margin (45 levels in the example).
  • Kernel: odd-sized, at least 2w + 1 for the widest speck in a dirty batch, at most half the smallest pitch you inspect. Size for the worst part, not the clean one.
  • Outline width: 1 px from the mask perimeter; a 2-3 px band means a gradient operator ran on the binary.
  • Stability: preview the binary across a full shift — lamp aging moves the peaks, and the live binary preview makes the drift visible at a glance.
  • Crests: inspect at magnification wherever fibers crossed the crest; an open notch that hole-filling cannot reach means the sized median has to carry that batch.
  • Small islands: nothing under 50 px² remains in the mask.

Then build the station around the measurement: the camera finder filters by resolution and interface, the camera configurator pairs the sensor with lens and light, and more image-chain fixes sit on the systems topic page. If the part and the lighting need a layout review before you order, reach us through the contact page.

Questions

Why does the outline come out two to three pixels wide after the Sobel pass?

Sobel is a 3×3 gradient. On a binary image the step sits between adjacent pixels, and every 3×3 window touching the step returns a nonzero gradient, so edge(I4,'Sobel') returns a band roughly 2-3 px wide instead of a line. The perimeter of the filled mask is 1 px by construction; if you need a gradient edge, run it on the filtered grayscale and thin the result.

Can I remove the dust from the binary mask instead of the grayscale?

Yes, and it is the better route when fibers are wide. Threshold so the dust falls on the background side, fill interior holes, then delete foreground islands below roughly the dust's own area — about 50 px² for an 8-px grain. The limit is a fiber that crosses the crest: its overlap leaves an open notch that filling cannot reach, and that batch needs the sized median.

Does the kernel size change when I re-image at a different resolution?

The pixel numbers move, the margin does not. Speck and pitch scale together, so the ratio of kernel floor (2w+1) to cap (p/2) is set by the optics. At 6 µm/px the 0.1 mm grain needs k = 2×17+1 = 35 while the cap rises to 104 (208 px pitch ÷ 2), so filterability is unchanged — cleaning and lighting stay the real levers.

How much margin should the threshold keep from the dust cluster?

About 20 gray levels on an 8-bit image, between the cut and the nearest edge of the dust cluster. That absorbs lamp drift of a few percent without flipping dust pixels across the cut, while leaving 45 levels above the screw peak in the worked example (90 − 45). If drift moves the background peak into the remaining gap, re-derive the cut from a fresh histogram rather than nudging the constant.

When is the dust too fine to filter at all?

Below about 5 px of speck width the kernel floor collapses toward 11 px and the window starts eating flank texture along with the dust. Fix the image instead: narrow the field or add pixels so grains reach 6-8 px, switch to a mono sensor for cleaner gray separation, and add backlight or polarization so glare stops mimicking contamination on the crest.