← Pipeline de inspección de calidad OpenCV en Raspberry Pi
Inspección visual (visión artificial)Raspberry Pi + cámara USBUSB/V4L2Visión artificial
Pipeline de inspección de calidad OpenCV en Raspberry Pi — ejemplo completo
Pipeline de preprocesado OpenCV para inspección de impresión en Raspberry Pi: umbralización, morfología, CLAHE y umbral adaptativo, bien explicados.
Programa completo y ejecutable para el Raspberry Pi + cámara USB (opencv-silkscreen-pipeline.py): incluye cabecera de conexionado, requisitos y notas de integración.
Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales
Vista de solo lectura.
# -*- coding: utf-8 -*-
"""
COMPLETE EXAMPLE — OpenCV vision pipeline for silkscreen inspection
Hardware: Raspberry Pi + USB camera
Based on: visual inspection project in production (vision.py)
Requirements:
pip install opencv-python numpy
What it does:
Processes ONE image end to end with the same pipeline used on the
production line to isolate the silkscreen print of a part:
gray -> Gaussian blur -> background threshold -> open/close morphology
-> largest contour -> crop -> CLAHE -> medianBlur
-> adaptive threshold
If no image path is given, it generates a synthetic part so the
script runs without hardware. It displays every stage on screen.
Integration with the catalog:
- The resulting binary image is the input of the rotational template
matching module (ejemplos/inspeccion-visual/rotational-template-matching.py).
- The 6 adjustable parameters are loaded from config.json
(ejemplos/inspeccion-visual/json-config-references.py).
- The live-tuning sliders are in the PySide6 GUI
(ejemplos/inspeccion-visual/gui-pyside6-camera-thread.py).
"""
import sys
import cv2
import numpy as np
# ---------------------------------------------------------------------------
# Pipeline parameters (the same ones the GUI exposes through sliders).
# In production they are read from config.json; here they are inline for clarity.
# ---------------------------------------------------------------------------
CFG = {
"bg_threshold": 60, # fixed threshold to separate part from dark background
"morph_kernel": 5, # morphology kernel size (odd)
"clahe_clip": 2.0, # CLAHE contrast limit
"clahe_grid": 8, # CLAHE grid size
"block_size": 31, # adaptive threshold neighborhood (odd)
"C": 7, # constant subtracted in the adaptive threshold
}
def synthetic_image():
"""Generates a light 'part' on a dark background with a silkscreen print."""
img = np.full((480, 640, 3), 25, np.uint8) # dark background
cv2.rectangle(img, (180, 120), (460, 360), (190, 190, 190), -1) # part
cv2.putText(img, "IS-42", (230, 260), cv2.FONT_HERSHEY_SIMPLEX,
1.6, (40, 40, 40), 4) # silkscreen
noise = np.random.randint(0, 18, img.shape, np.uint8) # camera noise
return cv2.add(img, noise)
def process(frame, cfg):
"""Full pipeline. Returns (gray_crop, binary) or None on failure."""
# 1) Gray + Gaussian blur: removes high-frequency camera noise
gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
blurred = cv2.GaussianBlur(gray, (5, 5), 0)
# 2) Background threshold: the part is light on a dark background
_, bg_mask = cv2.threshold(blurred, cfg["bg_threshold"], 255,
cv2.THRESH_BINARY)
# 3) Open morphology (removes specks) + close (fills holes in the part)
k = cfg["morph_kernel"]
kernel = cv2.getStructuringElement(cv2.MORPH_RECT, (k, k))
bg_mask = cv2.morphologyEx(bg_mask, cv2.MORPH_OPEN, kernel)
bg_mask = cv2.morphologyEx(bg_mask, cv2.MORPH_CLOSE, kernel)
# 4) Largest contour = the part. If there are no contours, there is no part.
contours, _ = cv2.findContours(bg_mask, cv2.RETR_EXTERNAL,
cv2.CHAIN_APPROX_SIMPLE)
if not contours:
return None
c = max(contours, key=cv2.contourArea)
x, y, w, h = cv2.boundingRect(c)
# 5) Crop of the region of interest (only the part, background out)
cropped = gray[y:y + h, x:x + w]
# 6) CLAHE: local equalization -> robust against uneven lighting
clahe = cv2.createCLAHE(clipLimit=cfg["clahe_clip"],
tileGridSize=(cfg["clahe_grid"],
cfg["clahe_grid"]))
cropped = clahe.apply(cropped)
# 7) medianBlur: smooths without destroying the silkscreen edges
cropped = cv2.medianBlur(cropped, 3)
# 8) Adaptive threshold: binarizes the silkscreen regardless of the
# global lighting level of the scene
binary = cv2.adaptiveThreshold(cropped, 255,
cv2.ADAPTIVE_THRESH_GAUSSIAN_C,
cv2.THRESH_BINARY_INV,
cfg["block_size"], cfg["C"])
return cropped, binary
def main():
# Real image via argument, or synthetic one to test without a camera
if len(sys.argv) > 1:
frame = cv2.imread(sys.argv[1])
if frame is None:
sys.exit(f"Could not read the image: {sys.argv[1]}")
else:
print("No argument: using synthetic demo image.")
frame = synthetic_image()
result = process(frame, CFG)
if result is None:
sys.exit("No part detected in the image.")
cropped, binary = result
print(f"Part detected. Crop: {cropped.shape[1]}x{cropped.shape[0]} px")
# Stage visualization (press a key to close)
cv2.imshow("1 - Input", frame)
cv2.imshow("2 - Crop + CLAHE", cropped)
cv2.imshow("3 - Final binary (matching input)", binary)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales