Closed jorgevelap closed 4 years ago
¿Has seguido los pasos de instalación del toolbox de BAdaCost? Si compilas el toolbox llamando a toolboxCompile.m debería compilar el fichero acfDetectBadacostTrees1.cpp.
Cuando sigo los pasos me da un error de fichero en el ultimo paso.
Es un bug de la versión de Matlab que tienes (parece que no está definido char16_t. Hay varias soluciones sencillas. Mira en esta entrada de stackoverflow, la respuesta de más arriba da varias formas de arreglarlo. Quizá la más sencilla es cambiar:
typedef unsigned short char16_t;
por:
typedef uint16_t char16_t;
Gracias por el enlace, Poniéndolo así me aparece un error, sin embargo dice en una de las lineas "Either put #define char16_t uint16_t before #include "mex.h"", lo que al ponerlo así ya no da problemas.
He tenido que añadir también en los ficheros multiclassTreeTrain1.cpp y multiclassQuantizedTreeTrain1.cpp #include < algorithm> ya que me aparecía un error diciendo que min no pertenecia a std, lo cual parece que se arregla con esto.
Me aparece un ultimo error en acfBadacostTrees1.cpp que dice lo siguiente: error C3016: 'c': index variable of OpenMP 'for' instruction must have a signed integer type
De momento no encuentro la solución, sigo con ello. En caso de encontrarla lo indicaré en este issue.
Comentando esa línea y con los otros cambios, compila todo y al ejecutar acfDetectBadacost lo ejecuta correctamente y no aparece ningún error. Queda por ver los resultados obtenidos tras esta ejecución.
He estado trabajando ya con estas funciones y trabajando con el codigo c++. De chnsPyramid, las imagenes que se obtienen de los distintos canales son:
De momento los resultados que se van obteniendo creo que van por buen camino. A continuación dejo 4 imágenes de unas de las escalas después de pasar por los filtros:
Por otro lado, para utilizar el clasificador, acfDetectBadacostTrees1, que la función a la que llama matlab, se puede utilizar tal cual para esta parte?
Muchas gracias.
Tiene muy buena pinta. Básicamente deberías poder usar acfDetectBadaCostTree1 para la detección. Lo único es que tendrás que cambiar el mex por una función que reciba los parámetros que saca el código C++ de las variables matlab que se le pasan:
Quizá lo mejor que se puede hacer es guardar en un XML todas las variables relevantes del clasficador desde Matlab y luego leerlo en un método "load" de una clase "BAdaCostDetector" que lea todas esas variables como atributos de la clase. Luego en un método "detect" se le pasa una imagen, hace la pirámide de canales (llamando a chnsPyramid) y luego va llamando al código de acfDetectBadaCostTree1 adaptado a C++.
He seguido trabajando sobre el clasificador y ya obtengo todos los datos del yml con una función load, para luego llamar a este clasificador desde la función detect de la clase badacostDetector.
Para guardarlo, utilizo un std::map, de forma que se puede acceder de forma sencilla, por ejemplo: clf["Cprime"] . No se si esta será la mejor opción.
La siguiente imagen muestra los datos de Matlab y de c++. Realizando diversas pruebas se obtienen resultados correctos.
La función load retorna true o false, si se ha cargado bien el clasificador, utilizando dos comprobaciones para ello (que el fichero exista y que cargue datos de los parámetros del clasificador).
El siguiente paso si no recuerdo mal es trabajar con el detector ya con los cv::Mat obtenidos.
Eso es. En cuanto tienes ya los datos necesarios del clasificador en C++, ya puedes usarlos para detectar.
La idea es implementar la clase BadacostDetector y el método BadacostDetector::detect(). Este BadacostDetector::detect(), dada una imagen:
La parte de detectSingleScale me está costando un poco. Para la otra parte, he cambiado chnsPyramid que estaba en la clase Utils, ahora es una clase que tiene distintas funciones, entre ellas load (para cargar los parámetros) y getScales(para obtener la pirámide). Creo que esta forma es mejor para poder obtener las distintas resoluciones.
De hecho, si lo pienso mejor, el chnhsPyramid quizá estaría mejor como método protected de la clase BAdacostDetector.
He estado trabajando sobre el detector para una escala pero estoy bloqueado. No consigo hacer las líneas correspondientes al bucle de la linea 274 de acfBadacostTrees1.cpp
while( child[k] ) { float ftr = chns1[cids[fids[k]]]; k = (ftr<thrs[k]) ? 1 : 0; k0 = k = child[k0]-k+offset; }
Cuando lo intento replicar utilizando los cv::Mat el valor de "k" siempre se queda igual y no pasa de ese bucle. La parte anterior a ese bucle he ido comparando y parece que se cogen bien los resultados. Cuando obtengo un valor fids[k] si que se obtiene el mismo resultado que en matlab, pero a partir de ahí no consigo avanzar.
También, la estructura cids (linea 134), la crea añadiendo una serie de valores, no se si habría que crearla como cv::Mat y como sería esto.
Gracias.
Le echo un vistazo y te cuento.
Buenas tardes, He estado revisando estos días el código y tras varios cambios parecía que iba mejor. Empecé de nuevo el código para ver donde estaba el error ya que siempre me ponía que había 8432 ventanas. Al ir poco a poco me ha salido en todo momento 3558 ventanas, resultado parecido al de matlab. Al dibujar una de esas ventanas he visto que no dibujaba donde estuviera el coche.
Revisando esto he visto que Matlab también retorna todas las ventanas aunque no haya coche, pero también retorna el parámetro "scores", estoy revisando si de ese depende el resultado final.
Actualizaré ahora el código. Al haberlo ido viendo es lo que hice en un primer momento con modificaciones de las que me enviaste, no están todas todavía. Seguiré mirando de donde puede venir el fallo.
Muchas gracias.
Con respecto a lo comentado en la ultima reunión, comentando los resultados de matlab, al hacer una revisión y ejecución del codigo de nuevo habia algo que no encajaba. He trabajado con el código de inicio y ha dado un resultado mas coherente. Lo primero que he detectado es que no en todas las escalas detecta coche, a diferencia de lo anterior que siempre detectaba de mas. Haciendo una ejecución ha salido un resultado como el de la imagen, por lo que voy a seguir trabajando por esta linea, con las pruebas ayer dichas y este codigo para evitar otros errores que pueda haber. Lo que muestra la imagen esta marcado manualmente pero con los resultados de pixeles que retornaba Matlab.
Entiendo que lo que dices es que has vuelto ha bajar el código de BAdaCost original ¿Es eso?
Si es eso, lo he vuelto a descargar por si tenia algún fallo. Debía haber alguna cosa distinta ya que algunos resultados no cuadraban. Iniciando de nuevo se ha obtenido eso.
Por otro lado, realizando otras pruebas, he estado comparando las imágenes justo antes de realizar la detección. Tras llamar a chnsPyramid, pasar las imágenes por el filtro y redimensionar, en matlab es: if(isfield(opts,'filters') && ~isempty(opts.filters)), shrink=shrink*2; for i=1:P.nScales, fs=opts.filters; C=repmat(P.data{i},[1 1 size(fs,4)]); for j=1:size(C,3), C(:,:,j)=conv2(C(:,:,j),fs(:,:,j),'same'); end P.data{i}=imResample(C,.5); end end
una de las comparaciones es esta (a la izquierda imagen de Matlab, a la derecha con c++):
Voy a realizar varias comprobaciones, pero hasta este punto parece que el resultado es aceptable.
A continuación subo alguna imagen mas comparando Matlab y C++. Si que da la sensación a primera vista de pequeñas diferencias entre unas y otras.
Buenas tardes, Con respecto a las pruebas que hablamos el último día, las actualizaciones obtenidas son las siguientes:
Se ha modificado ya en el código. En las pruebas he visto que al llamar a chnsExtract de luv hay que cambiar el orden de los canales de salida para obtener la misma imagen.
Sigo trabajando en ello, revisando todo y haciendo pruebas a ver si encuentro el fallo.
Gracias.
Es probable que haya tener cuidado con el chnsCompute ahora, dado que la imagen ya está en LUV, no tenemos que convertirla si ya está de esa manera en la extracción de LUV (no tenemos que llamar al extractChanneles de LUV dos veces), creo que el resumen de lo que vimos el otro día es que calcula LUV, y luego los canales de graciente se hace sobre la imagen LUV ¿O no?
Mira también en Matlab qué imagen se le pasa al cálculo de magnitud de gradiente y de histogramas de gradiente en chnsCompute ¿Es la RGB o ya la LUV tal y como hemos visto que se hace en chnsPyramid?
JM
Por lo que entiendo si, primero realiza la extracción de LUV y después ya es esta la imagen que utiliza. La imagen original solo la utiliza para llamar a RGBconvert. Ahora estoy mirando a ver los cambios en chnsCompute ya que la imagen ahora sale totalmente distinta a la de matlab.
En el código de matlab se le está pasando también la imagen en LUV en chnsCompute, creo que se ve en el siguiente fragmento del código:
% compute color channels p=pChns.pColor; nm='color channels'; I=rgbConvert(I,p.colorSpace); I=convTri(I,p.smooth); if(p.enabled), chns=addChn(chns,I,nm,p,'replicate',h,w); end
% compute gradient magnitude channel p=pChns.pGradMag; nm='gradient magnitude'; full=0; if(isfield(p,'full')), full=p.full; end if( pChns.pGradHist.enabled ) [M,O]=gradientMag(I,p.colorChn,p.normRad,p.normConst,full); elseif( p.enabled ) M=gradientMag(I,p.colorChn,p.normRad,p.normConst,full); end
Haciendo la prueba, cuando chnsPyramid llama a chnsCompute con la imagen LUV de entrada, es esta imagen con la que trabaja, por tanto es la imagen LUV con la que se calcula la magnitud de gradiente y de histogramas.
El siguiente paso que estoy siguiendo es el calculo de lambdas. Hay un bucle en el que crea unas lambdas en función de la imagen si estas no se especifican. Estoy intentando replicar ese bucle.
Después de hacer la función para calcular las lambdas, en matlab utiliza cada lambda para cada imagen que tiene de chnsPyramid (una LUV, otra de la gradientMag y las correspondientes a gradientHist. Nosotros al tener una imagen con los 10 canales, habría que calcular una lambda para canal? o seria una lambda unica?
No se si estoy entendiendo bien esta parte o si me estoy explicando bien.
Gracias.
Los canales que se usan en el detector, Locally Decorrelated Channel Features (LDCF), parten de los 10 canales de P. Dollar. Una vez ahí, se calculan 4 filtros diferentes por cada canal de Dollar: 4 x 10 = 40 canales en total es el resultado.
El alfa es un valor que multiplica a una imagen remuestreada de un canal (cambiada de tamaño) para que sea igual que redimensionar la imagen + calcular el canal. Esto te ahora el cálculo dle canal (la redimensión de la imagen sí que tienes que hacerla) y por tanto es más rápida.
Como cada uno de los 40 canales son diferentes entre sí, entonces debería de haber un alfa por cada canal (de los 40).
Resumen: Mira a ver cuántos valores de alfa hay. Si son 40 entonces es como yo te digo.
Cuando imprimo los valores de lambda me aparecen 3 valores:
lambdas =
0 0.0739 0.0725
El primero lo utiliza con lo recibido de LUV, el segundo del gradientMag y el tercero para lo recibido de gradientHist. No se si estoy entendiendo mal esta parte.
Mas adelante, cuando llama a imResample y utiliza este valor lambda, la imagen que pasa es de las dimensiones que haya retornado cada uno de lo anterior (3Dimensiones del LUV, por ejemplo).
Ok, entonces tenemos tantos lambdas como tipos de canales: LUG, magnitud de gradiente y HoG. Eso significa que tienes que usar correctamente los lambdas con cada tipo de canal.
Siguiendo los pasos comentados, el primer paso es calcular toda la pirámide sin aproximaciones, llamando a chnsCompute por cada canal. Tras esto, compruebo el orden de los canales de salida. Están en el mismo orden que muestra matlab. A continuación muestro dos imágenes, una con la comparativa, y otra mostrando una escala mas pequeña para mostrar que es correcto a distintas escalas (imágenes de la izquierda c++, a la derecha Matlab):
Tras eso, al comprobar addChannels en matlab, tiene la función resample la cual divide ancho y alto entre shrink, lo cual ya estaba añadido a la función y muestra también las imágenes correctamente.
El siguiente paso a realizar, si no recuerdo mal, es comprobar las imágenes tras los filtros, ver que el orden es correcto y que utiliza bien estos filtros.
Perfecto. Esto es correcto. Parece que el C++ tiene más resolución que los canales en Matlab ¿Eso es correcto o es un efecto de la visualización?
Efectivamente el siguiente punto es cromprobar los filtros y su orden. Y por último comprobar que los canales filtrados son iguales que los de matlab.
Si que parece que se ve con mejor resolución pero no se si se debe a la visualización o a otra cosa. Tengo que comprobar eso.
Por otro lado, comprobando los filtros, he visto que si los muestro igual que lo hace matlab. A continuación los valores de dos filtros como ejemplo:
Por último, me he dado cuenta que cometí un error en los canales filtrados, estoy con ello ahora. Cuando hice esta parte estuve haciendo ejemplos para una escala, por lo que siempre lo utilizo llamando a pyramids[0] (teniendo el cv::mat con toda la piramide). Lo que quiere decir que solo hago el filtrado con una escala de la imagen en lugar de todas las escalas. Voy modificar esto del código para hacerlo en todas las escalas y ver si es parte de la solución.
El error esta en chnsPyramid.cpp a partir de la linea 302.
Comparando las imagenes a la salida de los filtros, se obtienen resultados muy parecidos. A continuación se observan las imagenes obtenidas en matlab y en c++.
Con respecto a lo comentado anteriormente, solo trabajaba para una escala de la pirámide. Ya lo he modificado para que trabaje para las distintas escalas. Aún así sigue sin dar un resultado concreto. Por las pruebas realizadas, el error creo que estará en badacostDetector, creo que las pruebas que comentamos para ver si estaba en algún punto anterior son las realizadas. No se si me falta alguna por hacer.
Entiendo que los canales en OpenCV son FLOAT32 ¿verdad? ¿Los valores de los canales en matlab y opencv después de filtrar son los mismos? (por ejemplo mirando el mínimo y máximo) ¿Y los tamaños?
Si eso es correcto pasamos al BadacostDetector.
He revisado que los canales en OpenCV son float 32. Sin embargo, los valores obtenidos como máximo y mínimo son distintos. El mínimo en matlab es 0.0088 mientras que el máximo es 1.8361, sin embargo para c++ los valores son 0.015938 y 1.829792 para la primera imagen filtrada. Para la segunda imagen filtrada (por ejemplo) son 0.5792 y 4.1967 para matlab, y para c++ son 1.084617 y 4.147775. No parece una gran diferencia pero tampoco se a que se debe. Voy a seguir revisando por si veo algún valor muy diferente, no se si será este el principal problema.
En los tamaños de imagen si que he encontrado una gran diferencia, siendo en matlab de 625x353 mientras que en c++ es de 174x309.
El valor de los canales puede ser un problema o no (siempre entiendo que habrá cierta tolerancia). Parece que al menos están más o menos en el mismo rango.
Por otra parte, ¡Es mucho más preocupante el tamaño de los canales! Date cuenta que el acceder a las features en cada canal se hace con el tamaño que da matlab. Si el tamaño no es el mismo el clasificador está accediendo a celdas del canal que toque que no tienen nada que ver.
Si el tamaño de los canales voy a revisarlo ya porque no se a que se debe. Si que he visto que era una diferencia muy grande, voy a revisar donde esta el fallo. De pruebas anteriores no me suena que hubiera una diferencia tan grande. Cuando encuentre el error pondré a que se debe y resultados que salgan.
He estado mirando y el error se debía a un resize que tenia donde no debía. Ahora el tamaño es de 349x619. La diferencia de tamaño se debe a que en matlab se añade padding en los bordes de las imágenes. He continuado detectando pero sigue sin realizar detección hasta este punto. Lo que ahora los valores máximo y mínimo se acercan mas a los anteriores (0.005590 1.837858 c++ ; 0.0088 1.8361 matlab)
Ok. Mejor, pero sigue estando mal. El padding tiene que estar considerado. En matlab al aprender el detector se calculan los canales añadiendo píxeles (padding) fuera de la ventana anotada manualmente sobre el objeto. Esto evita los problemas de cálculo de características en los bordes de la ventana ... y claro, los canales tienen las características en sitios diferentes si no añades el padding.
Vale, estoy haciendo pruebas creando dicho padding con openCV. Estoy mirando la función copyMakeBorder( src, dst, top, bottom, left, right, borderType, value );, a la cual se le puede añadir BORDER_REPLICATE o BORDER_CONSTANT. Al añadirlo como ejemplo si que sale ya toda la piramide a la misma tanto en matlab como en c++. En una primera prueba realizada para comprobar el tamaño de las imágenes no retorna todavía resultados, pero si es igual ya en tamaño.
Fenomenal. A ver si termina funcionando. Yo creo que en matlab hacen BORDER_REPLICATE ... pero mira a ver cómo lo hace P. Dollar.
Si. Es igual a la función padarray de matlab y tiene varias opciones, he visto que utiliza replicate pero miraré mas a fondo por si depende de algún caso.
He revisado chnsPyramid para ver si el padding afectaba a alguna de las acciones. Cuando acfDetectBadacost llama a chnsPyramid pasa la imagen con su formato original y retorna la imagen con padding.
Cuando chnsPyramid llama a los extractores de características, todavía no ha añadadido el padding, por lo que en esos puntos la imagen se pasa correctamente.
Si que me he fijado que al realizar la pirámide, calcula de forma distinta el resize que tiene que hacer, con la imagen real y con la aproximada, pero el resultado final es el mismo cuando se obtienen los distintos tamaños.
Por otro lado, comprobando todos los valores mínimos y máximos, de todas las escalas, hay diferencias en algunos pero los resultados son bastante similares.
A continuación de lo anterior, revisando el código paso a paso y comparando los resultados de c++ y matlab. Repasando el código línea a línea, he encontrado un fallo bastante importante, y es que en el punto en el que se accede al valor del pixel, es decir, obtener el valor de la característica:
float ftr = chns1[cids[fids[k]]];
He comprobado y ya el primer valor al que accedía lo estaba obteniendo mal. Tal y como lo hago ahora, a partir del valor de [cids[fids[k]]], obtengo a que canal esta accediendo y al pixel (antes para este paso hacia una conversión a puntero de floats para acceder a la característica como esta en P.Dollar, pero en esa conversión había algo mal). Ejemplos de comparativas tras esta corrección son las siguientes:
Aunque comparando resultados son bastante parecidos sigue habiendo algún error, las comparativas realizadas hasta ahora en las primeras iteraciones del bucle tras las llamadas a las funciones getMinPositiveCost y getNegativeCost tienen también valores muy parecidos, pero al terminar de ejecutar el código sigue sin detectar objeto. La siguiente imagen es en la primera iteración, el resultado de trace = -(min_pos_cost - neg_cost);, mostrando primero los dos valores y después el resultado.
Por el momento, si que he visto que en el bucle:
for(int i=0; i<num_classes; i++)
{
margin_vector[i] += codified[i] * w1_weights[t];
}
Hay diferencia entre el valor que da matlab y c++ ( matlab da algún valor mas) pero no consigo detectar a que se debe.
Pues esto tenemos que hablarlo en directo.
Con respecto a las ultimas pruebas que hablamos, he realizado la de llamar a la función una matriz con valores puestos manualmente y observar la salida. El resultado obtenido es el siguiente:
Los resultados son iguales en ambos casos, por lo que creo que se esta realizando bien el proceso.
Voy a hacer alguna prueba mas por si detecto algún fallo en este tipo de pruebas y seguir revisando el código, a ver si se detectar el problema.
Hice también la prueba de llamar con las imágenes transpuestas, pero tampoco da resultados buenos.
Creo que las pruebas que comentamos son estas, sigo trabajando en esto. No se si hay alguna prueba mas específica que deba realizar.
Gracias.
Continuando con la búsqueda del problema, probando si la función filter2D estaba haciendo algo incorrecto, con la cual se hace una convolución de la imagen con el filtro (conv2 en matlab) poniendo una matriz simple como ejemplo he visto que el resultado tenia alguna variación, que afectaba sobre todo en el borde de las imagenes. Haciendo pruebas he visto que antes de realizar esta convolución hay que hacer el padding y luego este filtrado utilizando el parametro BORDER_CONSTANT e indicando que la posición del kernel no esta centrada con respecto al pixel. De esa forma se obtiene el mismo resultado.
En la ultima actualización subida es con los cambios del código que comentamos ayer, trabajando en varias iteraciones sobre una imagen sintética. A continuación dejo alguna imagen de ejemplos de esto. Cuando pruebo con las imágenes reales sigue sin salir un resultado correcto. Sigo trabajando para ver las diferencias.
En la ultima actualización, creando una matriz para sacar las características se obtienen los mismos resultados para matlab y para c++, comprobado hasta el valor de trace (linea 320).
Como hablamos la última vez, esto nos deja tranquilos en varias cosas: la carga de varias variables del clasificador en C++ desde fichero y por otro lado el código que accede a una característica en un canal concreto.
Ahora pienso que podríamos tener un problema muy sencillo de resolver:
¿Qué pasa si traspones la matriz Cprime en la línea 305 del detector?
Es que creo que podemos tener un follón entre lo que guardamos en matlab en el yaml y lo que hay que usar en la función.
Por las pruebas que he hecho en esa parte, al transponerla da un resultado diferente al que debería dar, ya que he revisado los valores de costs_vector , m_Cprime y margin_vector y coinciden con los valores de matlab. Pero voy a hacer alguna prueba más con esto para ver si soluciona algo.
Por otro lado, con respecto al problema que comente el otro día de gradientHist ya esta solucionado, ya puedo llamar a la función con los mismos parámetros que matlab, he hecho alguna prueba y no parece que cambie mucho el resultado final, pero voy a hacer alguna prueba mas a ver como podía estar afectando
El que Cprime coincida con matlab no está bien. El caso es que en matlab antes de llamar a acfDetectBadacostTrees1 ¡Cprime se traspone!.
Esto es así porque después de implementada y probada la parte de C++, me di cuenta de que tenía un error y la solución sencilla era trasponer Cprime antes de meterla en acfDetectBadacostTrees1.
Si cuando obtengo los valores de matlab lo hago desde la funcion acfDetectBadacostTrees1, que es la de c++ a la cual ha llamado ya con la matriz traspuesta. Por las pruebas que hago, ya con la matriz traspuesta parece que los valores son correctos. Si pruebo a trasponer a matriz desde badacostDetector los resultados obtenidos son diferentes.
Probando esta función de Matlab, estoy teniendo el problema cuando llego a la linea 92 en la cual llama a acfDetectBadacostTrees1, me aparece el siguiente error:
Unrecognized function or variable 'acfDetectBadacostTrees1'.
Sin embargo el fichero con la función está en la carpeta private, de la misma forma que estaban por ejemplo gradientMex que si que funcionan. ¿Cuál puede ser el problema?
Gracias