从 AVFrame 中取出帧(YUV)保存为 Mat 格式

从 AVFrame 中取出帧(YUV)保存为 Mat 格式

本文档针对 YUV420p 编码进行记录

AVFrame 结构体解析

这里列出一些重点变量

变量定义
用途
备注
uint8_t *data[AV_NUM_DATA_POINTERS]解码后原始数据
int linesize[AV_NUM_DATA_POINTERS]data中“一行”数据的大小一般大于图像的宽
int width, height视频帧宽和高
int nb_samples一个 AVFrame 中包含多少个音频帧一个 AVFrame 中只包含一个视频帧,但可能包含多个音频帧
int format解码后原始数据类型YUV420, YUV422, RGB24…
int key_frame是否是关键帧
enum AVPictureType pict_type帧类型I, B, P…
AVRational sample_aspect_ratio宽高比16:9, 4:3…
int64_t pts显示时间戳
int coded_picture_number编码帧序号
int display_picture_number帧序号
int interlaced_frame是否是隔行扫描

YUV 转 RGB 原理

YUV 图像有两种编码格式:

紧缩格式(packed formats): Y、U、V 三通道像素值依次排列,即 Y0 U0 V0 Y1 U1 V1 …

平面格式(planar formats): 先排列 Y 的所有像素值,再排列 U,最后排列 V

YUV420p 中使用平面格式,水平 2:1 取样,垂直 2:1 采样,即每 4 个 Y 分量对应一个 U、V 分量

