2016-02-03 21:47:37 jaych 阅读数 2195

最近在项目的过程中需要用到 YUV 的 Y通道数据,但是原始数据图像为RGB格式,所以自己写了一个RGB2YUV的程序,并且进行优化,对此总结如下。

RGB2YUV 原理

RGB及YUV是两种不同的颜色空间,具体可以换算关系如下:

这里写图片描述

根据该换算关系,我们直接可以得到Y通道数据。

程序1

void rgb2yuv2(unsigned char *R,unsigned char *G,unsigned char *B,unsigned char *Y,int len)
{
//这里应有对指针的有效性判断,此处略
    for(int i=len;i!=0;i--){
        *Y++ = *R*0.299 + *G*0.587 + *B*0.114;
        R++;B++;G++;
    }

}

优化思路

不过这里涉及大量的浮点数运算,使得程序运行十分缓慢,为了加速,我们可以采用查表法及定点运算进行优化。

1、查表法

因为R/G/B通道的系数都已经固定了,只要预先求出[0..255]这些数值乘以系数对应的数值,在读取到对应R通道数据时,可以通过查表形式获取。

2、定点运算

由于系数均为小数点3位数,我们可以 先乘以299再除以1000 达到 乘以0.299 的目的。
同时,为了简化乘法,采用移位操作,向右移位10bit,则表示除以1024,对应的,我们只要在生成 查表数值 的时候,乘以1024即可。

综合1、2,我们可以先将:

对于R通道,[0..255] 乘以 0.299,再乘以1024,得到 256个对应的数值。G/B通道也采取类似操作。
在计算Y通道时,取出R/G/B通道对应的数值,求和并右移10位,得到最终数据。

程序2

int array[256];
// 生成各个通道对应的数值,并保存至表格
void genArray(float factor){ // factor是对应通道的系数。
    int i=0;
    for(i=0;i<256;i++){
        array[i]=(int)(factor*i*1024+0.5); 
    }
}

// RGB转YUV
void rgb2yuv(unsigned char *R,unsigned char *G,unsigned char *B,unsigned char *Y,int len)
{
//这里应有对指针的有效性判断,此处略
         for(int i=len;i!=0;i--){
               *Y++ = (RT[*R++]+GT[*G++]+BT[*B++])>>10;
        }

}

经验证,实际得到的图像与程序1得到的图像类似,误差为±1

2018-11-28 16:33:12 Aidam_Bo 阅读数 965

最近在做图像处理,需要将试验得到的数据转化成RGB,再显示到界面

顺便学习了如下三个知识点:

  1. RGB数据生成BMP位图
  2. YUV 格式与 RGB 格式的相互转换公式总结(C++版)
  3. 将RGB数组在内存中压缩成JPEG文件

直接上Qt加载RGB图片数据代码:

 for(int i = 0;i<height;i++)
        {
            for(int j = 0;j<width;j++)
            {
                ImageRGB_m[i*3*width+3*j]  =ImageR_m[i*width+j];
                ImageRGB_m[i*3*width+3*j+1]=ImageG_m[i*width+j];
                ImageRGB_m[i*3*width+3*j+2]=ImageB_m[i*width+j];
            }
        }
        QImage *tempImage=new QImage(ImageRGB_m,width,height,QImage::Format_RGB888);
        ImageLable_m->setImagePix(tempImage);
        ImageLable_m->show();

        ui->image_LB_show->setPixmap(QPixmap::fromImage(*image));

 中间的ImageLable_m是我自己写的一个专门用于显示图片包含QLabel的一个类.这个是在5.5.1版本中可以先试采土代码。

网上发现很多人说RGB的彩图显示不了,最开始我的也是现实不了,后来发现是
 

QImage(ImageRGB_m,width,height,QImage::Format_RGB888);

这个是个重载函数,如果要限定每一行的数据大小,切记不要限定为width,这样也是导致显示不了的主要原因,这时候看tempImage的调试信息显示位invalid,将限定数据大小的去掉就可以正常显示彩图了。也就是不要写成QImage(ImageRGB_m,width,height,width ,QImage::Format_RGB888);,如果要防止图片扭曲可以写成下面这样就可以了
QImage *tempImage=new QImage(ImageRGB_m,width,height,3*width,QImage::Format_RGB888);


