iOS Accelerate Framework vImage: ¿mejora de performance?

He estado trabajando con OpenCV y el framework Accelerate de Apple y he encontrado que el performance de Accelerate es lento y la documentation de Apple es limitada. Tomemos por ejemplo:

void equalizeHistogram(const cv::Mat &planar8Image, cv::Mat &equalizedImage) { cv::Size size = planar8Image.size(); vImage_Buffer planarImageBuffer = { .width = static_cast<vImagePixelCount>(size.width), .height = static_cast<vImagePixelCount>(size.height), .rowBytes = planar8Image.step, .data = planar8Image.data }; vImage_Buffer equalizedImageBuffer = { .width = static_cast<vImagePixelCount>(size.width), .height = static_cast<vImagePixelCount>(size.height), .rowBytes = equalizedImage.step, .data = equalizedImage.data }; TIME_START(VIMAGE_EQUALIZE_HISTOGRAM); vImage_Error error = vImageEqualization_Planar8(&planarImageBuffer, &equalizedImageBuffer, kvImageNoFlags); TIME_END(VIMAGE_EQUALIZE_HISTOGRAM); if (error != kvImageNoError) { NSLog(@"%s, vImage error %zd", __PRETTY_FUNCTION__, error); } } 

Esta llamada tarda aproximadamente 20 ms. Cuál tiene el significado práctico de ser inutilizable en mi aplicación. Tal vez la ecualización del histogtwig es intrínsecamente lenta, pero también he probado BGRA-> escala de grises y he descubierto que OpenCV puede hacerlo en ~ 5 ms y que vImage toma ~ 20 ms.

En las testings de otras funciones encontré un proyecto que hizo una aplicación deslizante simple con una function de borrón (esencia) que limpié para probar. Aproximadamente ~ 20 ms también.

¿Hay algún truco para que estas funciones sean más rápidas?

Para get 30 fotogtwigs por segundo con la function ecualizar el histogtwig, debe desintercavar la image (convertir de ARGBxxxx a PlanarX) y ecualizar SOLAMENTE R (ed) G (reen) B (lue); si iguala A (lpha), la velocidad de cuadro se networkingucirá a al less 24.

