Windows Phone 的相机颜色转换(YCbCr 到 ARGB)

2012/2/9

使用 Windows Phone OS 7.1,您可以采用编程方式访问设备相机。除了拍摄照片之外,您还可以访问相机预览缓冲区以实时处理相机帧。PhotoCamera 类的 GetPreviewBuffer 方法采用两种格式(ARGB 和 YCbCr)提供相机预览缓冲区中的帧。ARGB 是用于描述应用程序 UI 中颜色的格式。YCbCr 启用高效的图形处理,但不能由 Silverlight 使用。如果您想在您的应用程序中操作某个 YCbCr 帧,则需要将该帧转换为 ARGB,然后该帧才能显示。本主题介绍 ARGB、YCbCr 以及如何将帧从 YCbCr 转换为 ARGB。

提示提示:

本主题中讨论的很多概念已在相机颜色选取器示例中进行了介绍。若要下载源代码,请参阅 Windows Phone 的代码示例

ARGB 格式指定在 Silverlight 中定义颜色的 Alpha、红色、绿色和蓝色分量。当使用 GetPreviewBufferArgb32(array<Int32>[]()[][]) 方法从相机预览缓冲区中获取帧时,采用一维整数 (Int32) 值数组的形式返回图像。这些值与 Silverlight 用来绘制 UI 的 32 位 ARGB 调色板相对应。预览缓冲区数组中的每个 32 位整数都与图像中的单个像素相对应。整数中的每 4 个字节都按如下方式与四个 ARGB 分量相对应:

  • Alpha 字节(25-32 位):表示透明度。相机中的帧将始终不透明,由十进制值 255 表示。

  • 红色字节(17-24 位):表示红色分量。范围从 0 到 255 的十进制值。

  • 绿色字节(9-16 位):表示绿色分量。范围从 0 到 255 的十进制值。

  • 蓝色字节(1-8 位):表示蓝色分量。范围从 0 到 255 的十进制值。

例如,下图中演示了描述颜色 mango 的 32 位整数。

AP_Con_Camera_ARGB_Array

尽管以一维数组的形式返回预览缓冲区,但是可以通过 PhotoCamera 类的 PreviewResolution 属性指定的宽度和高度推断出二维的图像。例如,如果相机缓冲区仅 12 个像素,则 GetPreviewBufferArgb32 将返回一个由 12 个整数组成的数组来描述 12 个像素。如果相应的 PreviewResolution 为 4 x 3 并且对数组应用了 WriteableBitmap 控件的 Pixels 属性,则通过从左到右、从上到下排列 12 个像素来组合成位图图像,如下图所示。

AP_Con_Camera_ARGB_Sequence

在该图中,数字对应于数组中每个整数(共 12 个整数)的索引值。有关如何使用 GetPreviewBufferArgb32 方法的示例,请参阅如何在 Windows Phone 的相机应用程序中使用灰度

YCbCr 格式指定相机帧的亮度和色差分量。亮度 Y 描述像素的亮度;它本身是图像的一个灰度版本。色差 Cb 和 Cr 描述蓝色和红色的色差分量。当使用 GetPreviewBufferYCbCr(array<Byte>[]()[][]) 方法从相机预览缓冲区中获取帧时,采用一维字节值数组的形式返回图像。

下图演示采用 YCbCr 格式指定的亮度和色差分量。

AP_Con_Camera_YCbCr_Diagram

在该图中,每个亮度和色差分量都用一个相对于 1 的值和相应的无符号字节值(在括号中)进行标记。无符号字节值与 GetPreviewBufferYCbCr 方法提供的字节数组中的 Y、Cb 和 Cr 的值相对应。例如,Y 的值 0.5 对应的字节值为 127,Cb 的值 0.0 对应的字节值为 127,Cr 的值 -1.0 对应的字节值为 0。

与 ARGB 格式不同,预览缓冲区数组中的值与图像中的像素之间没有一对一的关系。为了优化相机的性能,每个 Windows Phone 设备分四次对帧的色差分量进行取样。这意味着对于 Y 信息的每 4 个字节,只有一个字节的 Cb 和一个字节的 Cr 用于描述图像的对应 4 个像素。而且,数组中的一些字节可能未使用,目的是留出填充的空间(可能因设备型号而异)。由于 YCbCr 预览缓冲区数组的布局可能发生更改,请使用 YCbCrPixelLayout 类引用预览缓冲区中的相应字节。YCbCrPixelLayoutPhotoCamera 类的属性的形式可用于每个相机。