防扭曲操作:

同时限定每一行的数据大小可以保证图片显示不扭曲,这里之前每次生成的图片都是扭曲的,没行像素点都对不上,其实就是没有限定每一行数据的大小,当不限定每一行数据的大小的时候磨人的貌似是8的倍数的就刚好可以显示,否则就会发生错行,也就是扭曲的了。Qt里面有很多需要注意的细节的了,希望对遇到同样问题的人有帮助。
 

2018-08-07 18:44:30 mo4776 阅读数 2739

RGB数据的处理

基本概念

分辨率为640*480的图像,其像素点的个数为 widthxheight,即为640x480 = 307200

  1. 二值图像
    每个像素通过一位来存储即为二值图,取值只有0和1。
  2. 灰度图像
    在二值图像中加入许多介于黑色与白色之间的颜色深度,就构成了灰度图像,就典型的就是256色图,像素取值可以是0到255之间的整数值,那么每个像素占一个字节即8位,灰度图反映的是该图像的亮度信息,灰度级为0~255。占用内存的大小为widthxheight。
  3. RGB24图像
    每个像素占三个字节,对应于Red,Green,Blue三原色值,每个原色的取值是0到 255间的整数。这样的图也称为rgb24,24位真彩色。占用内存的大小为widthxheightx3

数字图像的表示

为了表述像素之间的相对和绝对位置,可以把图像看一个原点在左上角的二维坐标系。如下图
这里写图片描述

那么一副物理图像就被转化成了数字矩阵,成为了计算机能够处理的对象。
这里写图片描述

为M行N列的矩阵,对应的就是Height行width列的矩阵。

rgb图像在程序中的表示

  • 二维矩阵在C++中映射的就是二维数组。对分辨率为w*h的图像,可以在程序中定义
unsigned char imag[Height][width]

来表示。这里的imag是表示指向元素个数为width类型为unsigned char的数组的指针,unsigned char (*img)[width]。

  • 动态分配存放图像数据的二维数组的代码示例如下:
//指针的指针
unsigned char** pRGBData = NULL;

//元素个数为Height的unsigned char*数组
pRGBData = new unsigned char*[Height];
for (int i=0; i<Height; ++i)
{
    pRGBData[i] = new unsigned char[width];
}

内存结构示意:
这里写图片描述

rgb内存存储顺序

不同于字面的顺序,rgb数据实际的存储顺序为 BGR,BGR

bmp格式

windows有一种bmp的图像文件格式,它的图像数据就rgb数据,将rgb封装成bmp格式后可以很方便的进行预览。
bmp格式介绍

处理bmp数据涉及到如下两个问题:
1. 在图像处理中的坐标系的原点是在左上角,而bmp数据坐标的原点在左下角。这里涉及到坐标的转换。
2. bmp的数据是需要4字节对齐的,如果不为4字节的整数倍,则是会填充数据的,所以计算图像数据大小时就不能简单的通过width*height*像素字节数来计算,可以通过如下宏计算

// 在计算图像大小时,采用公式:biSizeImage = biWidth' × biHeight。
// 是biWidth',而不是biWidth,这里的biWidth'必须是4的整倍数,表示
// 大于或等于biWidth的,离4最近的整倍数。WIDTHBYTES就是用来计算
// biWidth'
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)
//这里计算的是rgb24的行说占的字节数
int nWidthBytes = WIDTHBYTES(iWidth*24);

示例程序

下面实现了一个rgb24处理类,包含如下功能:
1. 加载rgb24数据文件,这里的rgb数据文件并非指bmp文件,而指只有rgb数据的文件。
2. 获取/设置指定像素的值。
3. 获取指定像素的灰度值。
4. 将rgb24数据文件存成bmp格式文件。
5. 在rgb24图像上画线。

#include <stdio.h>
#include <string>
#include <Windows.h>
//#define RGB(r,g,b) ((unsigned long)(((unsigned char)(r)|((unsigned short)((unsigned char)(g))<<8))|(((unsigned long)(unsigned char)(b))<<16)))
#define WIDTHBYTES(bits) (((bits) + 31) / 32 * 4)

