Progreso febrero 2024 - jamarma/tfm-muva GitHub Wiki
Índice
Prueba de métodos de detección de carril del estado del arte
Prueba de métodos de detección de carril del estado del arte
Para empezar a familiarizarme con CUDA, Pytorch y la detección de carril empezaré por intentar hacer funcionar los códigos propuestos por los autores del estado del arte.
Cada proyecto depende de una versión de CUDA y Pytorch distintas. Manejar diferentes versiones de Pytorch es factible con entornos virtuales de Python, pero manejar varias versiones de CUDA puede provocar conflictos y no siempre son sencillas de instalar.
Tras estar pegándome con ello varios días, he conseguido solucionarlo y he escrito una Guía para manejar múltiples versiones de CUDA con todos los pasos necesarios para hacerlo funcionar correctamente.
Estuve mirando métodos como UFLD, CLRNet o LaneATT, pero la mayoría no tenía un código claro para realizar inferencia. Normalmente están enfocados en ser evaluados en datasets como CULane o TuSimple. Buscando encontré LaneDet que es un toolbox basado en Pytorch que ofrece la oportunidad de realizar inferencia con algunos de los métodos del estado del arte de detección de carril. Sabiendo esto, mi próximo objetivo es entender el código que usa para inferencia y de esta manera comprender cómo se usan los modelos de detección de carril.
LaneDet: Toolbox de inferencia para detección de carril
El primer paso es hacer funcionar el código en mi ordenador. El proyecto tiene como requisitos: python3.8, torch==1.8.0, torchvision==0.9.0 y CUDA 10.1. He intentado instalar todo eso en mi ordenador con Ubuntu 22.04 pero no ha sido posible, CUDA 10.1 sólo es compatible hasta Ubuntu 18.04.
No quería instalarme un SO que es 2 versiones más viejo y he conseguido hacer funcionar el código con: python3.10, torch==2.2.0, torchvision==0.17.0 y CUDA 12.1. Además he tenido que cambiar algunas partes del código Python para que fueran compatibles con las nuevas versiones de las librerías en Python 3.10, las apunto a continuación por si me vuelve a hacer falta.
In file lanedet/lanedet/datasets/process/transforms.py
import collections -> import collections.abc as collections
In file lanedet/lanedet/models/backbones/mobilenet.py
from torchvision.models.utils import load_state_dict_from_url -> from torch.hub import load_state_dict_from_url
El fichero completo de requirements es el siguiente.
torch==2.2.0
torchvision==0.17.0
pandas==2.2.0
addict==2.4.0
scikit-learn==1.4.0
opencv-python==4.9.0.80
pytorch_warmup==0.1.1
scikit-image==0.22.0
tqdm==4.66.2
p_tqdm==1.4.0
imgaug>=0.4.0
Shapely==1.7.0
ujson==1.35
yapf==0.40.2
albumentations==0.4.6
mmcv==1.2.5
pathspec==0.12.1
numpy==1.23.1
Con todo esto he conseguido correr el script tools/detect.py
y hacer inferencia en mi GPU sobre el dataset CULane con todos los modelos para los que ofrece soporte. A continuación muestro un ejemplo de LaneATT (backbone ResNet18) sobre algunos frames de Test de CULane.
El siguiente objetivo es poder hacer inferencia pero usando frames propios. Para ello, he intentado entender el código fuente de LaneDet y, de esta manera, poder usarlo con frames que no provengan de un dataset de competición. Sin embargo, me está resultando muy complicado entender su código fuente. Está programado de una forma muy modular y general, para que funcione con muchos tipos de modelos de detección de carril, lo que dificulta mucho su entendimiento de primeras.
Probé directamente a cambiar el path de frames de entrada sin tocar nada, pero no obtuve buenos resultados. Únicamente funciona bien si se introducen frames del tamaño de los datos del dataset con el que ha sido entrenado (1640x590px). Esto no me cuadra porque en los ficheros de configuración puedes cambiar la resolución esperada para los frames de entrada. Hay algo que se me está pasando por alto en el código.
Viendo lo complicado que es entender el código de LaneDet, decidí intentar entender el código fuente de alguno de los métodos por separado. Elegí LaneATT porque disponía de código para hacer inferencia en datos de test.
LaneATT
Funcionando en python3.10, CUDA 12.1, torch==2.2.1 y torchvision==0.17.1
Requirements:
opencv_python==4.9.0.80
Shapely==1.7.0
xmljson==0.2.0
thop==0.0.31.post2005241907
matplotlib==3.8.3
imgaug==0.4.0
scipy==1.12.0
p_tqdm==1.3.3
lxml==5.1.0
tqdm==4.43.0
ujson==5.9.0
numpy==1.23.1
PyYAML==6.0.1
scikit_learn==1.4.1.post1
tensorboard==2.3.0
gdown==5.1.0
Ejecución del código original con CULane
Enlace simbólico al directorio de CULane
ln -s /home/jamarma/master/tfm/culane/ /home/jamarma/master/tfm/LaneATT/datasets/culane
Ejecución
python main.py test --exp_name laneatt_r34_culane --view all
Método basado en anchors típico en detección de objetos (YOLO, SSD). Define un anchor como una línea virtual en el plano imagen compuesta por un punto de origen y una dirección. Se usa una CNN (ResNet) como backbone para extraer características de la imagen de entrada, después cada anchor se proyecta sobre el mapa de características, esta proyección se concatena con otro conjunto de características generado por un modelo de atención y, por último, hay dos cabezales que realizan las predicciones finales: uno de clasificación para dar la probabilidad de ser línea, y otro de regresión para ofrecer los parámetros de localización de la línea. Se utiliza el modelo de atención, que es una red densa que genera características que agregan información global, porque la información del vector de características de la proyección suele ser información local y no es suficiente para realizar predicciones en situaciones con oclusiones o líneas de carril borradas.
Esta vez sí que he logrado entender el código, por lo menos la forma con la que hace inferencia. A continuación hago un resumen de cómo tienen estructurado el código y cómo funciona:
Existe un fichero de configuración .yml para cada par modelo-dataset. Por ejemplo, uno para LaneAtt (ResNet18) con dataset CULane o para LaneAtt (ResNet34) con dataset TuSimple. En cada fichero tenemos lo siguiente necesario para inferencia:
- Configuración del modelo:
- name: modelo que queremos usar.
- backbone: backbone que queremos para el modelo.
- S: no sé lo que es, pero hace falta. Suele tener el valor 72.
- topk_anchors: indica el número de anchors que se mantienen.
- anchors_freq_path: cada dataset tiene su fichero dataset_anchors_freq.pt. No sé muy bien para qué lo usan, pero de ese fichero sacan una máscara de anchors que luego filtran con el número indicado en topk_anchors.
- img_h y img_w: el tamaño de las imágenes de entrada al modelo.
- test_parameters:
- conf_threshold: umbral para considerar una línea como buena.
- nms_thres: no sé lo que es.
- nms_topk: el número máximo de líneas que se quiere como salida.
Hay muchos más parámetros para el entrenamiento y para el dataset, pero eso no lo necesitamos de momento.
Los pasos para hacer inferencia los tienen encapsulados en el método eval() de la clase LaneATT/lib/runner.py
y, resumiendo, son los siguientes:
-
Crean una instancia del modelo indicado en la configuración con los parámetros indicados, en nuestro caso siempre es
LaneATT/lib/models/laneatt.py
. Es el script que contiene la arquitectura neuronal. -
Cargan los pesos al modelo, que los tienen almacenados en el directorio
LaneATT/experiments
. -
Crean un DataLoader con una clase
LaneATT/lib/datasets/lane_dataset
bastante compleja que preprocesa las etiquetas y frames de los datasets de competición para usarlas en su modelo. En nuestro caso lo podemos eliminar y basta con transformar nuestros frames de entrada a tensores con las siguientes transformaciones de Pytorch: ToPILImage(), Resize((360, 640)), ToTensor(). De todo lo que hacen en la clase, es lo único que he necesitado para que funcione. -
Meten cada uno de los tensores al modelo y los test_parameters. El modelo devuelve las predicciones y las procesan para transformarlas en una lista de objetos
LaneATT/lib/lane.py
. Cada objeto contiene las coordenadas de puntos de las líneas detectadas.
Tras entender cuáles eran las partes necesarias del código para hacer inferencia, me creé yo mi propio script con sólo esas partes y eliminé todo el código innecesario. Logré hacerlo funcionar, quedó todo muy simple y ya puedo hacer inferencia sobre frames propios.
A continuación muestro un ejemplo de LaneATT funcionando con mi script en un frame propio.
El proceso que seguí de ir leyendo el código fuente original (que al principio asustaba bastante y no sabía por donde cogerlo) e ir eliminando las partes innecesarias para nuestro propósito, me ha servido para comprender bien su funcionamiento. Además, me sirve como base para comprender cómo poder usar métodos futuros.
CLRNet
Funcionando en python3.10.12, CUDA 12.1, torch==2.2.0 y torchvision==0.17.0
Requirements editados:
pandas
addict
scikit-learn
opencv-python
pytorch_warmup
scikit-image
tqdm
p_tqdm
imgaug>=0.4.0
Shapely==1.7.0
ujson==1.35
yapf
pathspec
timm
mmcv==1.2.5
albumentations==0.4.6
pathspec
ptflops
numpy==1.23.1
He hecho funcionar su script de ejemplo con los datos de test de CULane, pero no he conseguido hacer yo un script propio que haga uso del modelo para inferencia.
CondLaneNet
No he conseguido hacerlo funcionar. Requiere CUDA 9 que es bastante antiguo.
He conseguido compilar las fuentes con CUDA 12.1 realizando cambios en los ficheros .cu para hacerlos compatibles con la nueva versión de CUDA, pero me ha acabado dando problemas el paquete mmcv de Python. Esto último no se puede solucionar porque las versiones recientes del paquete han eliminado módulos necesarios.
La solución sería compilarlo todo con CUDA 9, pero eso de momento creo que no me merece la pena.
UFLDv2
Funcionando en python3.10.12, CUDA 12.1, torch==2.2.0 y torchvision==0.17.0
He hecho funcionar su script de ejemplo con los datos de test de CULane, pero no he conseguido hacer yo un script propio que haga uso del modelo para inferencia.