YCbCrPixelLayout 类的属性描述预览缓冲区数组中排列 Y、Cb 和 Cr 字节的方式。Offset 属性(YOffsetCbOffsetCrOffset)指定数组中 Y、Cb 和 Cr 字节的起始位置。XPitch 属性(YXPitchCbXPitchCrXPitch)指定帧的当前行中分别到下一个 Y、Cb 和 Cr 字节的字节数。Pitch 属性(YPitchCbPitchCrPitch)指定行之间的字节数。

例如,对于 16 像素缓冲区的数组,下图演示常用的 YCbCr 布局中的 YCbCrPixelLayout 属性。

AP_Con_Camera_YCbCr_Layout

与该图中演示的预览缓冲区对应的 YCbCrPixelLayout 属性具有以下值。

YCbCrPixelLayout 属性

Y

Cb

Cr

OffsetYOffsetCbOffsetCrOffset

0

25

24

XPitchYXPitchCbXPitchCrXPitch

1

2

2

PitchYPitchCbPitchCrPitch

6

6

6

本示例还演示 2 个字节填充的缓冲区如何影响 Y、Cb 和 Cr 字节的布局。尽管此示例中的帧只有 4 个像素宽,但它需要 6 个字节才能达到相应字节的下一行。这反映在等于 6 的 Pitch 值中。YCbCr 缓冲区布局可能因设备型号而异;重要的是使用 YCbCrPixelLayout 属性来索引相应的亮度和色差分量。

使用对应的 PhotoCamera 对象中的 PreviewResolution 属性,您可以确定预览缓冲区数组中的字节和帧中的像素之间的关系。继续 16 像素帧的这个示例,下图演示了像素和字节的关系。

AP_Con_Camera_YCbCr_Assembly
重要说明重要说明:

使用 YCbCr 预览缓冲区时,使用 RequiredBufferSize 属性确定缓冲区的大小;使用 PreviewResolution 属性确定相应帧的尺寸。YCbCr 预览缓冲区可能包含未使用的字节,目的是为填充留出空间。

仅处理亮度

若要仅从相机预览缓冲区中获取亮度信息,请使用 GetPreviewBufferY(array<Byte>[]()[][]) 方法。该方法返回只包含 Y 值的字节数组。该字节数组中 Y 值的组织方式与此示例中演示的等效 YCbCr 的方式相同。

由于 YCbCr 格式比 ARGB 格式更加紧凑,因此 GetPreviewBufferYCbCr(array<Byte>[]()[][]) 方法为图像处理提供了一个有效的选项。本节介绍如何在相机预览缓冲区中选择像素以及如何将其从 YCbCr 格式转换为 ARGB 格式。

提示提示:

本节中的代码示例摘自相机颜色选取器示例。若要下载完整的源代码,请参阅 Windows Phone 的代码示例

从预览缓冲区中获取 Y、Cb 和 Cr 值

若要只操作相机预览缓冲区的子集,您的应用程序将需要收集与受影响的像素相对应的 Y、Cb 和 Cr 值。本节中的代码示例演示如何根据帧的坐标确定单个像素的 Y、Cb 和 Cr 值。此外,将 Cb 和 Cr 值转换为有符号的值以便为稍后进行的 ARGB 转换做准备。由于 Y(亮度)的范围介于 0.0 和 1.0 之间,因此不需要转换,可以保留字节值。

以下示例是以输出参数的形式返回帧中单个像素的 Y、Cb 和 Cr 值的方法。

