色彩转换系列之RGB格式与YUV格式互转原理及实现

写在前面
首先介绍一下YUV颜色空间,YUV(亦称YCrCb)是被欧洲电视系统所采用的一种颜色编码方法。在现代彩色电视系统中,通常采用三管彩色摄像机或彩色CCD摄影机进行取像,然后把取得的彩色图像信号经分色、分别放大校正后得到RGB,再经过矩阵变换电路得到亮度信号Y和两个色差信号R-Y(即U)、B-Y(即V),最后发送端将亮度和两个色差总共三个信号分别进行编码,用同一信道发送出去。这种色彩的表示方法就是所谓的YUV色彩空间表示。采用YUV色彩空间的重要性是它的亮度信号Y和色度信号U、V是分离的。如果只有Y信号分量而没有U、V信号分量,那么这样表示的图像就是黑白灰度图像。彩色电视采用YUV空间正是为了用亮度信号Y解决彩色电视机与黑白电视机的兼容问题,使黑白电视机也能接收彩色电视信号。
\quadYUV主要用于优化彩色视频信号的传输,使其向后相容老式黑白电视。与RGB视频信号传输相比,它最大的优点在于只需占用极少的频宽(RGB要求三个独立的视频信号同时传输)。其中“Y”表示明亮度(Luminance或Luma),也就是灰阶值;而“U”和“V” 表示的则是色度(Chrominance或Chroma),作用是描述影像色彩及饱和度,用于指定像素的颜色。“亮度”是透过RGB输入信号来建立的,方法是将RGB信号的特定部分叠加到一起。“色度”则定义了颜色的两个方面─色调与饱和度,分别用Cr和Cb来表示。其中,Cr反映了RGB输入信号红色部分与RGB信号亮度值之间的差异。而Cb反映的是RGB输入信号蓝色部分与RGB信号亮度值之同的差异。

网上关于 YUV 与 RGB 格式变换的文章挺多的,但是网上那些文章中给出了各种各样的变换公式,公式的系数又各不相同,让人看了之后越看越糊涂,自己实现往往得不到正确的结果。其实那些文章的公式基本都是对的,只不过因为作者忘记给出变换公式的定义域与值域,所以给读者使用造成了很大的麻烦。

有人梳理了一下各种博文中的各种公式:https://blog.csdn.net/liyuanbhu/article/details/68951683

 
RGB与YUV互转
图像中,不管如何变换,我们最直观的值域是[0,255],所以我们也希望能有一个转换公式将值域统一在[0,255],这样就避免了后期处理和不必要的错误。

值域为[0,255]常用的转换公式如下。

1、YUV2RGB:

                                              

2、RGB2YUV:

                                          

这个公式转换后,不必在关心值域问题,很方便。

 
以RGB2YUV为例---优化1
浮点型运算比较耗时,我们希望用整数计算代替浮点型,我们首先去掉浮点型运算,等式两边乘以256:

    Y * 256=0.299 * 256 R+0.587 * 256 G+0.114 * 256 B

                                   U * 256=-0.169 * 256 R-0.331 * 256 G+0.500 * 256 B+128*256

                                   V * 256=0.500 * 256 R-0.419 * 256 G-0.081 * 256 B+128*256

                                   

 化简上述公式:

 

                                 256Y = 76.544R + 150.272G + 29.184B

                                   256U=-43.264R-84.736 G+128.013 B+32768

                                   256V =128.0 R-107.26 G-20.736 B+32768

 这里要说明一下:我们这里的转换是有损的,适用于追求速度,而对效果要求不是100%准确的情况。

然后,我们就可以对上述公式进一步优化,彻底去掉小数:

                                  256Y = 77R + 150G + 29B

                                   256U=-43R-85 G+128B+32768

                                   256V =128 R-107G-21 B+32768

实际上就是四舍五入,为什么要乘以256,这是实际上是为了缩小误差,当然你这个地方乘数越大,误差越小。

 以RGB2YUV为例---优化2
 干掉所有乘法,用移位运算表示:

上述公式,我们可以用移位进行简单优化:

                                  Y = (77R + 150G + 29B)>>8

                                   U=(-43R-85 G+128B+32768)>>8

                                   V =(128 R-107G-21 B+32768)>>8

 做到此处,已经没有了浮点运算量了,但是我们发现虽然采用了移位运算,但是,公式中还有很多乘法运算,乘法跟移位运算相比,还是效率太低了,因此,我们将把所有乘法都改成移位运算:

 Y= ((R << 6) + (R << 3) + (R << 2) + R + (G << 7) + (G << 4) + (G << 2) + (G << 1) + (B << 4) + (B << 3) + (B << 2) + B) >> 8
 U= (-((R << 5) + (R << 3) + (R << 1)+ R) - ((G << 6) + (G << 4) + (G << 2)+G) + (B << 7) + 32768) >> 8
 V= ((R << 7) - ((G << 6) + (G << 5) + (G << 3) + (G << 3) + G) - ((B << 4) + (B << 2) + B) + 32768 )>> 8

至此,RGB2YUV的转换公式就优化完毕了,这个优化,在移动端,速度会有很大的提高,至于一些测试数据,我就不列举了,只给个效果图吧,大家可以直接试一下就知道了。YUV2RGB的优化过程也是一样的。

