Skip to content
alejoduquePublic

About

Sistema de identificación individual de ocelotes y posiblemente otras especies.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

13 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

🐆 Identificación individual de ocelotes — Proyecto MANAKAI

Herramienta para comparar dos videos o imágenes de cámara trampa y proponer, a una persona revisora, los pares de fotogramas con más evidencia compartida para decidir si se trata del mismo individuo de Leopardus pardalis (u otro felino con patrón de rosetas). El sistema no decide la identidad: separa lo que la máquina puede medir de lo que sólo una persona puede juzgar.

Salida de reid_compare.py: control positivo

"La tecnología estructura cómo los animales y los humanos comparten espacios, actúa como mediadora y 'conectora' de vidas a través del tiempo y el espacio. 'Facilita' formas de observar, medir, mover y matar, así como controlar, contener, conservar y cooperar con los animales. Configura las relaciones humanas con el mundo no humano, explorando a los animales no humanos como parientes, compañeros, alimento, transgresores, entretenimiento y herramientas." Tomado del libro "Compartiendo Espacios" para tener claro que eso que vemos con cámaras trampa genera también distanciamiento y una mirada desde el control, intrusiva y antropocéntrica.

¿Cómo preservar la alteridad radical de cada especie mientras sus individuos agencian dentro de redes amplias y complejas que no comprendemos? ¿Cómo y para qué realizar identificación de individuos sin simplificarlos a quasi-objetos sino como seres de una comunidad por venir?

Este repositorio hace parte de la tesis doctoral de Alejandro Duque en el programa D.A.C. (Diseño, Arte y Ciencia) de la Universidad Jorge Tadeo Lozano, con trabajo de campo en la Reserva MANAKAI.


Abstract (English)

Camera-trap re-identification tools usually collapse three different questions into one "similarity percentage": does a retrieval model relate the two images?, do the two images expose enough shared anatomy for a person to compare them responsibly?, and are they the same individual? Following the PF-ERI v2 framework (Shen, 2026), this repository separates those three layers explicitly. Version 3 replaces the earlier OpenCV-only heuristics with (1) MegaDetector v6 animal cropping, so that identical camera backgrounds cannot drive the match; (2) two frozen descriptors, MegaDescriptor-L-384 and DINOv2, aggregated over sampled video frames as retrieval support; (3) SIFT correspondences verified with MAGSAC++ as local pair evidence, reported with explicit failure states instead of silent zeros; and (4) a human review instrument (review_ready / not_review_ready / uncertain, structured reason codes, and a separate identity decision) appended to a CSV that can later calibrate — or refute — any automatic score. Controls on Creative-Commons ocelot camera-trap footage show the intended behaviour: two halves of the same video yield cosine ≈ 0.99 and 400–600 geometrically verified correspondences, while ocelots from different sites yield cosine ≈ 0.76–0.84 (the descriptors say "ocelot", not "this ocelot") and zero verified correspondences. No score in this repository is a calibrated probability of identity; identity remains a human decision.


⚡ Inicio rápido (cómo usar)

# 1. Instalar (una sola vez; requiere conda — ver §4)
git clone https://github.com/alejoduque/ID_indv.git && cd ID_indv
conda create -n ocelot python=3.11 -y && conda activate ocelot
pip install -r requirements.txt

# 2. Comparar dos videos (o imágenes) de cámara trampa
python reid_compare.py videos/A.mov videos/B.mp4

# 3. Mirar la figura y decidir como persona revisora (se guarda en reviews.csv)
python reid_compare.py videos/A.mov videos/B.mp4 --review --reviewer tu_nombre

Salida: outputs/A__B/review_figure.png (pares de fotogramas propuestos con sus correspondencias verificadas) y outputs/A__B/report.json. En consola verás tres capas: soporte de recuperación (descriptores), revisabilidad (evidencia local + pre-cribado) e identidad (siempre "decisión humana pendiente" hasta que la registres). Detalles y opciones en §5.

La primera ejecución descarga ~1.7 GB de pesos (MegaDescriptor, DINOv2, MegaDetector); después funciona sin conexión. Cada par de videos tarda ~25 s en un Mac Apple Silicon.


