ApplicationsTroubleshooting

Half-pixel offset in blob centroid: pixel coordinate origin

A blob centroid offset of half a pixel is a coordinate-origin issue, not thresholding: test your library's pixel origin, add 0.5 px and verify in mm.

LumaSpec Vision11 min read Share on LinkedIn

On this page 9 sections
  1. 01Why is my blob centroid half a pixel off to the north-west?
  2. 02Which point of the pixel does a coordinate actually name?
  3. 03How do I test my library's convention in one minute?
  4. 04Could the offset come from thresholding, lighting or fill holes instead?
  5. 05How much is half a pixel in millimetres?
  6. 06How do I correct the offset in code?
  7. 07When is half a pixel not a convention problem?
  8. 08What checks prove the fix before it ships?
  9. 09Questions

Your blob tool finds every circular fiducial, but the marks you draw at the reported centers sit about half a pixel up and to the left of the dots, and adding 0.5 px by hand makes them land. That systematic shift is not your threshold, your lighting or your camera: it is a mismatch between the point of the pixel your library counts from and the point your overlay draws. It shows on any sensor, from a legacy 640 × 480 camera to the current generation of area scan cameras and smart cameras, because the convention lives in the software. This guide shows you how to prove which convention you have with a synthetic image, what the bias costs in millimetres, how to correct it in one place in code, and which different signatures mean a different cause entirely.

Why is my blob centroid half a pixel off to the north-west?

Because your library indexes each pixel by its upper-left corner while your overlay marks the pixel center: the reported center of mass comes out exactly half a pixel low in x and low in y, which on a standard display reads as a shift toward the north-west.

Two conventions exist for naming a pixel with a number, and a center of mass is a weighted average of those numbers. If the integers fed to the average are corner references, the average inherits the corner reference. The pixel you call 10 covers the ground from 10 to 11, whose middle is 10.5, so the mean over a symmetric blob comes out half a pixel toward the origin in each axis.

Image coordinates place (0, 0) at the upper-left, x growing right and y growing down. Low in both axes therefore points up and left on screen, which is why adding half a pixel in x and in y pulls your mark back onto the dot.

This is a known migration path, not an anomaly: one long-established vision library referenced the upper-left corner through version 6.1 and moved to pixel-center coordinates in version 7.0. Code that drew marks at the raw values was correct before that change and half a pixel off after it, or the reverse for anyone who had already baked in a manual correction.

Which point of the pixel does a coordinate actually name?

In the pixel-center convention, integer coordinates (x, y) name the middle of a pixel, so one pixel spans x - 0.5 to x + 0.5; in the corner convention the integer names the upper-left corner, and that pixel spans x to x + 1.

pixel i spans [i, i + 1] (corner-referenced index i) true center of pixel i = i + 0.5 centroid = Σ(i · w_i) / Σ(w_i) (weighted mean of member indices) center-referenced centroid = centroid + 0.5 px bias in mm = 0.5 × (field of view / pixel count)

A convention has two parts: the point of the first pixel that (0, 0) names, and the corner the count starts from. Most current toolkits put integers at pixel centers; the corner convention survives in older libraries and in code written against them. Every sub-pixel output — center of mass, edge position, corner coordinate — is only as good as the frame it is expressed in, which is why the same question decides how to read sub-pixel corner coordinates and their half-pixel offsets. Two tools that disagree by exactly half a pixel in both axes on the same image are telling you their origins differ, not that one of them is inaccurate.

How do I test my library's convention in one minute?

Build a synthetic two-by-two white block at columns 8 to 9 and rows 12 to 13 of a black image and run your center-of-mass function on it: a center-referenced tool returns (9.0, 13.0) and a corner-referenced tool returns (8.5, 12.5).

The block covers x from 8 to 10, so its physical middle is 9.0; the mean of the member indices, (8 + 9) / 2, is 8.5. That half-pixel gap is the whole story. The pseudocode below is illustrative; the printed values are the proof:

# Synthetic test image: 300 rows × 400 columns, all zero
test_image: uint8[300][400] = 0
test_image[12:14, 8:10] = 255          # 2 × 2 white block
(x, y) = center_of_mass(test_image)    # your blob tool's call
# x = 9.0,  y = 13.0  ->  pixel-center convention
# x = 8.5,  y = 12.5  ->  corner convention: add 0.5 px per axis