ITU 在 BT.601[50] 中规定: ( ω R = 0.299 , ω = 0.114 ( ω G = 1 − ω B − ω R = 0.587 ) (\omega_R=0.299,\omega=0.114(\omega_G=1-\omega_B-\omega_R=0.587) (ωR=0.299,ω=0.114(ωG=1ωBωR=0.587)

变换可以写为:

( Y C b C y ) = ( 0.299 0.587 0.114 − 0.169 − 0.331 0.500 0.500 − 0.419 − 0.081 ) ⋅ ( R G B ) \begin{pmatrix}Y\\\\C_b\\\\C_y\end{pmatrix}=\begin{pmatrix}0.299&0.587&0.114\\\\-0.169&-0.331&0.500\\\\0.500&-0.419&-0.081\end{pmatrix}\cdot\begin{pmatrix}R\\\\G\\\\B\end{pmatrix} YCbCy=0.2990.1690.5000.5870.3310.4190.1140.5000.081RGB

反变换可写为:

( R B G ) = ( 1.000 0.000 1.403 1.000 − 0.344 − 0.714 1.000 1.773 0.000 ) ⋅ ( R G B ) \begin{pmatrix}R\\\\B\\\\G\end{pmatrix}=\begin{pmatrix}1.000&0.000&1.403\\\\1.000&-0.344&-0.714\\\\1.000&1.773&0.000\end{pmatrix}\cdot\begin{pmatrix}R\\\\G\\\\B\end{pmatrix} RBG=1.0001.0001.0000.0000.3441.7731.4030.7140.000RGB

颜色空间的应用不同,权值的规定也不同,比如在 ITU-BT.709[55] 中规定数字产品中 ω R = 0.2125 , ω B = 0.0721 \omega_R=0.2125,\omega_B=0.0721 ωR=0.2125,ωB=0.0721 C b C_b Cb C r C_r Cr 可能同时为正值或负值,为了便于对其进行数字化编码,通常会加上一个合适的便宜,使其始终为正值,比如对于 8 位的分量,该偏移值为 2 7 = 128 2^7=128 27=128

综上,YUV 与 RGB 的转换公式如下

R = Y + 1.4075 * (V - 128)
G = Y - 0.3455 * (U - 128) - (0.7169 * (V - 128))
B = Y + 1.7790 * (U - 128)

Y = R *  .299000 + G *  .587000 + B *  .114000
U = R * -.168736 + G * -.331264 + B *  .500000 + 128
V = R *  .500000 + G * -.418688 + B * -.081312 + 128

下面的公式中各个符号都带了 ',表示该符号在原值基础上进行了伽马校正,伽马校正有助于弥补在抗锯齿的过程中,线性分配伽马值所带来的细节损失,使图像细节更加丰富。在没有采用伽马校正的情况下,暗部细节不容易显现出来,而采用了这一图像增强技术以后,图像的层次更加清晰

Y' = 0.257*R' + 0.504*G' + 0.098*B' + 16
Cb' = -0.148*R' - 0.291*G' + 0.439*B' + 128
Cr' = 0.439*R' - 0.368*G' - 0.071*B' + 128
R' = 1.164*(Y’-16) + 1.596*(Cr'-128)
G' = 1.164*(Y’-16) - 0.813*(Cr'-128) - 0.392*(Cb'-128)
B' = 1.164*(Y’-16) + 2.017*(Cb'-128)

实现代码

在 AVFrame2Img 中输入需要转换格式的 AVFrame,返回 Mat

Mat AVFrame2Img(AVFrame *frame) {
    // 获取帧宽、高及通道数量
    int frame_height = frame->height;
    int frame_width = frame->width;
    int frame_channels = 3;

    // 初始化 Mat
    Mat img = Mat::zeros(frame_height, frame_width, CV_8UC3);

    // 初始化存放 YUV 编码图片的 buffer 内存空间
    uchar* yuv_buffer = (uchar*)malloc(frame_height * frame_width * sizeof(uchar)*frame_channels);

    // 获取图片原始数据
    // Y
    for (int i = 0; i < frame_height; i++) {
        memcpy(yuv_buffer + frame_width * i,
               frame->data[0] + frame->linesize[0] * i,
               frame_width);
    }
    // U
    for (int j = 0; j < frame_height / 2; j++) {
        memcpy(yuv_buffer + frame_width * frame_height + frame_width / 2 * j,
               frame->data[1] + frame->linesize[1] * j,
               frame_width / 2);
    }
    // V
    for (int k = 0; k < frame_height / 2; k++) {
        memcpy(yuv_buffer + frame_width * frame_height + frame_width / 2 * (frame_height / 2) + frame_width / 2 * k,
               frame->data[2] + frame->linesize[2] * k,
               frame_width / 2);
    }

    // 转换为 RGB 编码
    YUV420P2RGB32(yuv_buffer, img.data, frame_width, frame_height);

    // 释放 buffer 内存空间
    free(yuv_buffer);
    return img;
}

void YUV420P2RGB32(const uchar *yuv_buffer_in, const uchar *rgb_buffer_out, int width, int height) {
    uchar *yuv_buffer = (uchar *)yuv_buffer_in;
    uchar *rgb_buffer = (uchar *)rgb_buffer_out;

    int channels = 3;

    for (int y = 0; y < height; y++) {
        for (int x = 0; x < width; x++) {
            int index_Y = y * width + x;
            int index_U = width * height + y / 2 * width / 2 + x / 2;
            int index_V = width * height + width * height / 4 + y / 2 * width / 2 + x / 2;

            // 取出 YUV
            uchar Y = yuv_buffer[index_Y];
            uchar U = yuv_buffer[index_U];
            uchar V = yuv_buffer[index_V];

            // YCbCr420
            int R = Y + 1.402 * (V - 128);
            int G = Y - 0.34413 * (U - 128) - 0.71414*(V - 128);
            int B = Y + 1.772*(U - 128);

            // Y'Cb'Cr'420
//            int R = 1.164 * (Y - 16) + 1.596 * (V - 128);
//            int G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.392 * (U - 128);
//            int B = 1.164 * (Y - 16) + 2.017 * (U - 128);

            // 确保取值范围在 0 - 255 中
            R = (R < 0) ? 0 : R;
            G = (G < 0) ? 0 : G;
            B = (B < 0) ? 0 : B;
            R = (R > 255) ? 255 : R;
            G = (G > 255) ? 255 : G;
            B = (B > 255) ? 255 : B;

            rgb_buffer[(y*width + x)*channels + 2] = uchar(R);
            rgb_buffer[(y*width + x)*channels + 1] = uchar(G);
            rgb_buffer[(y*width + x)*channels + 0] = uchar(B);
        }
    }
}
  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值