lunes, 13 de junio de 2011

Volviendo el tiempo atrás... CUDA 2.3

Lamentablemente, y aunque la sintaxis de CUDA 4 está muy buena, debemos volver a la versión 2.3 sobre Windows y compilar el código del Onera [1] con una resolución del Lucas-Kanade en GPU.

Lamentablemente, como suele suceder, aparecen nuevos problemas a salvar:
  • NVCC sin un compilador no funciona. Necesitamos el Visual Studio...
  • Visual Studio 2008 Express no tiene soporte para procesadores x64 con lo que hay que optar por una versión Trial del Professional.
  • Para compilar por línea de comandos nos quedamos con un ejemplo pequeño de archivo .cu y logramos compilarlo sólo luego de leer un post interesante [2] en el foro de nvidia: es necesario ejecutar un batch que setea el entorno del VS en modo amd64 y luego indicar algunos parámetros extras al NVCC.
  • Dentro del VS2008, NVCC sigue sin compilar. Es necesario actualizar el archivo nvcc.profile para que tenga un include extra del visual:
INCLUDES += "-I$(TOP)/include" "-I$(TOP)/include/cudart" "-IC:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/include" $(_SPACE_)

[1] Onera - FOLKI GPU
[2] Post compilación x64

viernes, 10 de junio de 2011

Eligiendo la cámara - Conclusiones

Ganador momentaneo: PixelFly

Conclusiones generales:
  1. La Pulnix es demasiado lenta. Además no ofrece ninguna ventaja significativa.
  2. La PixelFly tiene un buen tiempo de transmisión con binning activado. Según las pruebas de concepto se obtendrían 10 ms entre frame y frame con lo cual se puede usar esta cámara.
  3. La PixelFly tiene además modo de doble shutter que no fue analizado pero brinda más alternativas.
  4. La SpeedCam, aún siendo la de mejor calidad y velocidad no tuvo buenos resultados. La transferencia resulta muy lenta con lo que queda descartada por el momento.
  5. Es importante aclarar que el análisis se basa en la necesidad de hacer un algoritmo lo más veloz posible y poder realiar cálculos en el momento. En caso de no ser esto así se puede reconsiderar los resultados y optar por la SpeedCam en lugar de la PixelFly, por ejemplo. Esto podría ocurrir en caso que se necesite tiempo extra para realizar procesamiento, no requerir 1 frame cada 10 ms sino 2 frames espaciados en 10 ms para, más tarde, repetir la toma de fotos y el proceso.

sábado, 4 de junio de 2011

Tiempos - Usando la SpeedCam programáticamente

En este punto, la experiencia se convirtió en algo bastante pesado. La SpeedCam no tiene una API de acceso programática. Tampoco permite sacar fotogramas de forma unitaria, sólo se puede sacar una ráfaga que es almacenada en el buffer interno de la cámara y luego transmitido a la PC.
La cámara posee código hecho en Java del cual se pueden obtener los .jar para emplearlos en un código de prueba.
Consultamos entonces a los fabricantes: nos responden que la compañía cambió de manos. Consultamos a los nuevos responsables y obtenemos como información que tenemos tres caminos:
  • utilizar una API a nivel registro con la cual se accede a la cámara enviando códigos de operación
  • revisar los archivos minivis.jar y minivis.dll
  • comprar otra cámara o adquirir el nuevo software.
Obviamente nos quedamos con la segunda por "simplicidad" (si es que se puede decir que esto sea simple). Entonces, armamos un archivo .Java y tratamos de adivinar cómo se usa la API que contiene el .jar.
Luego de varios días de revisar los resultados del Java Decompiler. Obtenemos una sucesión de pasos que permite obtener las imágenes con resultados bastante malos:
  • Snapshots tomados: 200
  • fps entre Snapshots: 2500
  • Tiempo Total: 7800ms (sólo en la transferencia, ya que la toma de imágenes se debe hacer previa a la este paso)
  • Tiempo Promedio: 39ms (es decir, 25 fps)
