缩小YUV 4:2:2的算法

时间:2018-03-20 23:00:19

标签: c# algorithm video

尝试编写一种有效的算法,将YUV 4:2:2按比例缩小2倍 - 并且不需要转换为RGB(这是CPU密集型)。

我已经在YUV到RGB转换的堆栈溢出上看到了大量代码 - 但这里只是YUV 4:2:0的缩放示例,我已经开始根据我的代码开始了。然而,这产生的图像实际上是具有损坏颜色的相同图像的3列,因此当应用于4:2:2时,算法出现问题。

有人能看出这段代码有什么问题吗?

public static byte[] HalveYuv(byte[] data, int imageWidth, int imageHeight)
{
    byte[] yuv = new byte[imageWidth / 2 * imageHeight / 2 * 3 / 2];

    int i = 0;
    for (int y = 0; y < imageHeight; y += 2)
    {
        for (int x = 0; x < imageWidth; x += 2)
        {
            yuv[i] = data[y * imageWidth + x];
            i++;
        }
    }

    for (int y = 0; y < imageHeight / 2; y += 2)
    {
        for (int x = 0; x < imageWidth; x += 4)
        {
            yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + x];
            i++;
            yuv[i] = data[(imageWidth * imageHeight) + (y * imageWidth) + (x + 1)];
            i++;
        }
    }
    return yuv;
}

2 个答案:

答案 0 :(得分:1)

生成低质量缩略图的一种快速方法是丢弃每个维度中的一半数据。

我们在4x2像素网格中打破图像 - 网格中的每对像素由4个字节表示。在缩小的图像中,我们通过复制前4个字节来获取网格中前2个像素的颜色值,同时丢弃其他12个字节的数据。

这种缩放可以推广到2(1 / 2,1 / 4,1 / 8,...)的任何幂 - 这种方法很快,因为它没有使用任何插值。这样可以提供质量较低的图像,但看起来很块 - 为了更好的结果,可以考虑一些采样方法。

public static byte[] FastResize(
    byte[] data, 
    int imageWidth, 
    int imageHeight, 
    int scaleDownExponent)
{
    var scaleDownFactor = (uint)Math.Pow(2, scaleDownExponent);

    var outputImageWidth = imageWidth / scaleDownFactor;
    var outputImageHeight = imageHeight / scaleDownFactor;
    // 2 bytes per pixel.
    byte[] yuv = new byte[outputImageWidth * outputImageHeight * 2];

    var pos = 0;
    // Process every other line.
    for (uint pixelY = 0; pixelY < imageHeight; pixelY += scaleDownFactor)
    { 
        // Work in blocks of 2 pixels, we discard the second.
        for (uint pixelX = 0; pixelX < imageWidth; pixelX += 2*scaleDownFactor)
        {
            // Position of pixel bytes.
            var start = ((pixelY * imageWidth) + pixelX) * 2;

            yuv[pos] = data[start];
            yuv[pos + 1] = data[start + 1];
            yuv[pos + 2] = data[start + 2];
            yuv[pos + 3] = data[start + 3];

            pos += 4;
        }
    }

    return yuv;
}

答案 1 :(得分:1)

我假设原始数据的顺序如下(从您的示例代码看起来如此):首先是图像像素的亮度(Y)值(size = imageWidth*imageHeight字节)。之后,存在色度分量UV,s.t。,单个像素的值彼此相继给出。这意味着原始图片的总大小为3*size

现在4:2:2子采样意味着丢弃水平色度分量的每个其他值。这将数据减小到大小size + 0.5*size + 0.5*size = 2*size,即,完全保持亮度,并且两个色度分量被分成一半。因此,结果图像应分配为:

byte[] yuv = new byte[2*imageWidth*imageHeight];

当图像的第一部分被完整复制时,第一个循环变为:

int i = 0;
for (int y = 0; y < imageHeight; y++)
{
    for (int x = 0; x < imageWidth; x++)
    {
        yuv[i] = data[y * imageWidth + x];
        i++;
    }
}

因为这只是复制数据的开头,所以可以简化为

int size = imageHeight*imageWidth;
int i = 0;
for (; i < size; i++)
{
    yuv[i] = data[i];
}

现在要复制其余部分,我们需要跳过所有其他水平坐标

for (int y = 0; y < imageHeight; y++)
{
    for (int x = 0; x < imageWidth; x += 2) // +2 skip each other horizontal component
    {
        yuv[i] = data[size + y*2*imageWidth + 2*x]; 
        i++;
        yuv[i] = data[size + y*2*imageWidth + 2*x + 1];
        i++;
    }
}

需要data中的因子2 - 数组索引,因为每个像素(两个色度分量)都有2个字节,所以每个&#34;行&#34;有2*imageWidth个字节的数据。