class CProcessRGB
{
public:
    CProcessRGB() :m_pRGBFile(NULL), m_iHeight(0),m_iWidth(0), m_pRGBData(NULL),m_pBmpFile(NULL)
    {

    }

    ~CProcessRGB()
    {
        if (NULL != m_pRGBFile)
        {
            fclose(m_pRGBFile);
            m_pRGBFile = NULL;
        }

        if (NULL != m_pRGBData)
        {
            for (int i = 0; i < m_iHeight; ++i)
            {
                if (NULL != m_pRGBData[i])
                {
                    delete[] m_pRGBData[i];
                    m_pRGBData[i] = NULL;
                }
            }
        }

        m_pRGBData = NULL;
    }

    int Init(int iWidth,int iHeight,const std::string& strRGBFile)
    {
        m_iWidth = iWidth;
        m_iHeight = iHeight;
        m_pRGBFile = fopen(strRGBFile.c_str(), "rb");
        m_pRGBData = new unsigned char*[iHeight];

        // 读取图像数据,WIDTHBYTES宏用于生成每行字节数
        int nWidthBytes = WIDTHBYTES(iWidth*24);

        for (int i = 0; i < iHeight; ++i)
        {
            m_pRGBData[i] = new unsigned char[nWidthBytes];
            fread(m_pRGBData[i], nWidthBytes, 1, m_pRGBFile);
        }

        return 0;
    }

    //获取指定像素点的颜色值
    unsigned long GetPixel(int x, int y)
    {
        unsigned long color = RGB(m_pRGBData[m_iHeight - y - 1][x * 3 + 2],
            m_pRGBData[m_iHeight - y - 1][x * 3 + 1],
            m_pRGBData[m_iHeight - y - 1][x * 3]);

        return color;
    }

    //设置指定像素的颜色值
    void SetPixel(int x, int y, unsigned long color,unsigned char** pData)
    {
        pData[m_iHeight - y - 1][x * 3] = color;
        pData[m_iHeight - y - 1][x * 3 + 1] = color >> 8;
        pData[m_iHeight - y - 1][x * 3 + 2] = color >> 16;
    }

    //计算指定像素点的灰度值
    unsigned char GetGray(int x, int y)
    {
        unsigned long ref = GetPixel(x, y);
        unsigned char r, g, b, byte;
        r = ref;
        g = ref >> 8;
        b = ref >> 16;

        if (r == g && r == b)
            return r;

        double dGray = (0.30*r + 0.59*g + 0.11*b);

        // 灰度化
        byte = (int)dGray;

        return byte;
    }

    void SaveToBmpFile(const std::string& strBmpFile)
    {
        if (NULL != m_pBmpFile)
        {
            fclose(m_pBmpFile);
            m_pBmpFile = NULL;
        }

        m_pBmpFile = fopen(strBmpFile.c_str(), "wb");

        BITMAPINFOHEADER InfoHeader = { 0 };
        BuildInfoHeader(m_iWidth, m_iHeight, 24, InfoHeader);

        BITMAPFILEHEADER bmfHeader = { 0 };
        BuildFileHeader(m_iWidth, m_iHeight, 24, bmfHeader);

        int nWidthBytes = WIDTHBYTES((m_iWidth)*24);
        int iSize = nWidthBytes*m_iHeight;

        fwrite(&bmfHeader, sizeof(BITMAPFILEHEADER), 1, m_pBmpFile);
        fwrite(&InfoHeader, sizeof(BITMAPINFOHEADER), 1, m_pBmpFile);

        //坐标转换
        for (int i = m_iHeight - 1; i>=0 ; --i)
        {
            fwrite(m_pRGBData[i], nWidthBytes, 1, m_pBmpFile);
        }
    }