Conclusión: muy mala velocidad para una cámara tan potente. Claramente, no está optimizada para utilizarla de forma programática en entornos real-time. Por el contrario, el buffer parece consumir mucho tiempo de la transferencia.
Pero entonces surge una duda ¿Y si los tiempos son lentos por estar utilizando java? Para tratar de falsear esta teoría, recurrimos a código C++ que intente levantar las DLLs directamente y no utilizar minivis.jar. Nuevamente tenemos que decompilar, en este caso decompilamos el archivo minivis.dll y hacemos un matcheo contra lo observado en minivis.jar. Igualmente, debemos notar que ambos archivos se comunican gracias a que se empleó JNI en C++. Lamentablemente esto simplemente complica la tarea de decompilación y reuso de la DLL original.
Con una sola prueba de concepto nos queda claro que ese no es el mejor camino:

Run-Time Check Failure #0 - The value of ESP was not properly saved across a function call.  This is usually a result of calling a function declared with one calling convention with a function pointer declared with a different calling convention

Aquí el código Java:


   1:  class DummyConsumer implements CallbackConsumer {
   2:      public void modeChanged(int paramInt){
   3:      }
   4:      public void statusChanged(int paramInt) {
   5:      }
   6:      public void shutterChanged(boolean paramBoolean, short paramShort) {
   7:      }
   8:  }
   9:   
  10:  public class Application {
  11:      public static void main(String[] arguments) {
  12:          int numberOfImages = Integer.parseInt(arguments[0]);
  13:          
  14:          MinivisFactory factory = MinivisFactory.getInstance();
  15:          try {
  16:              factory.discover();
  17:              TypedNetAddress[] addresses = factory.getKnownDevices();
  18:              String mac = addresses.length > 0 ? addresses[0].getEntryName() : "00-50-C2-1D-7E-AB";
  19:              DummyConsumer callbackConsumer = new DummyConsumer();
  20:              Proxy camera = factory.getCamera(mac, callbackConsumer);
  21:              try {
  22:                  camera.connect();
  23:                  try {
  24:                      takeSnapshots(camera, numberOfImages, debug);
  25:                  }            
  26:                  finally {
  27:                      camera.disconnect();
  28:                  }
  29:              }
  30:              finally {
  31:                  camera.release();
  32:              }
  33:          }
  34:          catch(Exception e) {
  35:              e.printStackTrace();
  36:          }
  37:      }
  38:      private static void takeSnapshots(Proxy camera, int numberOfImages, boolean debug) throws IOException, MDriverException, Exception {
  39:          camera.setMode(4); //0 LIVE 1 RECORD 2 TRIGGERED 3 DRAM 4 LOWLIGHT
  40:          camera.trigger();
  41:          
  42:          //missing code here: wait for trigger to finish
  43:          
  44:          MFrameInfo frameInfo = new MFrameInfo();
  45:          for(int i = 0; i < numberOfImages; ++i) {
  46:              byte[] frameBytes = camera.getFrame(-1, -1  , frameInfo);
  47:          }
  48:      }
  49:  }

Aquí el código C++:


   1:   
   2:  #include "stdafx.h"
   3:  #include <iostream>
   4:   
   5:   
   6:  int _tmain(int argc, _TCHAR* argv[])
   7:  {
   8:      HMODULE hMod = LoadLibrary("minivis.dll");
   9:      typedef long (*MinivisFactory_Create)();
  10:      typedef long (*GetCamera)(long, char*, void*);
  11:      MinivisFactory_Create n_MinivisFactory_Create = (MinivisFactory_Create)GetProcAdress("_Java_com_artho_visart_plugins_minivis_internal_jni_MinivisFactory_n_1MinivisFactory_1Create@8");
  12:      GetCamera n_GetCamera = (GetCamera)GetProcAdress("_Java_com_artho_visart_plugins_minivis_internal_jni_MinivisFactory_n_1GetCamera@24");
  13:      
  14:      
  15:      long adapterPtr = n_MinivisFactory_Create();
  16:      void* consumer = NULL;
  17:      long pointer = n_GetCamera(adapterPtr, "00-50-C2-1D-7E-AB", consumer);
  18:      
  19:      std::cout << "Camera pointer:" << pointer << std::endl;
  20:      std::cout << "Press any key to continue..." << std::endl;
  21:      char character;
  22:      std::cin >> character;
  23:      return 0;
  24:  }

lunes, 30 de mayo de 2011

Tiempos - Usando la PixelFly programáticamente

Similar al caso de la Pulnix, se escribe código usando la API provista para PixelFly y se miden los tiempos aproximados. En este caso se cuenta con dos APIs a utilizar: la original de la cámara (para VC++) y un SDK unificado para las PixelFly y las Sensicam llamado Uniform SDK. Se opta por el último por tener una interfaz más clara y no requerir elementos de WinApi para funcionar; según la documentación esta interfaz es más lenta que la original, pero igualmente sirve para computar órdenes de magnitud.

