YUV420与YUV444互转,YUV420与YUV444读取和保存,YUV的显示和播放功能

YUV420与YUV444互转,YUV420与YUV444读取和保存,YUV的显示和播放功能


尊重原创,转载请注明出处】:https://blog.csdn.net/guyuealian/article/details/82454945

    OpenCV提供了RGB与YUV420/YUV444互转的接口:cvtColor(),但根尴尬OpenCV就是没有提供YUV444与YUV420互转的接口,如果你想用OpenCV转换,那就只能使用间接的方法了,将YUV420转为BGR(CV_YUV2BGR_I420),然后再将BGR转为YUV444(CV_BGR2YUV),这很蛋疼!效率太低了。

    实质上,只要我们理解了YUV420与YUV444的数据格式,完全可以自己转换的,下面使用C++实现YUV420与YUV444读取和保存,以及UV420与YUV444互转,并实现YUV的显示和播放功能。

    YUV444和YUV420的测试文件,可以这里下载https://download.csdn.net/download/guyuealian/10697442


目录

C++读写YUV444和YUV420,现实YUV444与YUV420互转,支持OpenCV显示

一、YUV数据格式

1、YUV分三种采样方式:

2、YUV的存储格式:

二、YUV文件的读取和保存

1、读取YUV文件

2、保存YUV文件

三、C++实现YUV420与YUV444互转

1、YUV420转YUV444

2、YUV444转YUV420

四、显示和播放YUV文件

1、显示YUV图像

2、播放YUV数据 

五、完整的代码

参考资料:


一、YUV数据格式

     YUV有三个分量:Y(Luminance/Luma:亮度)、U和V表示色差,体现的是图片的色彩信息。相对于RGB彩色空间,将亮度信息和色彩信息分离。这种编码模式也更加适应于人眼,据研究表明,人眼对亮度信息比色彩信息更加敏感。而YUV下采样就是根据人眼的特点,将人眼相对不敏感的色彩信息进行压缩采样,得到相对小的文件进行播放和传输。与YUV相像YCbCr其实与其有少许不同,体现在参数的大小上,本质上都是将亮度信息与色彩信息相分开。


1、YUV分三种采样方式:

    YUV444:对于每一个像素都对应一个Y分量、一个U分量、一个V分量。

    YUV422:对于一个像素都对应一个Y分量,但是每两个像素(或者说Y分量)对应一个U分量和一个V分量。

    YUV420:对于一个像素都对应一个Y分量,但是每四个像素(或者说Y分量)对应一个U分量和一个V分量。

2、YUV的存储格式:

    YUYUV在存储时是以数组的形式存储的,可以看做连续的三个一维数组。三个数组分别单独存储Y、U、V分量。以一幅100×100的YUV444图像为例,下图表示的YUV444的存储格式YUV420的存储格式

   

  从总数据量来看,YUV444需要存储100×100×3个值,YUV422需要存储100×100×2个值,YUV420需要存储100×100×3/2个值。


二、YUV文件的读取和保存

1、读取YUV文件

    YUV文件实质上是保存了图像的YUV格式的原始数据,并没有像png、jpg等图像格式对图像的数据进行压缩。这也就是为什么,YUV格式的文件普遍大比较大了。既然 YUV文件是未经压缩的原始数据,那读取和保存YUV文件就十分简单了。基本方法是只需要调用C++的fopen()函数打开YUV文件获得指向该流的文件指针,然后使用fread()从文件流中直接读取YUV的数据即可。

    下面是实现读取YUV444和YUV420的完整代码:

	/*********************************************************	
	@brief         : 读取yuv图像文件,若输入的是多帧的图像文件,如yuv视频文件,则返回最后一帧的数据
	@param fileYUV : YUV文件
	@param w       : 宽width
	@param h       : 高height
	@param colorType: 颜色空间类型:CS_YUV444,CS_YUV420
	@return        : unsigned char* 返回YUV数据
	********************************************************/	
    unsigned char* read_yuvImage(string fileYUV, int w, int h, ColorSpace colorType) {
		FILE * pFileIn;      //输入CS_YUV444文件
		if (NULL == (pFileIn = fopen(fileYUV.c_str(), "rb"))) {
			printf("File input is can't open!\n");
			fclose(pFileIn);
			return nullptr;
		}
		int bufLen = 0;
		if (colorType == CS_YUV444) {//定义保存CS_YUV444的数组
			bufLen = w * h * 3;
		}
		else if (colorType == CS_YUV420) {//定义保存YUV420的数组
			bufLen = w*h * 3 / 2;
		}
		unsigned char* yuvBuf = new unsigned char[bufLen];
		//fread(yuvBuf, bufLen * sizeof(unsigned char), 1, pFileIn);//返回第一帧
		while (fread(yuvBuf, bufLen * sizeof(unsigned char), 1, pFileIn))//返回最后一帧
		{
		}
		fclose(pFileIn);
		return yuvBuf;
	}

    注意这里定义一个枚举类型ColorSpace

	enum ColorSpace {
		CS_YUV444,
		CS_YUV420
	};

说明:

    上面read_yuvImage函数若输入的是多帧的图像文件,如yuv视频文件,则返回最后一帧的YUV数据,当然了,你稍微改改就可以实现读取每一帧YUV数据了。下面章节会增加显示YUV数据部分,会借助OpenCV实现播放YUV的视频文件。