    //画线 ptStart表示起始点,nLen表示线的长度,nWide表示线的宽度,bHor是横线还是竖线
    void Line(POINT ptStart, int nLen, int nWide, BOOL bHor)
    {
        int i, j;
        DWORD dw = RGB(0, 0, 255);
        if (bHor)
        {
            for (i = ptStart.x; i <= nLen + ptStart.x; i++)
            {
                for (j = 0; j<nWide; j++)
                {
                    SetPixel(i, ptStart.y + j, dw, m_pRGBData);
                }
            }
        }
        else
        {
            for (j = ptStart.y; j <= nLen + ptStart.y; j++)
            {
                for (i = 0; i<nWide; i++)
                {
                    SetPixel(ptStart.x + i, j, dw, m_pRGBData);
                }
            }
        }
    }


private:
    void BuildInfoHeader(LONG lWidth, LONG lHeight, WORD wBitCount, BITMAPINFOHEADER &bitmapInfoHeader)
    {
        LONG lWidthStep = (((lWidth * wBitCount) + 31) & (~31)) / 8;

        bitmapInfoHeader.biSize = sizeof(BITMAPINFOHEADER);
        bitmapInfoHeader.biWidth = lWidth;
        bitmapInfoHeader.biHeight = lHeight;
        bitmapInfoHeader.biPlanes = 0;
        bitmapInfoHeader.biBitCount = wBitCount;
        bitmapInfoHeader.biCompression = BI_RGB;
        bitmapInfoHeader.biSizeImage = lWidthStep * lHeight;
        bitmapInfoHeader.biXPelsPerMeter = 0;
        bitmapInfoHeader.biYPelsPerMeter = 0;
        bitmapInfoHeader.biClrUsed = 0;
        bitmapInfoHeader.biClrImportant = 0;
    }

    void BuildFileHeader(LONG lWidth, LONG lHeight, WORD wBitCount, BITMAPFILEHEADER &bitmapFileHeader)
    {
        LONG lWidthStep = (((lWidth * wBitCount) + 31) & (~31)) / 8;

        bitmapFileHeader.bfType = ((WORD)('M' << 8) | 'B');  //'BM'
        bitmapFileHeader.bfSize = (DWORD) sizeof(BITMAPFILEHEADER) + (DWORD) sizeof(BITMAPINFOHEADER) + lWidthStep * lHeight;
        bitmapFileHeader.bfReserved1 = 0;
        bitmapFileHeader.bfReserved2 = 0;
        bitmapFileHeader.bfOffBits = (DWORD) sizeof(BITMAPFILEHEADER) + (DWORD) sizeof(BITMAPINFOHEADER);
    }

private:
    //存储RGB内存数据的指针
    unsigned char** m_pRGBData;
    FILE *m_pRGBFile;
    FILE *m_pBmpFile;
    int m_iHeight;
    int m_iWidth;
};

int main()
{
    CProcessRGB RGBProcess;
    //读取rgb24图像
    RGBProcess.Init(1280,720,"./preview_rgb24");

    //画一条横线
    POINT p1;
    p1.x = 0;
    p1.y = 359;
    RGBProcess.Line(p1, 1279, 3, true);

    //画一条竖线
    POINT p2;
    p2.x = 639;
    p2.y = 0;
    RGBProcess.Line(p2, 719, 3, false);

    //将rgb24数据存成bmp文件
    RGBProcess.SaveToBmpFile("./preview.bmp");
}

运行结果:
这里写图片描述

rgb原始数据文件下载
https://download.csdn.net/download/mo4776/10589099

2017-12-15 15:18:48 houxiaoni01 阅读数 416

参考:https://www.cnblogs.com/daner1257/p/10767570.html

RGB、YUV像素数据处理

1.RGB

RGB色彩模式是工业界的一种颜色标准,是通过对红®、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是目前运用最广的颜色系统之一。
采用这种编码方法,每种颜色都可用三个变量来表示-红色绿色以及蓝色的强度。记录及显示彩色图像时,RGB是最常见的一种方案。但是,它缺乏与早期黑白显示系统的良好兼容性。因此,许多电子电器厂商普遍采用的做法是,将RGB转换成YUV颜色空间,以维持兼容,再根据需要换回RGB格式,以便在电脑显示器上显示彩色图形。

2.YUV

