Back to Portfolio

Wear Scar Detection with YOLO, Python and LabVIEW

Why I Built This

In tribological testing (evaluating friction, wear and lubrication on ball-on-disc or pin-on-disk test rigs), measuring the wear scar on a metallic or ceramic test specimen is critical for calculating the wear volume and anti-wear properties of lubricants.

Traditionally, measuring a wear scar is done manually: an operator stares into an optical microscope, manually drags digital calipers across the circular or elliptical crater, and reads the crosshair measurements. This has severe drawbacks:

To solve this, I designed and deployed an automated optical measurement pipeline: a high-resolution digital microscope takes the picture, a custom-trained YOLO neural network locates the exact scar boundary, and LabVIEW drives the test sequence over a deterministic byte-level binary TCP protocol, converting pixel dimensions to calibrated micrometres (μm).

The 6-Step Implementation Pipeline

  1. Capturing high-resolution images from the microscope camera via DirectShow
  2. Annotating polygon contours using LabelMe
  3. Converting annotations and fine-tuning YOLOv5 to export best.pt
  4. Inference pipeline, Non-Maximum Suppression (NMS), and pixel-to-micrometre optical scaling
  5. Freezing Python into headless, standalone Windows executables (PyInstaller)
  6. Controlling the executables from LabVIEW using a robust, big-endian binary TCP protocol

How the Pieces Fit Together

LabVIEW is the single master executive on the test bench. It knows the exact test sequence, motor positions, and cycle counts. It spawns two independent Python background processes as standalone executables and commands them over TCP sockets.

System Architecture: LabVIEW Master · 2 Python EXEs · best.pt

LabVIEW Master Test Sequencer & State Machine (Runs on every test) TCP :5006 Camera Daemon (EXE) PyQt5 + DirectShow Video Stream Saves captured frame Optical Microscope USB 3.0 CMOS Camera DirectShow TCP :5007 Detection Viewer (EXE) YOLOv5 Inference + ROI Calc Returns JSON μm Measurements saved image best.pt Model Trained YOLOv5 Weights (Offline Fine-Tuned)

Step 1: Capturing Images with a Python Live-Stream App

Every high-performance model depends on a rock-solid, consistent dataset. The first application I wrote (livestream10.py) has one critical mission: display the live microscope camera stream and snap an uncompressed, crystal-clear frame whenever LabVIEW issues the trigger.

The dataset goal was 100 to 500 images acquired at calibrated optical magnification and fixed resolution (up to 3840 × 2880), saved with specimen and test IDs.

Internal Architecture:

Three Production Tweaks that Solved Rig Instability

1. Fast Mode Probing: Calling OpenCV's resolution probes sequentially causes driver deadlocks on DirectShow USB microscope cameras. Instead, I query supported sensor resolutions directly through QCameraInfo from the Windows kernel and fall back to DirectShow only if needed.

2. Hidden Frame Capturing: Every incoming frame is stored in a thread-safe last_frame buffer before UI rendering. Therefore, an image save request succeeds with zero frame drops even if the GUI window is minimized.

3. Camera Release on Minimize: Minimizing the window suspends the capture thread and releases the hardware lock on the USB sensor, cooling down the CMOS sensor during prolonged idle cycles.

Camera Command Set (TCP Port 5006)

Command Hex Code Payload Operational Description
Connect Camera0x01Camera index (e.g. 0)Probes resolutions, opens DirectShow stream, selects max mode
Start Stream0x02NoneActivates acquisition thread and displays frameless view
Stop Stream0x03NoneStops capture worker and hides GUI
Grayscale Mode0x04NoneSwitches capture stream to single-channel 8-bit mono
RGB Mode0x05NoneSwitches stream to full 24-bit RGB color
Minimize0x07NoneReleases camera hardware lock and hides window
Close App0x08NoneClean process termination (only if authorized)
List Modes0x12NoneReplies 0x90 with supported W x H resolution modes
Set Mode0x13Mode indexApplies selected resolution profile
Restore0x14NoneRe-locks camera and restores GUI to foreground
Save Image0x15Full filepathFlushes last_frame to disk with cv2.imwrite()

The core socket receiver ensures packet boundary integrity by enforcing a 3-byte header protocol:

# Big-endian packet parsing with socket framing protection
header = conn.recv(3)
cmd = header[0]
payload_len = int.from_bytes(header[1:3], "big")

# Enforce stream completeness
payload_raw = self.recv_exact(conn, payload_len)
payload = payload_raw.decode("utf-8", errors="ignore")

Step 2: Annotating the Scar with LabelMe

Because metallic wear scars have irregular, semi-circular or elliptical geometry with feathering at the boundaries, rectangle bounding boxes during annotation are insufficient. I used LabelMe to annotate the true boundary as a multi-point closed polygon.

{
  "shapes": [
    {
      "label": "scar",
      "points": [
        [412.3, 188.7], [430.1, 175.2], [455.8, 172.0],
        [468.4, 190.5], [462.0, 215.9], [430.6, 222.1],
        [410.2, 205.4]
      ],
      "shape_type": "polygon"
    }
  ],
  "imagePath": "specimen_014.jpg",
  "imageHeight": 480,
  "imageWidth": 640
}

Golden Rules for Industrial CV Datasets

  • Tight Boundary Tracing: The neural network's localization precision can never exceed the accuracy of the manual training ground truth.
  • Immutable Class Typography: Strict case normalization (e.g. always "scar", never "Scar" or "SCAR") prevents phantom classes.
  • Optical Diversity: Include specimens with different reflectivities, oil films, scratches, and variable microscope light angles to prevent the model from memorizing specific illuminations.