1. Qué cambió en la versión 3

v2 (baseline, OpenCV) v3 (reid/ + reid_compare.py)
Recorte del animal ninguno (el fondo de la cámara entraba en el matching) MegaDetector v6 (pesos oficiales, inferencia con ultralytics)
Descriptores SIFT/ORB sobre el fotograma completo MegaDescriptor-L-384 + DINOv2 ViT-B/14, congelados, sobre el recorte
Emparejamiento de rosetas por coordenadas absolutas en píxeles correspondencias SIFT + prueba de Lowe + homografía MAGSAC++
Resultado "90-95 % de confianza – mismo individuo" (sin calibrar) tres capas separadas: soporte de recuperación, revisabilidad, identidad (humana)
Fallos de medición silenciados (score 0 o 100 %) estados explícitos (no_detection, too_few_matches, ransac_failed, …)
Registro ninguno reviews.csv con la decisión de la persona revisora
Video 12 fotogramas × 12 fotogramas con puntuación saturada conjunto-a-conjunto: matriz coseno, media top-k, mejores pares por similitud × calidad

Los scripts v2 se conservan con sus errores más graves corregidos (sección 8) para que los resultados anteriores puedan reinterpretarse.


2. Marco teórico: tres capas, no un porcentaje

Adaptado de PF-ERI v2 — Pair-level evidence admission separates retrieval support from human reviewability in wildlife re-identification (Shen, 2026; repositorio público). PF-ERI trabajó con lince euroasiático y cuatro revisores humanos; sus conclusiones son directamente aplicables a felinos con patrón.

soporte de recuperación  →  revisabilidad del par  →  verdad de identidad
   (lo mide la máquina)     (lo juzga una persona;      (lo decide una persona,
                             la máquina pre-criba)       sólo si el par es revisable)
Capa Pregunta Quién responde En este repositorio
1. Soporte de recuperación ¿Dos descriptores congelados e independientes relacionan estas dos fuentes? la máquina reid/descriptors.py, reid/compare.py (pairwise_support, rank_support)
2. Revisabilidad del par ¿Los dos fotogramas exponen suficiente anatomía compartida (mismo flanco, rosetas visibles, sin oclusión) para comparar responsablemente? la persona revisora; la máquina sólo propone pares y pre-criba reid/local_match.py, reviewability_prescreen, reid/review.py
3. Verdad de identidad ¿Es el mismo individuo? la persona revisora, sólo cuando la capa 2 es review_ready campo identity en reviews.csv

Tres resultados de PF-ERI que cambian cómo leer cualquier "score":

  1. Soporte de recuperación ≠ revisabilidad. En su cola externa, 494 de 637 pares sin soporte de descriptores fueron igualmente juzgados revisables por humanos. Un coseno bajo no significa "no mirar"; un coseno alto no significa "es el mismo".
  2. Lo que mejora en desarrollo puede fallar al transportarse. El modelo que añadía evidencia local (P5) ganó en validación cruzada y perdió de forma decisiva (Δ Brier = −0.20, IC 95 % completamente negativo) al ejecutarse congelado sobre datos externos. Por eso aquí ningún umbral se presenta como calibrado hasta que exista un conjunto de pares con decisión humana registrada (reviews.csv).
  3. El endpoint humano es parte del problema científico. El acuerdo binario entre revisores fue 53 % en desarrollo y >92 % después con un instrumento más claro. La revisabilidad es una medición producida conjuntamente por la imagen y por el instrumento de revisión, no una propiedad de la imagen.

Para la tesis, esta separación es también una postura: la máquina no "reconoce" al ocelote; ordena encuentros y devuelve a una persona la responsabilidad de mirar. La identidad individual queda como decisión situada, registrada con su razón y su incertidumbre, no como salida de un clasificador.


3. Cómo funciona reid_compare.py