Run the same image through every path that reports a position, the on-device blob tool as well as the PC library, and write the outcome beside the function name. Neighbour rules change blob membership, not the origin, so the choice covered in connectivity 4 or 8 in blob analysis for circular markers does not affect this test.

Could the offset come from thresholding, lighting or fill holes instead?

No: thresholding and lighting errors move the centroid by fractions that vary with blob size, edge gradient and position in the field, while a convention error is exactly half a pixel in both axes at every point.

The pipeline order — binarize, fill holes, filter particles, center of mass — changes which pixels belong, never the frame they are counted in. Filling holes alters membership only for ring-shaped fiducials; on a solid circle it changes nothing. A particle filter drops whole particles; it cannot slide a surviving one sideways. Connectivity toggles a few boundary pixels, worth a few hundredths of a pixel on a fiducial ten or more pixels across.

A biased threshold does shift a centroid, when one side of the circle cuts in earlier than the other. That shift grows with edge blur and differs between fiducials, so it never lands on the same half pixel in both axes at every position. The fastest discriminator is to move the part: a constant offset is the convention, and anything that changes with position or lighting is optics. Using a grayscale-weighted centroid on a dark circle shrinks the threshold's influence while you chase the rest.

How much is half a pixel in millimetres?

Take your field of view divided by your pixel count: over a 120 mm × 90 mm field of view on a 4000 × 3000 pixel image, one pixel is 0.03 mm, so the convention bias is 0.5 × 0.03 = 0.015 mm.

In full: 120 mm / 4000 px = 0.03 mm per pixel in x, and 90 mm / 3000 px = 0.03 mm per pixel in y, square pixels with convenient numbers. Half a pixel is therefore 0.015 mm, or 15 µm. A fiducial 40 px across spans 40 × 0.03 = 1.2 mm on the part, so the uncorrected bias is 1.25 % of its diameter.

Whether 15 µm matters is a tolerance question with a number, not a judgement call. A robot guidance tolerance of ±0.05 mm spends 30 % of its budget on this bias in each axis; a presence check with a ±0.5 mm margin spends 3 %. You correct it in both cases, because the correction costs one line of code.

The camera in this example is the LS-CMR-0166, a 12 MP USB3 area scan camera with a 4000 × 3000 frame.

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
Resolution
12 MP
Shutter
Rolling
$560.00View partBuild a system with it
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

To work the sizing the other way, from a smallest feature and a tolerance to a pixel count, use the camera resolution calculator, then the camera finder to filter by resolution and shutter.

How do I correct the offset in code?

Add 0.5 px to both coordinates exactly once, at the point where pixel values convert to physical units, and keep the correction in one place instead of scattering it through the application.

# Corner-referenced values returned by the blob tool
centroid_x: float = 8.5
centroid_y: float = 12.5
scale_mm_per_px: float = 0.03      # 120 mm field of view / 4000 px
x_mm: float = (centroid_x + 0.5) * scale_mm_per_px   # 9.0 × 0.03 = 0.270 mm
y_mm: float = (centroid_y + 0.5) * scale_mm_per_px   # 13.0 × 0.03 = 0.390 mm

If your toolkit exposes the pixel reference as a property of the image or of the measurement step, set it there and skip the arithmetic, but keep the synthetic block test in your start-up checks so an upgrade cannot silently flip it back.

Apply the correction before the value enters a distortion map, a rotation transform or a robot frame; every downstream transform inherits the origin of its input, so one corrected hand-off point protects the whole chain. Once the pixel frame is right, setting a coordinate system on a circle center shows how to carry it into part coordinates for moving parts.

When is half a pixel not a convention problem?

When the offset is not exactly half a pixel in both axes, not constant across the field, or confined to one axis: those signatures point to lens distortion, a color-plane offset or rolling-shutter skew instead.

Signature Likely cause Quick test Fix
Exactly -0.5 px in x and y, everywhere Corner versus center pixel origin Synthetic 2 × 2 block test Add the half pixel, or set the pixel reference
Grows toward the image edges Lens distortion Dot grid: compare pitch at the corners with the center Distortion calibration
Half a pixel in one axis only Color-plane or chroma-subsampled input Recompute on the green plane Do geometry on a full-resolution plane
Small, 0.05 to 0.2 px, changes with lighting Threshold asymmetry at the edge Rotate the part 180° and recompute Even illumination, grayscale-weighted centroid
Appears only when the part moves Rolling-shutter skew Stop the conveyor and recompute Global shutter or strobed exposure