Converting LabelMe JSON to YOLOv5 Format

YOLO requires normalized coordinates in a companion .txt file matching each image: <class_id> <x_center> <y_center> <width> <height> normalized from 0.0 to 1.0.

x_center = (x_min + x_max) / (2 * W)   y_center = (y_min + y_max) / (2 * H)
width = (x_max - x_min) / W   height = (y_max - y_min) / H
Worked example (W=640, H=480, Box [410.2, 172.0] to [468.4, 222.1]) → 0 0.6864 0.4105 0.0909 0.1044

Step 3: Training YOLOv5 and Exporting best.pt

With images and normalized labels partitioned 80/20 into train/validation folders, fine-tuning is launched:

python train.py --img 640 --batch 16 --epochs 300 \
  --data dataset.yaml --weights yolov5s.pt --cfg yolov5s.yaml

The secret to achieving >98% mAP with only a few hundred images was transfer learning from yolov5s.pt pretrained on MS COCO. The lower convolutional layers already understand edge gradients, texture contrasts, and elliptical profiles.

Network Architecture Deep Dive:

Why Deploy best.pt Over last.pt?

During a 300-epoch run, last.pt merely represents the final epoch, which frequently suffers from minor overfitting. In contrast, best.pt preserves the checkpoint that achieved maximum validation fitness on unseen test data across mAP@0.5:0.95.

Step 4: Sub-Micrometre Optical Calibration

Neural networks output pixel coordinates. To obtain engineering units for ISO/ASTM wear scar standards, we must calibrate pixel distance against a certified optical stage micrometer:

k = Known Distance (μm) / Measured Distance (px)
Optical Calibration Constant (k in μm/pixel)
Major = L_px × k    Minor = S_px × k    Average = (Major + Minor) / 2
Dimensional wear diameter calculation

The Viewer and Measurement TCP Daemon (Port 5007)

A second PyQt5 application opens the captured image, displays the bounding ellipse and ROI overlay, and exposes measurement endpoints to LabVIEW:

Group Hex Commands Function
Image0x01, 0x02Open image file, Fit image to window
ROI0x03, 0x04, 0x14, 0x12Draw rectangle/line, Resize ROI, Clear all annotations
Zoom0x06, 0x07, 0x08Zoom in, Zoom out, Absolute zoom percentage
Measurements0x13Return JSON payload with Major, Minor, Mean (μm)
Replies0x80 (ACK), 0x81 (Error)Deterministic handshake response to LabVIEW

Step 5: Packaging Standalone Windows EXEs

Factory test benches and customer PC deployments must never depend on manual Python environments, CUDA toolkits, or pip dependencies. Both applications are compiled into self-contained Windows binaries using PyInstaller:

pyinstaller --noconfirm --onefile --noconsole livestream10.py

Critical Windows Multiprocessing Fix

When compiling Python scripts that spawn threads or subprocesses on Windows, failing to include multiprocessing.freeze_support() causes compiled binaries to spawn infinite fork-bomb loops on execution!

import multiprocessing
if __name__ == '__main__':
    multiprocessing.freeze_support()

Step 6: Driving the Entire Sequence from LabVIEW

LabVIEW initiates the test run, uses System Exec to launch the background executables, and connects via standard LabVIEW TCP sockets.

Binary Packet Specification:

Field Length Type Description
CMD1 ByteU8Command opcode (e.g. 0x01, 0x15)
LENGTH2 BytesU16 (Big-Endian)Payload byte count (MSB first)
PAYLOADN BytesUTF-8 StringFilepath, JSON parameter, or empty

Example: Sending filepath C:\LAB-IQ 150\images\test.png (29 bytes = 0x001D) with command 0x01:

01 00 1D 43 3A 5C 4C 41 42 2D 49 51 20 31 35 30 5C 69 6D 61 67 65 73 5C 74 65 73 74 2E 70 6E 67

End-to-End Automated Test Sequence:

  1. Launch & Handshake: LabVIEW starts camera daemon, connects to localhost:5006.
  2. Hardware Init: LabVIEW transmits 0x01 (Connect Camera) → 0x02 (Start Stream).
  3. Trigger Capture: Specimen reaches cycle count; LabVIEW sends 0x15 (Save Image) with file target.
  4. Release Resource: LabVIEW transmits 0x07 (Minimize) to release USB DirectShow handle.
  5. Inference & Measure: LabVIEW sends image path to detection viewer (0x01), then requests results (0x13).
  6. Report Generation: LabVIEW reads the JSON reply ({"major_um": 421.8, "minor_um": 415.2, "mean_um": 418.5}) and appends values to the certified ASTM test certificate.

Key Engineering Takeaways

  • Dataset Quality > Model Architecture: Eliminating reflections and standardizing magnification outperformed tuning hyperparameters by an order of magnitude.
  • Pretrained Weights are Mandatory: Fine-tuning pretrained COCO weights allowed training to converge in under 45 minutes on a standard laptop GPU.
  • Binary TCP beats REST/HTTP: In industrial environments, a 3-byte binary TCP header eliminated HTTP latency, parsing overhead, and third-party web server dependencies.
  • Decoupled Architecture: Keeping machine vision in Python and hardware sequencing in LabVIEW leverages the best strengths of both platforms without monolithic lock-in.
KS

Kiran Shivakumar

Application Engineer specializing in LabVIEW Test & Measurement, Computer Vision (YOLO/OpenCV), Robotics (ROS/SLAM), and Industrial Protocols.