扩展眼界iOS进阶iOS技术文章

UIImage转换成YUV420p及NEON、GPU等实现方式性

2016-07-21  本文已影响1903人  熊皮皮

本文档描述了如何转换UIImage至YUV420p或YUV420sp,实际是RGB与YUV两个颜色空间相互转换问题在特定平台上的实现问题。首先,确认被UIImage加载的图像,比如JPG、PNG等格式图片,其RGBA等通道在内存中的布局。再以正确的格式读取每个通道的数值并转换成YUV色彩空间。然后,比较Release模式下iPhone 6上运行BT. 601朴素实现、Accelerate框架、libyuv和NEON加速等实现方式的性能差异。

通过本文档的介绍,若需进行UIImage转换至YUV422,按YUV422的采样方式进行即可。另外,我将合并博客中与颜色转换相关的几个文档,原因是这些文档都属于相机项目开发过程中遇到的问题。

图片在iOS上的显示主要存在这些过程:加载、解压、渲染。一般情况下,由[UIImage imageNamed:]等方法加载到内存后,赋值给UIImageView作显示,这样就将解压及渲染操作都延后到UIImagView被添加到一个处于显示状态的视图后才执行。由于UI操作必须在主线程执行,为减少图像的解压和渲染流程占用较多主线程资源,一些开源项目,如SDWebImage、FastImageCache,它们通过CGBitmapContext在子线程进行绘制,强制解压及渲染图像,主线程显示时无需处理复杂计算,缓解了主线程压力,改善用户体验。但是,读取UIImage中指定像素点的RGB值并不是一个常见的需求。

我们的项目需要对JPG图像进行一些YUV颜色空间上的运算,由于我对图像知识了解较少,在实现过程当中遇到不少问题,下面逐一说明。

1、UIImage(CGImage)图像的内存布局

本节介绍读取UIImage中指定像素点的RGB值,因无明确的CGImage加载图像的内存布局资料,特意使用PhotoShop创建如下图像,以此为例进行验证。

上图JPG、下图PNG测试图及相关说明

1.1、读取UIImage的RGB值

UIImage不提供访问图像数据的接口,得通过CGImage等Core Foundation接口进行操作。

UIImage *image = [UIImage imageNamed:@"colorblock.jpg"];
CFDataRef pixelData = CGDataProviderCopyData(CGImageGetDataProvider(image.CGImage));
const uint8_t* data = CFDataGetBytePtr(pixelData);
int image_width = (int)imageSize.width;
int image_height = (int)imageSize.height;
size_t bitsPerPixel = CGImageGetBitsPerPixel(image.CGImage);
printf("bitsPerPixel = %lu\n", bitsPerPixel);
size_t gitsPerComponent = CGImageGetBitsPerComponent(image.CGImage);
printf("gitsPerComponent = %lu\n", gitsPerComponent);

即使是不含alpha通道的JPG格式图片,上述代码的输出并不是期望的24和8,而32与8,如下所示。

bitsPerPixel = 32
gitsPerComponent = 8

由CFDataGetBytePtr函数说明可知,上述图像按4个颜色通道进行存储,具体颜色通道顺序有待确认。

const UInt8 * CFDataGetBytePtr ( CFDataRef theData );
Returns a read-only pointer to the bytes of a CFData object.
This function is guaranteed to return a pointer to a CFData object's internal bytes. CFData, unlike CFString, does not hide its internal storage.

变量data指向了JPG或PNG图片的RGB数据。虽然,现在得到了指定图像RGB数据,但是,这些颜色通道的存储顺序确切是RGBA,ARGB还是其他顺序呢?只有正确读取出每个像素的颜色通道值才能进行正确的YUV转换。当然,也需要了解相应的YUV采样格式。
下面,先暴力逐行逐列遍历RGB数据。

for (int row = 0; row < image_height; ++row) {
    for (int col = 0; col < image_width; ++col) {
        // 由前面可知每个像素32位,即4字节
        int pixelInfo = ((image_width  * row) + col ) * 4; 
        int r = data[pixelInfo];
        int g = data[(pixelInfo + 1)];
        int b = data[pixelInfo + 2];
        int a = data[pixelInfo + 3];
    }
}

保存颜色读取结果。

char str[30] = {0};
char *rgbaPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"rgba.txt"].UTF8String;
unlink(rgbaPath);
FILE *rgbaFile = fopen(rgbaPath, "a+");
//
sprintf(str, "(%03d,%03d): %02X %02X %02X %02X\n", col, row, r, g, b, a);
fwrite(str, sizeof(uint8_t), sizeof(str), rgbaFile);
//
fclose(rgbaFile);

考虑UIImagePNGRepresentation和UIImageJPEGRepresentation方式读取图像RGB数据

1.2、确认UIImage加载的图像数据的颜色通道存储顺序

现在,分析文件rgba.txt中的颜色数据。

从上到下、从左到右各通道数值

对比源图像及上图可知,JPG格式图像通过UIImage加载后在内存中的颜色存储顺序是RGBA。有趣的是,在PhotoShop中每个颜色都是255,保存成JPG时被压缩了,可见部分通道是0xFE而非0xFF,或者0x00变成0x01。

2、RGB转换YUV

在iOS上做RGB、YUV转换比Android多一个现成的方案,就是Accelerate框架。可能Android有些厂家,如高通,也提供了相应的框架,只是我不了解。

2.1、CPU朴素实现BT. 601 RGB转成YUV420p公式

以BT. 601 Video Range转换矩阵为例进行计算。计算得到的Y、U和V的值范围[0, 255]。

想把YUV格式像素数据变成灰度图像,只需要将U、V分量设置成128即可。
这是因为U、V是图像中的经过偏置处理的色度分量。色度分量在偏置处理前的取值范围是-128至127,这时候的无色对应的是“0”值。经过偏置后色度分量取值变成了0至255,因而此时的无色对应的就是128了。

double f_y, f_u, f_v;
uint8_t y, u, v;

f_y = (float) ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
f_u = (float) ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
f_v = (float) ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;

y = correct_color_value(f_y);
u = correct_color_value(f_u);
v = correct_color_value(f_v);

correct_color_value用于溢出校正。

uint8_t correct_color_value(float color) {
    uint8_t intValue;
    if (color < 0) {
        intValue = 0;
    } else if (color > 255) {
        intValue = 255;
    } else {
        intValue = (uint8_t)color;
    }
    return intValue;
}

2.2、Accelerate实现RGB转成YUV420p

2.3、libyuv实现RGB转成YUV420p

在iOS或Android上按说明编译libyuv库后方可使用,在此先说明实现方式,后续文档介绍libyuv库的iOS编译步骤。

2.4、NEON实现RGB转成YUV420p

3、RGB转YUV的性能比较

附录

libyuv在iOS上的编译说明

BT. 601 YCrCb转换矩阵

BT. 709 YCrCb转换矩阵

上一篇下一篇

猜你喜欢

热点阅读