YUV是被欧洲电视系统所采用的一种颜色编码方法(属于PAL),是PAL和SECAM模拟彩色电视制式采用的颜色空间。在现代彩色电视系统中,通常采用三管彩色摄影机或彩色CCD摄影机进行取像,然后把取得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号B-Y(即U)、R-Y(即V),最后发送端将亮度和色差三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。采用YUV色彩空间的重要性是它的亮度信号Y和色度信号U、V是分离的。
  YUV格式有两大类:planar和packed。
  对于planar的YUV格式,先连续存储所有像素点的Y,紧接着存储所有像素点的U,随后是所有像素点的V。
  对于packed的YUV格式,每个像素点的Y,U,V是连续交*存储的。
  在这里插入图片描述
  与我们熟知的RGB类似,YUV也是一种颜色编码方法,主要用于电视系统以及模拟视频领域,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题。

(1)优点作用
  YUV主要用于优化彩色视频信号的传输,它将亮度信息(Y)与色彩信息(UV)分离,没有UV信息一样可以显示完整的图像,只不过是黑白的,这样的设计很好地解决了彩色电视机与黑白电视的兼容问题,使其向后相容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之间的差异。
  采用YUV色彩空间的重要性是它的亮度信号Y和色度信号U、V是分离的。如果只有Y信号分量而没有U、V分量,那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。
(2)存储方式
  YUV码流的存储格式其实与其采样的方式密切相关,主流的采样方式有三种,YUV4:4:4,YUV4:2:2,YUV4:2:0,关于其详细原理,可以通过网上其它文章了解,这里我想强调的是如何根据其采样格式来从码流中还原每个像素点的YUV值,因为只有正确地还原了每个像素点的YUV值,才能通过YUV与RGB的转换公式提取出每个像素点的RGB值,然后显示出来。

用三个图来直观地表示采集的方式吧,以黑点表示采样该像素点的Y分量,以空心圆圈表示采用该像素点的UV分量。

这里写图片描述

先记住下面这段话,以后提取每个像素的YUV分量会用到。
  a.YUV 4:4:4采样,每一个Y对应一组UV分量。
  b.YUV 4:2:2采样,每两个Y共用一组UV分量。
  c.YUV 4:2:0采样,每四个Y共用一组UV分量。
  下面我用图的形式给出常见的YUV码流的存储方式,并在存储方式后面附有取样每个像素点的YUV数据的方法。
  1)YUV 4:4:4
  YUV三个信道的抽样率相同,因此在生成的图像里,每个象素的三个分量信息完整(每个分量通常8比特),经过8比特量化之后,未经压缩的每个像素占用3个字节。
  下面的四个像素为: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
  存放的码流为: Y0 U0 V0 Y1 U1 V1 Y2 U2 V2 Y3 U3 V3
  2)YUV 4:2:2
  每个色差信道的抽样率是亮度信道的一半,所以水平方向的色度抽样率只是4:4:4的一半。对非压缩的8比特量化的图像来说,每个由两个水平方向相邻的像素组成的宏像素需要占用4字节内存。
  下面的四个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
  存放的码流为:Y0 U0 Y1 V1 Y2 U2 Y3 V3
  映射出像素点为:[Y0 U0 V1] [Y1 U0 V1] [Y2 U2 V3] [Y3 U2 V3]
  3)YUV 4:1:1
  4:1:1的色度抽样,是在水平方向上对色度进行4:1抽样。对于低端用户和消费类产品这仍然是可以接受的。对非压缩的8比特量化的视频来说,每个由4个水平方向相邻的像素组成的宏像素需要占用6字节内存。
  下面的四个像素为: [Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
  存放的码流为: Y0 U0 Y1 Y2 V2 Y3
  映射出像素点为:[Y0 U0 V2] [Y1 U0 V2] [Y2 U0 V2] [Y3 U0 V2]
  4)YUV4:2:0
  4:2:0并不意味着只有Y,Cb而没有Cr分量。它指得是对每行扫描线来说,只有一种色度分量以2:1的抽样率存储。相邻的扫描行存储不同的色度分量,也就是说,如果一行是4:2:0的话,下一行就是4:0:2,再下一行是4:2:0…以此类推。对每个色度分量来说,水平方向和竖直方向的抽样率都是2:1,所以可以说色度的抽样率是4:1。对非压缩的8比特量化的视频来说,每个由2x2个2行2列相邻的像素组成的宏像素需要占用6字节内存。
  下面八个像素为:[Y0 U0 V0] [Y1 U1 V1] [Y2 U2 V2] [Y3 U3 V3]
          [Y5 U5 V5] [Y6 U6 V6] [Y7U7 V7] [Y8 U8 V8]
  存放的码流为:Y0 U0 Y1 Y2 U2 Y3
         Y5 V5 Y6 Y7 V7 Y8
  映射出的像素点为:[Y0 U0 V5] [Y1 U0 V5] [Y2 U2 V7] [Y3 U2 V7]
           [Y5 U0 V5] [Y6 U0 V5] [Y7U2 V7] [Y8 U2 V7]

