如何旋转YUV图片数据且使用Qt显示

如何旋转YUV图片数据且使用Qt显示_第1张图片

前言

提一下这篇文章的需求:将USB相机获取到的YUV数据进行旋转,然后转为QImage进行显示。原本程序中是有旋转的代码,但不知道为什么,旋转出来的图片会花屏。关于花屏的问题,后面会稍微阐述一下。所以,经过查询各种资料,终于将这个问题给解决了,所以,自己也稍微总结一下解决问题的过程,以及最终的结果。

正文

一、环境

基本上这个的旋转跟你机器的环境也没多大关系,主要还是跟你YUV数据的格式有关系。

我所使用的环境是ARM+Qt+Win10

二、解决过程

1、解决思路

首先,我们解决问题,一定要明确自己解决问题的思路,才能不浪费时间。

起初对图片旋转的问题,没有一个清晰的解题思路,只知道不断的尝试各种旋转算法,导致浪费了部分时间,网上单独查询到的资料基本都是NV21或是NV12的图片旋转算法,所以,大部分都不可用。
最终梳理了一下,采取了以下流程解决了图片旋转的问题:

1、先将采集出的YUV数据保存成文件->

2、使用YUV Viwer进行显示,确认属于哪种格式的YUV数据。->

3、在网上搜索对应格式的YUV数据的旋转方式->

4、将YUV经过上一步的YUV数据旋转算法旋转->

5、保存旋转后的YUV数据、确保旋转后的YUV数据时没问题->

6、然后将YUV转QImage ,这里需注意,若旋转90度或270度,生成QImage时要注意宽高对调,否则会产生花屏。

我也大概按照这样的思路,来阐述我解决问题的思路。

2、如何保存采集到的YUV数据

以下有两种保存方式:

第一:把文件名称、YUV数据、YUV数据的宽高都传进去。

void CUsbVideo::_Yuv420pSaveFile(QString _sFileName, unsigned char *_inFile, int _iWidth, int _iHeight)
{
#if 1
    FILE *pFileOut;
    //    _sFileName = QString("/ics/File/test.yuv");
    std::string fileName = _sFileName.toStdString();

    if (NULL == (pFileOut = fopen(fileName.c_str(), "wb")))
    {
        LOG_INFO << "File output can not open";
        fclose(pFileOut);
        return;
    }
    int iBufLen = 0;
    iBufLen = _iWidth * _iHeight * 3 /2;//注意,这里YUV数据的大小一般是3/2  具体原因可以网上查下。
    fwrite(_inFile, iBufLen*sizeof(unsigned char), 1, pFileOut);
    fclose(pFileOut);
#endif
}

关于上面的fopen 也有一些知识:

以上的w和r分别代表写和读,但是这个是只写和只读,那要同时进行读写怎么办呢?没办法,那再来一个参数吧,于是+就出现了,于是r+、w+、a+就出现了,对应二进制文件就是rb+、rw+、ab+了。

1、采用r、rb、r+、rb+方式打开文件的时候,文件必须已经存在,否则会出错

2、采用w、wb、w+、wb+方式打开文件的时候,如果文件不存在,则新建该文件;如果文件已经存在,则删除该文件再重新建立

3、采用a、ab、a+、ab+方式打开文件,文件必须已经存在,否则出错。

3、使用YUV Viwer进行显示

将保存下的数据使用YUV Viewer进行显示。

YUView 2.13 这个版本的也不错

我是用上面这个

还有下面这个:

YUV-Viewer工具的下载

这两个工具好像不太一样,可以都下来看一下。

还有一个软件叫7yuv

使用软件去显示保存下来的Yuv文件,确定对应的类型。

4、YUV420 I420的旋转方式

我这边主要用到的是I420的yuv数据旋转方式,网上很多查询到的是NV21或是NV12的数据类型的旋转算法。都不太适用,都会造成花屏。

底下,直接贴算法了,包含了四种,90度,180度,270度,镜像:

//输入:_pYUV   输出:_pYUV180 
void CUsbVideo::_Yuv420I420Rotate180(unsigned char* _pYUV, unsigned char* _pYUV180)
{
    int width = m_iWidth;
    int height = m_iHeight;

    uint8_t* src_y = _pYUV;
    uint8_t* src_u = _pYUV + width * height;
    uint8_t* src_v = _pYUV + width * height + width * height / 4;
    uint8_t* dst_y = _pYUV180;
    uint8_t* dst_u = _pYUV180 + width * height;
    uint8_t* dst_v = _pYUV180 + width * height + width * height / 4;
    int index = 0;

    for (int i = width * height-1; i >= 0 ; --i)
    {
        dst_y[index++] = src_y[i];
    }

    int uv_index = 0;

    for (int i = width / 2 * height / 2; i >= 0; i--)
    {
        dst_u[uv_index] = src_u[i];
        dst_v[uv_index] = src_v[i];
        uv_index++;
    }
}

void CUsbVideo::_Yuv420I420Rotate270(unsigned char* _pYUV, unsigned char* _pYUV270)
{
    int width = m_iWidth;
    int height = m_iHeight;

    uint8_t* src_y = _pYUV;
    uint8_t* src_u = _pYUV + width * height;
    uint8_t* src_v = _pYUV + width * height + width * height / 4;
    uint8_t* dst_y = _pYUV270;
    uint8_t* dst_u = _pYUV270 + width * height;
    uint8_t* dst_v = _pYUV270 + width * height + width * height / 4;

    int index = 0;
    for (int i = width-1; i >= 0; --i)
    {
        for (int j = 0; j < height; ++j) {
            dst_y[index++] = src_y[j * width+i];
        }
    }

    int uv_index = 0;
    for (int i = width / 2-1; i >= 0; i--) {
        for (int j =  0; j< height / 2; j++)
        {
            dst_u[uv_index] = src_u[width / 2 * j+i];
            dst_v[uv_index] = src_v[width / 2 * j+ i];
            uv_index++;
        }
    }
}

void CUsbVideo::_Yuv420I420Rotate90(unsigned char* _pYUV, unsigned char* _pYUV90)
{
    int width = m_iWidth;
    int height = m_iHeight;

    uint8_t* src_y = _pYUV;
    uint8_t* src_u = _pYUV+ width* height;
    uint8_t* src_v = _pYUV + width * height + width * height/4;
    uint8_t* dst_y = _pYUV90 ;
    uint8_t* dst_u = _pYUV90 + width * height;
    uint8_t* dst_v = _pYUV90 + width * height + width * height / 4;

    int index = 0;
    for (int i = 0; i < width; ++i) {
        for (int j = height - 1; j >= 0; --j) {
            dst_y[index++] = src_y[j * width + i];
        }
    }

    int uv_index = 0;
    for (int i = 0; i < width / 2; i++) {
        for (int j = height / 2 - 1; j >= 0; j--)
        {
            dst_u[uv_index] = src_u[width / 2 * j + i];
            dst_v[uv_index] = src_v[width / 2 * j + i];
            uv_index++;
        }
    }
}

void CUsbVideo::_Yuv420I420RotateMirror(unsigned char* _pYUV, unsigned char* _pYUVMirror)
{
    int width = m_iWidth;
    int height = m_iHeight;

    uint8_t* src_y = _pYUV;
    uint8_t* src_u = _pYUV + width * height;
    uint8_t* src_v = _pYUV + width * height + width * height / 4;
    uint8_t* dst_y = _pYUVMirror;
    uint8_t* dst_u = _pYUVMirror + width * height;
    uint8_t* dst_v = _pYUVMirror + width * height + width * height / 4;

    for (int j = 0; j < height; ++j) {
        for (int i = 0; i < width; i++)
        {
            dst_y[j * width + width-1-i] = src_y[j * width + i];
        }
    }

    for (int j = 0; j < height / 2; ++j) {
        for (int i = 0; i < width / 2; i++)
        {
            dst_u[j * width / 2 + width / 2-1 - i] = src_u[j * width / 2 + i];
            dst_v[j * width / 2 + width / 2-1 - i] = src_v[j * width / 2 + i];
        }
    }
}

调用方式:

char* m_pData; ///<图像数据 YUV数据
m_pConverData = new unsigned char[m_iWidth * m_iHeight * 3/2];
_Yuv420I420Rotate90((unsigned char*)m_pData, (unsigned char*)m_pConverData);
5、将旋转后的YUV数据转为QImage进行显示
void CQueryVideoDialog::SLOT_NewImage(const QByteArray &_oYuv420, int _iWidth, int _iHeight)
{
    QMutexLocker oLocker(&m_mutex);
    //注意这里的宽高一定要进行对调,也就是创建QImage的宽高一定要进行对调,否则,就会产生花屏
    m_iWidth = _iHeight;
    m_iHeight = _iWidth;

    if (m_pRGBData == nullptr)
    {
        m_pRGBData = new uchar[_iWidth * _iHeight * 3];
        m_oImage = std::move(QImage(m_pRGBData, m_iWidth, m_iHeight, QImage::Format_RGB888));
    }

    m_iIndex++;

    CPixelFormatConverter::YUV420ToRGB24((uchar*)_oYuv420.data(), m_iWidth, m_iHeight, &m_pRGBData);

	m_pwgtCamera->SLOT_NewFrame(m_oImage);

    if (!m_oWatcher.isFinished())
    {
        return;
    }

    QImage oImg = m_oImage.copy();

    if (oImg.isNull())
    {
        LOG_INFO << "--> CQueryVideoDialog::SLOT_NewImage oImg is Null";
        return;
    }
    auto fn = [this, oImg]() {
        QVector<QRect> vtFaceRectPos;
        auto oRet = gKldFaceDevice::instance()->DetectFace(oImg, vtFaceRectPos);
        if (oRet.first && false == m_bStop)
        {
            QMetaObject::invokeMethod(m_pTimer, "start");
            m_bStop = true;
        }
        QMetaObject::invokeMethod(this, "_ShowFaceRect",Q_ARG(QVector<QRect>, vtFaceRectPos));
    };
    m_oWatcher.setFuture(QtConcurrent::run(fn));
}

int CPixelFormatConverter::YUV420ToRGB24(const unsigned char* data, int width, int height,unsigned char** pOutData)
{
    int iRet = 0;

    int dstStride[4];
    av_image_fill_linesizes(dstStride, AV_PIX_FMT_RGB24, width);

    AVFrame* pFrameYuv = av_frame_alloc();
    av_image_fill_linesizes(pFrameYuv->linesize, AV_PIX_FMT_YUV420P, width);
    av_image_fill_arrays(pFrameYuv->data, pFrameYuv->linesize, data, AV_PIX_FMT_YUV420P, width, height, 1);

    SwsContext * pSwsContext = nullptr;
    pSwsContext = sws_getCachedContext(pSwsContext, width, height, AV_PIX_FMT_YUV420P, width, height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL);

    iRet = sws_scale(pSwsContext, pFrameYuv->data, pFrameYuv->linesize, 0, height, pOutData, dstStride);

    sws_freeContext(pSwsContext);
    av_frame_free(&pFrameYuv);
    return iRet >= 0 ? 0 : -1;
}

主要遇到的花屏的问题都是因为宽高的问题,宽高没有适当的对调,当对YUV数据进行90度和270度旋转的时候,一定要对宽高进行对调

基本上我这边使用的旋转的主题的流程就都在上面了,下面就稍微介绍一下我在解决问题的过程中,所记录下来的一些知识,也稍微扩展一下知识面。

三、知识扩展

1、YUV 的相关知识

1、YUV数据的相关格式可以看这篇文章,总结的很好:详解 YUV 格式(I420/YUV420/NV12/NV12/YUV422)

2、我的I420的图像旋转主要就是参考这篇文章:视音频数据处理入门:I420 图像90° 、180°、270°旋转和左右镜像翻转

3、主要参考这篇文章:c++ 读取 yuv 图片_从YUV到RGB 主流的YUV420的格式:

这些主流YUV格式中,YUV420更常见(因为更节省空间啊),而从YUV420这种采样格式引申出来,叠加上不同的存储格式,就又可以细分为很多。Gemfield没有精力介绍这么多,你可以参考:http://www.fourcc.org/yuv.php 。