Resultados:
  • Snapshots tomados: 200
  • fps entre Snapshots: 50
  • Tiempo Total: 4000ms
  • Tiempo Promedio: 20 ms (es decir, 50 fps)
Misma conclusión que la obtenida con la Pulnix: transferencia despreciable. Igualmente seguimos teniendo el doble del tiempo previsto para la toma de fotos.
Afortunadamente, la PixelFly presenta una opción de binning que puede reducir los tiempos empleados. El binning consiste en realizar algún procesamiento sobre un conjunto de píxeles y generar un único píxel que contiene información de los anteriores. En el común de los casos la operación consiste en promediar los valores.
Los nuevos resultados:

  • Snapshots tomados: 200
  • fps entre Snapshots: ?
  • Tiempo Total: 2000ms
  • Tiempo Promedio: 10 ms (es decir, 100 fps)
 Conclusión: ya tenemos el tiempo que queríamos !!
Al parecer el binning permite incluso que la cámara tome fotografías en una frecuencia más alta. Queda pendiente evaluar la capacidad de double shutter.
    Aquí el código:


       1:  #define GAIN 1
       2:  #define DELAY 0 //ms
       3:  #define EXPOSURE_TIME 5 //ms
       4:  #define ROIX 2 //from 1 to 20
       5:  #define ROIY 2 //from 1 to 20
       6:   
       7:  int main(int argc, char* argv[]) {
       8:      int totalSnapshots;
       9:      std::cout << "Qty of snapshots to take: ";
      10:      std::cin >> totalSnapshots;
      11:      
      12:      int camId;
      13:      CAMINFO camData[8];
      14:   
      15:      int boardNumber = 0;
      16:      int error;
      17:      if (error = SELECT_CAMERA("pixelfly", boardNumber, &camId, camData))
      18:          showErrorAndClose(error);
      19:      else {
      20:          if (error = SETUP_CAMERA(camId, camData, 0, 0, 0, 1, ROIX, 1, ROIY, 1, 1, GAIN, DELAY, EXPOSURE_TIME))
      21:              showErrorAndClose(error);
      22:          else {    
      23:              time_t beginTime, endTime;
      24:              time(&beginTime);
      25:      
      26:              int snapshotNumber = 0;
      27:              while (! error && snapshotNumber < totalSnapshots) {
      28:                  if (error = SNAP(camId, camData, 0))
      29:                      showErrorAndClose(error);
      30:                  else {
      31:                      if (error = GETIMAGE(camId, camData, 2000))
      32:                          showErrorAndClose(error);
      33:                      else 
      34:                          // image in memory at this point
      35:                  }
      36:                  snapshotNumber++;
      37:              }
      38:              time(&endTime);
      39:              std::cout << std::endl << "Tiempo Promedio por imagen: " << 1000.0 * (double)difftime(endTime, beginTime) / (float)totalSnapshots << "ms." << std::endl;
      40:          }
      41:          CLOSE_CAMERA(camId, camData);
      42:      }
      43:      return 0;
      44:  }

    miércoles, 25 de mayo de 2011

    Tiempos - Usando la Pulnix programáticamente

    Código feo... necesitamos saber si el tiempo de transferencia de la Pulnix con la placa adquisidora es suficiente para alcanzar los 100 fps.
    Usamos la API de Imagenation (muy vieja pero efectiva). Sacamos N snapshots y medimos el tiempo entre cada una de ellas usando time().
    Resultados:
    • Snapshots tomados: 120
    • fps entre Snapshots: 30
    • Tiempo Total: 4000ms
    • Tiempo Promedio: 33.3ms (es decir, 30 fps)

    Conclusión: el tiempo de transferencia es despreciable o bien se compenza con el tiempo de toma de la próxima foto.

    Aquí un snippet del código:


       1:  int main(int argc, char* arg[])
       2:  {
       3:      PXD pxd;
       4:      FRAMELIB frameLib;
       5:      long hFG=0;
       6:      char arcFileName[20];
       7:      static int num=0;
       8:      
       9:      int NO_FRAMES;
      10:      std::cout << "Ingrese el Nro de imágenes a capturar (30 fps)"; std::cin >> NO_FRAMES;
      11:   
      12:      imagenation_OpenLibrary("PXD_32.DLL", &pxd, sizeof(PXD));
      13:      imagenation_OpenLibrary ("frame_32.dll", &frameLib, sizeof(FRAMELIB));
      14:   
      15:      
      16:      //"tm-9701 progressive free-run.cam";
      17:      char configFile[] = {"C:\\PXD\\bin\\default.cam"};
      18:      hFG= pxd.AllocateFG (-1);
      19:   
      20:      CAMERA_TYPE *configInMem = pxd.LoadConfig(configFile);
      21:      pxd.SetCameraConfig(hFG,configInMem);
      22:      pxd.FreeConfig(configInMem);
      23:      time_t beginTime, endTime;
      24:      printf("Inicio de Captura %d imagenes \n\n",NO_FRAMES);
      25:      time(&beginTime);
      26:      for(num= 0; num {
      27:          time(&captureTime);
      28:          FRAME* pFRAME = pFRAME = pxd.AllocateBufferList (pxd.GetWidth(hFG), pxd.GetHeight(hFG), pxd.GetPixelType(hFG), 1 /*solo un frame*/);
      29:          pxd.Grab (hFG, pFRAME,IMMEDIATE);
      30:          sprintf(arcFileName,"30fps%.3d.bmp", num);
      31:          frameLib.ExtractPlane(pFRAME,1);
      32:          //frameLib.WriteBMP ( frameLib.ExtractPlane(pFRAME,1), arcFileName,1);
      33:          frameLib.FreeFrame (pFRAME);
      34:      }
      35:   
      36:      time(&endTime);
      37:      std::cout << std::endl << "Tiempo Promedio por imagen: " << 1000.0 * (double)difftime(endTime, beginTime) / (float)NO_FRAMES << "ms." << std::endl;
      38:      pxd.FreeFG (hFG);
      39:      return 0;
      40:  }
      41:      
      42:      

    miércoles, 4 de mayo de 2011

    Eligiendo la cámara

    Como ya se comentó, es necesario tomar imágenes del sistema bajo estudio para realizar ciertos chequeos y de esa forma alimentar al lazo cerrado. Por lo tanto, el siguente paso será realizar mediciones mediante las distíntas cámaras que se poseen en el laboratorio. Como siempre, realizamos una tabla comparativa:

    Nombre Tipo Resolución Max FPS Cropping / ROI Buffer / Tipo de Transferencia Url
    PXD Frame Grabber Family - Cyber Optics Placa Adq. 32Kx32K 160 (512x512 con 32bits) Si Interfaz  propietaria Link
    PixelFly qe - Cooke Coroporation Cámara 640x480 50 Si Sin buffer. Transf. directa por RJ45 en placa Adq. propietaria Link
    SpeedCam Mini Vis e2 - Weinberger Cámara 512x512 2500 Si2GB buffer. Luego Transf. por placa Ethernet 1 Gbit Link
    TM-9701 - Pulnix Cámara 768x484 30 No Sin buffer. Transf. directa por placa Adq. PXD Link

    Si bien la SpeedCam Minivis es la mejor cámara de las tres disponibles, la mejor opción siempre depende de las características que se necesiten.

    En nuestro caso particular son:

    1. Debe ser programable.
    2. Se necesitan frames con una Dif. de tiempo menor a 10ms. Es decir, 100 fps.
    3. Si bien la Dif. entre frames debe ser menor a 10ms, se puede aceptar un tiempo de transferencia un tanto mayor pero que permita procesar frames de a pares, retornar respuesta al sistema y volver a pedir un par de frames.
    4. Tanto la luminosidad como la resolución no son factores importantes.
    Lamentablemente, los puntos 1 a 3 deben ser confirmados empíricamente con lo cual se demora la elección hasta tener información en el campo práctico sobre todas las cámaras.

    martes, 26 de abril de 2011

    Placa más computadora instaladas y funcionando

    Ya está la placa GTX570 funcionando en un Ubuntu. Posiblemente sea necesario realizar la instalación para Windows dado que muchos de los sensores y actuadores se comunican usando drivers propietarios de Windows.

    Se instaló CUDA Tools y Toolkit versión 4.0 (release candidate 2, mejor arrancar con el soft de vanguardia dado que lo tienen casi en una versión productiva) junto con el GPU SDK examples de Nvidia (ver).


    Linux
    Luego de instalar todos los paquetes necesarios (libgl1-mesa-dev, libgl1-mesa-dri, libglu-mesa-dev, freeglut3-dev, libxmu-dev, libxi-dev, etc) y de instalar/reinstalar los drivers y toolkits de Nvidia varias veces llegamos a la primera compilación. Obviamente se trata de ejemplos pre-armados, dentro del SDK. Compilamos y corremos algunos tests como deviceQuery y bandwidthTest:

    cd ~/workspace/NVIDIA_GPU_Computing_SDK
    make
    cd C/bin/linux/release
    ./deviceQuery

    ./deviceQuery Starting...

     CUDA Device Query (Runtime API) version (CUDART static linking)

    There is 1 device supporting CUDA

    Device 0: "GeForce GTX 570"
      CUDA Driver Version / Runtime Version          4.0 / 4.0
      CUDA Capability Major/Minor version number:    2.0
      Total amount of global memory:                 1279 MBytes (1341325312 bytes)
      (15) Multiprocessors x (32) CUDA Cores/MP:     480 CUDA Cores
      GPU Clock Speed:                               1.57 GHz
      Memory Clock rate:                             2100.00 Mhz
      Memory Bus Width:                              320-bit
      L2 Cache Size:                                 655360 bytes
      Max Texture Dimension Size (x,y,z)             1D=(65536), 2D=(65536,65535), 3D=(2048,2048,2048)
      Max Layered Texture Size (dim) x layers        1D=(16384) x 2048, 2D=(16384,16384) x 2048
      Total amount of constant memory:               65536 bytes
      Total amount of shared memory per block:       49152 bytes
      Total number of registers available per block: 32768
      Warp size:                                     32
      Maximum number of threads per block:           1024
      Maximum sizes of each dimension of a block:    1024 x 1024 x 64
      Maximum sizes of each dimension of a grid:     65535 x 65535 x 65535
      Maximum memory pitch:                          2147483647 bytes
      Texture alignment:                             512 bytes
      Concurrent copy and execution:                 Yes with 1 copy engine(s)
      Run time limit on kernels:                     No
      Integrated GPU sharing Host Memory:            No
      Support host page-locked memory mapping:       Yes
      Concurrent kernel execution:                   Yes
      Alignment requirement for Surfaces:            Yes
      Device has ECC support enabled:                No
      Device is using TCC driver mode:               No
      Device supports Unified Addressing (UVA):      Yes
      Device PCI Bus ID / PCI location ID:           3 / 0
      Compute Mode:
         < Default (multiple host threads can use ::cudaSetDevice() with device simultaneously) >

    deviceQuery, CUDA Driver = CUDART, CUDA Driver Version = 4.0, CUDA Runtime Version = 4.0, NumDevs = 1, Device = GeForce GTX 570
    [./deviceQuery] test results...
    PASSED

    ./bandwidthTest


    ./bandwidthTest Starting...

    Running on...

     Device 0: GeForce GTX 570
     Quick Mode

     Host to Device Bandwidth, 1 Device(s), Paged memory
       Transfer Size (Bytes) Bandwidth(MB/s)
       33554432 2961.9

     Device to Host Bandwidth, 1 Device(s), Paged memory
       Transfer Size (Bytes) Bandwidth(MB/s)
       33554432 2753.8

     Device to Device Bandwidth, 1 Device(s)
       Transfer Size (Bytes) Bandwidth(MB/s)
       33554432 130016.2

    [./bandwidthTest] test results...
    PASSED

    Win7 
    La instalación demoró muchísimo menos tiempo (menos de dos horas totales) comparada con la instalación en Linux (unas 12 horas de lucha).
    El deviceQuery entrega CASI los mismos resultados. Hay diferencia en


      Run time limit on kernels:                     Yes
      Device supports Unified Addressing (UVA):      No


    que luego investigaremos.

    Por otro lado, el bandwidthTest entrega:

     Host to Device Bandwidth, 1 Device(s), Paged memory
       Transfer Size (Bytes)        Bandwidth(MB/s)
       33554432                     2521.2
     Device to Host Bandwidth, 1 Device(s), Paged memory
       Transfer Size (Bytes)        Bandwidth(MB/s)
       33554432                     2551.9
     Device to Device Bandwidth, 1 Device(s)
       Transfer Size (Bytes)        Bandwidth(MB/s)
       33554432                     130377.2

    que claramente ofrece menos performance que en la versión Linux.