fuente A (video/imagen)            fuente B (video/imagen)
     │ 12 fotogramas uniformes           │
     ▼                                   ▼
 MegaDetector v6 ──► recorte del animal (fotogramas sin detección: excluidos y contados)
     │                                   │
     ▼                                   ▼
 calidad por recorte: píxeles, nitidez (var. Laplaciano), exposición/recorte de histograma
     │                                   │
     ▼                                   ▼
 MegaDescriptor-L-384 ─┐          ┌─ MegaDescriptor-L-384        CAPA 1
 DINOv2 ViT-B/14 ──────┤  coseno  ├─ DINOv2 ViT-B/14              matriz nA×nB por descriptor
                       └──────────┘                              max · media top-k · mediana de máximos por fila
                             │
                             ▼
             selección de pares: similitud media × √(calidad_A · calidad_B), sin repetir fotogramas
                             │
                             ▼
      SIFT + Lowe 0.75 + homografía MAGSAC++ (6 px) ──► inliers, cobertura, estado      CAPA 2
                             │
                             ▼
      pre-cribado: review_candidate | low_quality | no_pairs (+ razones)
                             │
                             ▼
      figura + report.json  ──►  --review: formulario en consola  ──►  reviews.csv    CAPA 3

Detalles que importan:

  • Recorte primero. Dos videos de la misma cámara comparten fondo, hojas, ramas y la franja de fecha/hora; sin recorte, SIFT "coincide" con el fondo aunque los animales sean distintos. Esa es la explicación más probable del "100 % SIFT, desviación 0" del análisis v2.
  • Dos descriptores, no uno. Igual que PF-ERI, se exige acuerdo de MegaDescriptor y DINOv2. Con --gallery el soporte se calcula por rangos (both_reciprocal / both_agreement / unsupported), que es la definición original y no depende de umbrales.
  • Evidencia local con verificación geométrica. Una correspondencia sólo cuenta si es distintiva (Lowe) y consistente con una transformación común (RANSAC). Los fallos se devuelven como categorías (too_few_keypoints, too_few_matches, ransac_failed), no como cobertura 0.
  • Video como conjunto. Cada video es un conjunto de recortes; la similitud es una matriz, no un número. Se reporta el máximo (mejor par), la media de los k mejores (robusta a un fotograma casual) y la mediana de los máximos por fila (¿cuántos fotogramas de A tienen algún buen par en B?).

4. Instalación

Probado en macOS (Apple M-series, aceleración MPS) con Python 3.11. torch no soporta aún Python ≥3.14 (el de Homebrew), así que se recomienda conda:

git clone https://github.com/alejoduque/ID_indv.git
cd ID_indv
conda create -n ocelot python=3.11 -y
conda activate ocelot
pip install -r requirements.txt

Primer uso: se descargan automáticamente los pesos de MegaDescriptor-L-384 (~1.2 GB, Hugging Face BVRA/MegaDescriptor-L-384), DINOv2 ViT-B/14 (~350 MB, timm) y MegaDetector MDV6-yolov9-c (~100 MB, Zenodo 15398270 → ~/.cache/megadetector/). Después todo corre sin conexión.

Sólo el baseline v2: pip install -r requirements-baseline.txt.


5. Uso

# Dos videos
python reid_compare.py videos/IMAG0033.AVI videos/IMAG0059.mp4

# Imagen fija contra video (o imagen contra imagen)
python reid_compare.py foto.jpg videos/IMAG0059.mp4

# Con galería: el soporte se calcula por rangos entre todas las fuentes de la carpeta (modo PF-ERI)
python reid_compare.py videos/A.mov videos/B.mp4 --gallery videos/ --top-k 5

# Con revisión humana al final (formulario en consola → reviews.csv)
python reid_compare.py videos/A.mov videos/B.mp4 --review --reviewer ad

# Opciones útiles
#   --frames 20          más fotogramas por video (default 12)
#   --pairs 5            más pares propuestos para revisión (default 3)
#   --no-detector        sin recorte (sólo si las imágenes ya vienen recortadas)
#   --megadescriptor base --dinov2 small    modelos más livianos
#   --gray               embeber en escala de grises (ver §6.3 antes de usarlo)
#   --device cpu

