← Template matching con búsqueda de rotación en OpenCV
Inspección visual (visión artificial)Raspberry Pi + cámara USBUSB/V4L2Visión artificial
Template matching con búsqueda de rotación en OpenCV — ejemplo completo
Template matching tolerante a rotación para inspección pass/fail en OpenCV. Rota la captura de -15 a +15 grados y puntúa con TM_CCOEFF_NORMED.
Programa completo y ejecutable para el Raspberry Pi + cámara USB (rotational-template-matching.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 — Template matching with rotational search
Hardware: Raspberry Pi + USB camera
Based on: visual inspection project in production (vision.py)
Requirements:
pip install opencv-python numpy
What it does:
Compares the binary image of a part with its reference trying
rotations from -15 to +15 degrees in steps of 2 (TM_CCOEFF_NORMED).
The part arrives from the conveyor with variable orientation: without
the rotational search, a good part rotated 8 degrees would give a low
score and a false reject. Score >= 0.85 -> PASS.
The script generates a reference and a synthetic "live capture"
(the same silkscreen rotated and with noise) so it runs without
hardware, and prints the score for every angle tried.
Integration with the catalog:
- The input (binary image of the part) is produced by the pipeline
(ejemplos/inspeccion-visual/opencv-silkscreen-pipeline.py).
- The reference is saved/loaded as a processed PNG from references/
(ejemplos/inspeccion-visual/json-config-references.py).
- The OK/NOK verdict is shown in the operation tab of the GUI
(ejemplos/inspeccion-visual/gui-pyside6-camera-thread.py).
"""
import cv2
import numpy as np
# ---------------------------------------------------------------------------
# Search parameters (in production they are read from config.json)
# ---------------------------------------------------------------------------
ANGLE_MIN = -15 # degrees
ANGLE_MAX = 15
STEP = 2 # precision / cycle-time trade-off on the Pi
OK_THRESHOLD = 0.85 # minimum TM_CCOEFF_NORMED score to return PASS
def binary_silkscreen(text="IS-42", angle=0.0, noise=False):
"""Generates a binary silkscreen image, optionally rotated."""
img = np.zeros((200, 300), np.uint8)
cv2.putText(img, text, (60, 120), cv2.FONT_HERSHEY_SIMPLEX,
1.5, 255, 4)
if angle:
M = cv2.getRotationMatrix2D((150, 100), angle, 1.0)
img = cv2.warpAffine(img, M, (300, 200))
if noise: # simulates stray pixels left by the adaptive threshold
salt = np.random.rand(*img.shape) > 0.995
img[salt] = 255
return img
def rotational_match(live, ref_img):
"""
Searches for the reference inside the capture trying rotations.
Returns (best_score, best_angle, best_position).
"""
# Padding around the capture: no corners are lost when rotating and
# matchTemplate requires the image to be larger than the template.
pad = max(ref_img.shape) // 2
live_padded = cv2.copyMakeBorder(live, pad, pad, pad, pad,
cv2.BORDER_CONSTANT, value=0)
h, w = live_padded.shape
center = (w // 2, h // 2)
size = (w, h)
best_match, best_angle, best_loc = -1.0, 0, (0, 0)
for angle in range(ANGLE_MIN, ANGLE_MAX + 1, STEP):
# We rotate the capture (not the reference): the processed
# reference is the "good" pattern and stays untouched.
M = cv2.getRotationMatrix2D(center, angle, 1.0)
rotated = cv2.warpAffine(live_padded, M, size)
# TM_CCOEFF_NORMED: invariant to global brightness changes
res = cv2.matchTemplate(rotated, ref_img, cv2.TM_CCOEFF_NORMED)
_, max_val, _, max_loc = cv2.minMaxLoc(res)
print(f" angle {angle:+3d} degrees -> score {max_val:.3f}")
if max_val > best_match:
best_match, best_angle, best_loc = max_val, angle, max_loc
return best_match, best_angle, best_loc
def main():
# "Good pattern" reference and simulated capture: the same silkscreen
# rotated 8 degrees with noise, as it would arrive from the conveyor.
ref_img = binary_silkscreen(angle=0)
live = binary_silkscreen(angle=8, noise=True)
print("Searching for the reference with rotations from "
f"{ANGLE_MIN} to {ANGLE_MAX} degrees (step {STEP}):")
score, angle, loc = rotational_match(live, ref_img)
verdict = "PASS" if score >= OK_THRESHOLD else "DEFECTIVE"
print(f"\nBest score: {score:.3f} at {angle:+d} degrees "
f"(threshold {OK_THRESHOLD}) -> {verdict}")
# Visualization: reference and capture side by side
cv2.imshow("Reference (pattern)", ref_img)
cv2.imshow(f"Live capture -> {verdict}", live)
cv2.waitKey(0)
cv2.destroyAllWindows()
if __name__ == "__main__":
main()
Descarga el pack completo del proyecto — gratisEste ejemplo + los relacionados + lista de materiales