AI visual inspection uses cameras and trained models to decide whether a part is good or defective, at line speed and without operator fatigue. This article explains how an inspection station is built, which defects AI handles better than rule-based machine vision, and how to publish every pass/fail result over MQTT so line control, dashboards and traceability records stay in sync.
What AI Visual Inspection Does
Traditional machine vision relies on hand-tuned rules: measure an edge, count pixels above a threshold, compare against a template. That works well for dimensional checks on consistent parts. It struggles when defects vary in shape and appearance, such as scratches, dents, porosity, contamination, wrinkled labels or inconsistent welds, or when lighting and part finish change during a shift.
AI visual inspection replaces or complements those rules with a learned model. Depending on the problem, the model is one of three kinds:
- Classification: the whole image is labelled
okorng(not good), or assigned a defect class. - Object detection or segmentation: the model draws boxes or masks around defects, so you know where and how large they are.
- Anomaly detection: the model is trained mostly on good parts and flags anything that looks unusual. This is valuable when defective samples are rare, which is common on a mature line.
The output that matters to the factory is simple: a verdict, a confidence, the defect type and an identifier that ties the result to a specific part.
Anatomy of an Inspection Station
A working station is much more than a model. The parts that decide whether it succeeds are usually physical.
| Component | What to decide | Common pitfall |
|---|---|---|
| Camera | Area or line scan, resolution, monochrome or colour | Choosing resolution before knowing the smallest defect size |
| Optics | Lens focal length, working distance, depth of field | Parts at different heights going out of focus |
| Lighting | Ring, dome, backlight, low-angle dark field | Ambient light from windows changing results through the day |
| Trigger | Photo sensor or encoder signal from the PLC | Software-timed capture that drifts with conveyor speed |
| Compute | Industrial PC with GPU, Jetson, or smart camera | Running inference on a shared office PC |
| Reject | Air blast, diverter, robot pick | No confirmation that the reject actually happened |
| Data path | MQTT to line control, MES and dashboards | Results stored only on the inspection PC |
Lighting deserves the most attention. A good lighting setup makes defects obvious to a human looking at the raw image; if you cannot see the scratch, the model is unlikely to learn it reliably.
Building the Defect Model
The model workflow follows a predictable path:
- Collect images from the real station, with the final camera, lens and lighting. Images from a phone on the bench do not transfer.
- Label consistently. Write a short defect catalogue with examples of each class and the boundary between acceptable and reject. Disagreement between labellers becomes model noise.
- Train and validate on images the model has never seen, including parts from different batches and shifts.
- Choose the threshold with quality engineers. Missing a defect (escape) and rejecting a good part (false reject) have very different costs, so the confidence threshold is a business decision.
- Run in shadow mode first: the model publishes verdicts but does not trigger rejects, and you compare with manual inspection.
- Deploy to the edge, typically as an optimized model on a GPU or accelerator next to the camera. See deploying ML models to edge devices for packaging and update strategies.
Inference belongs at the edge because the reject decision must happen before the part leaves the reject window. Sending full images to the cloud adds latency and bandwidth for no benefit. What travels over the network is the result. The edge AI vs cloud AI comparison covers that trade-off in more detail.
Why MQTT for AI Visual Inspection Results
An inspection verdict has several consumers: the PLC or line controller that fires the reject, the MES or traceability database that records the result against a serial number, an andon board, a quality dashboard and possibly an engineer's laptop during commissioning. Wiring point-to-point connections to each of them is fragile. With publish/subscribe, the station publishes once and every consumer subscribes to what it needs.
MQTT fits this well because it is lightweight, supports QoS 1 for at-least-once delivery, and lets you publish a retained station status so a dashboard that starts later immediately knows whether the station is online. Topic hierarchies following ISA-95 levels give every result a clear address, for example:
connectmq/plant1/assembly/line2/inspect1/result
connectmq/plant1/assembly/line2/inspect1/status
connectmq/plant1/assembly/line2/inspect1/stats
connectmq/plant1/assembly/line2/inspect1/cmd/threshold
ConnectMQ detects ISA-95 and Unified Namespace style topics and shows devices by enterprise, site, area, line and cell in its devices view, which makes a growing set of inspection stations easier to navigate. If you already use Sparkplug B on the line, the Sparkplug B and Unified Namespace guide explains how the two topic styles coexist.
Result Payload Design
Keep one message per inspected part, small and self-describing:
{
"partId": "SN-24-104877",
"ts": "2026-10-16T09:41:07.312Z",
"verdict": "ng",
"defect": "scratch",
"confidence": 0.93,
"bbox": [412, 188, 470, 214],
"model": "housing-defects-v7",
"threshold": 0.80,
"inferenceMs": 18,
"imageRef": "inspect1/2026-10-16/SN-24-104877.jpg"
}
The model and threshold fields are what make results traceable months later: when a customer complaint arrives, you can show which model version judged the part and with which setting. The image itself stays on local storage or goes to an image store; the MQTT message carries a reference, not the pixels.
Example: Publishing Inspection Results in Python
This example simulates the station loop with paho-mqtt 2.x. Replace run_model() with your real inference call. It publishes results at QoS 1, a retained online/offline status with a Last Will, and periodic yield statistics.
import json
import random
import time
import uuid
from datetime import datetime, timezone
import paho.mqtt.client as mqtt
HOST, PORT = "broker.connectmq.com", 1883
RUN_ID = uuid.uuid4().hex[:8]
BASE = f"connectmq/demo/{RUN_ID}/plant1/assembly/line2/inspect1"
MODEL = "housing-defects-v7"
threshold = 0.80
def run_model(image=None):
"""Stand-in for real inference: returns (label, confidence, bbox)."""
if random.random() < 0.08:
return random.choice(["scratch", "dent", "missing_screw"]), random.uniform(0.6, 0.99), [412, 188, 470, 214]
return "ok", random.uniform(0.85, 0.99), None
def on_connect(client, userdata, flags, reason_code, properties):
print("connected:", reason_code)
client.publish(f"{BASE}/status", "online", qos=1, retain=True)
client.subscribe(f"{BASE}/cmd/threshold", qos=1)
def on_message(client, userdata, msg):
global threshold
try:
value = float(msg.payload)
if 0.5 <= value <= 0.99:
threshold = value
print("threshold set to", threshold)
except ValueError:
print("ignored invalid threshold", msg.payload)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"inspect1-{RUN_ID}")
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
client.on_connect = on_connect
client.on_message = on_message
client.connect(HOST, PORT, keepalive=30)
client.loop_start()
ok = ng = 0
try:
for n in range(1, 10_000):
start = time.perf_counter()
label, conf, bbox = run_model()
inference_ms = round((time.perf_counter() - start) * 1000, 1)
verdict = "ng" if label != "ok" and conf >= threshold else "ok"
ok, ng = (ok + 1, ng) if verdict == "ok" else (ok, ng + 1)
part_id = f"SN-{RUN_ID}-{n:06d}"
result = {
"partId": part_id,
"ts": datetime.now(timezone.utc).isoformat(timespec="milliseconds"),
"verdict": verdict,
"defect": None if label == "ok" else label,
"confidence": round(conf, 3),
"bbox": bbox,
"model": MODEL,
"threshold": threshold,
"inferenceMs": inference_ms,
}
client.publish(f"{BASE}/result", json.dumps(result), qos=1)
if n % 20 == 0:
stats = {"ok": ok, "ng": ng, "yield": round(ok / (ok + ng), 4)}
client.publish(f"{BASE}/stats", json.dumps(stats), qos=1, retain=True)
time.sleep(0.5) # stays well under the public broker's 10 msg/s per client
finally:
client.publish(f"{BASE}/status", "offline", qos=1, retain=True)
client.loop_stop()
client.disconnect()
Watch the results from another terminal, replacing the ID with the one in the topic:
mosquitto_sub -h broker.connectmq.com -p 1883 -i qa-viewer-91c2 \
-t 'connectmq/demo/<run-id>/plant1/assembly/line2/inspect1/#' -v
The public test area is fine for trying the payload and topic design, but it is open to everyone. Production inspection data belongs on a private broker over TLS on port 8883 with the credentials and client ID issued for the station.
Closing the Loop with Line Control
The reject actuator should not depend on a cloud round trip. In most plants the station signals the PLC directly through I/O or a fieldbus for the reject itself, and MQTT carries the same verdict for everything else: counting, traceability, dashboards and alarms. Where a PLC or edge controller does subscribe to the result topic, keep the broker close to the line or make sure the reject logic fails safe when the connection drops.
A few rules keep the system honest:
- Publish a reject confirmation from the diverter sensor, so you can match
ngverdicts against actual rejects. - Put the model version in every message and retain a
statustopic describing the active model. - Restrict who can publish to
cmd/topics, and validate every command value on the station, as the example does for the threshold. - Alert on yield drift. A sudden drop in yield is often a lighting or camera issue rather than a real quality problem.
ConnectMQ threshold alert rules can watch numeric values such as the yield in a stats message and record alerts in the alerts log, which is a quick way to notice drift without building a separate monitoring service.
Common Problems in Production
| Symptom | Likely cause | What to check |
|---|---|---|
| Accuracy dropped after a maintenance stop | Camera or light moved | Compare a reference image against today's capture |
| More false rejects in the afternoon | Ambient light | Enclosure or shroud around the inspection area |
| Missing results in the traceability database | Subscriber offline, QoS 0 | QoS 1 and a persistent session for the database writer |
| Duplicate records | QoS 1 redelivery | Use partId as an idempotent key when inserting |
| New defect type not detected | Model never saw it | Feed escapes back into labelling and retrain |
For related vision projects, see YOLO object detection over MQTT, the Jetson Nano MQTT guide, and the AI and IoT pillar guide.
Connect Your Inspection Stations to ConnectMQ
Every station, dashboard and traceability writer needs a reliable place to meet. ConnectMQ gives you a hosted MQTT broker with TLS, MQTT 5, an ISA-95 devices view and threshold alerts, without running broker infrastructure on the plant network. Create a broker on the free Serverless plan at app.connectmq.com, no credit card required, and move to a Dedicated or BYOC deployment when the line goes into production. If you are still evaluating, the public MQTT test broker is a good place to prototype the payload.
Frequently Asked Questions
How many images do I need to train an AI visual inspection model?
It depends on how varied the parts and defects are. Start with images from the real station, cover every defect class and normal variation, and add examples whenever the model misses something in shadow mode.
Should images be sent over MQTT?
Generally no. Send the verdict, confidence and an image reference over MQTT and store images locally or in an object store, because large binary messages hit broker size limits and slow every subscriber.
Can AI visual inspection replace rule-based machine vision?
Not always. Precise dimensional measurement is still best done with rules, while AI handles variable cosmetic defects. Many stations combine both and publish a single verdict.
What QoS should inspection results use?
QoS 1 is the usual choice, because every result must arrive for traceability. Make the database insert idempotent on the part ID so occasional duplicates do no harm.



