System designGuide

Line scan sinusoidal jitter correction without a reference

Sinusoidal jitter correction for a line scan image with no reference: bound the amplitude, estimate each row shift by total variation, resample each row once.

LumaSpec Vision12 min read Share on LinkedIn

On this page 10 sections
  1. 01The decision, up front
  2. 02What the vibration actually does
  3. 03The condition that makes blind correction possible
  4. 04Estimate each row by total variation
  5. 05Known amplitude and frequency: search the phase
  6. 06Worked example: a 2K line on a swinging bracket
  7. 07Choosing the fix
  8. 08When blind correction is the wrong answer
  9. 09Checks before you ship
  10. 10Questions

A line scan camera on a bracket never sits perfectly still. When the bracket swings sideways while the web moves, every captured row lands a few pixels left or right of its true position, and the rebuilt image shows a wave: straight edges wander, circles grow lobes, print appears to ripple. A vertical reference bar spanning the image makes the correction trivial, but full-width webs, wide sheets and beds of loose product leave nowhere to put one. This guide gives the blind procedure: bound the amplitude, estimate each row's shift by minimizing row-to-row total variation, fit one sinusoid, resample once. You get the conditions that decide whether it works, a worked example on a 2K line, and the cases where software is the wrong tool.

The decision, up front

Run down this list and stop at the first step your machine allows.

  1. Add a measured reference. A fixed vertical edge in every frame, an encoder on the swinging part, or an accelerometer sampled once per line turns estimation into measurement. A clean edge centroid yields about 0.1 px, and no blind method matches that.
  2. Skip correction under 0.3 px. Resampling every row softens edges by more than the error you remove; leave it alone.
  3. Amplitude and frequency known, phase unknown. Warp the whole image with a single sinusoid for each candidate phase and keep the warp with the lowest cost. Expect the corrected rows within 0.5 px.
  4. Nothing known. Estimate each row by total variation, read the frequency from an FFT of those estimates, then apply step 3.
  5. No horizontal detail, or a print repeat below twice the amplitude. No estimator has anything to lock onto. Spend the budget on the bracket, not on code.

Accept one property up front: every blind method returns the image up to one constant horizontal offset. A pure translation of the whole frame is harmless for both inspection and measurement.

What the vibration actually does

Number the rows y and write the lateral shift of each row, in pixels, as d(y). One vibrating mode gives:

d(y) = A · sin(2π · y / λ + φ) Δd_max = 2π · A / λ smear = 2π · f_vib · A_mm · t_exp

Here A is the amplitude in pixels, λ the spatial period in rows, f_vib the vibration frequency in hertz, A_mm that amplitude at the object in millimetres, and t_exp the exposure time. Line one is the correctable part. Line three is not: while the shutter is open the bracket keeps moving along its swing and smears content inside the row. Hold that smear under 0.3 px, because no shift correction recovers it.

A third distortion often hides in the same picture: when web speed varies, rows land at uneven spacing along the motion. That is a different fault with a different cure — trigger the camera from an encoder, as described in encoder-triggered line scan capture: wiring quadrature inputs and make line scan line rate follow machine speed at runtime. Lateral vibration survives encoder triggering untouched.

The condition that makes blind correction possible

Two numbers decide it. First, the row-to-row change Δd_max = 2πA/λ must stay below 0.5 px, so the shift found on one row is still valid on the next. At A = 15 px that means λ ≥ 2π · 15 / 0.5 ≈ 189, so 190 rows per vibration cycle or slower. λ follows from the acquisition numbers, λ = line rate / f_vib; if no line rate is set yet, how to set the line rate of a line scan camera walks through the choice.

Second, the rows must carry lateral information. A row that is flat from left to right looks the same at every candidate shift. Weight each pixel by its horizontal gradient and keep only pixels above twice the temporal noise σ, a few gray levels read off a static scene. Features should also be at least 2 px wide on the object, so that sub-pixel interpolation of the row is faithful.

Estimate each row by total variation

For row y, try candidate shifts s inside the bound and keep the one that makes the row resemble the corrected row above it:

cost_y(s) = Σ_x w(x) · |I_y(x − s) − J_(y−1)(x)|