3.常见存储格式

YU12/I420

该格式属于4:2:0类型,存储方式上面已经说过,就是先存储把全部的Y分量存完,再存U分量,最后存V分量,从网上找了一张很形象的图:
在这里插入图片描述
可以看到,第一行的Y1Y2和第二行的Y7Y8共同使用一组UV分量U1V1。

YV12

该格式与YU12基本一样,唯一的区别是先存储V分量再存储U分量,对应到上图把第五行和第六行位置互换一下就是了。
以上两种格式我们可以看到都是4:2:0的,因为都是planar方式存储,简称420p。
除了上面两种,还有两种4:2:0,NV12和NV21,这两种是比较特殊的存储格式,是planar和packed混合存储的,分别看下

NV12

该格式是先存储全部的Y分量,然后UV分量交叉存储,用图像表示下:
在这里插入图片描述

NV21

该格式与NV12的区别和上面YU12/YV12一样,唯一的区别只是UV分量交叉的顺序不同,NV12是U排前面,NV21是V排前面,用图像表示如下:
在这里插入图片描述
上面两种虽然也是4:2:0类型,但是并不是完全的planar格式,所以又称为420sp,与420p进行区分。
上面说的都是4:2:0类型的,下面说几个4:2:2类型较常见的

YUV422P

名字中带P表示是planar格式存储,该格式存储方式与I420是一样的,唯一的区别是UV分量的数量不同,I420中四个Y共用一组UV,而该格式中两个Y共用一组UV,也就是说UV分量相对于I420在数量上多了一倍,从网上找了一张图,如下:
在这里插入图片描述
如上图,在渲染时Y00与Y01会共用U00和V00。

YUYV/YUY2

该格式属于4:2:2类型,且是用packed形式存储的,上面也简单的说过,存储方式如下图:
在这里插入图片描述
可以看到,每两个Y分量共用一组UV分量,存储顺序是YUYV。

YVYU

该格式与YUYV相似,只是存储时UV分量顺序不同而已,为YVYU。

UYVY

该格式也是4:2:2类型,与上面两种方式并无大的不同,从网上找了一张图如下:
在这里插入图片描述
可以看到存储时YUV分量的顺序如名字所示:UYVY。

补充知识:

① YUV422P也属于YUV422的一种,它是一种Plane模式,即平面模式,并不是将YUV数据交错存储,而是先存放所有的Y分量,然后存储所有的U(Cb)分量,最后存储所有的V(Cr)分量。其每一个像素点的YUV值提取方法也是遵循YUV422格式的最基本提取方法,即两个Y共用一个UV。比如,对于像素点Y0、Y1 而言,其U、V的值均为 U0、V1。
  ② 如果视频帧的宽和高分别为w和h,那么一帧YUV420P像素数据一共占用w*h*3/2 Byte的数据。其中前w*h Byte存储Y,接着的w*h*1/4 Byte存储U,最后w*h*1/4 Byte存储V。
  ③ 如果想把YUV格式像素数据变成灰度图像,只需要将U、V分量设置成128即可。这是因为U、V是图像中的经过偏置处理的色度分量。色度分量在偏置处理前的取值范围是-128至127,这时候 的无色对应的是“0”值。经过偏置后色度分量取值变成了0至255,因而此时的无色对应的就是128了。
  ④ 如果打算将图像的亮度减半,只要将图像的每个像素的Y值取出来分别进行除以2的工作就可以了。图像的每个Y值占用1 Byte,取值范围是0至255,对应C语言中的unsigned char数据类型。
  ⑤ 与YUV420P三个分量分开存储不同,RGB24格式的每个像素的三个分量是连续存储的。一帧宽高分别为w、h的RGB24图像一共占用wh3 Byte的存储空间。RGB24格式规定首先存储第一个像素的R、G、B,然后存储第二个像素的R、G、B…以此类推。类似于YUV420P的存储方式称为Planar方式,而类似于RGB24的存储方式称为Packed方式。

