zoukankan      html  css  js  c++  java
  • 从 AVFrame 中取出帧(YUV)保存为 Mat 格式

    由于 cnblogs 不支持科学公式,完整内容请移步原文链接

    原文地址:从 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 分量

    综上,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);
            }
        }
    }
    
  • 相关阅读:
    软件工程15 结对编程作业
    软工网络15个人阅读作业2——提问题
    软件工程网络15个人阅读作业1
    第15周-反射与JSP
    Java课程设计-定时器(团队)
    Java课程设计-定时器
    第14周-数据库
    网络15软工个人作业5——软件工程总结
    软工网络15个人作业4——alpha阶段个人总结
    软工网络15个人作业3——案例分析
  • 原文地址:https://www.cnblogs.com/leviatan/p/11166221.html
Copyright © 2011-2022 走看看