Salidas en outputs/<A>__<B>/:

  • review_figure.png — los pares propuestos (recorte A, recorte B, correspondencias verificadas en verde) y las matrices de similitud por descriptor.
  • report.json — todo lo anterior en cifras: estados del detector, calidad por fotograma, agregados por descriptor, categoría de soporte, pares y su evidencia local, pre-cribado y sus razones, y "identity": "human_decision_pending".

Lectura de la consola:

CAPA 1 · Soporte de recuperación (descriptores congelados)
  megadescriptor  max=0.996  top5_mean=0.996  mediana_rowmax=0.989
  dinov2          max=0.967  top5_mean=0.964  mediana_rowmax=0.936
  categoría: both_agreement  (umbrales NO calibrados)
CAPA 2 · Revisabilidad del par (pre-cribado automático)
  review_candidate
  par 1: A#162 (5.40s) vs B#61 (2.03s) | local=ok inliers=437 cobertura=0.310
CAPA 3 · Identidad: la decide la persona revisora (ver --review / reviews.csv)

6. Controles y hallazgos

Videos e imagen de prueba: Wikimedia Commons (ver créditos en §11). No hay verdad de identidad entre sitios; el control positivo es el mismo video partido en dos mitades.

6.1 Resultados

Caso MegaDescriptor top-5 DINOv2 top-5 Evidencia local (SIFT+MAGSAC)
Positivo: mitades del mismo video (São Paulo) 0.996 0.964 ok, 395–624 inliers, cobertura 0.28–0.44
Negativo: São Paulo vs Rubinéia (sitios distintos, ambos IR) 0.760 0.836 ransac_failed (4–5 inliers) / too_few_matches
Foto diurna a color (Rio Doce) vs video IR (Rubinéia) 0.091 0.643 too_few_matches

Control negativo: alta similitud global, sin evidencia local

Lectura: los descriptores globales dicen "esto es un ocelote con esta pose" (0.76–0.84 entre individuos de sitios distintos), y sólo la evidencia local verificada separa el mismo individuo del que no lo es. Es exactamente la separación entre capas de PF-ERI.

6.2 MegaDetector: tamaño de entrada

MDV6-yolov9-c está entrenado a 640 px. A 1280 px (el tamaño que PytorchWildlife fija por defecto) la confianza sobre un ocelote diurno nítido cayó de 0.94 a 0.05. reid/detect.py usa 640 para las variantes -c, 1280 para las -e, y reintenta a la otra escala si no hay detección. Además, se evitó la dependencia PytorchWildlife (arrastra yolov5, gradio y bioacústica, y falla al importar); los pesos oficiales se cargan directo con ultralytics.

6.3 Brecha color / infrarrojo y la opción --gray

MegaDescriptor cae a ≈0.09 entre una foto diurna a color y video IR nocturno; DINOv2 es menos sensible (0.64). Embeber en escala de grises reduce la brecha (0.09 → 0.33) pero produjo 0.93 entre la foto de Rio Doce y el video de São Paulo, que muestran flancos opuestos del cuerpo y no pueden ser evidencia de identidad; RANSAC dio 0 inliers en todos los pares. Conclusión provisional: en gris el descriptor global responde a pose y textura genérica. Se deja --gray como opción documentada, no por defecto, y se recomienda comparar color↔IR sólo con la evidencia local y la revisión humana.

6.4 Umbrales

DEFAULT_THRESHOLDS = {megadescriptor: 0.85, dinov2: 0.90} en reid/pipeline.py están entre el negativo (0.76/0.84) y el positivo (0.996/0.96) observados — con n = 1 en cada caso. Son marcadores de posición, etiquetados calibrated: false en cada reporte. El camino para calibrarlos es la sección 7.


7. Instrumento de revisión humana

Adaptado de la Tabla S3 de PF-ERI. Se registra con --review o programáticamente con reid.review.append_review.

Campo Valores Notas
reviewability review_ready · not_review_ready · uncertain ¿Hay anatomía comparable? No es "¿es el mismo?"
reason_codes opposite_side, no_overlap, partial_body, occlusion, blur, lighting, scale, pose, sufficient, other lista cerrada, separados por ;
confidence low · medium · high certeza sobre la revisabilidad
identity same · different · undetermined sólo se acepta si reviewability == review_ready
identity_confidence low · medium · high
technical_problem yes · no la revisión no pudo completarse (excluir del análisis)
contexto automático megadescriptor_topk, dinov2_topk, support_category, local_match_status lo que la máquina vio, para calibrar después