private void GetYCbCrFromPixel(YCbCrPixelLayout layout, byte[] currentPreviewBuffer, int xFramePos, int yFramePos, out byte y, out int cr, out int cb)
{
    // Find the bytes corresponding to the pixel location in the frame.
    int yBufferIndex = layout.YOffset + yFramePos * layout.YPitch + xFramePos * layout.YXPitch;
    int crBufferIndex = layout.CrOffset + (yFramePos / 2) * layout.CrPitch + (xFramePos / 2) * layout.CrXPitch;
    int cbBufferIndex = layout.CbOffset + (yFramePos / 2) * layout.CbPitch + (xFramePos / 2) * layout.CbXPitch;

    // The luminance value is always positive.
    y = currentPreviewBuffer[yBufferIndex];

    // The preview buffer contains an unsigned value between 255 and 0.
    // The buffer value is cast from a byte to an integer.
    cr = currentPreviewBuffer[crBufferIndex];
            
    // Convert to a signed value between 127 and -128.
    cr -= 128;

    // The preview buffer contains an unsigned value between 255 and 0.
    // The buffer value is cast from a byte to an integer.
    cb = currentPreviewBuffer[cbBufferIndex];

    // Convert to a signed value between 127 and -128.
    cb -= 128;
}

该方法使用以下参数和变量:

  • layout:来自对应的 PhotoCamera 对象的 YCbCrPixelLayout 属性的一个 YCbCrPixelLayout 对象。

  • currentPreviewBuffer:由 GetPreviewBufferYCbCr(array<Byte>[]()[][]) 方法加载的相机预览缓冲区字节数组。

  • xFramePos:相机帧中像素的 X 坐标。

  • yFramePos:相机帧中像素的 Y 坐标。

  • y:像素的亮度。以范围从 0 到 255 的字节值形式返回(亮度值始终为正值)。

  • cr:像素的红色色差(色差)。以有符号值的形式返回,范围从 -128 到 127 的整数值。

  • cb:像素的蓝色色差(色差)。以有符号值的形式返回,范围从 -128 到 127 的整数值。

  • yBufferIndex:与指定像素相对应的 Y(亮度)字节在预览缓冲区中的索引位置。

  • crBufferIndex:与指定像素相对应的 Cr 字节在预览缓冲区中的索引位置。

  • cbBufferIndex:与指定像素相对应的 Cb 字节在预览缓冲区中的索引位置。

使用仅整数的除法进行 YCbCr 到 ARGB 的转换

有很多方法可以将 YCbCr 值转换为 RGB 颜色空间。本节中的代码示例演示仅整数除法的方法,该方法根据国际电信联盟的无线通信部门 (ITU-R) 标准进行 YCbCr 到 RGB888 的转换。

使用该方法,将 YCbCr 转换为 ARGB 有三个步骤:

  1. 仅整数除法:Y 值强制转换为一个整数并用无符号值计算 Cb 和 Cr。

  2. 钳位:在 0 到 255 范围之外的任何 R、G 或 B 整数值都转换为相应的最大和最小可接受值。

  3. 封装:使用 OR 运算符合并 Alpha 值为 255 的 R、G 和 B 分量中的二进制信息。使用左移运算符相应地移动 R 和 G 整数中较低的 8 位。

采用以下方法演示了该转换,该方法根据对应的 YCbCr 分量返回一个 32 位的 ARGB 值。

private int YCbCrToArgb(byte y, int cb, int cr)
{
    // Individual RGB components.
    int r, g, b;

    // Used for building a 32-bit ARGB pixel.
    uint argbPixel;

    // Assumes Cb & Cr have been converted to signed values (ranging from 127 to -128).
            
    // Integer-only division.
    r = y + cr + (cr >> 2) + (cr >> 3) + (cr >> 5);
    g = y - ((cb >> 2) + (cb >> 4) + (cb >> 5)) - ((cr >> 1) + (cr >> 3) + (cr >> 4) + (cr >> 5));
    b = y + cb + (cb >> 1) + (cb >> 2) + (cb >> 6);

    // Clamp values to 8-bit RGB range between 0 and 255.
    r = r <= 255 ? r : 255;
    r = r >= 0 ? r : 0;
    g = g <= 255 ? g : 255;
    g = g >= 0 ? g : 0;
    b = b <= 255 ? b : 255;
    b = b >= 0 ? b : 0;

    // Pack individual components into a single pixel.
    argbPixel = 0xff000000; // Alpha
    argbPixel |= (uint)b;
    argbPixel |= (uint)(g << 8);
    argbPixel |= (uint)(r << 16);

    // Return the ARGB pixel.
    return unchecked((int)argbPixel);
}

显示: