System designExplainer

Hough line detection for cracks: when to switch to contour stats

Hough line detection works for straight cracks only; arcs get no votes. Contour statistics catch curved breaks and measure the biscuit in the same pass.

LumaSpec Vision10 min read Share on LinkedIn

On this page 11 sections
  1. 01What HoughLines actually returns
  2. 02Why counting red pixels proves nothing
  3. 03Where the line test breaks: arcs and texture
  4. 04The contour-statistics decision
  5. 05Worked example: a round biscuit with a curved crack
  6. 06From pixels to millimetres
  7. 07Hough lines versus contour statistics
  8. 08Thresholds that survive production
  9. 09Hardware: pixels, mono sensors and on-device decisions
  10. 10Checks before you trust the decision
  11. 11Questions

Your crack decision runs HoughLines and counts results, and it works while every crack is straight. Then a curved break returns an empty line vector on a cracked biscuit, or a ridged edge on a good one invents lines, and the pass/fail logic flips. This page explains what the line transform actually returns, why counting the red pixels you drew proves nothing, and how contour statistics decide on cracks of any shape while measuring the biscuit in the same pass. The worked example carries the numbers from a cracked round biscuit: axis ratio 0.987, circularity 0.871, largest defect contour 100.9 px, 79.45% of the part diameter. You finish with thresholds you can defend on the line.

What HoughLines actually returns

The call HoughLines(dst, lines, 1, CV_PI/180, 33, 1, 1) fills a std::vector of cv::Vec2f. Each element is a pair (rho, theta): rho is the perpendicular distance of an infinite line from the origin in pixels, theta is its angle in radians. The arguments set the resolution — 1 px in rho, 1° in theta (CV_PI/180) — and the vote threshold: a bin must collect 33 edge points before the line is reported. The edge map feeding it comes from Canny(src, dst, 30, 160, 3), with hysteresis bounds 30 and 160 and aperture 3.

The decision that an empty vector means no crack is legitimate while every crack you must reject is a straight segment: lines.size() == 0 means no collinear run gathered 33 votes. Decide on the vector, never on the drawing.

std::vector<cv::Vec2f> lines;                 // one (rho, theta) pair per line
cv::HoughLines(dst, lines, 1, CV_PI/180, 33, 1, 1);
bool crackFound = lines.size() > 0;           // branch here, not on drawn pixels

Vote arithmetic sets the false-positive risk: with 1 px and 1° bins, a collinear run of roughly 33 edge pixels can tip a bin over (quantisation spreads votes, so the exact count drifts either side of 33). Any run about that long — a tray edge, a pressed ridge in the dough, a string of crumb highlights — reports a line on a clean part.

Why counting red pixels proves nothing

The loop draws every detected line in red before any counting happens. After line(cdst, pt1, pt2, Scalar(0,0,255), 3, CV_AA), one reported line has become a strip of red pixels three pixels wide and as long as the segment was drawn; thickness and anti-aliasing set the count, not the crack. Counting Scalar(0,0,255) pixels answers whether your code drew, which is always yes when a line was found and always no when it was not — the test re-states lines.size() with extra steps and a pen width.

The repair costs nothing: delete the drawing loop from the decision path and branch on lines.size(). That is the entire fix inside the line approach — and it still leaves the arc problem.

Where the line test breaks: arcs and texture

The transform votes for infinite straight lines. An arc spreads its edge points across many (rho, theta) bins, so no single bin reaches 33 votes and lines.size() stays at 0 with a real crack in view. A strongly textured edge does the opposite: several bins tip over, and a clean part reports lines it does not have. Same code, both failure modes.

The commented-out HoughLinesP(dst, lines, 1, CV_PI/180, 50, 50, 10) variant returns Vec4i segments instead of infinite lines — threshold 50, minimum segment length 50 px, maximum gap 10 px — and you can sort segments by computed length. Segments are still straight: a curved crack splits into many short segments or none, so minLineLength rejects specks but never makes an arc detectable.

We would not ship the line test past the first curved crack on the reject set. If every crack you must catch is genuinely straight, keep it — the test is one boolean deep and fast.

The contour-statistics decision

