使用 OpenCV 将图像从 YV12 转换为 NV12

如何解决使用 OpenCV 将图像从 YV12 转换为 NV12

我有一个网络摄像头,可以从中读取 NV12 格式的帧。我将帧转换为 RGB,然后转换为 YV12,我的目标是将它们转换回 NV12,以进行验证。我正在做这样的事情:

cv::cvtColor(InputFrame,InputRGB,cv::COLOR_YUV2RGB_NV12);
cv::cvtColor(InputRGB,OutputYV12,cv::COLOR_RGB2YUV_YV12);

我编写了以下函数来从 YV12 转换为 NV12(类似于这篇文章 - Convert YV12 to NV21 (YUV YCrCb 4:2:0)),这似乎不起作用。我得到的灰度图像上半部分混合了模糊的洋红色副本,结果图像的下半部分混合了模糊的绿色副本。

在我下面的函数中,我假设了一个布局,其中 V 平面位于矩阵中的 U 平面旁边。我不知道这是否正确。我首先尝试遵循 YV12 的布局,如 https://docs.microsoft.com/en-us/windows/win32/medfound/recommended-8-bit-yuv-formats-for-video-rendering 所示,其中 U/V 平面位于彼此下方而不是彼此相邻,但这导致了崩溃。

void YV12toNV12(const cv::Mat& input,cv::Mat& output,int width,int height) {

        input.copyTo(output);

        for (int row = 0; row < height/2; row++) {
                for (int col = 0; col < width/2; coL++) {
                        output.at<uchar>(height + row,2 * col) = input.at<uchar>(height + row,col);
                        output.at<uchar>(height + row,2 * col + 1) = input.at<uchar>(height + row,width/2 + col);
                }
        }
}

任何提示表示赞赏。

解决方法

使用索引应用转换令人困惑。
我的建议是将 YV12 图像视为 3 个单独的图像。

  • Y(宽 x 高)- 顶部图像。
  • V(宽/2 x 高/2) - 低于 Y
  • U(宽/2 x 高/2) - 低于 V

根据 following 文档:

YV12 与 I420 完全一样,但 U 和 V 平面的顺序相反。

I420 订购为 here:
enter image description here

BGR to I420 转换受 OpenCV 支持,并且与 YV12 相比文档格式更多,因此我们最好从 I420 开始测试,然后继续使用 YV12(通过切换 U 和 V 通道)。


主要思想是用 cv:Mat 对象“包装”V 和 U 矩阵(通过向输入 data 指针添加偏移量来设置矩阵 data 指针)。

  • inV 在 Y 之后(每个轴的分辨率减半,步幅减半):
    cv::Mat inV = cv::Mat(cv::Size(width/2,height/2),CV_8UC1,(unsigned char*)input.data + stride*height,stride/2);
  • inU 在 V 之后(每个轴的分辨率减半,步幅减半):
    cv::Mat inU = cv::Mat(cv::Size(width/2,(unsigned char*)input.data + stride*height + (stride/2)*(height/2),stride/2);

这里是转换函数:

void YV12toNV12(const cv::Mat& input,cv::Mat& output) {
    int width = input.cols;
    int height = input.rows * 2 / 3;
    int stride = (int)input.step[0];    //Rows bytes stride - in most cases equal to width

    input.copyTo(output);

    //Y Channel
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY

    //V Input channel
    // VVVVVVVV
    // VVVVVVVV
    // VVVVVVVV
    cv::Mat inV = cv::Mat(cv::Size(width / 2,height / 2),(unsigned char*)input.data + stride * height,stride / 2);   // Input V color channel (in YV12 V is above U).

    //U Input channel
    // UUUUUUUU
    // UUUUUUUU
    // UUUUUUUU
    cv::Mat inU = cv::Mat(cv::Size(width / 2,(unsigned char*)input.data + stride * height + (stride / 2)*(height / 2),stride / 2);  //Input V color channel (in YV12 U is below V).

    for (int row = 0; row < height / 2; row++) {
        for (int col = 0; col < width / 2; col++) {
            output.at<uchar>(height + row,2 * col) = inU.at<uchar>(row,col);
            output.at<uchar>(height + row,2 * col + 1) = inV.at<uchar>(row,col);
        }
    }
}

实施和测试:

使用 FFmpeg 命令行工具创建 NV12 示例图像:

ffmpeg -y -f lavfi -i testsrc=size=192x108:rate=1:duration=1 -pix_fmt nv12 -f rawvideo test.nv12
ffmpeg -y -f rawvideo -pixel_format gray -video_size 192x162 -i test.nv12 -pix_fmt gray test_nv12.png

使用 MATLAB(或 OCTAVE)创建 YV12 示例图像:

NV12 = imread('test_nv12.png');
Y = NV12(1:108,:);
U = NV12(109:end,1:2:end);
V = NV12(109:end,2:2:end);

f = fopen('test.yv12','w');
fwrite(f,Y','uint8');
fwrite(f,V',U','uint8');
fclose(f);

f = fopen('test.yv12','r');
I = fread(f,[192,108*1.5],'*uint8')';
fclose(f);
imwrite(I,'test_yv12.png');

C++ 实现(I420toNV12 和 YV12toNV12):

#include "opencv2/opencv.hpp"

void YV12toNV12(const cv::Mat& input,col);
        }
    }
}