2014-12-24 15:48:44 beijingmake209 阅读数 9006

YUV2RGB格式转换

       最近在arm板子上做一个项目,arm板上采集的图像是YUV格式的,在后续的图像处理中需要用到RGB格式的图像。在网上查了很多YUV转RGB的资料,由于每个人得到的YUV的数据排列格式不一样,所以要找到适合自己工程的代码很不容易。其中有两篇博客里的资料对我有很大启发,这里向大家介绍一下。网址如下:

1. http://blog.csdn.net/dreamd1987/article/details/7259479#

2. http://blog.csdn.net/Lu597203933/article/details/23791177


网上普遍有两种方法,1. 通过opencv自带的cvCvtColor函数进行转换。2. 公式法。

1. opencv自带cvCvtColor

测试数据可以发文件问我要(我也是下载别人的测试数据)

测试文件名:football_cif.yuv, FOOTBALL_352x288_30_orig_01.yuv

图像大小:352*288

这两个文件的数据存储格式为:YUV,即先存储所有的Y,再存储所有的U,最后存储所有的V(非交叉存储)

具体代码如下:

#include <iostream>
#include <highgui.h>
#include <cv.h>
#include <fstream>
#include <sstream>
using namespace std;
#define FCount 10
#define ISizeX 352	//图像宽度
#define ISizeY 288	//图像高度

unsigned char Y[FCount][ISizeY][ISizeX];   
unsigned char U[FCount][ISizeY/2][ISizeX/2];	
unsigned char V[FCount][ISizeY/2][ISizeX/2];

// 将图片文件写入
void FileWriteFrames()
{
	char *filename = "football_cif.yuv";
	ifstream readMe(filename, ios::in | ios::binary);  // 打开并读yuv数据
	IplImage *image, *rgbimg, *yimg, *uimg, *vimg, *uuimg, *vvimg;
	cvNamedWindow("yuv",CV_WINDOW_AUTOSIZE);
	rgbimg = cvCreateImage(cvSize(ISizeX, ISizeY), IPL_DEPTH_8U, 3);
	image = cvCreateImage(cvSize(ISizeX, ISizeY), IPL_DEPTH_8U, 3);
    
	yimg = cvCreateImageHeader(cvSize(ISizeX, ISizeY), IPL_DEPTH_8U, 1);    // 亮度分量
	uimg = cvCreateImageHeader(cvSize(ISizeX/2, ISizeY/2), IPL_DEPTH_8U, 1);  // 这两个都是色度分量
	vimg = cvCreateImageHeader(cvSize(ISizeX/2, ISizeY/2), IPL_DEPTH_8U, 1);
    
	uuimg = cvCreateImage(cvSize(ISizeX, ISizeY), IPL_DEPTH_8U, 1);
	vvimg = cvCreateImage(cvSize(ISizeX, ISizeY), IPL_DEPTH_8U, 1);
	int nframes;
	for(nframes = 0; nframes < FCount; nframes ++)
	{
		char nframesstr[20];

		readMe.read((char*)Y[nframes],ISizeX*ISizeY);	//读取Y分量
		readMe.read((char*)U[nframes],ISizeX/2*ISizeY/2);//读取U分量
		readMe.read((char*)V[nframes],ISizeX/2*ISizeY/2);//读取V分量

		cvSetData(yimg,Y[nframes],ISizeX);
		cvSetData(uimg,U[nframes], ISizeX/2);
		cvSetData(vimg,V[nframes], ISizeX/2);
		
		
		cvResize(uimg,uuimg, CV_INTER_LINEAR);
		cvResize(vimg,vvimg, CV_INTER_LINEAR);
		cvMerge(yimg,uuimg,vvimg,NULL,image);   // 合并单通道为三通道
		cvCvtColor(image,rgbimg,CV_YCrCb2RGB);  //	YUV转换为RGB
		
		stringstream ss;  // 类型转换统一转换为char* 类型
		ss << nframes;
		ss << ".jpg" ;
		ss >> nframesstr;
		cvShowImage("yuv", rgbimg);
		cvSaveImage(nframesstr,rgbimg);
		int c = cvWaitKey(300);
		if((char)c == 27)
		{
			break;
		}
	}
	readMe.close();
	cvReleaseImage(&uuimg);
    <span style="white-space:pre">	</span>cvReleaseImage(&vvimg);
	cvReleaseImageHeader(&yimg);
	cvReleaseImageHeader(&uimg);
	cvReleaseImageHeader(&vimg);
	cvReleaseImage(&image);
	cvDestroyWindow("yuv");	
}