当YUV420采样格式叠加上planar 储存格式后,可以产生出YUV420P和YUV420SP格式。YUV420P是Y之后U之后V来储存,相当于RGB中的chw了,又可以细分为I420(又叫YU12)和YV12格式;而YUV420SP是Y之后UV交替…,又可以细分为NV12和NV21格式。这四种细分格式如下所示:

  • NV12 ,FourCC为0x3231564E ,1个像素12 bit, 8-bit Y plane,Y通道平面结束后是交叉进行的 U/V 通道平面,U/V使用2x2的采样率(各是Y的四分之一)。
  • NV21 ,FourCC为0x3132564E ,1个像素12 bit, 和NV12一样——除了U/V交叉的时候是先V再U,也就是U/V和V/U的区别;这个是Android上摄像头图像的标准
  • I420 (也叫YU12),FourCC为0x30323449 ,1个像素12 bit,8 bit Y 通道平面结束后,是U通道平面,最后是V通道平面;
  • YV12 ,FourCC为0x32315659,1个像素12 bit,8 bit Y 通道平面结束后,是V通道平面,最后是U通道平面;

4、YUV420的图片旋转:

1、Y表示亮度(Luminance),也就是灰度值。 "U"和"V"表示的则是色度(Chrominance),描述影像色彩及其饱和度,用于指定像素的颜色。

2、YUV的四个结论:

三个结论先记好:
1.YUV 4:4:4采样,每一个Y对应一组UV分量。
2.YUV 4:2:2采样,每两个Y共用一组UV分量。
3.YUV 4:2:0采样,每四个Y共用一组UV分量。

3、特别提醒旋转90和270后宽高要记得对调,不然会花屏

5、花屏问题该如何解决:音视频开发之视频花屏问题汇总分析

除了需要注意的,旋转的时候,一定要注意宽高对调。其他的问题,可以从上面找找答案。

2、YUV420sp 图片旋转90度

网上应该可以找到更好的,这个是我找到的,这里只是做一个记录,只有一个90度的:

void CUsbVideo::_Yuv420spRotate90(unsigned char* _pYUV, unsigned char* _pYUV90)
{
    int width = m_iHeight;
    int height = m_iWidth;
    int count = 0;
    int uvHeight = height >> 1;
    int imgSize = width * height;
    //    byte[] des = new byte[imgSize * 3 >> 1];
    //copy y
    for (int j = width - 1; j >= 0; j--) {
        for (int i = 0; i < height; i++) {
            _pYUV90[count++] = _pYUV[width * i + j];
        }
    }
    //u,v
    for (int j = width - 1; j > 0; j -= 2) {
        for (int i = 0; i < uvHeight; i++) {
            _pYUV90[count++] = _pYUV[imgSize + width * i + j - 1];
            _pYUV90[count++] = _pYUV[imgSize + width * i + j];
        }
    }
}
3、YUV NV21或NV12的旋转

这个代码没有测试过,不保真,麻烦自己测试下,有啥问题,概不负责。

void CUsbVideo::_Yuv420Rotate180(unsigned char* _pYUV, unsigned char* _pYUV180)
{
    //旋转180:将右下角的点作为第一个点,从右往左,从下往上取点
    //Y 宽:[0,w-1]  高:[0,h-1]
    int idx = 0;
    for (int i = m_iHeight-1; i >=0; i--){
        for (int j = m_iWidth-1 ; j >= 0; j--){
            _pYUV180[idx++] = *(_pYUV+(i*m_iWidth+j));
        }
    }
    uint8_t* uheader = _pYUV + m_iWidth*m_iHeight;
    uint8_t* vheader = uheader + m_iWidth*m_iHeight/4;
    int iVSize = m_iWidth*m_iHeight/4;
    //U
    for (int i = m_iHeight/2 - 1; i >= 0; i--){
        for (int j = m_iWidth/2-1 ; j >= 0; j--){
            _pYUV180[idx] = *(uheader + (i*m_iWidth / 2 + j));
            _pYUV180[idx++ + iVSize] = *(vheader + (i*m_iWidth / 2 + j));
        }
    }
}

