首先介绍一下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
图像中,不管如何变换,我们最直观的值域是[0,255],所以我们也希望能有一个转换公式将值域统一在[0,255],这样就避免了后期处理和不必要的错误。
值域为[0,255]常用的转换公式如下。
1、YUV2RGB:
2、RGB2YUV:
这个公式转换后,不必在关心值域问题,很方便。
浮点型运算比较耗时,我们希望用整数计算代替浮点型,我们首先去掉浮点型运算,等式两边乘以256:
化简上述公式:
这里要说明一下:我们这里的转换是有损的,适用于追求速度,而对效果要求不是100%准确的情况。
然后,我们就可以对上述公式进一步优化,彻底去掉小数:
实际上就是四舍五入,为什么要乘以256,这是实际上是为了缩小误差,当然你这个地方乘数越大,误差越小。
干掉所有乘法,用移位运算表示:
上述公式,我们可以用移位进行简单优化:
做到此处,已经没有了浮点运算量了,但是我们发现虽然采用了移位运算,但是,公式中还有很多乘法运算,乘法跟移位运算相比,还是效率太低了,因此,我们将把所有乘法都改成移位运算:
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
#include
#include
#include
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(i, j);
int B = rgb[0]; int G = rgb[1]; int R = rgb[2];
if (accelerate == false){
dst.at(i, j)[0] = R*0.299 + G*0.587 + B*0.114; //Y
dst.at(i, j)[1] = -0.169*R - 0.331*G + 0.500*B + 128; //U
dst.at(i, j)[2] = 0.500*R - 0.419*G - 0.081*B + 128; //V
}
else{
dst.at(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(i, j)[1] = (-((R << 5) + (R << 3) + (R << 1)+ R) - ((G << 6) + (G << 4) + (G << 2)+G) + (B << 7) + 32768) >> 8; //U
dst.at(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(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(i, j)[0] = Y + 1.770*U;//B
dst.at(i, j)[1] = Y - 0.343*U - 0.714*V;//G
dst.at(i, j)[2] = Y + 1.403*V;//R
}
else{
dst.at(i, j)[0] = Y + U + ((U * 198) >> 8);
dst.at(i, j)[1] = Y -((U * 88) >> 8) - ((V * 183)>>8);
dst.at(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