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.
- 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.
- Skip correction under 0.3 px. Resampling every row softens edges by more than the error you remove; leave it alone.
- 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.
- Nothing known. Estimate each row by total variation, read the frequency from an FFT of those estimates, then apply step 3.
- 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:
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:
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.
- Lens mount
- M42
- Interface
- GigE
- Line length
- 2K
- Pixel size
- 7 μm
- Sensor format
- APS-H
- Mount
- M42
- Image circle
- 30-45 mm
- Focal length
- 25-50 mm
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.
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.
- Size
- 200-400 mm
- Connector
- 16M-6J
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
- 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.
- 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.
- 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.
- Pass a known defect through the pipeline before and after. Its contrast against the background must improve, never degrade.
- 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.