2、保存YUV文件

    保存YUV文件也十分简单,调用C++的fopen()函数打开YUV文件获得指向该流的文件指针,然后使用fwrite()把YUV的数据流保存到打开的文件即可。

    下面是实现保存YUV444和YUV420数据的完整代码:

	/*********************************************************
	@brief         : 保存YUV数据流
	@param fileYUV : 输出YUV文件的路径
	@param inbuf   : 需要保存的YUV数据流
	@param w       : 宽width
	@param h       : 高height
	@param colorType: 颜色空间类型:CS_YUV444,CS_YUV420
	@return        : void
	********************************************************/
	void write_yuvImage(string fileYUV, unsigned char* inframe, int w, int h, ColorSpace colorType) {
		FILE * pFileOut;     //输出YUV文件
		if (NULL == (pFileOut = fopen(fileYUV.c_str(), "wb"))) {
			printf("File output is can't open!\n");
			fclose(pFileOut);
			return;
		}
		int bufLen = 0;
		if (colorType == CS_YUV444) {//定义保存CS_YUV444的数组
			bufLen = w * h * 3;
		}
		else if (colorType == CS_YUV420) {//定义保存YUV420的数组
			bufLen = w*h * 3 / 2;
		}
		fwrite(inframe, bufLen * sizeof(unsigned char), 1, pFileOut);
		fclose(pFileOut);
	}

三、C++实现YUV420与YUV444互转

    OpenCV提供了RGB与YUV420/YUV444互转的接口:cvtColor(),但根尴尬OpenCV就是没有提供YUV444与YUV420互转的接口,如果你想用OpenCV转换,那就只能间接的方法了,将YUV420转为BGR(CV_YUV2BGR_I420),然后再将BGR转为YUV444(CV_BGR2YUV),这很蛋疼!效率太低了。

    实质上,只要我们理解了YUV420与YUV444的数据格式,完全可以自己转换的,下面使用C++实现YUV420与YUV444互转,这部分不依赖OpenCV,因此效率十分高的!!!


1、YUV420转YUV444

    YUV420转YUV444,实际就是对UV两个分量色度进行上采样,最为简单的实现思路是直接填充。

    以U分量为例,其序列如下

    对U进行插值,每个田字格四个位置使用一个U值。

    在代码中,可以直接通过对数组循环赋值即可完成。对V分量的操作相同。 填充方式实现简单,但效果较差,一般可以通过插值来完成上采样。

   下面是C++实现YUV420转YUV444的代码:

	void YUV420TOYUV444(unsigned char *inbuf, unsigned char *outbuf, int w, int h) {
		unsigned char *srcY = NULL, *srcU = NULL, *srcV = NULL;
		unsigned char *desY = NULL, *desU = NULL, *desV = NULL;
		srcY = inbuf;//Y
		srcU = srcY + w * h;//U
		srcV = srcU + w * h / 4;;//V

		desY = outbuf;
		desU = desY + w * h;
		desV = desU + w * h;
		memcpy(desY, srcY, w * h * sizeof(unsigned char));//Y分量直接拷贝即可
	   //UV分量转换
		int i, j;
		for (i = 0; i < h; i += 2) {//行
			for (j = 0; j < w; j += 2) {//列
										//U
				desU[i*w + j] = srcU[i / 2 * w / 2 + j / 2];
				desU[i*w + j + 1] = srcU[i / 2 * w / 2 + j / 2];
				desU[(i + 1)*w + j] = srcU[i / 2 * w / 2 + j / 2];
				desU[(i + 1)*w + j + 1] = srcU[i / 2 * w / 2 + j / 2];
				//V
				desV[i*w + j] = srcV[i / 2 * w / 2 + j / 2];
				desV[i*w + j + 1] = srcV[i / 2 * w / 2 + j / 2];
				desV[(i + 1)*w + j] = srcV[i / 2 * w / 2 + j / 2];
				desV[(i + 1)*w + j + 1] = srcV[i / 2 * w / 2 + j / 2];
			}
		}
	}

2、YUV444转YUV420

    与YUV420与YUV444思路类似的,我们反过来计算,将YUV444下采样为YUV420即可,

    下面是C++实现YUV444转YUV420的代码:

	void YUV444TOYUV420(unsigned char *inbuf, unsigned char *outbuf, int w, int h) {
		unsigned char *srcY = NULL, *srcU = NULL, *srcV = NULL;
		unsigned char *desY = NULL, *desU = NULL, *desV = NULL;
		srcY = inbuf;//Y
		srcU = srcY + w * h;//U
		srcV = srcU + w * h;//V

		desY = outbuf;
		desU = desY + w * h;
		desV = desU + w * h / 4;

		int half_width = w / 2;
		int half_height = h / 2;
		memcpy(desY, srcY, w * h * sizeof(unsigned char));//Y分量直接拷贝即可
		 //UV
		for (int i = 0; i < half_height; i++) {
			for (int j = 0; j < half_width; j++) {
				*desU = *srcU;
				*desV = *srcV;
				desU++;
				desV++;
				srcU += 2;
				srcV += 2;
			}
			srcU = srcU + w;
			srcV = srcV + w;
		}
	}

四、显示和播放YUV文件

