如何从 OpenCV 中的 YUV 文件中读取帧?

Posted

技术标签:

【中文标题】如何从 OpenCV 中的 YUV 文件中读取帧?【英文标题】:How to read a frame from YUV file in OpenCV? 【发布时间】:2011-01-14 22:45:50 【问题描述】:

如何在 OpenCV 中从 YUV 文件中读取一帧?

【问题讨论】:

【参考方案1】:

供将来参考:我已将 @xianyanlin's brilliant answer 转换为 Python 3。以下代码适用于从 Raspberry Pi 相机拍摄的视频,并且似乎输出正确的颜色和纵横比。

警告:它使用 numpy 格式来指定高度 * 宽度的分辨率,例如1080 * 1920, 480 * 640。

class VideoCaptureYUV:
    def __init__(self, filename, size):
        self.height, self.width = size
        self.frame_len = self.width * self.height * 3 // 2
        self.f = open(filename, 'rb')
        self.shape = (int(self.height*1.5), self.width)

    def read_raw(self):
        try:
            raw = self.f.read(self.frame_len)
            yuv = np.frombuffer(raw, dtype=np.uint8)
            yuv = yuv.reshape(self.shape)
        except Exception as e:
            print(str(e))
            return False, None
        return True, yuv

    def read(self):
        ret, yuv = self.read_raw()
        if not ret:
            return ret, yuv
        bgr = cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR_I420, 3)
        return ret, bgr

【讨论】:

【参考方案2】:

我写了一个非常简单的python代码来从二进制文件中读取YUV NV21流。

import cv2
import numpy as np

class VideoCaptureYUV:
    def __init__(self, filename, size):
        self.height, self.width = size
        self.frame_len = self.width * self.height * 3 / 2
        self.f = open(filename, 'rb')
        self.shape = (int(self.height*1.5), self.width)

    def read_raw(self):
        try:
            raw = self.f.read(self.frame_len)
            yuv = np.frombuffer(raw, dtype=np.uint8)
            yuv = yuv.reshape(self.shape)
        except Exception as e:
            print str(e)
            return False, None
        return True, yuv

    def read(self):
        ret, yuv = self.read_raw()
        if not ret:
            return ret, yuv
        bgr = cv2.cvtColor(yuv, cv2.COLOR_YUV2BGR_NV21)
        return ret, bgr


if __name__ == "__main__":
    #filename = "data/20171214180916RGB.yuv"
    filename = "data/20171214180916IR.yuv"
    size = (480, 640)
    cap = VideoCaptureYUV(filename, size)

    while 1:
        ret, frame = cap.read()
        if ret:
            cv2.imshow("frame", frame)
            cv2.waitKey(30)
        else:
            break

【讨论】:

对于 YUV 4:2:2,frame_len 与2 相乘,形状将变为self.shape = (self.height, self.width, 2),并且转换颜色代码也需要更改为 YUV 422 系列代码之一。 docs.opencv.org/3.1.0/d7/d1b/…【参考方案3】:

如前所述,YUV 格式有很多种:

http://www.fourcc.org/yuv.php

在 OpenCV 中从 YUV 格式转换为 RGB 非常简单:

    为该帧数据创建一个大小合适的一维 OpenCV Mat 为具有所需尺寸和 3 个通道的 RGB 数据创建一个空 Mat 最后使用 cvtColor 在两个 Mats 之间进行转换,使用正确的转换标志枚举

以下是 YV12 格式的 YUV 缓冲区示例:

Mat mYUV(height + height/2, width, CV_8UC1, (void*) frameData);
Mat mRGB(height, width, CV_8UC3);
cvtColor(mYUV, mRGB, CV_YUV2RGB_YV12, 3);

关键技巧是在转换之前定义 RGB Mat 的尺寸。

【讨论】:

这是正确的答案。我正在处理 YUV 的 NV12 变体,这些帮助我理解了格式:wiki.videolan.org/YUV/#NV12, commons.wikimedia.org/wiki/…【参考方案4】:

更新这里有更新版本的代码:https://github.com/chelyaev/opencv-yuv

我发布了一些代码,可以读取 单个 YUV 4:2:0 平面图像文件。您可以直接将其应用于大多数 YUV 文件(只需继续从同一个 FILE 对象读取)。 例外是在处理YUV files that have a header 时(通常,它们具有*.y4m 扩展名)。如果你想处理这样的文件,你有两种选择:

    在使用下面的代码之前,编写您自己的函数来使用来自FILE 对象的标头数据 从 *.y4m 图像中去除标题(使用 ffmpeg 或类似工具)。这是我更喜欢的选项,因为它最简单。

它也不适用于任何其他形式的 YUV 格式(非平面、不同的色度抽取)。正如@Stephane 指出的那样,有很多这样的格式(并且大多数都没有任何识别标头),这可能是 OpenCV 不支持开箱即用的原因。

但使用它们相当简单:

从图像及其尺寸开始(读取 YUV 文件时需要这样做) 将亮度和色度读取到 3 张单独的图像中 将色度图像放大 2 倍以补偿色度抽取。 请注意,实际上有几种方法可以补偿色度抽取。上采样只是最简单的 合并成 YUV 图像。如果你想要 RGB,你可以使用cvCvtColor

