首页 > 解决方案 > opencv 无法打开 yuv422 图像,而 rawpixels.net 可以显示图像

问题描述

我正在尝试打开一个 yuv 格式的图像。我可以用 rawpixels.net 打开它并在设置以下内容后显示它

width:1920
height:1080
predefined format: yuv420 (nv12)
pixel format yuv

但是,如果我使用以下代码打开 opencv,我将无法打开。

#include <iostream>
#include <opencv2/core.hpp>
#include <opencv2/opencv.hpp>

int main() {
    std::cout << "OpenCV version: " << CV_VERSION << std::endl;


    cv::Mat image = cv::imread("camera_capture_256_2020_10_07_11_11_02.yuv");
    if (image.empty() == true) {

        std::cout << "image empty"<< std::endl;

        return 0;
    }   
        
    cv::imshow("opencv_logo", image);
    cv::waitKey(0);    

    return 0;
}

该程序打印为“图像为空”。

我很困惑为什么我不能用opencv打开文件。

示例图像可在此处找到。

用 rawpixels.net 打开的 yuv 图像看起来像这样。

在此处输入图像描述

谢谢,

标签: c++imageopencvyuvlibyuv

解决方案


处理原始(RGB、BGR、YUV、NV12 和其他)图像时要做的第一件事是了解图像的像素尺寸——没有这些你真的很迷茫——尽管你可以做一些技巧来寻找相关性以找到行宽,因为每一行通常与上面的行基本相似。


接下来是检查文件大小是否正确。因此,如果它是 RGB 和 8 位 1920x1080,则您的文件大小必须为 1920x1080x3 像素 - 如果不是,则存在问题。您的图像为 1920x1080 和 NV12,即每像素 12 位或 1.5 字节,所以我希望您的文件为 1920x1080*1.5 字节。不是这样的,所以马上就有问题了。有一个标题,或多个帧或尾随数据或其他一些问题。

那么,文件中的图像数据在哪里呢?在开始时?在最后?解决此问题的一种方法是将文件视为纯粹的灰度图像,并查看是否有大块零字节或填充的黑色块。由于没有已知的图像大小,我通常以字节为单位获取文件大小并访问Wolfram Alpha网站并输入“XXX 的因子”,其中 XXX 是文件大小,然后选择文件大小平方根附近的 2 个数字所以我得到了一个方形的图像。因此,对于您的,我选择了 2720x3072 并将您的文件视为该尺寸的单个灰度图像。在终端中使用ImageMagick :

magick -depth 8 -size 2720x3072 gray:camera_preview_250_2020_10_07_11_11_02.yuv image.jpg

在此处输入图像描述

我可以一眼看出,数据在文件的开头,而文件的结尾是零填充,即黑色。如果黑色出现在图像的开头,我会获取最后的H x W x 1.5字节。

此步骤的另一种替代方法是以字节为单位的文件大小,然后将其除以图像宽度以获得行数并查看其外观。因此,您的文件是 8355840 字节,即 8355840/1920 或 4,325 行。让我们试试:

magick -depth 8 -size 1920x4352 gray:camera_preview_250_2020_10_07_11_11_02.yuv image.jpg

在此处输入图像描述

这是非常令人鼓舞的,因为我们可以看到Y文件开头的(灰度)图像和一些较低分辨率的UV通道,并且后面没有 2 个单独的通道这一事实可能意味着它们是隔行扫描、交替UV采样而不是平面U样品后跟V样品。


好的,如果您的数据是 YUV 或 NV12,那么最好的工具就是ffmpeg. 我们已经知道数据位于文件的开头,并且我们知道尺寸和格式。我们也知道图像后面有填充,所以我们只需要像这样取第一帧:

ffmpeg -s 1920x1080 -pix_fmt nv12 -i cam*yuv -frames:v 1 image.png

在此处输入图像描述


现在我们对尺寸和格式有了信心,我们需要OpenCV来读取它。普通人cv2.imread()无法读取它,因为它只是原始数据,并且与 JPEG、PNG 或 TIFF 不同,标题中没有图像高度和宽度——它只是纯粹的传感器数据。

因此,您需要使用常规的 C/C++read()系统调用来获取前 1920x1080x1.5 字节。然后您需要调用cv2.cvtColor()接收到的缓冲区将其转换为常规 BGR 格式Mat


推荐阅读