1、显示YUV图像

    YUV数据需要转到RGB才能显示正常,显示部分需要OpenCV的支持,OpenCV的配置,就不说啦,自己搞搞吧!!!这里提供有一个鄙人已经写好的cv_imageshow()函数,可以实现将输入的YUV数据流转为BGR格式,再用OpenCV的imshow()直接进行显示。注意,这里提供cv_imageshow()只能显示一帧数据,即你输入的inbuf数据流必须是一帧的YUV数据。

	/*********************************************************
	@brief         : 显示YUV图片,这里会将YUV转到RGB上进行显示
	@param winname : 窗口显示的名称
	@param inbuf   : YUV数据流,直接将read_yuvImage返回的YUV数据流传入即可显示
	@param w       : 宽width
	@param h       : 高height
	@param colorType: 颜色空间类型:CS_YUV444,CS_YUV420
	@return        : void
	********************************************************/
	void cv_imageshow(string winname, unsigned char* inframe, int w, int h, ColorSpace colorType) {
		if (colorType == CS_YUV444) {//定义保存CS_YUV444的数组
			cv::Mat yuvMat = cv::Mat::zeros(h, w, CV_8UC3);
			std::vector<cv::Mat> yuvChannels;
			cv::split(yuvMat, yuvChannels);
			yuvChannels.at(0).data = (unsigned char*)inframe;//Y
			yuvChannels.at(1).data = (unsigned char*)inframe + w * h;//U
			yuvChannels.at(2).data = (unsigned char*)inframe + w * h * 2;//V
			cv::merge(yuvChannels, yuvMat);
			cv::Mat bgrMat;
			cv::cvtColor(yuvMat, bgrMat, CV_YUV2BGR);
			cv::imshow(winname, bgrMat);
			cv::waitKey(30);
		}
		else if (colorType == CS_YUV420) {//定义保存YUV420的数组
			int bufLen = w*h * 3 / 2;
			cv::Mat yuvImg = cv::Mat::zeros(h * 3 / 2, w, CV_8UC1);
			memcpy(yuvImg.data, inframe, bufLen * sizeof(unsigned char));
			//cv::imshow(winname, yuvImg);
			//cv::waitKey(30);
			cv::Mat bgrImg= cv::Mat::zeros(h , w, CV_8UC3);
			cv::cvtColor(yuvImg, bgrImg, CV_YUV2BGR_I420);
			cv::imshow(winname, bgrImg);
			cv::waitKey(30);
		}
	}

2、播放YUV数据 

    上面提供的cv_imageshow()为了显示图片,所以只能显示一帧数据,即你输入的inbuf数据流必须是一帧的YUV数据。但很多YUV文件实质上是多帧的视频文件,要实现YUV文件的播放功能也很简单:循环读取YUV数据流,使用OpenCV的imshow()一帧一帧的显示,就实现了播放YUV的功能了:

    下面是C++实现的cvPlayYUV444和cvPlayYUV420的函数,可以实现播放YUV444和YUV420的文件:

	/*********************************************************
	@brief         : 播放YUV420/YUV444文件
	@param fileYUV : YUV文件
	@param w       : 宽width
	@param h       : 高height
	@return        : void
	********************************************************/
	void cvPlayYUV444(string fileYUV, int w, int h) {
		FILE* pFileIn = fopen(fileYUV.c_str(), "rb+");
		int bufLen = w * h * 3;
		unsigned char* pYuvBuf = new unsigned char[bufLen];
		cv::Mat yuvMat = cv::Mat::zeros(h, w, CV_8UC3);
		std::vector<cv::Mat> yuvChannels;
		cv::split(yuvMat, yuvChannels);
		while (fread(pYuvBuf, bufLen * sizeof(unsigned char), 1, pFileIn))
		{
			//cv::Mat Y(i, w, CV_8UC1, (unsigned char*)pSrc);
			//cv::Mat U(i, w, CV_8UC1, (unsigned char*)pSrc + w * i);
			//cv::Mat V(i, w, CV_8UC1, (unsigned char*)pSrc + w * i * 2);
			yuvChannels.at(0).data = (unsigned char*)pYuvBuf;//Y
			yuvChannels.at(1).data = (unsigned char*)pYuvBuf + w * h;//U
			yuvChannels.at(2).data = (unsigned char*)pYuvBuf + w * h * 2;//V
			cv::merge(yuvChannels, yuvMat);
			cv::Mat bgrMat;
			cv::cvtColor(yuvMat, bgrMat, CV_YUV2BGR);
			cv::imshow(" cvPlayYUV444", bgrMat);
			cv::waitKey(30);
		}
		delete[] pYuvBuf;
		fclose(pFileIn);
	}


	void cvPlayYUV420(string fileYUV, int w, int h)
	{
		FILE* pFileIn = fopen(fileYUV.c_str(), "rb+");
		int bufLen = w*h * 3 / 2;
		unsigned char* pYuvBuf = new unsigned char[bufLen];
		while (fread(pYuvBuf, bufLen * sizeof(unsigned char), 1, pFileIn))
		{
			cv::Mat yuvImg, rgbImg;
			yuvImg.create(h * 3 / 2, w, CV_8UC1);
			memcpy(yuvImg.data, pYuvBuf, bufLen * sizeof(unsigned char));
			cv::Mat gray = yuvImg(cv::Rect(0, 0, w, h));
			cv::cvtColor(yuvImg, rgbImg, CV_YUV2BGR_I420);
			//
			cv::imshow(" cvPlayYUV420", rgbImg);
			cv::waitKey(30);
		}
		delete[] pYuvBuf;
		fclose(pFileIn);
	}

五、完整的代码

     整理一下,这里提供了YUV.h和YUV.cpp源文件和测试main程序,注意定义了namespace名称空间为cs

     YUV444和YUV420的测试文件,可以这里下载https://download.csdn.net/download/guyuealian/10697442


