将yuv 420转换为图像<Bgr,byte>

5
我有一个带有YUV420数据的字节数组。
byte[] yuv420;//yuv data

如何将这个转换为 Image<Bgr, byte>

我找到了一个将其转换为 RGB 再转换为 Image<Bgr, byte> 的数学公式,但速度非常慢。有没有更快的方法进行转换?

Emgu 中有一个用于转换的类。

COLOR_CONVERSION(enum CV_YUV2RGB    Convert YUV color to RGB)

但是我不知道如何使用这个类。有人可以帮忙吗?

static Bitmap ConvertYUV2RGB(byte[] yuvFrame, byte[] rgbFrame, int width, int height)
{
    int uIndex = width * height;
    int vIndex = uIndex + ((width * height) >> 2);
    int gIndex = width * height;
    int bIndex = gIndex * 2;

    int temp = 0;


    //图片为pic1,RGB颜色的二进制数据转换得的int r,g,b;
    Bitmap bm = new Bitmap(width, height);

    int r = 0;
    int g = 0;
    int b = 0;


    for (int y = 0; y < height; y++)
    {
        for (int x = 0; x < width; x++)
        {
            // R分量
            temp = (int)(yuvFrame[y * width + x] + (yuvFrame[vIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[0, 2]);
            rgbFrame[y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
            // G分量
            temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[1, 1] + (yuvFrame[vIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[1, 2]);
            rgbFrame[gIndex + y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
            // B分量
            temp = (int)(yuvFrame[y * width + x] + (yuvFrame[uIndex + (y / 2) * (width / 2) + x / 2] - 128) * YUV2RGB_CONVERT_MATRIX[2, 1]);
            rgbFrame[bIndex + y * width + x] = (byte)(temp < 0 ? 0 : (temp > 255 ? 255 : temp));
            Color c = Color.FromArgb(rgbFrame[y * width + x], rgbFrame[gIndex + y * width + x], rgbFrame[bIndex + y * width + x]);
            bm.SetPixel(x, y, c);
        }
    }
    return bm;

}

static double[,] YUV2RGB_CONVERT_MATRIX = new double[3, 3] { { 1, 0, 1.4022 }, { 1, -0.3456, -0.7145 }, { 1, 1.771, 0 } };
static byte clamp(float input)
{
    if (input < 0) input = 0;
    if (input > 255) input = 255;
    return (byte)Math.Abs(input);
}
4个回答

7

你很幸运,因为我之前就解决了这个问题。代码中有一些链接可以提供更多信息。

通常在进行图像处理时,尽量使用指针,并避免在嵌套循环中调用函数。在我的代码中,大小比较是最慢的部分,但不幸的是它是必需的(尝试使用预处理器开关关闭它)。

我必须说,最终我从未使用过这个函数,因为它太慢了,我选择用C++实现并使用P Invoke从C#中调用它。

private static unsafe void YUV2RGBManaged(byte[] YUVData, byte[] RGBData, int width, int height)
    {

        //returned pixel format is 2yuv - i.e. luminance, y, is represented for every pixel and the u and v are alternated
        //like this (where Cb = u , Cr = y)
        //Y0 Cb Y1 Cr Y2 Cb Y3 

        /*http://msdn.microsoft.com/en-us/library/ms893078.aspx
         * 
         * C = Y - 16
         D = U - 128
         E = V - 128
         R = clip(( 298 * C           + 409 * E + 128) >> 8)
         G = clip(( 298 * C - 100 * D - 208 * E + 128) >> 8)
         B = clip(( 298 * C + 516 * D           + 128) >> 8)

         * here are a whole bunch more formats for doing this...
         * https://dev59.com/cFHTa4cB1Zd3GeqPPCvN
         */


        fixed(byte* pRGBs = RGBData, pYUVs = YUVData)
        {
            for (int r = 0; r < height; r++)
            {
                byte* pRGB = pRGBs + r * width * 3;
                byte* pYUV = pYUVs + r * width * 2;

                //process two pixels at a time
                for (int c = 0; c < width; c += 2)
                {
                    int C1 = pYUV[1] - 16;
                    int C2 = pYUV[3] - 16;
                    int D = pYUV[2] - 128;
                    int E = pYUV[0] - 128;

                    int R1 = (298 * C1 + 409 * E + 128) >> 8;
                    int G1 = (298 * C1 - 100 * D - 208 * E + 128) >> 8;
                    int B1 = (298 * C1 + 516 * D + 128) >> 8;

                    int R2 = (298 * C2 + 409 * E + 128) >> 8;
                    int G2 = (298 * C2 - 100 * D - 208 * E + 128) >> 8;
                    int B2 = (298 * C2 + 516 * D + 128) >> 8;
#if true
                    //check for overflow
                    //unsurprisingly this takes the bulk of the time.
                    pRGB[0] = (byte)(R1 < 0 ? 0 : R1 > 255 ? 255 : R1);
                    pRGB[1] = (byte)(G1 < 0 ? 0 : G1 > 255 ? 255 : G1);
                    pRGB[2] = (byte)(B1 < 0 ? 0 : B1 > 255 ? 255 : B1);

                    pRGB[3] = (byte)(R2 < 0 ? 0 : R2 > 255 ? 255 : R2);
                    pRGB[4] = (byte)(G2 < 0 ? 0 : G2 > 255 ? 255 : G2);
                    pRGB[5] = (byte)(B2 < 0 ? 0 : B2 > 255 ? 255 : B2);
#else
                    pRGB[0] = (byte)(R1);
                    pRGB[1] = (byte)(G1);
                    pRGB[2] = (byte)(B1);

                    pRGB[3] = (byte)(R2);
                    pRGB[4] = (byte)(G2);
                    pRGB[5] = (byte)(B2);
#endif

                    pRGB += 6;
                    pYUV += 4;
                }
            }
        }
    }

如果您决定在C++中实现此功能

void YUV2RGB(void *yuvDataIn,void *rgbDataOut, int w, int h, int outNCh)
{

    const int ch2 = 2 * outNCh;

    unsigned char* pRGBs = (unsigned char*)rgbDataOut;
    unsigned char* pYUVs = (unsigned char*)yuvDataIn;

    for (int r = 0; r < h; r++)
    {
        unsigned char* pRGB = pRGBs + r * w * outNCh;
        unsigned char* pYUV = pYUVs + r * w * 2;

        //process two pixels at a time
        for (int c = 0; c < w; c += 2)
        {
            int C1 = pYUV[1] - 16;
            int C2 = pYUV[3] - 16;
            int D = pYUV[2] - 128;
            int E = pYUV[0] - 128;

            int R1 = (298 * C1 + 409 * E + 128) >> 8;
            int G1 = (298 * C1 - 100 * D - 208 * E + 128) >> 8;
            int B1 = (298 * C1 + 516 * D + 128) >> 8;

            int R2 = (298 * C2 + 409 * E + 128) >> 8;
            int G2 = (298 * C2 - 100 * D - 208 * E + 128) >> 8;
            int B2 = (298 * C2 + 516 * D + 128) >> 8;

            //unsurprisingly this takes the bulk of the time.
            pRGB[0] = (unsigned char)(R1 < 0 ? 0 : R1 > 255 ? 255 : R1);
            pRGB[1] = (unsigned char)(G1 < 0 ? 0 : G1 > 255 ? 255 : G1);
            pRGB[2] = (unsigned char)(B1 < 0 ? 0 : B1 > 255 ? 255 : B1);

            pRGB[3] = (unsigned char)(R2 < 0 ? 0 : R2 > 255 ? 255 : R2);
            pRGB[4] = (unsigned char)(G2 < 0 ? 0 : G2 > 255 ? 255 : G2);
            pRGB[5] = (unsigned char)(B2 < 0 ? 0 : B2 > 255 ? 255 : B2);

            pRGB += ch2;
            pYUV += 4;
        }
    }
}

非常感谢。还有其他的想法吗?因为这个函数不够快。 - user1541069
你试了哪个版本?C++版本大约快三倍,所以请使用它。如果你需要尽可能快的速度,C#不能满足你的需求。 - morishuz
如何将 byte[] RGBData 转换为位图? - user1541069
那是个分开的问题。你查过谷歌吗?关于这个问题,stackoverflow 上有很多答案。如果你解决不了它,提出另一个问题,我可以给你一些代码来解决它。 - morishuz
顺便提一下,如果您对原始问题(YUV转RGB转换)的答案感到满意,请将其标记为已回答,或者让我知道它为什么不能回答您的问题。 - morishuz

0

一种更快的模式。每个像素两次乘法和两次加法少:

private static unsafe void YUV2RGBManaged(byte[] YUVData, byte[] RGBData, int width, int height)
{
    //returned pixel format is 2yuv - i.e. luminance, y, is represented for every pixel and the u and v are alternated
    //like this (where Cb = u , Cr = y)
    //Y0 Cb Y1 Cr Y2 Cb Y3 

    /*http://msdn.microsoft.com/en-us/library/ms893078.aspx
     * 
     C = 298 * (Y - 16) + 128
     D = U - 128
     E = V - 128
     R = clip(( C           + 409 * E) >> 8)
     G = clip(( C - 100 * D - 208 * E) >> 8)
     B = clip(( C + 516 * D          ) >> 8)

     * here are a whole bunch more formats for doing this...
     * https://dev59.com/cFHTa4cB1Zd3GeqPPCvN
     */


    fixed(byte* pRGBs = RGBData, pYUVs = YUVData)
    {
        for (int r = 0; r < height; r++)
        {
            byte* pRGB = pRGBs + r * width * 3;
            byte* pYUV = pYUVs + r * width * 2;

            //process two pixels at a time
            for (int c = 0; c < width; c += 2)
            {
                int C1 = 298 * (pYUV[1] - 16) + 128;
                int C2 = 298 * (pYUV[3] - 16) + 128;
                int D = pYUV[2] - 128;
                int E = pYUV[0] - 128;

                int R1 = (C1 + 409 * E) >> 8;
                int G1 = (C1 - 100 * D - 208 * E) >> 8;
                int B1 = (C1 + 516 * D) >> 8;

                int R2 = (C2 + 409 * E) >> 8;
                int G2 = (C2 - 100 * D - 208 * E) >> 8;
                int B2 = (298 * C2 + 516 * D) >> 8;

                //check for overflow
                //unsurprisingly this takes the bulk of the time.
                pRGB[0] = (byte)(R1 < 0 ? 0 : R1 > 255 ? 255 : R1);
                pRGB[1] = (byte)(G1 < 0 ? 0 : G1 > 255 ? 255 : G1);
                pRGB[2] = (byte)(B1 < 0 ? 0 : B1 > 255 ? 255 : B1);

                pRGB[3] = (byte)(R2 < 0 ? 0 : R2 > 255 ? 255 : R2);
                pRGB[4] = (byte)(G2 < 0 ? 0 : G2 > 255 ? 255 : G2);
                pRGB[5] = (byte)(B2 < 0 ? 0 : B2 > 255 ? 255 : B2);

                pRGB += 6;
                pYUV += 4;
            }
        }
    }
}

0

我刚刚找到了一段旧代码,可能对你有所帮助。使用OpenCVSharp进行YUV转换。(免责声明:我删除了一些不必要的代码,并且还没有测试过!)

IplImage yuvImage = new IplImage(w, h, BitDepth.U8, 3);
IplImage rgbImage = new IplImage(w, h, BitDepth.U8, 3);

Cv.CvtColor(yuvImage, rgbImage, ColorConversion.CrCbToBgr);

回答你的另一个问题 - 将 byte[] 转换为 Bitmap,请使用以下代码

int w= 100;
int h = 200;
int ch = 3;

byte[] imageData    = new byte[w*h*ch]; //you image data here
Bitmap bitmap       = new Bitmap(w,h,PixelFormat.Format24bppRgb);
BitmapData bmData   = bitmap.LockBits(new System.Drawing.Rectangle(0, 0, bitmap.Width, bitmap.Height), ImageLockMode.ReadWrite, bitmap.PixelFormat);
IntPtr pNative      = bmData.Scan0;
Marshal.Copy(imageData,0,pNative,w*h*ch);
bitmap.UnlockBits(bmData);

当我使用旧的ConvertYUV2RGB方法进行转换时,转换效果很好但非常慢!!!而当我使用YUV2RGBManaged方法并像您的帖子中一样转换为bmp文件时,bmp文件是绿色的!!为什么??? - user1541069
我可以转换为:使用GCHandle handle = GCHandle.Alloc(fmain.ALLImage[channelNumber], GCHandleType.Pinned); 使用(Image<Gray, Byte> yuv420sp = new Image<Gray, byte>(704, (576 >> 1) * 3, 704, handle.AddrOfPinnedObject())) { Image<Bgr, Byte> image = new Image<Bgr, Byte>(704,576); CvInvoke.cvCvtColor(yuv420sp, image, Emgu.CV.CvEnum.COLOR_CONVERSION.CV_YUV420sp2BGR); //image现在包含了相同的yuv图像,但是是在bgr颜色空间中}handle.Free(); 但是bmp文件是黑白的,不是彩色的。如何解决? - user1541069
请将您的所有代码都发布出来,否则我无法帮助您。请通过更新您的问题来完成这一步骤,因为在评论中它不易读取! - morishuz

0

在这段代码中,最大的问题是使用了Bitmap.SetPixel;在每个内部循环迭代中执行此操作非常缓慢。相反,使用一个字节数组来存储RGB值,并在填充完成后将其复制到位图作为单个步骤

其次,要理解y、u和v是字节,因此只能有256个可能的值。因此,完全可以建立r、g和b的查找表,这样就不必在内部循环中执行任何计算。

最后,如果您真的想要性能,您将不得不使用指针算术在C++中编写此代码,并使用所有优化进行编译。由于每个迭代都处理独立数据,因此该循环也是并行for的非常好的候选项。还可以使用SSE内置函数进一步优化此过程,每个指令转换多个像素。

希望这能让您入门。


谢谢,但我无法相信没有dll可以使用dll将yuv420转换为rgb?使用emgu类COLOR_CONVERSION进行转换怎么样? - user1541069
可能有dll可以做到这一点。这是一个常见的问题。我现在想不起来为什么要写自己的实现,但可能是因为我找不到一个实现,或者因为我不想为了一个函数而链接OpenCV带来的复杂性。 - morishuz
我将在我的项目中使用emgu,最好的方式是转换为图像<bgr,byte>格式。我有16个相机,每秒25帧(704x576),回调函数返回yuv420帧。将其转换为rgb必须尽可能快速。 - user1541069
看一下FFmpeg中的swscale函数,我相信它可以实现这个功能。 - Asik

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