Threshold the grayscale image so the biscuit is one bright blob on a dark tray, run cv::findContours, and call the largest contour the part. Every other contour in the part's region is a candidate defect. No shape is assumed, so an arc, a Y-shaped branch and a hairline arrive as ordinary outlines. For each contour, three numbers drive the decision:

axis ratio = short axis / long axis equivalent diameter d = 2 × sqrt(A / π) circularity C = 4 × π × A / P²

A perfect circle scores C = 1.00. Every other shape inflates the perimeter P for its area A, so C falls as the outline deviates from round — which is exactly what an edge-breaking crack does. The part contour's axis ratio and equivalent diameter check roundness and size; the defect contours' extents check cracks, whatever their curvature.

std::vector<cv::Point> contour;               // one contour from cv::findContours
double area  = cv::contourArea(contour);      // px²
double perim = cv::arcLength(contour, true);  // closed perimeter, px
double circ  = 4 * CV_PI * area / (perim * perim);

Fine hairline cracks inside the surface, rather than breaks through the outline, need a different first stage; crack segmentation that separates cracks from noise covers that pixel-level problem.

Worked example: a round biscuit with a curved crack

Inputs, as printed for one cracked biscuit:

  • Part contour area A = 12386 px²
  • Fitted axes: 125.292 px and 126.988 px
  • Circularity as printed: 0.870751
  • Largest defect contour: 100.896 px, printed as 79.4533% of the part
  • Edge source in the original attempt: Canny at 30/160, aperture 3

The rule: a round pressed part passes only when its outline is round (axis ratio ≥ 0.95), unbroken (circularity ≥ 0.90) and free of any defect contour reaching 10% of the part diameter.

Arithmetic:

  • Axis ratio: 125.292 / 126.988 = 0.9866 — a round part, well inside the 0.95 band.
  • Equivalent diameter: 12386 / π = 3942.6; sqrt(3942.6) = 62.8; × 2 = 125.6 px, agreeing with the fitted axes, so the fit is consistent.
  • Crack fraction: 100.896 / 126.988 = 0.7945 — the break runs 79.45% of the way across the biscuit.
  • Circularity: 0.8708 against the 0.90 pass line — 0.03 short.

Result: a round part with a broken outline, carrying a crack nearly as long as its diameter.

Decision: reject, on both crack tests with margin — the crack fraction is 7.9× the 10% limit and the circularity misses by 0.03. A Hough line test returns an empty or meaningless vector on this arc: the one decision the line test cannot make. Once the decision exists, turning biscuit crack results into line decisions is the next step down the line.

From pixels to millimetres

The stats stay in pixels until you calibrate. Put a ruler, or a good biscuit of known diameter, in the part plane with the camera square to the tray, then divide: mm per pixel = known length / measured length in px. The factor holds while lens and working distance stay put.

Take 0.10 mm per pixel as the calibration for this arithmetic. The example's 126.988 px long axis is 126.988 × 0.10 = 12.70 mm of biscuit, and a 0.5 mm crack spans 0.5 / 0.10 = 5 px. Size the optics the other way: choose mm per pixel so the narrowest crack you must reject spans at least 3 px, and 5 or more where you measure it. Camera resolution for defect detection walks the full sizing rule.

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 report how far a crack runs rather than that it exists, measure crack endpoint distance applies the same calibration to the gap between the ends.

Hough lines versus contour statistics

Hough line decision Contour statistics
Shapes caught straight segments only any outline: arcs, curves, branches
Decision input lines.size(), one boolean area, axes, circularity per contour
Measures the part no yes: diameter, axis ratio, area
False rejects tray edges and texture reach 33 votes filtered by extent and area floors
Drawn overlay needed for a human to see it; never for deciding none
Tuning knobs rho, theta, vote threshold binarisation, extent floor, circularity line

For a round part inspected whole, contour statistics win every row, and they deliver the size measurement the original task asked for in the same pass. Keep Hough transforms for features that match their model — live circle detection with the circle Hough transform shows the transform doing exactly that on circular marks.

Thresholds that survive production