YUV.h头文件:

#ifndef YUV_H
#define YUV_H
#include<opencv2/opencv.hpp>
#include <string>
using namespace std;
namespace cs {
	enum ColorSpace {
		CS_YUV444,
		CS_YUV420
	};
	enum ConvertColorSpace {
		CS_YUV444TOYUV420,
		CS_YUV420TOYUV444
	};
	/*********************************************************	
	@brief         : 读取yuv图像文件,若输入的是多帧的图像文件,如yuv视频文件,则返回最后一帧的数据
	@param fileYUV : YUV文件
	@param w       : 宽width
	@param h       : 高height
	@param colorType: 颜色空间类型:CS_YUV444,CS_YUV420
	@return        : unsigned char* 返回YUV数据
	********************************************************/
	unsigned char* read_yuvImage(string fileYUV, int w, int h, ColorSpace colorType);

	/*********************************************************
	@brief         : 播放YUV420/YUV444文件
	@param fileYUV : YUV文件
	@param w       : 宽width
	@param h       : 高height
	@return        : void
	********************************************************/
	void cvPlayYUV420(string fileYUV, int w, int h);
	void cvPlayYUV444(string fileYUV, int w, int h);

	/*********************************************************
	@brief         : 显示YUV图片,这里会将YUV转到RGB上进行显示
	@param winname : 窗口显示的名称
	@param inbuf   : YUV数据流,直接将read_yuvImage返回的YUV数据流传入即可显示
	@param w       : 宽width
	@param h       : 高height
	@param colorType: 颜色空间类型:CS_YUV444,CS_YUV420
	@return        : void
	********************************************************/
	void cv_imageshow(string winname, unsigned char* inbuf, int w, int h, ColorSpace colorType);

	/*********************************************************
	@brief         : 保存YUV数据流
	@param fileYUV : 输出YUV文件的路径
	@param inbuf   : 需要保存的YUV数据流
	@param w       : 宽width
	@param h       : 高height
	@param colorType: 颜色空间类型:CS_YUV444,CS_YUV420
	@return        : void
	********************************************************/
	void write_yuvImage(string fileYUV, unsigned char* inbuf, int w, int h, ColorSpace colorType);

	/*********************************************************
	@brief         : 实现YUV420与YUV444数据之间的转换,实质上会分别调用YUV420TOYUV444和YUV444TOYUV420函数
	@param inbuf   : 输入YUV数据流
	@param w       : 宽width
	@param h       : 高height
	@param ConvertColorSpace: 转换颜色空间类型:
	                         CS_YUV444TOYUV420:实现YUV444转YUV420
							 CS_YUV420TOYUV444:实现YUV420转YUV444
	@return        : unsigned char* 返回转换后的YUV数据
	********************************************************/
	unsigned char* convertYUV(unsigned char* inbuf, int w, int h, ConvertColorSpace type);
	void YUV420TOYUV444(unsigned char *inbuf, unsigned char *outbuff, int w, int h);
	void YUV444TOYUV420(unsigned char *inbuf, unsigned char *outbuff, int w, int h);

	/*********************************************************
	@brief         : 将YUV数据流转为OpenCV的Mat类型,方便使用OpenCV显示
	@param inbuf   : 输入YUV数据流
	@param w       : 宽width
	@param h       : 高height
	@param colorType: 颜色空间类型:CS_YUV444,CS_YUV420
	@return        : Mat类型图像数据
	********************************************************/
	cv::Mat YUVBufTOYUVMat(unsigned char* inbuf, int w, int h, ColorSpace colorType);

	/*********************************************************
	@brief         : 将OpenCV的Mat类型图像转YUV数据流
	@param inbuf   : OpenCV的Mat类型图像
	@param colorType: Mat类型图像颜色空间类型:CS_YUV444,CS_YUV420
	@return        : unsigned char* 返回转换后的YUV数据流
	********************************************************/
	unsigned char* YUVMatTOYUVBUF(cv::Mat yuvMat, ColorSpace colorType);
}
#endif

 


YUV.cpp实现文件: 

#include "YUV.h"
#include <opencv2/opencv.hpp>