int main()
{
	FileWriteFrames();
	return 0;
}


2. 公式法

测试数据:test1.bmp, test2.bmp(如有需要可以给我留言,留下你的邮箱)

数据存储格式:UYVY(交叉存储)

图像大小:1024*480

测试代码如下:

#include <iostream>
#include <highgui.h>
#include <cv.h>
#include <fstream>
#include <sstream>

using namespace std;

#define width 1024 //图像宽度
#define height 480	//图像高度

unsigned char *yuvtorgb24(char *bufyuv, int w, int h)
{
	int r1,g1,b1,r2,g2,b2;
	int y1,u,v,y2;
	unsigned char *ps=(unsigned char *)bufyuv;
	unsigned char *pd;
	int len;
	
	if ((pd = (unsigned char *)malloc (w * h * 3)) == NULL)
		return NULL;
	
	len=w*h;
	while(len>0)
	{
		len-=2;
		u=*ps++;	
		y1 =*ps++;
		v=*ps++;	
		y2 =*ps++;	
		r1=(10000*y1+14075*(v-128))/10000;
		g1=(10000*y1-3455*(u-128)-7169*(v-128))/10000;
		b1=(10000*y1+17990*(u-128))/10000;
		r2=(10000*y2+14075*(v-128))/10000;
		g2=(10000*y2-3455*(u-128)-7169*(v-128))/10000;
		b2=(10000*y2+17990*(u-128))/10000;
		
		if(r1>255)r1=255;if(r1<0)r1=0;
		if(g1>255)g1=255;if(g1<0)g1=0;
		if(b1>255)b1=255;if(b1<0)b1=0;
		if(r2>255)r2=255;if(r2<0)r2=0;
		if(g2>255)g2=255;if(g2<0)g2=0;
		if(b2>255)b2=255;if(b2<0)b2=0;
		
		*pd++=b1;*pd++=g1;*pd++=r1;
		*pd++=b2;*pd++=g2;*pd++=r2;
	}


	return pd-(w*h*3);
}


int main()
{
	
	int i, j;
	unsigned char *p;
	char recvBuf[width*height*2];
	IplImage *srcImg;
	char *filename = "test2.bmp";

	srcImg = cvCreateImage(cvSize(width, height), 8, 3);
	cvZero(srcImg);

	ifstream fin(filename, ios::in | ios::binary);  // 打开并读yuv数据  
	fin.read(recvBuf, width*height*2*sizeof(char)); 
/*	查看数据
	for(i=0; i<500; i++)
	{
		unsigned char tmp = (unsigned char)recvBuf[i];
		cout<< (unsigned short)tmp<<endl;
	}
*/
	//	yuv格式转换为rgb格式
	p = yuvtorgb24(recvBuf, 1024, 480);
	//	图像颜色赋值
	for(i=0; i<height; i++)
	{
		for(j=0; j<width; j++)
		{
			((uchar *)(srcImg->imageData + i*srcImg->widthStep))[j*srcImg->nChannels + 0] = p[(i*width+j)*3 + 0];
			((uchar *)(srcImg->imageData + i*srcImg->widthStep))[j*srcImg->nChannels + 1] = p[(i*width+j)*3 + 1];
			((uchar *)(srcImg->imageData + i*srcImg->widthStep))[j*srcImg->nChannels + 2] = p[(i*width+j)*3 + 2];
		}
	}
	free(p);
	cvSaveImage("mytest2.bmp", srcImg);
	cvNamedWindow("YUV2RGB", 1);
	cvShowImage("YUV2RGB", srcImg);
	cvWaitKey(0);
	cvReleaseImage(&srcImg);
	return 0;
}



没有更多推荐了,返回首页