Aquí está el código que hace exactamente lo que desea, tan rápido como desee:

 - (CVPixelBufferRef)copyRendenetworkingPixelBuffer:(CVPixelBufferRef)pixelBuffer { CVPixelBufferLockBaseAddress( pixelBuffer, 0 ); unsigned char *base = (unsigned char *)CVPixelBufferGetBaseAddress( pixelBuffer ); size_t width = CVPixelBufferGetWidth( pixelBuffer ); size_t height = CVPixelBufferGetHeight( pixelBuffer ); size_t stride = CVPixelBufferGetBytesPerRow( pixelBuffer ); vImage_Buffer _img = { .data = base, .height = height, .width = width, .rowBytes = stride }; vImage_Error err; vImage_Buffer _dstA, _dstR, _dstG, _dstB; err = vImageBuffer_Init( &_dstA, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImageBuffer_Init (alpha) error: %ld", err); err = vImageBuffer_Init( &_dstR, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImageBuffer_Init (networking) error: %ld", err); err = vImageBuffer_Init( &_dstG, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImageBuffer_Init (green) error: %ld", err); err = vImageBuffer_Init( &_dstB, height, width, 8 * sizeof( uint8_t ), kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImageBuffer_Init (blue) error: %ld", err); err = vImageConvert_ARGB8888toPlanar8(&_img, &_dstA, &_dstR, &_dstG, &_dstB, kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImageConvert_ARGB8888toPlanar8 error: %ld", err); err = vImageEqualization_Planar8(&_dstR, &_dstR, kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImageEqualization_Planar8 (networking) error: %ld", err); err = vImageEqualization_Planar8(&_dstG, &_dstG, kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImageEqualization_Planar8 (green) error: %ld", err); err = vImageEqualization_Planar8(&_dstB, &_dstB, kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImageEqualization_Planar8 (blue) error: %ld", err); err = vImageConvert_Planar8toARGB8888(&_dstA, &_dstR, &_dstG, &_dstB, &_img, kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImageConvert_Planar8toARGB8888 error: %ld", err); err = vImageContrastStretch_ARGB8888( &_img, &_img, kvImageNoError ); if (err != kvImageNoError) NSLog(@"vImageContrastStretch_ARGB8888 error: %ld", err); free(_dstA.data); free(_dstR.data); free(_dstG.data); free(_dstB.data); CVPixelBufferUnlockBaseAddress( pixelBuffer, 0 ); return (CVPixelBufferRef)CFRetain( pixelBuffer ); 

}

Tenga en count que asigno el canal alfa, aunque no realice nada en él; eso es simplemente porque la conversión de ida y vuelta entre ARGB8888 y Planar8 requiere asignación y reference de búfer de canal alfa. Mismo performance y mejoras de calidad, independientemente.

También tenga en count que realizo estiramientos de contraste después de convertir los tampones Planar8 en un solo tampón ARGB8888; eso es porque es más rápido que aplicar la function canal por canal, como lo hice con la function de ecualización del histogtwig, y ​​obtiene los mismos resultados que hacerlo individualmente (la function de estiramiento del contraste no provoca la misma distorsión del canal alfa que la ecualización del histogtwig) .

No sigas asignando vImage_Buffer si puedes evitarlo.

Una cosa que es fundamental para acelerar el performance de vImage es la reutilización de vImage_Buffers. No puedo decir cuántas veces leí en la documentation limitada de Apple sugerencias para este efecto, pero definitivamente no estaba escuchando.

En el ejemplo del código de borrón antes mencionado, he revisado la aplicación de testing para configurar los buffers de input y salida de vImage_Buffer una vez por image en lugar de una vez para cada llamada a boxBlur. Caí <10 ms por llamada que hizo una diferencia notable en el time de respuesta.

Esto dice que Accelerate necesita time para calentar antes de comenzar a ver mejoras de performance. La primera llamada a este método tomó 34 ms.

 - (UIImage *)boxBlurWithSize:(int)boxSize { vImage_Error error; error = vImageBoxConvolve_ARGB8888(&_inputImageBuffer, &_outputImageBuffer, NULL, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend); if (error) { NSLog(@"vImage error %zd", error); } CGImageRef modifiedImageRef = vImageCreateCGImageFromBuffer(&_outputImageBuffer, &_inputImageFormat, NULL, NULL, kvImageNoFlags, &error); UIImage *returnImage = [UIImage imageWithCGImage:modifiedImageRef]; CGImageRelease(modifiedImageRef); return returnImage; } 

Para usar vImage con OpenCV, pase una reference a su matriz OpenCV a un método como este:

 long contrastStretch_Accelerate(const Mat& src, Mat& dst) { vImagePixelCount rows = static_cast<vImagePixelCount>(src.rows); vImagePixelCount cols = static_cast<vImagePixelCount>(src.cols); vImage_Buffer _src = { src.data, rows, cols, src.step }; vImage_Buffer _dst = { dst.data, rows, cols, dst.step }; vImage_Error err; err = vImageContrastStretch_ARGB8888( &_src, &_dst, 0 ); return err; } 

La llamada a este método, desde su bloque de código OpenCV, se ve así:

 - (void)processImage:(Mat&)image; { contrastStretch_Accelerate(image, image); } 

Es así de simple, y como estas son references de puntero, no hay "copy profunda" de ningún tipo. Es tan rápido y eficiente como puede ser, todas las cuestiones de context y otras consideraciones relacionadas con el performance, aparte (también puedo ayudarte con esas cuestiones).

SIDENOTE: ¿Sabía que tiene que cambiar la permutación del canal al mezclar OpenCV con vImage? De lo contrario, antes de llamar a cualquier function de vImage en una matriz OpenCV, llame a:

 const uint8_t map[4] = { 3, 2, 1, 0 }; err = vImagePermuteChannels_ARGB8888(&_img, &_img, map, kvImageNoFlags); if (err != kvImageNoError) NSLog(@"vImagePermuteChannels_ARGB8888 error: %ld", err); 

Realice la misma llamada, map y todo, para devolver la image al order de canal adecuado para una matriz OpenCV.