J is the previous row resampled at its accepted shift and w the gradient weight. The absolute value — total variation, the same L1 measure behind TV denoising — lets a defect or a spark disagree on a few pixels without dragging the estimate toward it, which a squared difference would do.

Search in 0.25 px steps. The per-row estimate only has to seed the sinusoid fit; the fit averages hundreds of rows and delivers the sub-pixel accuracy. On the first row, sweep the full bound; on every later row, search ±1 px around the previous estimate, because Δd_max < 0.5 px guarantees the truth sits inside. At a ±16 px bound the first sweep tests 129 candidates and each later row 9; on a 2048-px row that is roughly 55,000 multiply-accumulates per row, and a 1,000-row image finishes in well under a second in compiled code.

import numpy as np

img = np.zeros((1000, 2048), dtype=np.uint8)  # your captured frame, 0-255
rows, cols = img.shape
A_MAX, STEP, TRACK = 16.0, 0.25, 1.0       # float, px: bound, step, track half-window
I = img.astype(np.float32)
grid = np.arange(cols, dtype=np.float32)

def resample(row, s):                      # row: float32 (cols,), s: float, px
    return np.interp(grid - s, grid, row)

shifts = np.zeros(rows, dtype=np.float32)  # per-row estimates, px
for y in range(1, rows):
    ref = resample(I[y - 1], shifts[y - 1])
    w = np.abs(np.gradient(ref))           # lateral information per pixel
    lo = max(-A_MAX, shifts[y - 1] - TRACK)
    hi = min(A_MAX, shifts[y - 1] + TRACK)
    cand = np.arange(lo, hi + STEP / 2, STEP)
    costs = [np.sum(w * np.abs(resample(I[y], s) - ref)) for s in cand]
    shifts[y] = cand[int(np.argmin(costs))]

k = 2.0 * np.pi * 40.0 / 10345.0           # rad/row, from f_vib and line rate
yi = np.arange(rows, dtype=np.float32)
basis = np.stack([np.sin(k * yi), np.cos(k * yi)], axis=1)
a, b = np.linalg.lstsq(basis, shifts, rcond=None)[0]   # floats, px
fit = a * np.sin(k * yi) + b * np.cos(k * yi)          # equals A·sin(k·y + φ)
assert np.max(np.abs(shifts - fit)) < 2.0              # px; scatter proves tracking held
out = np.stack([resample(I[r], fit[r]) for r in range(rows)])

For long streams, process in blocks with 200 rows of overlap discarded after fitting — the discipline explained in line scan image overlap across frame boundaries.

Known amplitude and frequency: search the phase

When amplitude and frequency are known to about ±20% and only the phase is free, drop the per-row freedom. Warp the whole image with d(y) = A · sin(2πy/λ + φ) for each candidate φ, score the total variation between all neighbouring rows, keep the winner. Sample φ at 15° first — 24 warps — refine around the winner at 0.5° — 30 more — then polish with the least-squares fit from the code above. If the amplitude is only known loosely, grid it too: five amplitudes across ±20% times 24 phases is 120 warps. Each warp on a 1000 × 2048 image costs about 2.05 million cost terms, so the full search stays near a hundred million terms: seconds in compiled code, about a minute in an interpreted loop.

The global search is the one to trust on printed webs. A free per-row tracker can slip by whole print periods and still minimize its local cost; a single bounded sinusoid cannot jump a period without leaving the amplitude bound.

If λ is unknown, run the per-row tracker first and read the frequency from an FFT of its estimates. With 1,000 rows the bins are 0.001 cycle/row wide; interpolating between the two nearest bins pins λ to around 5%.

Worked example: a 2K line on a swinging bracket

Printed foil at 300 mm/s, camera bracket measured with a hand-held accelerometer at ±0.44 mm and 40 Hz. Parts first, then the arithmetic.

The LS-LCM-0016 is a 2K GigE line scan camera with 7 μm pixels, a 58 kHz line rate and an M42 mount. A 2K line is 2048 pixels. The LS-LSL-0215 is a 25 mm, 0.24x line scan lens in an M42 mount with a 30-45 mm image circle. A 2K sensor line of 7 μm pixels spans 2048 × 7 μm = 14.3 mm, well inside that circle; matching lens to sensor is the ground covered in line scan lens selection for 4K, 8K and 16K cameras.

