使用sws_Scale()AVFrame YUV 420 p-> openCV Mat BGR 24和背面的压缩伪影

hfwmuf9z  于 2023-10-24  发布在  其他
关注(0)|答案(1)|浏览(280)

我使用C++和FFmpeg将.mp4容器中的H264视频转换为.mp4容器中的H265视频。这与清晰的图像和通过FFprobe检查确认的编码转换完美地结合在一起。
然后,我在H264解码结束和H265编码开始之间调用一个额外的函数。此时,我有一个分配的AVFrame*,我将其作为参数传递给该函数。
该函数将AVFrame转换为openCV cv::Mat并向后转换。从技术上讲,这是简单的部分,但我在此过程中遇到了压缩伪影问题,我不明白为什么会发生。
函数代码(包括以下问题的解决方法)如下所示:

void modifyVideoFrame(AVFrame * frame)
{
    // STEP 1: WORKAROUND, overwriting AV_PIX_FMT_YUV420P BEFORE both sws_scale() functions below, solves "compression artifacts" problem;
    frame->format = AV_PIX_FMT_RGB24; 
        
    // STEP 2: Convert the FFmpeg AVFrame to an openCV cv::Mat (matrix) object.
    cv::Mat image(frame->height, frame->width, CV_8UC3);
    int clz = image.step1();

    SwsContext* context = sws_getContext(frame->width, frame->height, (AVPixelFormat)frame->format, frame->width, frame->height, AVPixelFormat::AV_PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    sws_scale(context, frame->data, frame->linesize, 0, frame->height, &image.data, &clz);
    sws_freeContext(context);

    // STEP 3 : Change the pixels.
    if (false)
    {
        // TODO when "compression artifacts" problem with baseline YUV420p to BGR24 and back BGR24 to YUV420P is solved or explained and understood.
    }
    
    // UPDATE: Added VISUAL CHECK
    cv::imshow("Visual Check of Conversion AVFrame to cv:Map", image);
    cv::waitKey(20);

    // STEP 4: Convert the openCV Mat object back to the FFmpeg AVframe.
    clz = image.step1();
    context = sws_getContext(frame->width, frame->height, AVPixelFormat::AV_PIX_FMT_BGR24, frame->width, frame->height, (AVPixelFormat)frame->format, SWS_FAST_BILINEAR, NULL, NULL, NULL);
    sws_scale(context, &image.data, &clz, 0, frame->height, frame->data, frame->linesize);
    sws_freeContext(context);
}

所示的代码,包括变通方法,工作得很好,但不被理解。
使用FFprobe,我确定输入像素格式是YUV420p,这实际上是AV_PIX_FMT_YUV420p,在帧格式中找到。如果我将其转换为BGR24,然后再转换回YUV420p,而不使用第1步中的解决方案,那么我会有轻微的压缩伪影,但在使用VLC查看时可以清楚地看到。所以在某个地方有损失,这是我试图理解的。
然而,当我使用第1步中的解决方法时,我会获得完全相同的输出,就好像没有调用这个额外的函数一样(即清晰明确的H265,没有压缩伪影)。为了确保转换发生,我修改了红色值(在代码中现在说if(false)的部分),当用VLC播放H265输出文件时,我确实可以看到变化。
从该测试中可以清楚地看到,在将AVFrame中的输入数据从YUV420 P转换为cv::Map BGR 24之后,将其转换回原始YUV420 P输入数据所需的所有信息和数据都可用。然而,如果没有解决方案,压缩伪影证明了这一点。
我使用的前17秒的电影剪辑“收费”编码在H264和可在'搅拌机'网站。
有没有人有一些解释,或者可以帮助我理解为什么没有解决方案的代码不能很好地将输入数据向前转换,然后向后转换回原始输入数据。
这就是我看到的:

与我使用变通方法或(更新)视觉检查部分(cv::imshow)所看到的相比,如果代码的第4部分被注解:

这些是我在输入上使用的FFmpeg StreamingParams:

copy_audio => 1
copy_video => 0
vid_codec => "libx265"
vid_video_codec_priv_key => "x265-params"
vid_codec_priv_value => "keyint=60:min-keyint=60:scenecut=0"

// Encoder output
x265 [info]: HEVC encoder version 3.5+98-753305aff
x265 [info]: build info [Windows][GCC 12.2.0][64 bit] 8bit+10bit+12bit
x265 [info]: using cpu capabilities: MMX2 SSE2Fast LZCNT SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2
x265 [info]: Main profile, Level-3.1 (Main tier)
x265 [info]: Thread pool 0 using 64 threads on numa nodes 0
x265 [info]: Slices                              : 1
x265 [info]: frame threads / pool features       : 1 / wpp(12 rows)
x265 [info]: Coding QT: max CU size, min CU size : 64 / 8
x265 [info]: Residual QT: max TU size, max depth : 32 / 1 inter / 1 intra
x265 [info]: ME / range / subpel / merge         : hex / 57 / 2 / 2
x265 [info]: Lookahead / bframes / badapt        : 15 / 4 / 0
x265 [info]: b-pyramid / weightp / weightb       : 1 / 1 / 0
x265 [info]: References / ref-limit  cu / depth  : 3 / on / on
x265 [info]: AQ: mode / str / qg-size / cu-tree  : 2 / 1.0 / 32 / 1
x265 [info]: Rate Control / qCompress            : ABR-2000 kbps / 0.60
x265 [info]: VBV/HRD buffer / max-rate / init    : 4000 / 2000 / 0.750
x265 [info]: tools: rd=2 psy-rd=2.00 rskip mode=1 signhide tmvp fast-intra
x265 [info]: tools: strong-intra-smoothing lslices=4 deblock sao
dojqjjoe

dojqjjoe1#

从原始视频数据到H264 YUV 420 P的转换由于每像素12位的子采样(4:2:0)而产生少量损失,并且帧线保持3个平面(linesize,linesize/2,linesize/2)。
当我将其转换为RGB 24或BGR 24以用于openCV的cv::Mat时,原始的未被恢复,但原始的处理成YUV 420被恢复。因此RGB/BGR已经开始了一个小的损失,但一般来说,一个单一的步骤几乎看不见。
然后,当我将RGB 24转换回YUV 420时,无论是否进行处理,都会再次进行一轮子采样,但是这次二次采样从RGB/BGR开始,之前已经处理过了,与原始的不匹配。输出大小保持不变(每像素12位),因为YUV 420到RGB确实恢复了原始大小(只是不是原始质量)。当将新的YUV 420写入H265格式的文件时,它通过了编解码器处理,这与H264不同,这可能解释了为什么除了压缩伪影之外,图像变得更暗,正如Christoph拉克维茨在评论中注意到并提到的那样。
总的来说,图片中显示的退化是结果。
如果无法访问原始视频数据,解决方案是创建一个新的FFmpeg AVFrame来保存YUV 444 P。这将使文件大小加倍,因为它使用每像素24位来实际保存每像素12位的信息。
然后,YUV 444 P,经过可选的处理,如应用覆盖,可以提供给H265编解码器。任何其他子采样4:2:2,4:1:1,4:1:0,3:1:1,甚至4:2:0本身,导致额外的损失,在那些原来的4:2:0转换的基础上,由于子采样再次和不从原来的开始。
如果有原始视频数据,那么当然可以在第一次转换/子采样之前预先完成所有处理,然后直接转换为想要的像素格式和格式(例如H265 YUV 420或其他任何格式)。
所以OP的问题不是FFmpeg sws_scale()的问题,而是我在开始编码之前没有思考的问题:)

相关问题