C++ Qt 使用openCV库将图片文件互转YUV文件

一、前言

最近需要yuv文件作为素材,但是在网上没有找到对应格式的。只有420p格式的,我需要422p和444p,本来想的是自己写一个420p转422p的函数,但是那样素材就太少了,只能下载已知,想到以前使用过的opencv库,决定自己将图像文件转为yuv文件,这样素材就丰富了。由于习惯使用Qt编程,所以下列的代码都是用Qt,图片素材我使用的都是jpg格式的图片,所以这里的图片也都是用JPG文件。

二、open CV库的安装

关于这个,请看我以前写的一篇文章,里面有提到:

QT QPixmap或者QImage加载图片程序异常结束问题(code: 0xc0000602: ,)本文讲述了,当图片 - 掘金 (juejin.cn)

三、yuv文件格式解析

关于YUV数据格式分类,可以看下面这篇文章,是我看过的几篇文章中讲解的最清晰的了:

图像原始格式 YUV444 YUV422 YUV420 详细解析-CSDN博客

四、图片转YUV422p

根据上面的介绍,假设图片为1*4,图片数据格式为YYYYUUVV,将mat数据按像素解析,422p的格式取出填入yuv文件即可,函数封装如下:

C++ 复制代码
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中,函数如下:

C++ 复制代码
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) * height
    size_t ySize = width * height;
    size_t uvSize = (width / 2) * height;
    size_t frameSize = width * height * 2;  // Since YUV422p uses 2 bytes per pixel
    unsigned 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 file
    size_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的Mat
    cv::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];  // 第一个像素的Y
            yuvImage.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格式保存为JPEG
    if (cv::imwrite(jFileName.toStdString(), bgrImage)) {
//        qDebug() << "图片保存失败!";
//        return false;
    }

    return true;
}

六、cv::Mat 的介绍

如上述,每个像素都有三个分量值,分别是Y、U、V分量,如果为图片文件,需要cv::Mat转换成功后才可正确拿取,代码介绍:

C++ 复制代码
    // cv::Mat image 转换为 YUV 格式,方便拿取YUV数据
    cv::Mat yuvImage;
    cv::cvtColor(image, yuvImage, cv::COLOR_BGR2YUV);
    // 拿取其第i行,j列的数据,i取值0->height,j取值0->width
    cv::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为例,代码解释如下:

C++ 复制代码
    // 创建一个存放YUV422的Mat
    cv::Mat yuvImage(height, width, CV_8UC2);  // 2通道的 YUV422p 格式
    //同样使用i行j列为例
    // Y 分量
    yuvImage.at<cv::Vec2b>(i, j)[0] = pYBuffer[yIndex1];  // 第一个像素的Y
    yuvImage.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对象正常初始化即可。

有问题欢迎留言。

相关推荐
----云烟----12 分钟前
QT中QString类的各种使用
开发语言·qt
机器视觉知识推荐、就业指导1 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Yang.993 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王3 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表
zz40_3 小时前
C++自己写类 和 运算符重载函数
c++
如若1234 小时前
主要用于图像的颜色提取、替换以及区域修改
人工智能·opencv·计算机视觉
六月的翅膀4 小时前
C++:实例访问静态成员函数和类访问静态成员函数有什么区别
开发语言·c++
liujjjiyun4 小时前
小R的随机播放顺序
数据结构·c++·算法
¥ 多多¥4 小时前
c++中mystring运算符重载
开发语言·c++·算法