最后是代码:

IplImage * 
cvLoadImageYUV(FILE *fin, int w, int h)

    assert(fin);

    IplImage *py      = cvCreateImage(cvSize(w,    h), IPL_DEPTH_8U, 1);
    IplImage *pu      = cvCreateImage(cvSize(w/2,h/2), IPL_DEPTH_8U, 1);
    IplImage *pv      = cvCreateImage(cvSize(w/2,h/2), IPL_DEPTH_8U, 1);
    IplImage *pu_big  = cvCreateImage(cvSize(w,    h), IPL_DEPTH_8U, 1);
    IplImage *pv_big  = cvCreateImage(cvSize(w,    h), IPL_DEPTH_8U, 1);
    IplImage *image   = cvCreateImage(cvSize(w,    h), IPL_DEPTH_8U, 3);
    IplImage *result  = NULL;

    assert(py);
    assert(pu);
    assert(pv);
    assert(pu_big);
    assert(pv_big);
    assert(image);

    for (int i = 0; i < w*h; ++i)
    
        int j = fgetc(fin);
        if (j < 0)
            goto cleanup;
        py->imageData[i] = (unsigned char) j;
    

    for (int i = 0; i < w*h/4; ++i)
    
        int j = fgetc(fin);
        if (j < 0)
            goto cleanup;
        pu->imageData[i] = (unsigned char) j;
    

    for (int i = 0; i < w*h/4; ++i)
    
        int j = fgetc(fin);
        if (j < 0)
            goto cleanup;
        pv->imageData[i] = (unsigned char) j;
    

    cvResize(pu, pu_big, CV_INTER_NN);
    cvResize(pv, pv_big, CV_INTER_NN);
    cvMerge(py, pu_big, pv_big, NULL, image);

    result = image;

cleanup:
    cvReleaseImage(&pu);
    cvReleaseImage(&pv);

    cvReleaseImage(&py);
    cvReleaseImage(&pu_big);
    cvReleaseImage(&pv_big);

    if (result == NULL)
        cvReleaseImage(&image);

    return result;

【讨论】:

我现在也有同样的问题,我正在尝试打开并使用具有 UYVY(4:2:2) 作为编解码器的视频,我尝试了你的代码,但它不起作用我知道您在回答中提到了这一点,但您能说出原因吗?提前感谢您的帮助 我发布的代码处理 YUV 4:2:0。由于您的视频在 YUV 4:2:2 中,那么我的代码肯定不会直接在您的视频上运行。您将需要调整代码以处理您的格式。更多详情请见:en.wikipedia.org/wiki/Chroma_subsampling#4:2:2【参考方案5】:

我遇到了同样的问题。我的解决方案是 1.将一个yuv帧(如I420)读入一个字符串对象“yuv”。 2.将yuv帧转换为BGR24格式。我使用 libyuv 来做到这一点。为 libyuv 函数编写 python 包装器很容易。现在你得到另一个 BGR24 格式的字符串对象“bgr”。 3. 使用 numpy.fromstring 从“bgr”字符串对象中获取图像对象。您需要更改图像对象的形状。

下面是一个简单的 yuv 查看器供您参考。

import cv2
# below is the extension wrapper for libyuv
import yuvtorgb
import numpy as np

f = open('i420_cif.yuv', 'rb')

w = 352
h = 288
size = 352*288*3/2

while True:
    try:
        yuv = f.read(size)
    except:
        break
    if len(yuv) != size:
        f.seek(0, 0)
        continue

    bgr = yuvtorgb.i420_to_bgr24(yuv, w, h)

    img = np.fromstring(bgr, dtype=np.uint8)
    img.shape = h,w,3

    cv2.imshow('img', img)

    if cv2.waitKey(50) & 0xFF == ord('q'):
        break

cv2.destroyAllWindows()

【讨论】:

【参考方案6】:

我认为至少在当前版本中是不可能的。当然,做起来并不难,但也不是什么有趣的功能,比如:

OpenCV 通常适用于 RGB 格式的网络摄像头流,或直接解码为 RGB 用于显示目的的编码文件; OpenCV 专用于计算机视觉,其中 YUV 是一种比 Coding 社区更不常见的格式; 有许多不同的 YUV 格式,这意味着实现它们需要大量工作。

尽管使用cvCvtColor(),仍然可以进行转换,这意味着无论如何它还是有一些意义的。

【讨论】:

以上是关于如何从 OpenCV 中的 YUV 文件中读取帧?的主要内容,如果未能解决你的问题,请参考以下文章

OpenCV 从 YUYV 像素格式视频帧创建 YUV420 或灰度 Mat 对象

在YUV_420_888中将图像从Android发送到OpenCV Mat中的JNI的最有效方式

从 QByteArray 创建一个适当的 cv::Mat,包含 YUV420p 帧

从 OpenCV 中的 VideoCapture 中读取每第 n 帧

opencv读取ENVI标准格式

如何opencv读取摄像头并保存每一帧图像?