写在前面

首先介绍一下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:

                                              \begin{array}{l}{R=Y+1.403 \times(V-128)} \\ {G=Y-0.343 \times(U-128)-0.714 \times(V-128)} \\ {B=Y+1.770 \times(U-128)}\end{array}

2、RGB2YUV:

                                          \begin{aligned} Y &=0.299 R+0.587 G+0.114 B \\ C_{r}=V &=0.500 R-0.419 G-0.081 B+128 \\ C_{b}=U &=-0.169 R-0.331 G+0.500 B+128 \end{aligned}

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

 

以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