Reglas heredadas de PF-ERI que conviene mantener en MANAKAI:

  • Dos revisiones por par, ciegas entre sí; si difieren en las tres categorías, una tercera persona adjudica. reid.review.agreement_summary calcula el acuerdo exacto y binario.
  • Un par puede ser revisable y de individuos distintos: la anatomía compartida basta para excluir con confianza. Y puede ser del mismo individuo y no revisable (flancos opuestos).
  • Nunca reajustar umbrales mirando los resultados del par que se está revisando. La calibración se hace después, sobre el CSV acumulado, y se congela antes de aplicarse a pares nuevos.

Con unas decenas de pares registrados se puede: (a) estimar qué valores de coseno y de cobertura local corresponden a review_ready; (b) medir cuántos pares "sin soporte" resultan revisables (en PF-ERI: la mayoría); (c) reemplazar los umbrales provisionales por un modelo simple (regresión logística ridge, como P3 en PF-ERI) evaluado en pliegues disjuntos por individuo y video.


8. Baseline v2 (OpenCV) — qué era y qué se corrigió

Los scripts ocelot_video_comparison.py, ocelot_pattern_comparison.py y ocelot_reid_interactive.py se conservan como referencia. Errores encontrados y corregidos:

Problema Efecto Corrección
Score ORB = min(100, top50/30·100) siempre 100 % (el JSON histórico lo muestra: media 100, desviación 0) prueba de razón + RANSAC; score = fracción de keypoints verificados
SIFT sin verificación geométrica contaba correspondencias espurias y de fondo RANSAC (MAGSAC++)
weighted_score sumaba conteos absolutos (hasta 108) contra umbrales 8/5/3 casi cualquier par salía "probablemente el mismo individuo" (histórico: 549.0) normalizado por número de comparaciones
"Confianza 90-95 %" porcentajes sin ninguna calibración etiquetas ordinales marcadas uncalibrated, guiadas por la evidencia verificada
extract_frames_from_video devolvía [] en error ValueError al desempaquetar lanza excepción con mensaje
ocelot_pattern_comparison.py ignoraba los argumentos el README decía que los aceptaba acepta <img1> <img2>
dependencias scipy, scikit-image no documentadas ImportError requirements-baseline.txt

Limitación que no se corrigió porque exige rediseño: rosetas y manchas (HoughCircles / blob) se emparejan por coordenadas absolutas de píxel entre videos distintos; el "score de patrones" sale 55 % en el control negativo y ya no manda la conclusión. El diseño v3 lo reemplaza.

Con las correcciones, el baseline discrimina los controles (ORB verificado: 1.4 % negativo vs 88.6 % positivo), pero sigue sin recorte del animal: úsese sólo para reinterpretar resultados antiguos.


9. Limitaciones y lo que este repositorio no hace

  • No produce probabilidades de identidad. Ninguna cifra es calibrada hasta que exista reviews.csv con pares suficientes (§7).
  • Los descriptores no fueron ajustados a ocelotes ni a IR; MegaDescriptor se entrenó con muchas especies (incluyendo felinos) pero su respuesta a IR es pobre (§6.3).
  • La evidencia local (SIFT + homografía) supone que el flanco es aproximadamente planar y que la pose no cambia radicalmente; poses muy distintas darán ransac_failed aunque sea el mismo individuo. Eso es una no-revisabilidad, no una exclusión.
  • Un fotograma por posición uniforme en el tiempo puede perder el instante en que el flanco es visible; --frames 20 o más ayuda a costa de tiempo.
  • No se separa flanco izquierdo/derecho automáticamente. Es el primer código de razón del instrumento (opposite_side) y el candidato más claro para la siguiente iteración.
  • Pruebas: pytest tests/ cubre agregación, rangos, RANSAC sobre patrones sintéticos, calidad y el CSV; no descarga modelos.

