ALL WORK

CASE STUDY · IDENTIUM TECH SOLUTIONS

Component Counter

Photograph loose parts, get a count and a stock record.

ROLE
Full-Stack · Computer Vision
CLIENT
Identium Tech Solutions
YEAR
2026
STACK
Python 3.10FlaskOpenCV (opencv-python)NumPycryptography (self-signed TLS)qrcodeVanilla JavaScriptHTML/CSS

OVERVIEW

Counting a pile of loose SMD parts by hand is slow and easy to get wrong. This app turns it into one photo: the phone opens a capture page served off the PC, the user frames the parts inside a guide box and taps the shutter, and the server returns a count plus an annotated image with every detected part outlined and numbered. The count can be nudged with +/- if it is off, then written to a named inventory item as either "add to stock" or "set stock to". A PC dashboard shows the current stock and recent activity. Everything runs on the local Wi-Fi, so it works on a bench with no internet.

THE CHALLENGE

The hard part was that none of the failure modes were about code, they were about photos taken on a real bench. Parts can be darker or lighter than the surface, the light is uneven, parts touch and overlap, and whatever is around the parts (a hand, the table edge, the texture of the mat) looks like more parts to a naive threshold. Fixing each one without breaking the others took a chain of filters with explicit reasoning behind every constant: deviation from a smooth background model for lighting, per blob distance thresholds for mixed part sizes, border and median based rejection for scene clutter, and finally an opt-in similarity cluster for the case where the surface itself has texture. Each guard had to be capped so it could not eat genuine parts, for example the speck filter is capped relative to min_area so a small screw next to large capacitors is never dropped just for being far from the median.

WHAT I BUILT

  • 01

    A classical CV counting pipeline in server/counter.py: the background is modelled as a heavy Gaussian blur computed at 1/16 resolution, foreground is the absolute deviation from that model thresholded with Otsu against a fixed 12 gray level noise floor, OR'd with an HSV saturation mask so coloured parts on white surfaces are not missed. Because the threshold is on absolute deviation, parts darker and lighter than the surface are both found, and slow lighting gradients and soft shadows get absorbed into the background model instead of showing up as phantom parts.

  • 02

    Overlap splitting: each blob gets its own distance transform, thresholded against that blob's own maximum distance rather than a global value, so big and small parts in the same photo split correctly. The resulting cores seed a watershed. A 0 to 100 slider on the phone maps to the seed ratio (0.25 to 0.65) when clusters of touching parts come back undercounted.

  • 03

    An 'All items identical' mode that fixed textured mats and paper edges being counted as parts. Regions are described by area, mean Lab colour and contrast against the background; for each region the set of similar regions forms a candidate cluster, scored as member count times median contrast to the power 0.75, and only the winning cluster survives. The contrast weighting stops a numerous but faint texture cluster from outvoting fewer real, high contrast parts.

  • 04

    A Flask server that binds plain HTTP on 8000 in a background thread and self-signed HTTPS on 8443 on the main thread (the phone camera API needs a secure context), prints an ASCII QR code of the LAN URL to the console for connecting, and regenerates the certificate whenever the SAN no longer covers the current LAN IP, so a new DHCP lease does not leave a permanent hostname mismatch warning on the phone.

  • 05

    A phone capture page in plain JS with a live camera preview, a dashed guide box, and cropping of the source frame to exactly that box before upload so hands and table edges never reach the counter. It falls back to the camera file picker on plain HTTP, redrawing the picked JPEG through createImageBitmap so EXIF rotation is baked in, and disables the save button after a successful write until something actually changes so a double tap cannot add the same stock twice.

  • 06

    A JSON inventory store guarded by a module lock, written atomically via temp file plus os.replace, with corrupt files quarantined aside instead of overwritten, quantities clamped at zero and history capped at 1,000 entries. Upload handling parses JPEG, PNG and WebP headers for dimensions and rejects anything over 40 MP before cv2.imdecode, since a tiny solid-colour JPEG can decode to a multi-gigabyte array.

OUTCOME

The 13-case synthetic test suite passes, covering dark and light backgrounds, overlapping pairs, illumination gradients, corner shadows, mixed part sizes, and textured mats with and without uniform mode. It was verified end to end on a real phone counting SMD resistors over a USB bridge, with the last upload written to disk so any miscount can be replayed against the exact photo that caused it.

NEXT TRANSMISSIONPayTime Auto Download