Line scan cameras2K GigE line scan camera, 7 μm, 58 kHz, TDI, M42 (LS-LCM-0016)LS-LCM-0016
Lens mount
M42
Interface
GigE
Line length
2K
Pixel size
7 μm
$950.00View partBuild a system with it
Line scan lenses25 mm, 0.24x, M42-mount line scan lens (LS-LSL-0215)LS-LSL-0215
Sensor format
APS-H
Mount
M42
Image circle
30-45 mm
Focal length
25-50 mm
$1,449.00View partBuild a system with it
A line scan camera building an image line by line The camera images one thin line of the moving web per exposure, lit by a line light. An encoder on the conveyor triggers each line so the pixels stay square at any speed.one line per exposureLine scanline lightweb speed vencoderline rate = v / object pixelexposure < 1 / line rate
A line scan camera building an image line by line. The camera images one thin line of the moving web per exposure, lit by a line light. An encoder on the conveyor triggers each line so the pixels stay square at any speed.

On the object, one pixel is 7 μm / 0.24 = 29 μm, so the field of view is 2048 × 29 μm = 59.4 mm — the calculation set out in line scan camera field of view: how to calculate it. At 300 mm/s the line rate is 300 / 0.029 = 10,345 Hz, which is 18% of a 58 kHz ceiling. The line period is 97 μs; a 30 μs exposure smears 300 mm/s × 30 μs = 9 μm = 0.3 px along the motion.

Line-scan camera line rate
Units
Pixel on the part
– mm
Line rate needed
– kHz
Longest exposure
– µs
Data rate
– MB/s
Lines per metre
–
Encoder setting
–

Square pixels: line rate = speed / pixel on the part; the exposure must end within one line period. Encoder: lines per pulse = (π × roller diameter / pulses per revolution) / pixel on the part.

Find line scan cameras Open the full calculator

Now the jitter. Amplitude in pixels: 0.44 mm / 29 μm = 15 px, so set the bound one pixel higher at ±16 px. Spatial period: λ = 10,345 / 40 = 259 rows. Row-to-row change: 2π × 15 / 259 = 0.36 px, inside the 0.5 px limit, so tracking holds. Smear inside a row: peak lateral velocity is 2π × 40 Hz × 0.44 mm = 111 mm/s, and 111 mm/s × 30 μs = 3.3 μm = 0.11 px — safely under 0.3 px, so every row is a clean shifted sample of the foil.

The phase search runs 24 coarse warps plus 30 fine ones, about 110 million cost terms in total. Per-row estimates scatter ±2 px around the fitted sinusoid; the fitted curve is what you correct with, and it lands within 0.5 px. Resample each row once at its fitted shift, then crop 16 px per side where shifted rows ran out of image: 2016 px, or 58.5 mm of clean width.

A 30 μs exposure at 10.3 kHz is a 31% strobe duty cycle, and the light for it needs real power — sizing it is the subject of line scan light: how much power a fast line really needs and lighting controllers for line scan cameras: what they fix.

Line scan lights300 x 17 mm Line light source, 87 W red / 108 W white (LS-LSC-0527)LS-LSC-0527
Size
200-400 mm
Connector
16M-6J
$1,009.00View partBuild a system with it

The LS-LSC-0527 is a 300 x 17 mm line light source at 87 W in red or 108 W in white. Three hundred millimetres of line over a 59 mm field of view leaves margin and lets you slide the light along its own axis to move a specular streak off the print. Other widths and colours start from line scan lights, with the cameras and lenses in line scan cameras and line scan lenses.

Choosing the fix

Fix You must add Accuracy to expect Use it when It fails when
Reference edge in frame a fixed vertical target ~0.1 px the machine allows a target the target fouls or leaves the field
Accelerometer per line a sensor and pixel scaling 0.2-0.5 px the bracket can be instrumented mount resonance masks the band
Phase search amplitude and frequency ≤0.5 px bounds known ±20%, phase unknown print repeat below twice the amplitude
Per-row tracking, then fit nothing ≤1 px no measured numbers at all repeating print, gradient-poor rows
Mechanical isolation mounts, mass, damping removes the cause amplitude above 15 px or smear above 0.3 px never — but it needs a machine change

