一、前言
最近需要yuv文件作为素材,但是在网上没有找到对应格式的。只有420p格式的,我需要422p和444p,本来想的是自己写一个420p转422p的函数,但是那样素材就太少了,只能下载已知,想到以前使用过的opencv库,决定自己将图像文件转为yuv文件,这样素材就丰富了。由于习惯使用Qt编程,所以下列的代码都是用Qt,图片素材我使用的都是jpg格式的图片,所以这里的图片也都是用JPG文件。
二、open CV库的安装
关于这个,请看我以前写的一篇文章,里面有提到:
QT QPixmap或者QImage加载图片程序异常结束问题(code: 0xc0000602: ,)_qt qpixmap加载图片失败-CSDN博客https://blog.csdn.net/Blasit/article/details/135913362?ops_request_misc=%257B%2522request%255Fid%2522%253A%252233BEE42C-80DC-46B3-B201-11172E136977%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=33BEE42C-80DC-46B3-B201-11172E136977&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-135913362-null-null.nonecase&utm_term=opencv&spm=1018.2226.3001.4450
三、yuv文件格式解析
关于YUV数据格式分类,可以看下面这篇文章,是我看过的几篇文章中讲解的最清晰的了:
图像原始格式 YUV444 YUV422 YUV420 详细解析-CSDN博客https://blog.csdn.net/sway913/article/details/120602052
四、图片转YUV422p
根据上面的介绍,假设图片为1*4,图片数据格式为YYYYUUVV,将mat数据按像素解析,422p的格式取出填入yuv文件即可,函数封装如下:
bool jpegFileToYUV422pFile(QString jFileName, QString yuvFileName)
{// 读取JPEG图像cv::Mat image = cv::imread(jFileName.toStdString());if (image.empty()) {qDebug() << "无法读取图片";return false;}// 调整图像宽度为偶数,以便处理 YUV422 格式if (image.cols % 2 != 0) {cv::resize(image, image, cv::Size(image.cols - 1, image.rows));}// 转换为YUV格式cv::Mat yuvImage;cv::cvtColor(image, yuvImage, cv::COLOR_BGR2YUV);QFile file(yuvFileName);if (!file.open(QIODevice::WriteOnly)) {qDebug() << "无法打开文件" << yuvFileName;return false;}int width = yuvImage.cols;int height = yuvImage.rows;qDebug() << width << height << yuvImage.total() * yuvImage.elemSize();for (int i = 0; i < height; ++i) {for (int j = 0; j < width; ++j) {cv::Vec3b yuvPixel = yuvImage.at<cv::Vec3b>(i, j);unsigned char Y = yuvPixel[0]; // Y分量file.write((const char*)&Y, 1);}}for (int i = 0; i < height; ++i) {for (int j = 0; j < width; j += 2) {cv::Vec3b yuvPixel = yuvImage.at<cv::Vec3b>(i, j);unsigned char U = yuvPixel[1]; // U分量file.write((const char*)&U, 1);}}for (int i = 0; i < height; ++i) {for (int j = 0; j < width; j += 2) {cv::Vec3b yuvPixel = yuvImage.at<cv::Vec3b>(i, j);unsigned char V = yuvPixel[2]; // V分量file.write((const char*)&V, 1);}}file.close();qDebug() << "文件保存完成:" << yuvFileName;return true;
}
五、YUV422p转图片
还是一样,假设图片为1*4,图片数据格式为YYYYUUVV,然后读出数据解析即可得到Y、U、V各个分量的数据,然后填充在Mat中,函数如下:
bool yuv422pFileToJpegFile(QString yuvFileName, QString jFileName, int width, int height)
{FILE *yuvFile = fopen(yuvFileName.toStdString().c_str(), "rb");if (!yuvFile) {perror("Failed to open YUV file");return false;}// YUV422p: Y plane size = width * height, U and V plane size = (width/2) * heightsize_t ySize = width * height;size_t uvSize = (width / 2) * height;size_t frameSize = width * height * 2; // Since YUV422p uses 2 bytes per pixelunsigned char *yuvData = (unsigned char*)malloc(frameSize);unsigned char *pYBuffer = (unsigned char*)malloc(ySize);unsigned char *pUBuffer = (unsigned char*)malloc(uvSize);unsigned char *pVBuffer = (unsigned char*)malloc(uvSize);if (!yuvData || !pYBuffer || !pUBuffer || !pVBuffer) {perror("Failed to allocate memory for YUV data");fclose(yuvFile);return false;}// Read the YUV data from the filesize_t readSize = fread(yuvData, 1, frameSize, yuvFile);if (readSize != frameSize) {perror("Failed to read complete YUV frame");free(yuvData);free(pYBuffer);free(pUBuffer);free(pVBuffer);fclose(yuvFile);return false;}memcpy(pYBuffer, yuvData, ySize);memcpy(pUBuffer, yuvData + ySize, uvSize);memcpy(pVBuffer, yuvData + ySize + uvSize, uvSize);free(yuvData);fclose(yuvFile);// 创建一个存放YUV422的Matcv::Mat yuvImage(height, width, CV_8UC2); // 2通道的 YUV422p 格式// 填充YUV422p数据for (int i = 0; i < height; ++i) {for (int j = 0; j < width; j += 2) {int yIndex1 = i * width + j;int yIndex2 = yIndex1 + 1;int uvIndex = (i * (width / 2)) + (j / 2);// Y 分量yuvImage.at<cv::Vec2b>(i, j)[0] = pYBuffer[yIndex1]; // 第一个像素的YyuvImage.at<cv::Vec2b>(i, j+1)[0] = pYBuffer[yIndex2]; // 第二个像素的Y// U、V 分量交错存放yuvImage.at<cv::Vec2b>(i, j)[1] = pUBuffer[uvIndex]; // U分量yuvImage.at<cv::Vec2b>(i, j+1)[1] = pVBuffer[uvIndex]; // V分量}}free(pYBuffer);free(pUBuffer);free(pVBuffer);// 将YUV422p转换为BGR格式cv::Mat bgrImage;cv::cvtColor(yuvImage, bgrImage, cv::/*COLOR_YUV2BGR_Y422*/COLOR_YUV2BGR_YUYV); // YUV422 to BGR// 将BGR格式保存为JPEGif (cv::imwrite(jFileName.toStdString(), bgrImage)) {
// qDebug() << "图片保存失败!";
// return false;}return true;
}
六、cv::Mat 的介绍
如上述,每个像素都有三个分量值,分别是Y、U、V分量,如果为图片文件,需要cv::Mat转换成功后才可正确拿取,代码介绍:
// cv::Mat image 转换为 YUV 格式,方便拿取YUV数据cv::Mat yuvImage;cv::cvtColor(image, yuvImage, cv::COLOR_BGR2YUV);// 拿取其第i行,j列的数据,i取值0->height,j取值0->widthcv::Vec3b yuvPixel = yuvImage.at<cv::Vec3b>(i, j);unsigned char Y = yuvPixel[0]; // Y分量unsigned char U = yuvPixel[1]; // U分量unsigned char V = yuvPixel[2]; // V分量
如果是YUV文件,需要初始化对应的cv::Mat对象,再将其数据填充,使用422p为例,代码解释如下:
// 创建一个存放YUV422的Matcv::Mat yuvImage(height, width, CV_8UC2); // 2通道的 YUV422p 格式//同样使用i行j列为例// Y 分量yuvImage.at<cv::Vec2b>(i, j)[0] = pYBuffer[yIndex1]; // 第一个像素的YyuvImage.at<cv::Vec2b>(i, j+1)[0] = pYBuffer[yIndex2]; // 第二个像素的Y// 422p格式的数据在CV_8UC2图片中保存为,U、V 分量交错存放yuvImage.at<cv::Vec2b>(i, j)[1] = pUBuffer[uvIndex]; // U分量yuvImage.at<cv::Vec2b>(i, j+1)[1] = pVBuffer[uvIndex]; // V分量
七、结语
还有很多其他格式的YUV数据互相转换为图片文件,例如444p、420p、422YUYV、NV12等等,就不一一列举了,清楚YUV对应的数据结构后,按照对应格式解析、填充即可。关于编程语言和图片格式,也可以使用其他的,只要Mat对象正常初始化即可。
有问题欢迎留言。