MVP en Python para medir calibre (mm) y color de fruta a partir de una foto, orientado a packing agrícola. Primer objetivo: cítricos/mandarina; diseño extensible a uva de mesa.
- Escala: se detecta un marcador ArUco (diccionario
DICT_4X4_50) de lado conocido (default 50 mm) apoyado en el mismo plano que la fruta. Del tamaño del marcador en píxeles se derivamm/px. - Segmentación (dos backends,
--segmentador):hsv(default): máscara por saturación (la fruta es saturada; el fondo neutro y el marcador no) + morfología + contornos externos. Rápido y sin dependencias, pero exige fondo neutro.yolo: YOLOv8-seg preentrenado en COCO (clases orange/apple/ banana — cubre mandarina sin entrenar nada). Funciona con fondo real de packing. Requierepip install "fruta-vision[yolo]"; los pesos (models/yolov8n-seg.pt, 6.7 MB) se descargan solos si faltan. Ambos backends comparten el mismo filtro (exclusión del marcador, área mínima, orden izquierda→derecha) y el resto del pipeline.
- Calibre: diámetro del círculo mínimo envolvente ×
mm/px→ código de calibre según tabla de mandarina (bandas simplificadas sin traslape basadas en UNECE FFV-14; configurable ensrc/fruta_vision/measure.py). - Color: HSV medio dentro del contorno (borde erosionado para no
promediar con el fondo), CCI (Citrus Color Index =
1000·a/(L·b)en CIELAB) y clase de madurez:naranja | pintona | quiebre | verde. - Salida: imagen anotada por foto +
results.csvcon una fila por fruta.
python3.11 -m venv .venv
.venv/bin/pip install -e ".[dev]"
# generar imágenes de muestra sintéticas
PYTHONPATH=src .venv/bin/python scripts/make_samples.py
# procesar un directorio (o una imagen individual)
.venv/bin/fruta-vision data/samples --marker-mm 50 --out out
# con fondo real (packing): backend YOLO preentrenado
.venv/bin/pip install "ultralytics>=8.2"
.venv/bin/fruta-vision foto_packing.jpg --segmentador yolo --out out
# regenerar la muestra con naranjas reales (recorta de foto USDA/Wikimedia)
PYTHONPATH=src .venv/bin/python scripts/make_real_sample.pySalidas en out/: <imagen>_annotated.jpg y results.csv con columnas
image, fruit_id, diameter_mm, diameter_eq_mm, area_mm2, circularity, calibre, color_class, hue_mean, saturation_mean, value_mean, cci, mm_per_px.
Para imprimir un marcador:
import cv2
from fruta_vision.aruco import generate_marker
cv2.imwrite("marker_50mm.png", generate_marker(marker_id=0, side_px=400))
# imprimir a 50 x 50 mm exactos y pegarlo sobre superficie rígida.venv/bin/pytest -q
.venv/bin/ruff check src tests scripts
.venv/bin/black --check src tests scripts- Foto cenital con la fruta y el marcador en el mismo plano y sin oclusiones entre frutas. No se corrige perspectiva ni distorsión de lente (siguiente iteración: homografía con 4 marcadores o calibración de cámara).
- Fondo neutro (mesa blanca/gris/negra): la segmentación es por saturación, robusta y sin modelo entrenado. Con fondo complejo se necesitaría un modelo de segmentación (p. ej. SAM/YOLO-seg).
- Frutas separadas entre sí; no hay watershed para racimos/contacto. Para uva de mesa en racimo, la siguiente iteración es watershed sobre distancia + mínimos locales.
- La tabla de calibres es de mandarina y las bandas están simplificadas
para no traslapar (la norma UNECE permite traslape entre códigos).
Ajustar
CALIBRE_MANDARINAa la norma del cliente/exportadora; las uvas reportan diámetro en mm y quedanfuerade esa tabla, por diseño. - Umbrales de matiz para la clase de madurez (
color.py) son aproximados; calibrar con fruta real y luz del packing (idealmente caja de luz D65). data/samples/contiene imágenes sintéticas (círculos + ArUco real) generadas conscripts/make_samples.py, porque no hay fotos reales aún. Los tests validan diámetro con tolerancia 3 % y el signo del CCI.- El diámetro reportado es el del círculo mínimo envolvente (equivale al calibre ecuatorial si la foto es cenital); también se reporta el diámetro equivalente por área como control.
src/fruta_vision/
aruco.py # detección ArUco y escala mm/px
segment.py # máscara HSV y contornos de fruta
measure.py # calibre, área, circularidad y tabla de calibres
color.py # HSV medio, CCI y clase de madurez
annotate.py # imagen anotada
pipeline.py # orquestación + CSV
cli.py # entrada de línea de comandos
synthetic.py # escenas sintéticas (tests y muestras)
tests/ # 21 tests sobre escenas sintéticas de escala conocida
scripts/make_samples.py
data/samples/ # muestras sintéticas
En esta máquina el editable install (pip install -e .) no agrega src a
sys.path (el .pth no se procesa); por eso pytest usa
pythonpath = ["src"] y el CLI se instala con pip install . normal. Si
editas el código fuente, reinstala con
.venv/bin/pip install -q --force-reinstall --no-deps ..