namespace cs {
	//void read_yuvImage(string fileYUV, unsigned char* outYUV, int w, int i, ColorSpace colorType)
	/*读取yuv图像文件,注意:若输入的是多帧的图像文件,如yuv视频文件,则返回最后一帧的数据*/
	unsigned char* read_yuvImage(string fileYUV, int w, int h, ColorSpace colorType) {
		FILE * pFileIn;      //输入CS_YUV444文件
		if (NULL == (pFileIn = fopen(fileYUV.c_str(), "rb"))) {
			printf("File input is can't open!\n");
			fclose(pFileIn);
			return nullptr;
		}
		int bufLen = 0;
		if (colorType == CS_YUV444) {//定义保存CS_YUV444的数组
			bufLen = w * h * 3;
		}
		else if (colorType == CS_YUV420) {//定义保存YUV420的数组
			bufLen = w*h * 3 / 2;
		}
		unsigned char* yuvBuf = new unsigned char[bufLen];
		//fread(yuvBuf, bufLen * sizeof(unsigned char), 1, pFileIn);//返回第一帧
		while (fread(yuvBuf, bufLen * sizeof(unsigned char), 1, pFileIn))//返回最后一帧
		{
		}
		fclose(pFileIn);
		return yuvBuf;
	}
	void cvPlayYUV444(string fileYUV, int w, int h) {
		FILE* pFileIn = fopen(fileYUV.c_str(), "rb+");
		int bufLen = w * h * 3;
		unsigned char* pYuvBuf = new unsigned char[bufLen];
		cv::Mat yuvMat = cv::Mat::zeros(h, w, CV_8UC3);
		std::vector<cv::Mat> yuvChannels;
		cv::split(yuvMat, yuvChannels);
		while (fread(pYuvBuf, bufLen * sizeof(unsigned char), 1, pFileIn))
		{
			//cv::Mat Y(i, w, CV_8UC1, (unsigned char*)pSrc);
			//cv::Mat U(i, w, CV_8UC1, (unsigned char*)pSrc + w * i);
			//cv::Mat V(i, w, CV_8UC1, (unsigned char*)pSrc + w * i * 2);
			yuvChannels.at(0).data = (unsigned char*)pYuvBuf;//Y
			yuvChannels.at(1).data = (unsigned char*)pYuvBuf + w * h;//U
			yuvChannels.at(2).data = (unsigned char*)pYuvBuf + w * h * 2;//V
			cv::merge(yuvChannels, yuvMat);
			cv::Mat bgrMat;
			cv::cvtColor(yuvMat, bgrMat, CV_YUV2BGR);
			cv::imshow(" cvPlayYUV444", bgrMat);
			cv::waitKey(30);
		}
		delete[] pYuvBuf;
		fclose(pFileIn);
	}


	void cvPlayYUV420(string fileYUV, int w, int h)
	{
		FILE* pFileIn = fopen(fileYUV.c_str(), "rb+");
		int bufLen = w*h * 3 / 2;
		unsigned char* pYuvBuf = new unsigned char[bufLen];
		while (fread(pYuvBuf, bufLen * sizeof(unsigned char), 1, pFileIn))
		{
			cv::Mat yuvImg, rgbImg;
			yuvImg.create(h * 3 / 2, w, CV_8UC1);
			memcpy(yuvImg.data, pYuvBuf, bufLen * sizeof(unsigned char));
			cv::Mat gray = yuvImg(cv::Rect(0, 0, w, h));
			cv::cvtColor(yuvImg, rgbImg, CV_YUV2BGR_I420);
			//
			cv::imshow(" cvPlayYUV420", rgbImg);
			cv::waitKey(30);
		}
		delete[] pYuvBuf;
		fclose(pFileIn);
	}

	void write_yuvImage(string fileYUV, unsigned char* inframe, int w, int h, ColorSpace colorType) {
		FILE * pFileOut;     //输出YUV文件
		if (NULL == (pFileOut = fopen(fileYUV.c_str(), "wb"))) {
			printf("File output is can't open!\n");
			fclose(pFileOut);
			return;
		}
		int bufLen = 0;
		if (colorType == CS_YUV444) {//定义保存CS_YUV444的数组
			bufLen = w * h * 3;
		}
		else if (colorType == CS_YUV420) {//定义保存YUV420的数组
			bufLen = w*h * 3 / 2;
		}
		fwrite(inframe, bufLen * sizeof(unsigned char), 1, pFileOut);
		fclose(pFileOut);
	}

	void cv_imageshow(string winname, unsigned char* inframe, int w, int h, ColorSpace colorType) {
		if (colorType == CS_YUV444) {//定义保存CS_YUV444的数组
			cv::Mat yuvMat = cv::Mat::zeros(h, w, CV_8UC3);
			std::vector<cv::Mat> yuvChannels;
			cv::split(yuvMat, yuvChannels);
			yuvChannels.at(0).data = (unsigned char*)inframe;//Y
			yuvChannels.at(1).data = (unsigned char*)inframe + w * h;//U
			yuvChannels.at(2).data = (unsigned char*)inframe + w * h * 2;//V
			cv::merge(yuvChannels, yuvMat);
			cv::Mat bgrMat;
			cv::cvtColor(yuvMat, bgrMat, CV_YUV2BGR);
			cv::imshow(winname, bgrMat);
			cv::waitKey(30);
		}
		else if (colorType == CS_YUV420) {//定义保存YUV420的数组
			int bufLen = w*h * 3 / 2;
			cv::Mat yuvImg = cv::Mat::zeros(h * 3 / 2, w, CV_8UC1);
			memcpy(yuvImg.data, inframe, bufLen * sizeof(unsigned char));
			//cv::imshow(winname, yuvImg);
			//cv::waitKey(30);
			cv::Mat bgrImg= cv::Mat::zeros(h , w, CV_8UC3);
			cv::cvtColor(yuvImg, bgrImg, CV_YUV2BGR_I420);
			cv::imshow(winname, bgrImg);
			cv::waitKey(30);
		}
	}