void I420toNV12(const cv::Mat& input,cv::Mat& output) {
    int width = input.cols;
    int height = input.rows * 2 / 3;
    int stride = (int)input.step[0];    //Rows bytes stride - in most cases equal to width
    
    input.copyTo(output);

    //Y Channel
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY
    // YYYYYYYYYYYYYYYY
    
    //U Input channel
    // UUUUUUUU
    // UUUUUUUU
    // UUUUUUUU
    cv::Mat inU = cv::Mat(cv::Size(width / 2,stride / 2);   // Input U color channel (in I420 U is above V).

    //V Input channel
    // VVVVVVVV
    // VVVVVVVV
    // VVVVVVVV
    cv::Mat inV = cv::Mat(cv::Size(width/2,stride/2);  //Input V color channel (in I420 V is below U).

    for (int row = 0; row < height / 2; row++) {
        for (int col = 0; col < width / 2; col++) {
            output.at<uchar>(height + row,col);
        }
    }
}


int main()
{   
    //cv::Mat input = cv::imread("test_I420.png",cv::IMREAD_GRAYSCALE);
    //cv::Mat output;
    //I420toNV12(input,output);
    //cv::imwrite("output_NV12.png",output);

    cv::Mat input = cv::imread("test_YV12.png",cv::IMREAD_GRAYSCALE);
    cv::Mat output;

    YV12toNV12(input,output);
    cv::imwrite("output_NV12.png",output);

    cv::imshow("input",input);
    cv::imshow("output",output);
    cv::waitKey(0);
    cv::destroyAllWindows();
}

使用 MATLAB(或 OCTAVE)测试输出:

A = imread('test_nv12.png');
B = imread('output_NV12.png');
display(isequal(A,B))

输入(YV12作为灰度图像):
enter image description here

输入(NV12作为灰度图像):
enter image description here

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其他元素将获得点击?
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。)
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbcDriver发生异常。为什么?
这是用Java进行XML解析的最佳库。
Java的PriorityQueue的内置迭代器不会以任何特定顺序遍历数据结构。为什么?
如何在Java中聆听按键时移动图像。
Java“Program to an interface”。这是什么意思?
Java在半透明框架/面板/组件上重新绘画。
Java“ Class.forName()”和“ Class.forName()。newInstance()”之间有什么区别?
在此环境中不提供编译器。也许是在JRE而不是JDK上运行?
Java用相同的方法在一个类中实现两个接口。哪种接口方法被覆盖?
Java 什么是Runtime.getRuntime()。totalMemory()和freeMemory()?
java.library.path中的java.lang.UnsatisfiedLinkError否*****。dll
JavaFX“位置是必需的。” 即使在同一包装中
Java 导入两个具有相同名称的类。怎么处理?
Java 是否应该在HttpServletResponse.getOutputStream()/。getWriter()上调用.close()?
Java RegEx元字符(。)和普通点?