On a color sensor with a Bayer pattern, the red and blue samples sit on a grid offset half a pixel from the green samples, so a centroid computed on a single color plane inherits a genuine half-pixel shift in both axes; use the green plane or the demosaiced image for geometry. The distortion row is the one worth calibration effort: lens distortion correction for pixel-to-mm accuracy covers the target and the math. For the motion row, start with strobe and exposure timing checks.

What checks prove the fix before it ships?

Run four checks on real hardware: after your correction the synthetic block test returns the block's starting index plus 0.5, the moved-part test shows no residual, the dot-grid pitch error stays under 0.05 px, and a 180° rotation shows no directional bias.

  • Synthetic block test. The deployed build, not your desk copy, must return the block's starting index plus 0.5 in each axis once your correction is in place; this pins the convention the shipped software actually uses.
  • Moved-part test. Put the fiducial at the center, then near each corner of the field. After correction the residual stays at zero; drift with position is distortion, not convention.
  • Dot-grid pitch test. Measure the spacing between adjacent dots of a target with a known pitch. An error under 0.05 px per spacing confirms scale and origin together.
  • 180° rotation test. Rotate the part, not the camera. Any remaining directional bias is lighting or threshold asymmetry, and the convention correction will not remove it.

The same four checks apply when the blob tool runs on the device. A smart camera such as the LS-SMC-0063, a 1.6 MP model with its own 16 mm lens and polarized white light, reports centers in a pixel frame with the same two possible origins, so feed its synthetic block image through the production measurement, not a preview tool.

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

The decision, one line per case. Exactly half a pixel in both axes, everywhere: pixel-origin convention, add the correction once at unit conversion. Offset growing toward the edges: distortion, calibrate it. One axis only: measure on a full-resolution color plane. Small and lighting-dependent: fix the illumination and switch to a grayscale-weighted centroid. Motion-only: shutter skew, freeze the part. When the correction is in and the microns per pixel still need to shrink, the vision system configurator starts at the camera step, and more measurement write-ups sit on the applications topic page. The contact page takes your field of view and pixel count for a check against your fixture.

Questions

Why does the half-pixel offset always point up and left?

Because image coordinates start at the upper-left corner, with x growing right and y growing down. A corner-referenced library subtracts half a pixel from the true center along both axes, which pulls the reported point toward the origin — north-west on a normal display. If your framework defines (0, 0) at a different corner, the offset follows that origin, so the shift always points toward wherever zero is defined.

Will a higher-resolution camera shrink the half-pixel error?

In millimetres, yes; in pixels, no. The pixel error stays exactly half a pixel until you correct the convention, but the physical bias scales with your mm-per-pixel value. At 0.03 mm per pixel the bias is 0.015 mm; doubling the pixel count over the same field of view makes it 0.0075 mm. Fix the convention first, then spend resolution on what remains of the error budget.

Do smart cameras and vision sensors have the same pixel-origin issue?

Yes. The convention lives in the measurement function, not the camera, so any platform that reports a center of mass in pixel units can use either origin. The synthetic block test works unchanged on a vision sensor: if the reported center equals the block's starting index instead of the index plus 0.5, apply the correction before converting to millimetres or robot coordinates.

How repeatable should a centroid be once the convention is fixed?

On a high-contrast circular fiducial with even lighting, a grayscale or binary centroid typically repeats to a few hundredths of a pixel. Residual scatter above roughly 0.1 px after the half-pixel correction points at threshold asymmetry, vibration or distortion instead. A dot target separates them: scale error grows with spacing, vibration smears in one direction, and threshold error follows the lighting.

Does the half-pixel rule apply to edges and corners too?

Yes. Every sub-pixel measurement — edge position, corner, centroid — is expressed in the same frame, so a convention mismatch shifts all of them by the same half pixel. Drawing and overlay functions carry a convention too, which is why mixing one library for measurement and another for display is the classic way the offset appears. Fix the frame once, at the hand-off between them.