在iOS中,如何快速将RGB24转换为BGR24?

7

我使用 Accelerate.framework 中的 vImageConvert_RGB888toPlanar8vImageConvert_Planar8toRGB888 函数将 RGB24 转换为 BGR24,但当需要转换的数据非常大,如 3M 或 4M 时,所需的时间约为 10ms。有没有人知道更快的方法?我的代码如下:

- (void)transformRGBToBGR:(const UInt8 *)pict{
rgb.data = (void *)pict;

vImage_Error error = vImageConvert_RGB888toPlanar8(&rgb,&red,&green,&blue,kvImageNoFlags);
if (error != kvImageNoError) {
    NSLog(@"vImageConvert_RGB888toARGB8888 error");
}

error = vImageConvert_Planar8toRGB888(&blue,&green,&red,&bgr,kvImageNoFlags);
if (error != kvImageNoError) {
    NSLog(@"vImagePermuteChannels_ARGB8888 error");
}

free((void *)pict);
}

当我看到你的标题时,我立刻想到了accelerate.framework。但是既然你已经在使用它,我认为在iOS上没有更好的方法来完成这样的事情了。 - iGranDav
@iGranDav:按照OP的方式使用这个框架并不能保证高速运行。数据复制太多了。请看我的回答,其中有详细的解释和针对这个具体任务的ARM网站链接。 - Viktor Latypov
@iGranDav 这个 planar8 调用完全错误。如果你想交换字节,应该使用 permute。 - Cameron Lowell Palmer
2个回答

8
使用RGB888ToPlanar8调用,您需要散布数据,然后再次收集数据。这非常非常糟糕。如果33%的内存开销可以承受,请尝试使用RGBA格式并就地排列B/R字节。
如果您想节省33%的空间,则可以尝试以下建议。迭代所有像素,但仅读取4个字节的倍数(因为最小公倍数为3,4是12,即3个双字)。
uint8_t* src_image;
uint8_t* dst_image;

uint32_t* src = (uint32_t*)src_image;
uint32_t* dst = (uint32_t*)dst_image;

uint32_t v1, v2, v3;
uint32_t nv1, nv2, nv3;
for(int i = 0 ; i < num_pixels / 12 ; i++)
{
     // read 12 bytes
     v1 = *src++;
     v2 = *src++;
     v3 = *src++;
     // shuffle bits in the pixels
     // [R1 G1 B1 R2 | G2 B2 R3 G3 | B3 R4 G4 B4]
     nv1 = // [B1 G1 R1 B2]
      ((v1 >> 8) & 0xFF) | (v1 & 0x00FF0000) | ((v1 >> 16) & 0xFF) | ((v2 >> 24) & 0xFF);
     nv2 = // [G2 R2 B3 G3]
       ...
     nv3 = // [R3 B4 G4 R4]
       ...
     // write 12 bytes
     *dst++ = nv1;
     *dst++ = nv2;
     *dst++ = nv3;
}

使用NEON指令可以实现更好的效果。

请查看此链接来自ARM网站,了解如何进行24位交换。

BGR转RGB可以像这样就地完成:

void neon_asm_convert_BGR_TO_RGB(uint8_t* img, int numPixels24)
{
    // numPixels is divided by 24
    __asm__ volatile(
        "0:                \n"
        "# load 3 64-bit regs with interleave: \n"
        "vld3.8      {d0,d1,d2}, [%0]   \n"
        "# swap d0 and d2 - R and B\n"
        "vswp d0, d2   \n"
        "# store 3 64-bit regs: \n"
        "vst3.8      {d0,d1,d2}, [%0]!      \n"
        "subs        %1, %1, #1       \n"
        "bne         0b            \n"
        :
        : "r"(img), "r"(numPixels24)
        : "r4", "r5"
     );
}

非常遗憾,在我的测试中,迭代所有像素,但只读取4字节的倍数(因为lcm(3,4)为12,即3个双字)RGB888ToPlanar8更慢,而后者的成本是RGB888ToPlanar8的两倍。在我的实验中,RGB888ToPlanar8需要10毫秒,而另一个需要20毫秒。 - zhzhy
然后使用汇编版本,C版本不是Accelerate框架的竞争对手。 - Viktor Latypov
抱歉在位移方面出现了错误。ARM既可以是BE/LE,但默认情况下通常是小端模式。而我的C代码明显是大端模式的。 - Viktor Latypov
nv1 = ((v1 >> 16) & 0x0000FF) | (v1 & 0x0000FF00) | ((v1 << 16) & 0x00FF0000) | ((v2 << 16) & 0xFF000000); nv2 = ((v1 >> 16) & 0x0000FF00) | (v2 & 0xFF) | (v2 & 0xFF000000)| (v3 << 16 & 0x00FF0000); nv3 = ((v2 >> 16) & 0x000000FF) | (v3 >> 16 & 0x0000FF00) | (v3 << 16 & 0xFF000000) | (v3 | 0x00FF0000);类似这样的代码不能正常工作,为什么?输出颜色是错误的。 - zhzhy
是的,我对目标指针进行了双重递增。我会修复它。请参见编辑。 - Viktor Latypov
显示剩余4条评论

0

只需交换通道 - BGRA 到 RGBA

- (void)convertBGRAFrame:(const CLPBasicVideoFrame &)bgraFrame toRGBA:(CLPBasicVideoFrame &)rgbaFrame
{
    vImage_Buffer bgraImageBuffer = {
        .width = bgraFrame.width,
        .height = bgraFrame.height,
        .rowBytes = bgraFrame.bytesPerRow,
        .data = bgraFrame.rawPixelData
    };

    vImage_Buffer rgbaImageBuffer = {
        .width = rgbaFrame.width,
        .height = rgbaFrame.height,
        .rowBytes = rgbaFrame.bytesPerRow,
        .data = rgbaFrame.rawPixelData
    };

    const uint8_t byteSwapMap[4] = { 2, 1, 0, 3 };

    vImage_Error error;
    error = vImagePermuteChannels_ARGB8888(&bgraImageBuffer, &rgbaImageBuffer, byteSwapMap, kvImageNoFlags);
    if (error != kvImageNoError) {
        NSLog(@"%s, vImage error %zd", __PRETTY_FUNCTION__, error);
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接