	void YUV420TOYUV444(unsigned char *inbuf, unsigned char *outbuf, int w, int h) {
		unsigned char *srcY = NULL, *srcU = NULL, *srcV = NULL;
		unsigned char *desY = NULL, *desU = NULL, *desV = NULL;
		srcY = inbuf;//Y
		srcU = srcY + w * h;//U
		srcV = srcU + w * h / 4;;//V

		desY = outbuf;
		desU = desY + w * h;
		desV = desU + w * h;
		memcpy(desY, srcY, w * h * sizeof(unsigned char));//Y分量直接拷贝即可
	   //UV分量转换
		int i, j;
		for (i = 0; i < h; i += 2) {//行
			for (j = 0; j < w; j += 2) {//列
										//U
				desU[i*w + j] = srcU[i / 2 * w / 2 + j / 2];
				desU[i*w + j + 1] = srcU[i / 2 * w / 2 + j / 2];
				desU[(i + 1)*w + j] = srcU[i / 2 * w / 2 + j / 2];
				desU[(i + 1)*w + j + 1] = srcU[i / 2 * w / 2 + j / 2];
				//V
				desV[i*w + j] = srcV[i / 2 * w / 2 + j / 2];
				desV[i*w + j + 1] = srcV[i / 2 * w / 2 + j / 2];
				desV[(i + 1)*w + j] = srcV[i / 2 * w / 2 + j / 2];
				desV[(i + 1)*w + j + 1] = srcV[i / 2 * w / 2 + j / 2];
			}
		}
	}
	void YUV444TOYUV420(unsigned char *inbuf, unsigned char *outbuf, int w, int h) {
		unsigned char *srcY = NULL, *srcU = NULL, *srcV = NULL;
		unsigned char *desY = NULL, *desU = NULL, *desV = NULL;
		srcY = inbuf;//Y
		srcU = srcY + w * h;//U
		srcV = srcU + w * h;//V

		desY = outbuf;
		desU = desY + w * h;
		desV = desU + w * h / 4;

		int half_width = w / 2;
		int half_height = h / 2;
		memcpy(desY, srcY, w * h * sizeof(unsigned char));//Y分量直接拷贝即可
		 //UV
		for (int i = 0; i < half_height; i++) {
			for (int j = 0; j < half_width; j++) {
				*desU = *srcU;
				*desV = *srcV;
				desU++;
				desV++;
				srcU += 2;
				srcV += 2;
			}
			srcU = srcU + w;
			srcV = srcV + w;
		}
	}

	unsigned char* convertYUV(unsigned char* inbuf, int w, int h, ConvertColorSpace type) {
		int bufLen = 0;
		if (type == CS_YUV420TOYUV444) {//定义保存CS_YUV444的数组
			bufLen = w * h * 3;
			unsigned char* pDst = new unsigned char[bufLen];//定义保存YUV420的数组
			YUV420TOYUV444(inbuf, pDst, w, h);
			return pDst;
		}
		else if (type == CS_YUV444TOYUV420) {
			bufLen = w*h * 3 / 2;
			unsigned char* pDst = new unsigned char[bufLen];//定义保存YUV420的数组
			YUV444TOYUV420(inbuf, pDst, w, h);
			return pDst;
		}
	}
	cv::Mat YUVBufTOYUVMat(unsigned char* inbuf,int w,int h, ColorSpace colorType) {
		if (colorType == CS_YUV444) {//定义保存CS_YUV444的数组
			cv::Mat yuvMat = cv::Mat::zeros(h, w, CV_8UC3);
			std::vector<cv::Mat> yuvChannels;
			cv::split(yuvMat, yuvChannels);
			yuvChannels.at(0).data = (unsigned char*)inbuf;//Y
			yuvChannels.at(1).data = (unsigned char*)inbuf + w * h;//U
			yuvChannels.at(2).data = (unsigned char*)inbuf + w * h * 2;//V
			cv::merge(yuvChannels, yuvMat);
			return yuvMat;
		}
		else if (colorType == CS_YUV420) {//定义保存YUV420的数组
			int bufLen = w*h * 3 / 2;
			cv::Mat yuvMat= cv::Mat::zeros(h * 3 / 2, w, CV_8UC1);
			memcpy(yuvMat.data, inbuf, bufLen * sizeof(unsigned char));
			return yuvMat;
		}

	}

	unsigned char* YUVMatTOYUVBUF(cv::Mat yuvMat, ColorSpace colorType) {
		if (colorType == CS_YUV444) {//定义保存CS_YUV444的数组
			int w = yuvMat.cols;
			int h = yuvMat.rows;
			int bufLen = w*h * 3;
			unsigned char* outbuf = new unsigned char[bufLen];
			std::vector<cv::Mat> yuvChannels;
			cv::split(yuvMat, yuvChannels);
			memcpy(outbuf, yuvChannels.at(0).data, w * h * sizeof(unsigned char));//Y分量直接拷贝即可
			memcpy(outbuf + w * h, yuvChannels.at(1).data, w * h * sizeof(unsigned char));//Y分量直接拷贝即可
			memcpy(outbuf + w * h * 2, yuvChannels.at(2).data, w * h * sizeof(unsigned char));//Y分量直接拷贝即可
			return outbuf;
		}
		else if (colorType == CS_YUV420) {//定义保存YUV420的数组
			int w = yuvMat.cols;
			int h = yuvMat.rows;
			int bufLen = yuvMat.total();//w*h ;
			unsigned char* outbuf = new unsigned char[bufLen];
			//outbuf = yuvMat.data;
			memcpy(outbuf,yuvMat.data, bufLen * sizeof(unsigned char));
			return outbuf;
		}
	}
}

    YUV444与YUV420互转测试:

int main() {
	string fileYUV = "D:\\imageEnhance\\YUVdata\\YUV444_1366_768.yuv";
	//string fileYUV = "D:\\imageEnhance\\YUVdata\\yuv444_image_640_480.yuv";
	//string fileYUV = "D:\\imageEnhance\\YUVdata\\sintel_640_360.yuv";
	int w = 1366;
	int h = 768;
	char unsigned *yuv444_buf= cs::read_yuvImage(fileYUV, w, h, cs::CS_YUV444);//读取YUV数据
	cs::cv_imageshow("CS_YUV444", yuv444_buf, w, h, cs::CS_YUV444);
	char unsigned *yuv420_buf = cs::convertYUV(yuv444_buf, w, h, cs::CS_YUV444TOYUV420);//将YUV444转为YUV420
	cs::cv_imageshow("CS_YUV420", yuv420_buf, w, h, cs::CS_YUV420);
	cin.get();
	return 0;
}

    YUV444与YUV420播放测试:

int main() {
	//string fileYUV = "D:\\imageEnhance\\YUVdata\\YUV444_1366_768.yuv";
	//string fileYUV = "D:\\imageEnhance\\YUVdata\\yuv444_image_640_480.yuv";
	string fileYUV = "D:\\imageEnhance\\YUVdata\\sintel_640_360.yuv";
	int w = 640;
	int h = 360;
	cs::cvPlayYUV420(fileYUV,w,h);
	cin.get();
	return 0;
}


参考资料:

[1]《YUV420转YUV444》https://blog.csdn.net/lin453701006/article/details/53053185,这个博客的思路不错,就是代码太烂了。

[2]《YUV444与YUV422下采样》https://blog.csdn.net/qq_36113899/article/details/68958344

如果你觉得该帖子帮到你,还望贵人多多支持,鄙人会再接再厉,继续努力的~

发布了178 篇原创文章 · 获赞 1606 · 访问量 193万+
展开阅读全文

我在网上找了个bmp转YUV的程序进行修改,但读不出数据,不知什么问题,有谁懂的请帮忙看下,谢谢了

04-28