Start here and move each line from logged data, not from feel:

  • Circularity pass line 0.90. A rasterised round part sits a little under 1.00 because pixelated boundaries overestimate perimeter; clean edges land near 0.95, crumbly edges lower. The cracked example scored 0.871.
  • Axis ratio band ≥ 0.95 catches oval and chipped parts; the example's 0.9866 passes with room.
  • Crack decision: reject when the largest defect contour reaches 10% of the part diameter. The example's 79.45% gives 7.9× margin.
  • Speck floor: drop a defect contour as texture only when both hold — extent under 5% of the part diameter (0.05 × 126.988 = 6.3 px here) and area under 1% of part area (12386 × 0.01 ≈ 124 px²). The 100.9 px crack exceeds both and survives the filter; a 3 px crumb speck fails both and disappears.

Hardware: pixels, mono sensors and on-device decisions

The algorithm costs nothing to run; the pixels carry it. Size the field for the 3–5 px rule, then take the shortest exposure that fills the histogram. Mono sensors skip the colour-interpolation step, which buys cleaner contours at the same resolution. More pixels over the same field shrink mm per pixel directly: the LS-CMR-0166 delivers 4000 × 3000 pixels over USB3 at 30 fps.

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

Two routes exist for deployment. A camera plus PC runs the OpenCV above and gives you full control of the thresholds. A smart camera runs binarise–contour–measure logic on the device and switches an output, with no C++ to maintain beside the line. Browse the area scan cameras in the camera finder, or the smart cameras category for the on-device route; the camera configurator steps through exposure, trigger and mount.

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 sensorsHikrobot MV-SC5060XM-08M-WBN SC5000X series Smart Camera, 6 MP, Mono, 8 mm focal length, M12-mountMV-SC5060XM-08M-WBN
Interface
GigE
Resolution
6-10 MP
$1,629.00View part

Checks before you trust the decision

  • Light for contrast: bright biscuit on a dark tray, or backlight the outline. Shadows and specular streaks read as cracks.
  • Pin exposure and gain in manual. An auto-exposure ROI that re-adjusts as parts arrive moves your threshold under you; auto-exposure ROI instability is that failure mode in detail.
  • Threshold the grayscale image for the contour route. If you keep Canny, its 30/160 pair is a tuning knob — re-check it after any lighting change.
  • Apply the two-part speck floor before scoring defects.
  • Test the extremes: the most textured good biscuit and the shortest known-bad arc must each land on the right side.
  • Log every stat. Revisit thresholds after the first week of production data, then leave them alone.

When you move from bench to line, the sizing above turns tray dimensions, crack width and cycle time into pixel budgets directly; send them through the quote request and we will size the camera, lens and light against them. More decision-logic write-ups sit on the systems topic page.

Questions

Can I keep HoughLinesP and just set a minimum line length?

Yes, as a noise filter. HoughLinesP returns Vec4i end points, so you can compute each segment's length as the distance between them, and its minLineLength parameter drops shorter segments before they reach you. That removes speck-induced lines. It does not make arcs fit the model: a curved crack still returns many short segments or none, so keep it for straight-crack products only.

What circularity score counts as a crack?

A perfect circle scores 1.00, and a rasterised round part with a clean edge scores close to 0.95 because pixelated boundaries overestimate perimeter. The cracked biscuit in the worked example printed 0.871. Start the pass line at 0.90; if good and bad parts overlap anywhere near it, demote circularity to a corroborating check and reject on the defect contour instead.

How many pixels wide must a crack be?

At least 3 pixels across the narrowest crack you must reject, and 5 or more where you measure its length or width. Below about 3 pixels the anti-aliased edge blurs into the background and the perimeter, so the circularity, gets noisy. At 0.10 mm per pixel a 0.5 mm crack is exactly 5 pixels.

How do I convert contour statistics into millimetres?

Calibrate once with a known length in the part plane: mm per pixel equals the real diameter of a good biscuit divided by its measured pixel axes. At 0.10 mm per pixel, the example's 126.988 px long axis is a 12.7 mm biscuit. Keep the camera square to the tray, because a tilted view stretches one axis and biases the axis ratio at the same time.

Can this decision run on a smart camera instead of a PC?

Yes. Smart cameras and vision sensors run threshold, contour and measurement rule sets on the device and switch an output, so the axis-ratio, circularity and extent logic ships without maintaining C++ beside the line. Before you pick a model, check it exposes area and shape measurements plus a discrete output, then port the thresholds from the worked example.