void CUsbVideo::_Yuv420RotateFlip(unsigned char* _pYUV, unsigned char* _pYUVFlip)
{
    int idx = 0;
    //水平翻转:将右上角的点作为第一个点,从右往左,从上往下取点
    //Y 宽:[0,w-1]  高:[0,h-1]
    for (int i = 0; i<m_iHeight; i++){
        for (int j = m_iWidth - 1; j >= 0; j--){
            _pYUVFlip[idx++] = *(_pYUV + (i*m_iWidth + j));
        }
    }
    uint8_t* uheader = _pYUV + m_iWidth*m_iHeight;
    uint8_t* vheader = uheader + m_iWidth*m_iHeight / 4;
    int iVSize = m_iWidth*m_iHeight / 4;
    //U
    for (int i = 0; i < m_iHeight / 2 ; i++){
        for (int j = m_iWidth / 2 - 1; j >= 0; j--){
            _pYUVFlip[idx] = *(uheader + (i*m_iWidth / 2 + j));
            _pYUVFlip[idx++ + iVSize] = *(vheader + (i*m_iWidth / 2 + j));
        }
    }
}

void CUsbVideo::_Yuv420Rotate270(unsigned char* _pYUV, unsigned char* _pYUV270)
{
    // Rotate the Y luma
    int width = m_iHeight;
    int height = m_iWidth;
    int count = 0;
    int uvHeight = height >> 1;
    //copy y
    for (int j = width - 1; j >= 0; j--) {
        for (int i = 0; i < height; i++) {
            _pYUV270[count++] = _pYUV[width * i + j];
        }
    }
    //u,v
    uint8_t* uheader = _pYUV + m_iWidth * m_iHeight;
    uint8_t* vheader = uheader + m_iWidth * m_iHeight / 4;
    int iVSize = m_iWidth*m_iHeight / 4;
    width = width / 2;
    for (int j = width - 1; j > 0; j--) {
        for (int i = 0; i < uvHeight; i++) {
            _pYUV270[count] = uheader[width * i + j];
            _pYUV270[count++ + iVSize] = vheader[+ width * i + j];
        }
    }
}

void CUsbVideo::_Yuv420Rotate90(unsigned char* _pYUV, unsigned char* _pYUV90)
{
    // Rotate the Y luma
    int width = m_iHeight;
    int height = m_iWidth;
    int count = 0;
    int uvHeight = height >> 1;
    //copy y
    for (int j = 0; j < width; j++) {
        for (int i = 0; i < height; i++) {
            _pYUV90[count++] = _pYUV[width * i + j];
        }
    }
    //u,v
    uint8_t* uheader = _pYUV + m_iWidth * m_iHeight;
    uint8_t* vheader = uheader + m_iWidth * m_iHeight / 4;
    int iVSize = m_iWidth*m_iHeight / 4;
    width = width / 2;
    for (int j = 0; j < width; j++) {
        for (int i = 0; i < uvHeight; i++) {
            _pYUV90[count] = uheader[width * i + j];
            _pYUV90[count++ + iVSize] = vheader[+ width * i + j];
        }
    }
}
4、使用OpenCV进行图像翻转
Mat angleRectify(Mat img, float angle)
{
	cout << "--> angleRectify:" << img.rows << img.cols;
	Mat retMat = Mat::zeros(img.cols, img.rows, CV_8UC3);
	float anglePI = (float)(angle * CV_PI / 180);
	int xSm, ySm;
	for (int i = 0; i < retMat.rows; i++)
		for (int j = 0; j < retMat.cols; j++)
		{
			xSm = (int)((i - retMat.rows / 2) * cos(anglePI) - (j - retMat.cols / 2) * sin(anglePI) + 0.5);
			ySm = (int)((i - retMat.rows / 2) * sin(anglePI) + (j - retMat.cols / 2) * cos(anglePI) + 0.5);
			xSm += img.rows / 2;
			ySm += img.cols / 2;
			if (xSm >= img.rows || ySm >= img.cols || xSm <= 0 || ySm <= 0) {
				retMat.at<Vec3b>(i, j) = Vec3b(0, 0);
			}
			else {
				retMat.at<Vec3b>(i, j) = img.at<Vec3b>(xSm, ySm);
			}
		}
	return retMat;
}

Mat dstImg2 = angleRectify(srcImg, 270);
imshow("src", srcImg);

参考

你可能感兴趣的:(Linux,OpenCV,qt,c++,开发语言)