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.


    miércoles, 2 de marzo de 2011

    Comparador de performance online para CPU, GPU, etc.

    No sé si es un hallazgo, pero no dejó de impresionarme tener tanta información para poder decidir sobre uno u otro producto:

    http://www.anandtech.com

    martes, 1 de marzo de 2011

    ¿Priorizando relación precio-prestaciones? No siempre.

    Estuvimos analizando las tablas comparativas de las distintas placas. El análisis resultante es el siguiente:

    Serie Tesla: precios muy elevados. Gama muy alta de placas sin siquiera salida de video (son sólo para procesamiento). Tienen soporte para escalar horizontalmente (más placas) por driver. Cuenta con buena performance para doble precisión (DP) -de aprox. 1/2 respecto de simple precisión- y soporte ECC para la memoria (un problema bastante frecuente en GPGPU es la alta tasa de error en memorias) en varios modelos.

    Serie Quadro: uso compartido entre cálculo y gráficas de alta calidad. Al igual que la Tesla, tienen soporte ECC para algunos modelos (aunque el costo es mucho más elevado) y buen manejo de DP.

    Serie Quadro FX: muy mala en comparación con la anterior. Orientada a video juegos por completo. Los costos no disminuyen en todos los casos.

    Serie GTX500: muy buena relación precio-prestaciones. Son modelos nuevos bastante competitivos con buenas velocidades, cantidad de memoria, ancho de banda, cores CUDA, etc. Lamentablemente no tienen soporte DP de calidad (1/8 respecto de simple precisión. Ver Ref.) ni ECC en ningún caso. La 580 está algo sobrevaluada por ser la más nueva de las placas. La 560 es la más barata y aún así no reduce mucho las características.

    Serie GTX400: al parecer va a quedar obsoleta. Prestaciones similares a la serie 500 con costos más elevados. Quejas de usuarios por performance, ruidos molestos ¿? :), etc.

    Serie GTS450: muy buena opción económica. El precio es muy bueno para las características generales. Lamentablemente estamos buscando un nivel más alto de paralelismo.


    Conclusiones finales:
    Tesla descartada por excesivo. Quadro se vuelve muy caro por importación y las tarjetas con ECC de por sí duplican el costo. En cualquiera de los casos no se prevé hacer uso intensivo de DP de cálculo con lo cual se opta por series GTX500 o GTX400. 
    Dado que la serie 500 posee mejores perspectivas a futuro, nos quedamos con ella. Se descarta la placa 580 por ser la última de la serie y tener un costo excesivo. Finalmente nos quedamos con la GTX570 sobre la 560 por tener mejores prestaciones. Aunque se reconoce que la 560 posee una mejor relación precio-características se opta por el modelo siguiente para tener una PC más competitiva y amortizar durante un lapso más grande (meses) el tiempo invertido en la compra, instalación, puesta a punto, capacitación y pruebas.