实现
#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/highgui.hpp>
#include <opencv2/imgproc.hpp>
using namespace cv;
 
cv::Mat RGB2YUV(cv::Mat src, bool accelerate = false){
 
    CV_Assert(src.channels() == 3);
    cv::Mat dst(src.size(), CV_32FC3);  //这里最好要用浮点型,避免丧失精度
    cv::Vec3b rgb;
    int r = src.rows;
    int c = src.cols;
 
    for (int i = 0; i < r; ++i){
        for (int j = 0; j < c; ++j){
            rgb = src.at<cv::Vec3b>(i, j);
            int B = rgb[0]; int G = rgb[1]; int R = rgb[2];
            if (accelerate == false){
                dst.at<Vec3f>(i, j)[0] = R*0.299 + G*0.587 + B*0.114; //Y
                dst.at<Vec3f>(i, j)[1] = -0.169*R - 0.331*G + 0.500*B + 128; //U
                dst.at<Vec3f>(i, j)[2] = 0.500*R - 0.419*G - 0.081*B + 128;  //V
            }
            else{
                dst.at<Vec3f>(i, j)[0] = ((R << 6) + (R << 3) + (R << 2) + R + (G << 7) + (G << 4) + (G << 2) + (G << 1) + (B << 4) + (B << 3) + (B << 2) + B) >> 8; //Y
                dst.at<Vec3f>(i, j)[1] = (-((R << 5) + (R << 3) + (R << 1)+ R) - ((G << 6) + (G << 4) + (G << 2)+G) + (B << 7) + 32768) >> 8; //U
                dst.at<Vec3f>(i, j)[2] = ((R << 7) - ((G << 6) + (G << 5) + (G << 3) + (G << 3) + G) - ((B << 4) + (B << 2) + B) + 32768 )>> 8; //V
            }
        }
    }
    dst.convertTo(dst, CV_8UC3);
    return dst;
}
 
cv::Mat YUV2RGB(cv::Mat src, bool accelerate = false){
    CV_Assert(src.channels() == 3);
    cv::Mat dst(src.size(), CV_32FC3); //这里一定要用浮点型,避免丧失精度
    cv::Vec3b yuv;
    int r = src.rows;
    int c = src.cols;
    for (int i = 0; i < r; ++i){
        for (int j = 0; j < c; ++j){
            yuv = src.at<cv::Vec3b>(i, j);
            int Y = yuv[0]; int U = yuv[1]; int V = yuv[2];
            U = U - 128;
            V = V - 128;
            if (accelerate == false){
                dst.at<Vec3f>(i, j)[0] = Y + 1.770*U;//B
                dst.at<Vec3f>(i, j)[1] = Y - 0.343*U - 0.714*V;//G
                dst.at<Vec3f>(i, j)[2] = Y + 1.403*V;//R
            }
            else{
                dst.at<Vec3f>(i, j)[0] = Y + U + ((U * 198) >> 8);
                dst.at<Vec3f>(i, j)[1] = Y -((U * 88) >> 8) - ((V * 183)>>8);
                dst.at<Vec3f>(i, j)[2] = Y + V + ( (V * 103) >> 8);
            }
        }
    }
    dst.convertTo(dst, CV_8UC3);
    return dst;
}
 
 
int main(){
    cv::Mat src = cv::imread("I:\\Learning-and-Practice\\2019Change\\Image process algorithm\\Img\\4.JPG");
 
    if (src.empty()){
        return -1;
    }
    cv::Mat dst, dst1, dst2;
 
    opencv自带/
    double t2 = (double)cv::getTickCount(); //测时间
 
    cv::cvtColor(src, dst1, CV_RGB2YUV); //RGB2YUV
 
    t2 = (double)cv::getTickCount() - t2;
    double time2 = (t2 *1000.) / ((double)cv::getTickFrequency());
    std::cout << "Opencv_RGB2YUV=" << time2 << " ms. " << std::endl << std::endl;
 
    //RGB2YUV//
    double t1 = (double)cv::getTickCount(); //测时间
 
    dst = RGB2YUV(src,true); //RGB2YUV
    dst2 = YUV2RGB(dst,true); //YUV2BGR
 
    t1 = (double)cv::getTickCount() - t1;
    double time1 = (t1 *1000.) / ((double)cv::getTickFrequency());
    std::cout << "My_RGB2YUV=" << time1 << " ms. " << std::endl << std::endl;
 
 
    cv::namedWindow("src", CV_WINDOW_NORMAL);
    imshow("src", src);
    cv::namedWindow("My_RGB2YUV", CV_WINDOW_NORMAL);
    imshow("My_RGB2YUV", dst);
    cv::namedWindow("My_YUV2RGB", CV_WINDOW_NORMAL);
    imshow("My_YUV2RGB", dst2);
    cv::namedWindow("Opencv_RGB2YUV", CV_WINDOW_NORMAL);
    imshow("Opencv_RGB2YUV", dst1);
    cv::waitKey(0);
    return 0;
 
}

效果

参考:

https://blog.csdn.net/liyuanbhu/article/details/68951683

https://blog.csdn.net/Trent1985/article/details/52053397

 https://blog.csdn.net/just_sort/article/details/87102898
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
                        
原文链接:https://blog.csdn.net/weixin_40647819/article/details/92619298

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值