Desarrollo avanzado en Android Miguel Ángel Lozano, Boyán Bonev, Pablo Suau, Juan Manuel Sáez {malozano,boyan,pablo,jmsaez}@dccia.ua.es Mobile Vision Research Lab Dep. Ciencia de la Computación e Inteligencia Artificial Instituto Universitario de Investigación en Informática Universidad de Alicante Esquema Mobile Vision Research Lab Orígenes: robótica móvil y visión Trabajo actual Desarrollo avanzado en Android NDK NEON Necessitas Mobile Vision Research Lab Creación en 2011 Origen: Robot Vision Group Miembros Francisco Escolano Miguel Ángel Lozano Juan Manuel Sáez Pablo Suau Antonio Peñalver Boyán Bonev Actividades de investigación Visión artificial (monocular y estéreo) Robótica móvil Reconocimiento de patrones (teoría de la información) Visión en dispositivos móviles Visión móvil en robótica Visión móvil en robótica Visión móvil en robótica Reconocimiento de patrones Reconocimiento de patrones Visión móvil en el teléfono Visión en Android Métodos estándar de manejo de imágenes Clase Bitmap setPixels, getPixels Array de enteros Formato ARGB_8888 Operaciones a nivel de bit Alternativa: getPixel, setPixel Necesidad de copiar imágenes Visión en Android SetPixels y getPixels muy lentos Ejemplo: pasar a gris, Samsung Galaxy S, imagen de 320x240, 10 segundos (con enteros) Alternativas NDK NEON Necessitas Android NDK http://developer.android.com/sdk/ndk/index.html Herramienta instalada por separado Código nativo en C para las partes críticas de la aplicación Requiere Android 1.5 o superior Android 2.2 o superior para tratar con imágenes Java Native Interface Plataforma para ejecutar código escrito en otros lenguajes desde Java http://java.sun.com/developer/onlineTraining/Pr ogramming/JDCBook/jni.html http://java.sun.com/developer/onlineTraining/Pr ogramming/JDCBook/jniexamp.html Ejemplo NDK Carpeta jni Android.mk archivo.c Contenido de Android.mk LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := imagenes LOCAL_SRC_FILES := nativo.c LOCAL_LDLIBS := -llog -ljnigraphics -lm include $(BUILD_SHARED_LIBRARY) Ejemplo NDK Contenido de nativo.c (1) #include #include #include #include #include #include #include <jni.h> <string.h> <math.h> <time.h> <stdlib.h> <android/bitmap.h> <android/log.h> // La macro LOGE permite mostrar en el log de android (ejecutando en un terminal adb -logcat) // diferentes mensajes de error. El formato de los parámetros es exactamente igual a los del // printf de c #define LOG_TAG "libimages" #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) // FROM RGB888 TO RGB565 // r,g and b values are 8-bit values (from 0 to 255) inline int rgb(int r, int g, int b) { return((r>>3<<11) + (g>>2<<5) + (b>>3)); } // FROM RGB565 TO RGB888 inline int red(uint16_t color) { return (color&0xf800)>>11<<3;} inline int green(uint16_t color) { return (color&0x07e0)>>5<<2;} inline int blue(uint16_t color) { return (color&0x001f)<<3;} Ejemplo NDK Contenido de nativo.c (2) // Convertir a escala de grises void colorToGray(AndroidBitmapInfo info, uint16_t *pixels16) { int i,r,g,b,gray; } for (i=0;i<info.width*info.height;i++) { r = red(pixels16[i]); g = green(pixels16[i]); b = blue(pixels16[i]); gray = (299*r + 587*g + 114*b)/1000; // Las operaciones se hacen con //enteros para que el procesamiento sea más rápido pixels16[i] = rgb(gray, gray, gray); } Ejemplo NDK Contenido de nativo.c (3) // Método que se llamará desde java. El formato del nombre debe ser como se muestra. En primer lugar la palabra // Java, seguida por el nombre del paquete, el nombre de la clase, y el nombre del método (en este caso processImage) JNIEXPORT jfloat JNICALL Java_rvg_ua_es_Gris_procesarImage(JNIEnv * env, jobject bitmap) { AndroidBitmapInfo info; void *pixels; int ret; jfloat timep; clock_t start, finish; if ((ret = AndroidBitmap_getInfo(env, bitmap, &info)) < 0) { LOGE("Fallo en AndroidBitmap_getInfo()! error=%d", ret); return; } if (info.format != ANDROID_BITMAP_FORMAT_RGB_565) { LOGE("El formato del bitmap no es RGB_565 !"); return; } if ((ret = AndroidBitmap_lockPixels(env, bitmap, &pixels)) < 0) { LOGE("Fallo en AndroidBitmap_lockPixels()! error=%d", ret); return; } // EMPEZANDO PROCESAMIENTO uint16_t *pixels16 = (uint16_t *)pixels; start = clock(); // Transformar a escala de grises colorToGray(info, pixels16); // TERMINANDO PROCESAMIENTO AndroidBitmap_unlockPixels(env, bitmap); finish = clock(); timep = ((double)finish - (double)start)/CLOCKS_PER_SEC; return timep; // El método devuelve el tiempo de ejecución } Ejemplo NDK Compilación Directorio jni ndk­build Se genera el fichero libimagenes.so Importante: si no se cambió el código Java, hacer un Clear del proyecto Ejemplo NDK Utilizar código nativo desde Java private native float processImage(Bitmap bitmap); static { System.loadLibrary("images"); } Tiempo: 0.2s NEON Set de instrucciones ARMv7 Arquitectura SIMD (Single Instruction Multiple Data) Registros vectoriales a los que se les aplica una misma operación 32 registros de 64 bits (ó 16 registros de 128 bits) Procesamiento imágenes, video, gráficos http://www.arm.com/products/technologies/neo n.php Usando NEON en Android Conjuntamente con NDK Añadir a Android.mk ifeq ($(TARGET_ARCH_ABI),armeabi-v7a) LOCAL_CFLAGS := -DHAVE_NEON=1 LOCAL_SRC_FILES += native-neon.c.neon endif LOCAL_C_INCLUDES := $(NDK_ROOT)/sources/cpufeatures LOCAL_STATIC_LIBRARIES := cpufeatures include $(NDK_ROOT)/sources/cpufeatures/Android.mk Extensión ­neon.c a archivos con código NEON Usando NEON en Android Librería cpu­features.h para la comprobación de la existencia de instrucciones NEON // Comprobando la existencia del set de instrucciones NEON if (android_getCpuFamily() == ANDROID_CPU_FAMILY_ARM && (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_NEON)!=0 && (android_getCpuFeatures() & ANDROID_CPU_ARM_FEATURE_ARMv7)!=0) { LOGE(”NEON disponible”); } else { LOGE("El procesador no es ARMv7 o no es compatible con NEON"); return(-1); } Código alternativo en caso de incompatibilidad con NEON Set de instrucciones NEON http://gcc.gnu.org/onlinedocs/gcc/ARM­NEON­ Intrinsics Ejemplos Suma: Comparación: uint32x2_t vadd_u32 (uint32x2_t, uint32x2_t) uint32x2_t vceq_u32 (uint32x2_t, uint32x2_t) Ejemplo NEON (1) // Transforms image from rgb to grayscale void rgbtogray(AndroidBitmapInfo info, uint8_t *pixels) { int i; // Tamaño total de la imagen en pixels int n = info.width*info.height; // Puntero auxiliar para acceder en cada iteracion del bucle // a una posicion absoluta de la imagen, en lugar de ir incrementando // el puntero pixels uint8_t *auxp = pixels; int pos = 0; // Tres variables vectoriales compuestas por 8 valores de 8 bits. En // concreto, estos son los coeficientes por los que se van a multiplicar // los valores RGB de cada pixel para obtener el tono de gris uint8x8_t rfac = vdup_n_u8(77); uint8x8_t gfac = vdup_n_u8(151); uint8x8_t bfac = vdup_n_u8(28); // Dividimos el tamaño entre 8 porque los pixeles se van a procesar de // 8 en 8 (por lo que hay 8 veces menos iteraciones) n /= 8; // 8 pixels are processed simultaneously for (i=0;i<n;i++) { uint16x8_t temp; uint8x8_t result; // La variable rgb se comportará como un registro, con un unico // campo, un vector llamado val, de 4 componentes. Cada componente // de ese vector es un registro vectorial // Asi que aqui lo que estamos haciendo es almacenar las cuatro // componentes RGBA de los ocho pixeles actuales (apuntados por // auxp) en cuatro registros vectoriales uint8x8x4_t rgb = vld4_u8(auxp); // Lo siguiente son instrucciones de multiplicacion y // multiplicacion con acumulacion temp = vmull_u8(rgb.val[0], rfac); temp = vmlal_u8(temp, rgb.val[1], gfac); temp = vmlal_u8(temp, rgb.val[2], bfac); Ejemplo NEON (2) // Se realiza un desplazamiento para dividir y volver a tener // valores entre 0 y 255 result = vshrn_n_u16(temp,8); // Se asigna // del pixel rgb.val[0] = rgb.val[1] = rgb.val[2] = el mismo valor de intensidad a todas las componentes para tener una imagen en tonos de gris result; result; result; // Se vuelven a almacenar los 4 conjuntos de 8 bytes // (correspondientes a los 8 pixeles procesados esta iteracion) // en su posicion correspondiente de memoria vst4_u8(auxp,rgb); // Se apuna al siguiente conjunto de pixeles (aumentamos 8*4 bytes) pos += 32; // En lugar de ir incrementando el valor de pixels, vamos calculando // auxp como una suma absoluta. // Si no se hacía de esta forma el resultado no era correcto. Quiza // es debido a que el procesamiento con NEON se realiza de forma // asincrona auxp = pixels + pos; } return; } Tiempo: 0.04s (50 veces más rápido que sin NEON, 250 veces más rápido que sin NDK) Necessitas Port de QT a Android http://sourceforge.net/p/necessitas/home/necessitas/ Ventajas Código nativo Compilación basada en el set de NEON Inconvenientes Estado muy prematuro No se manejan correctamente algunos eventos de interfaz Necesidad de instalar gran cantidad de librerías (Ministro) Necessitas Reconstrucción 2D en tiempo real basada únicamente en visión Necessitas Necessitas Desarrollo avanzado en Android Miguel Ángel Lozano, Boyán Bonev, Pablo Suau, Juan Manuel Sáez {malozano,boyan,pablo,jmsaez}@dccia.ua.es Mobile Vision Research Lab Dep. Ciencia de la Computación e Inteligencia Artificial Instituto Universitario de Investigación en Informática Universidad de Alicante