#include <stdio.h> #include <stdlib.h> #include "bmp2rgb.h" u_int8_t BMP2RGB(BITMAPFILEHEADER file_header,BITMAPINFOHEADER info_header, FILE* bmpFile, u_int8_t* rgbBuf);//24bit RGB u_int8_t RGB24ToYUV420(int Width,int Height,u_int8_t* rgbBuf,u_int8_t*YuvBuffer); #define max(a,b) (((a)>(b))?(a):(b)) #define min(a,b) (((a) < (b)) ? (a) : (b)) int main(int argc, char** argv) { //设置命令行参数 argv[1]= "boot_logo.bmp"; argv[2]="boot_logo.yuv"; //相当于设置文件名 char* bmpFileName = argv[1]; char* yuvFileName = argv[2]; //打开文件 FILE* bmpFile = fopen(bmpFileName, "rb"); if (bmpFile == NULL) { printf(" Open the BMP file.\n"); exit(1); } else { printf("The BMP file is %s\n", bmpFileName); } FILE* yuvFile = fopen(yuvFileName, "wb"); if (yuvFile == NULL) { printf("Cannot open the YUV file.\n"); exit(1); } else { printf("The YUV file is %s\n", yuvFileName); } //读取BMP文件头,信息头,读取错误时的处理代码 BITMAPFILEHEADER file_header; BITMAPINFOHEADER info_header; if (fread(&file_header, sizeof(BITMAPFILEHEADER), 1, bmpFile) != 1) if (file_header.bfType != 0x4D42) { printf("Not BMP file.\n"); exit(1); } if (fread(&info_header, sizeof(BITMAPINFOHEADER), 1, bmpFile) != 1) { printf("read info header error!"); exit(1); }//结束读取BMP文件头 //读取图像尺寸 int width = info_header.biWidth; int height = info_header.biHeight; //开辟缓冲区 buf u_int8_t* yBuf = (u_int8_t*)malloc(height*width); u_int8_t* uBuf = (u_int8_t*)malloc(height*width / 4); u_int8_t* vBuf = (u_int8_t*)malloc(height*width / 4); u_int8_t* rgbBuf = (u_int8_t*)malloc(height*width * 3); u_int8_t*YuvBuffer =(u_int8_t*)malloc(height*width * 5); if (yBuf == NULL || uBuf == NULL || vBuf == NULL || rgbBuf == NULL || YuvBuffer==NULL) { printf("Not enough memory\n"); exit(1); } //BMP与RGB的转换,得到RGB数据 if (BMP2RGB(file_header, info_header, bmpFile, rgbBuf)) { printf("BMP2RGB error\n"); exit(1); } //RGB与YUV的转换,得到YUV数据 // int flip = 0; /*读取到的图像数据是倒序存放的,flip=0保证了RGB2YUV可以正确地对其转换*/ /* if (RGB2YUV(width, height, rgbBuf, yBuf, uBuf, vBuf, flip)) { printf("RGB2YUV error\n"); exit(1); } //将yuv按顺序写入yuvfile文件 fwrite(yBuf, 1, width * height, yuvFile); fwrite(uBuf, 1, (width * height) / 4, yuvFile); fwrite(vBuf, 1, (width * height) / 4, yuvFile);*/ if( RGB24ToYUV420( width, height, rgbBuf,YuvBuffer)) { printf("RGB24ToYUV420 error\n"); exit(1); } int len=0; len= fwrite(YuvBuffer, 1,sizeof(YuvBuffer), yuvFile); printf("len ==%d byte\n",len); //打印宽高,方便yuv观看程序打开 printf("width is %d", width); printf("\n"); printf("height is %d", height); printf("\n"); //清理内存 free(rgbBuf); free(YuvBuffer); free(yBuf); free(uBuf); free(vBuf); fclose(bmpFile); fclose(yuvFile); return 0; } u_int8_t BMP2RGB(BITMAPFILEHEADER file_header,BITMAPINFOHEADER info_header, FILE* bmpFile, u_int8_t* rgbBuf) { BITMAPFILEHEADER file_h=file_header; BITMAPINFOHEADER info_h=info_header; FILE* pFile =bmpFile; int w=0,h=0; //确定像素的实际点阵数 w = (info_h.biWidth*info_h.biBitCount + 31) / 32 * 4;//w为实际一行的字节数 h = info_h.biHeight;//h为列数 // printf("w==%d,h==%d\n",w,h); //开辟实际字节数量的缓冲区,读数据,一次读取一个字节 u_int8_t* dataBuf = (u_int8_t*)malloc(w*h); /*使用文件头的字节偏移属性bfOffBits 直接把文件指针定位到像素值数据的起始 */ fseek(pFile, file_h.bfOffBits, 0); fread(dataBuf, 1, w*h, pFile); unsigned char* data = dataBuf; u_int8_t* rgb = rgbBuf; //开始写入rgb int i, j; for (j = 0; j < h; j++)//j控制行循环 { for (i = 0; i < w; i += 3)//i控制列循环 { *rgb = data[i + w*j];//B *(rgb + 1) = data[i + w*j + 1];//G *(rgb + 2) = data[i + w*j + 2];//R rgb += 3; } } //释放内存 free(dataBuf); return 0; } /***************************************************************************************************************/ u_int8_t RGB24ToYUV420(int Width,int Height,u_int8_t* rgbBuf,u_int8_t*YuvBuffer) { u_int8_t* yuvBuf=YuvBuffer;//YUV空间 int nWidth=Width; int nHeight=Height; /////////////////////下面转换算法是网上查到的 int i, j; u_int8_t*bufY = yuvBuf; u_int8_t*bufU = yuvBuf + nWidth * nHeight; u_int8_t*bufV = bufU + (nWidth* nHeight* 1/4); u_int8_t*Y=bufY; u_int8_t*U=bufU; u_int8_t*V=bufV; u_int8_t*bufRGB; unsigned char y, u, v, r, g, b; if (NULL==rgbBuf) { printf("NULL==rgbBuf\n"); return 1 ; } for (j = 0; j<nHeight;j++) { bufRGB = rgbBuf + nWidth * (nHeight - 1-j) * 3 ; for (i = 0;i<nWidth;i++) { int pos = nWidth * i + j; r= *(bufRGB++); g = *(bufRGB++); b = *(bufRGB++); y =(unsigned char)(( 66 * r + 129 * g + 25 * b + 128) >>8) + 16;//16 v = (unsigned char)((-38 * r - 74 * g + 112 * b + 128) >>8) +128 ; //128 u = (unsigned char)((112 * r - 94 * g - 18 * b + 128) >> 8) + 128 ; *(bufY++)=max(0,min(y, 255 )); if (j%2==0&&i%2 ==0) { if (u>255) { u=255; } if (u<0) { u = 0; } *(bufU++) =u; //存u分量 } else { //存v分量 if (i%2==0) { if (v>255) { v = 255; } if (v<0) { v = 0; } *(bufV++) =v; } } } } return 0; } ``` #include <stdio.h> #include "sys/types.h" #include <stdlib.h> typedef unsigned long DWORD;//32bit typedef unsigned short WORD;//16bit typedef unsigned long LONG; //32bit typedef struct tagBITMAPFILEHEADER { //0x00~0x01,说明文件的类型 WORD bfType; //0x02~0x05,说明文件的大小,用字节B为单位 DWORD bfSize; //0x06~0x07,保留,设置为0 WORD bfReserved1; //0x08~0x09,保留,设置为0 WORD bfReserved2; //0x0a~0x0d,说明从BITMAP_FILE_HEADER结构开始到实际的图像数据之间的字节偏移量 DWORD bfOffBits; } BITMAPFILEHEADER; typedef struct tagBITMAPINFOHEADER { //0x0e~0x11,说明当前结构体所需字节数 DWORD biSize; //0x12~0x15,以像素为单位说明图像的宽度 LONG biWidth; //0x16~0x19,以像素为单位说明图像的高度 LONG biHeight; //0x1a~0x1b,说明位面数,必须为1 WORD biPlanes; //0x1c~0x1d,说明图像的位深度 WORD biBitCount; //0x1e~0x21,说明图像是否压缩及压缩类型 DWORD biCompression; //0x22~0x25,以字节为单位说明图像大小,必须是4的整数倍 DWORD biSizeImage; //0x26~0x29,目标设备的水平分辨率,像素/米 LONG biXPelsPerMeter; //0x2a~0x2d,目标设备的垂直分辨率,像素/米 LONG biYPelsPerMeter; //0x2e~0x31,说明图像实际用到的颜色数,如果为0,则颜色数为2的biBitCount次方 DWORD biClrUsed; //0x32~0x35,说明对图像显示有重要影响的颜色索引的数目,如果是0,表示都重要。 DWORD biClrImportant; } BITMAPINFOHEADER; ``` 运行环境linux,bmp图片24位 运行结果: The BMP file is boot_logo.bmp The YUV file is boot_logo.yuv len ==8 byte width is 185729024 height is 0 问答

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

©️2019 CSDN 皮肤主题: 编程工作室 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览