Per case: a machine you can stop and retool gets the reference edge — half a day of fitting and 0.1 px thereafter. A bracket you cannot instrument permanently but can measure once with an accelerometer gets the phase search, re-measured after any mechanical change. A line with no measured numbers gets per-row tracking first, upgraded to the phase search once the FFT yields λ. Amplitude above 15 px or smear above 0.3 px takes you out of software: stiffen the mount, add mass, or bolt the camera to the stationary side of the joint.

When blind correction is the wrong answer

  • Print repeat below 2A. The pattern repeats inside the search window and the minimum becomes ambiguous; at A = 15 px that is any repeat under 30 px.
  • Long stretches without gradient. The sinusoid fit bridges gaps up to about λ/5 — 50 rows in this example — but a web with no horizontal detail anywhere gives nothing to align.
  • Amplitude under 0.3 px. The resampling blur outweighs the error removed.
  • λ under 190 rows at A = 15 px. Rows change too fast to track, and the content inside each row is stretched and compressed, not merely shifted.
  • Smear above 0.3 px. Shorten the exposure and add light before touching the algorithm.

Checks before you ship

  1. Capture with the web stationary and the strobe running. The per-row estimates must sit within ±0.3 px of zero; a trend means the estimator is following lighting drift, not geometry.
  2. Take a clean image, add d(y) = 15 · sin(2πy / 259 + φ) numerically, and run the correction at several phases. The recovered curve must stay within 0.5 px of the truth.
  3. Lay a straight physical edge across the web. After correction, a straight-line fit through it must stay within 0.5 px end to end.
  4. Pass a known defect through the pipeline before and after. Its contrast against the background must improve, never degrade.
  5. Crop the 16 px margins and confirm no step at the frame seam; the overlap discipline covers the boundary.

Correction restores row alignment, not metrology. Once the geometry is stable, finish with line scan camera calibration with a moving checkerboard target to pin scale and perspective, then set the parts against these numbers in the line scan camera finder or from the camera step of the system configurator.

Questions

Can the correction recover sub-pixel shifts, or only whole pixels?

Sub-pixel, in two stages. The per-row search samples candidate shifts at 0.25 px on interpolated rows, and the sinusoid fit then averages hundreds of estimates, which is where the final accuracy comes from — averaging 1,000 rows cuts a single estimate's noise by roughly the square root of 1,000. Resample each row once with a cubic kernel. Below about 0.3 px of amplitude, skip correction: the interpolation loss outweighs the gain.

How wide should the search window be around the measured amplitude?

Set it about one pixel above the largest amplitude you have measured, and no wider. In the worked example a measured 15 px becomes a ±16 px bound. Extra width multiplies the candidates and, on a printed web, opens the door to locking one full print period away — that shows up as a sudden step in the per-row estimates mid-image. Re-measure after any change to the bracket or the web path, because a bound that no longer contains the true swing breaks tracking.

Will an encoder or a faster line rate remove the jitter?

An encoder removes the wrong defect: it fixes uneven row spacing caused by speed variation, not the lateral shift caused by vibration. A higher line rate does help the arithmetic, because the spatial period in rows grows — λ = line rate divided by vibration frequency — which lowers the row-to-row change 2πA/λ that the 0.5 px limit caps. It does nothing for the smear inside a row; only a shorter exposure with more light does that.

What lighting makes the estimator work better?

Lighting that creates horizontal gradient: oblique or dark-field line light that turns surface texture into edges, and enough power for a 30 μs-class exposure without approaching saturation. Clipped highlights have a flat top with zero gradient and carry no lateral information. A gradient-weighted cost ignores them, but a web lit to saturation across most of its width starves the estimator by design.

When is software correction simply impossible?

Four hard cases: a web with no horizontal detail anywhere, so every candidate shift costs the same; a print repeat smaller than twice the amplitude, which makes the minimum ambiguous; a spatial period under about 190 rows at 15 px amplitude, where rows change too fast to track; and intra-row smear above 0.3 px, which no shift can undo. In all four the fix is mechanical — stiffen, damp, or isolate the mount.