Hoja de ruta sugerida: (1) acumular revisiones en MANAKAI con dos revisores; (2) galería de individuos conocidos → --gallery y soporte por rangos; (3) clasificador de flanco; (4) LightGlue/SuperPoint como matcher local alternativo, comparado contra SIFT con el mismo CSV; (5) sólo entonces, un modelo de calibración congelado y evaluado externamente, como P3 en PF-ERI.


10. Estructura

reid_compare.py                 CLI principal (v3)
reid/
  frames.py                     fotogramas de video/imagen + métricas de calidad
  detect.py                     MegaDetector v6 vía ultralytics (recorte del animal)
  descriptors.py                MegaDescriptor / DINOv2 congelados (timm), coseno
  local_match.py                SIFT + Lowe + MAGSAC++, estados de fallo, dibujo
  compare.py                    agregación conjunto-a-conjunto, soporte (umbral / rangos), pares, pre-cribado
  review.py                     instrumento humano → reviews.csv, acuerdo entre revisores
  report.py                     JSON + figura
  pipeline.py                   featurize / compare_pair / gallery_support
tests/test_reid.py              pruebas sin modelos
docs/                           figuras de ejemplo
ocelot_video_comparison.py      baseline v2 (corregido)
ocelot_pattern_comparison.py    baseline v2, dos imágenes (corregido)
ocelot_reid_interactive.py      baseline v2, menú interactivo
MANUAL_USO_SISTEMA_OCELOTE.md   documentación v2 (histórica)
OCELOT_IDENTIFICATION_GUIDE.md  documentación v2 (histórica)
RESUMEN_ANALISIS_OCELOTE.md     resultados v2 (histórico; reinterpretar con §8)
*.png                           figuras v2 (históricas)

11. Referencias y créditos

  • Shen, D. (2026). Pair-level evidence admission separates retrieval support from human reviewability in wildlife re-identification (PF-ERI v2). https://github.com/Dean-999/pferi-reviewability — marco de tres capas, instrumento de revisión, diseño congelado/externo.
  • Čermák, V., Picek, L., Adam, L., Papafitsoros, K. (2024). WildlifeDatasets: An open-source toolkit for animal re-identification. WACV. — MegaDescriptor (BVRA/MegaDescriptor-L-384).
  • Oquab, M. et al. (2023). DINOv2: Learning Robust Visual Features without Supervision. — vit_base_patch14_dinov2.lvd142m (timm).
  • Beery, S., Morris, D., Yang, S. (2019). Efficient pipeline for camera trap image review — MegaDetector; pesos v6 de Microsoft AI for Good / PytorchWildlife (Hernandez et al., 2024), Zenodo 15398270.
  • Lowe, D. G. (2004). Distinctive image features from scale-invariant keypoints. IJCV. — SIFT y prueba de razón.
  • Barath, D. et al. (2020). MAGSAC++, a fast, reliable and accurate robust estimator. CVPR. — verificación geométrica (cv2.USAC_MAGSAC).

Medios de prueba (no incluidos en el repositorio; las figuras en docs/ son derivadas):

  • Ocelot-foraging-Sao-Paulo-Brazil.webm, Miguelrangeljr, 2024, CC BY-SA 4.0, Wikimedia Commons.
  • Ocelot-foraging-Rubineia-Brazil.webm, Miguelrangeljr, CC BY 4.0, Wikimedia Commons.
  • Ocelot (Leopardus pardalis) at the Rio Doce state park 001.png, Richard Hatakeyama, 2014, CC BY-SA 4.0, Wikimedia Commons.

12. Licencia y contacto

Código bajo licencia MIT. Las figuras derivadas de medios CC BY-SA en docs/ conservan esa licencia.

Proyecto MANAKAI — Monitoreo de fauna con cámaras trampa. Issues y contribuciones vía GitHub.

Última actualización: septiembre 2026 · Versión: 3.0 — separación de capas (PF-ERI), recorte, descriptores congelados, evidencia local verificada, instrumento de revisión humana.

About

Sistema de identificación individual de ocelotes y posiblemente otras especies.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages