iOS Accelerate Framework vImage - Улучшение производительности?

Я работал с OpenCV и платформой Apple Accelerate и обнаружил, что производительность Accelerate низкая, а документация Apple ограничена. Давайте возьмем для примера:

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);
    }
}

Этот звонок занимает примерно 20 мс. Который имеет практическое значение непригодности в моем приложении. Возможно, выравнивание гистограммы по сути медленное, но я также протестировал BGRA->Grayscale и обнаружил, что OpenCV может сделать это за ~5 мс, а vImage занимает ~20 мс.

При тестировании других функций я нашел проект, который сделал простое приложение-слайдер с функцией размытия (gist), которое я очистил для тестирования. Примерно ~20 мс.

Есть ли какая-то хитрость, чтобы эти функции были быстрее?

3 ответа

Решение

Чтобы получить 30 кадров в секунду с помощью функции equalizeHistogram, вы должны деинтерлейсировать изображение (преобразовать из ARGBxxxx в PlanarX) и выровнять ТОЛЬКО R(ed)G(reen)B(lue); если вы уравняете A(lpha), частота кадров упадет как минимум до 24.

Вот код, который делает именно то, что вы хотите, так быстро, как вы хотите:

- (CVPixelBufferRef)copyRenderedPixelBuffer:(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 (red) 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 (red) 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 );

}

Обратите внимание, что я выделяю альфа-канал, хотя я ничего не выполняю на нем; это просто потому, что для преобразования туда и обратно между ARGB8888 и Planar8 требуется распределение и ссылка на буфер альфа-канала. Те же улучшения производительности и качества, независимо.

Также обратите внимание, что я выполняю растяжение контраста после преобразования буферов Planar8 в один буфер ARGB8888; это происходит потому, что это быстрее, чем применение функции "канал за каналом", как я делал с функцией выравнивания гистограммы, и получает те же результаты, что и индивидуальное выполнение (функция растяжения контраста не вызывает такое же искажение альфа-канала, как и выравнивание гистограммы),

Не продолжайте перераспределять vImage_Buffer, если вы можете избежать этого.

Одна вещь, которая имеет решающее значение для ускорения производительности vImage, - это повторное использование vImage_Buffers. Я не могу сказать, сколько раз я читал в ограниченной документации Apple намеки на это, но я определенно не слушал.

В приведенном выше примере кода размытия я переработал тестовое приложение, чтобы настроить входной и выходной буферы vImage_Buffer один раз для каждого изображения, а не один раз для каждого вызова boxBlur. Я пропустил <10 мс за звонок, что заметно изменило время ответа.

Это говорит о том, что для ускорения требуется время для прогрева, прежде чем вы начнете видеть улучшения производительности. Первый вызов этого метода занял 34 мс.

- (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;
}

Чтобы использовать vImage с OpenCV, передайте ссылку на вашу матрицу OpenCV методу, подобному этому:

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;
}

Вызов этого метода из вашего блока кода OpenCV выглядит следующим образом:

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

Это так просто, и так как все это ссылки на указатели, никакого "глубокого копирования" не существует. Это настолько быстро и эффективно, насколько это возможно, за исключением всех вопросов контекста и других связанных с этим вопросов производительности (я тоже могу вам в этом помочь).

СИДЕНОТ: Знаете ли вы, что вам нужно изменить перестановку каналов при смешивании OpenCV с vImage? Если нет, перед вызовом любых функций vImage в матрице OpenCV вызовите:

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);

Выполните тот же вызов, map и all, чтобы вернуть изображение в порядок каналов, соответствующий матрице OpenCV.

Другие вопросы по тегам