FFmpeg(yuv420p、nv12) 与 OpenCV Mat 互相转换
简介
OpenCV 可以使用 VideoCapture 类读取视频,虽然同样是封装了 FFmpeg,但是也屏蔽了很多接口,想做一些复杂操作就很不方便。
所以改用 FFmpeg 读取视频传递给 OpenCV 使用,将视频帧 FFmpeg AVFrame 转换为 OpenCV Mat。
解码帧
软解码
软解码解析出的 AVFrame 格式为:(AVPixelFormat)AV_PIX_FMT_YUV420P。
需要使用 SwsContext 类转换为 Mat BGR24。
部分视频解析出的格式为:(AVPixelFormat)AV_PIX_FMT_YUVJ420P,直接转换会提示警告:Convert Deprecated Format,警告不重要,但最好还是转换不推荐的格式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
AVPixelFormat ConvertDeprecatedFormat(enum AVPixelFormat format) { switch (format) { case AV_PIX_FMT_YUVJ420P: return AV_PIX_FMT_YUV420P; break; case AV_PIX_FMT_YUVJ422P: return AV_PIX_FMT_YUV422P; break; case AV_PIX_FMT_YUVJ444P: return AV_PIX_FMT_YUV444P; break; case AV_PIX_FMT_YUVJ440P: return AV_PIX_FMT_YUV440P; break; default: return format; break; } }
|
硬解码
硬解码解析出的 AVFrame 格式为:显存 NV12。
硬解码类型:AV_HWDEVICE_TYPE_CUDA 解析结果为 (AVPixelFormat)AV_PIX_FMT_CUDA。
硬解码类型:AV_HWDEVICE_TYPE_DXVA2 解析结果为 (AVPixelFormat)AV_PIX_FMT_FXVA2_VLD。
硬解码类型:AV_HWDEVICE_TYPE_D3D11VA 解析结果为 (AVPixelFormat)AV_PIX_FMT_D3D11。
使用 av_hwframe_transfer_data 函数把显存数据统一转换为内存数据 NV12。
1 2 3 4 5 6
| if (pCodecCtx->hw_device_ctx) { AVFrame* hw_frame; hw_frame = av_frame_alloc(); av_hwframe_transfer_data(hw_frame, frame, 0); }
|
内存数据 NV12 格式为:(AVPixelFormat)AV_PIX_FMT_NV12。
同样需要使用 SwsContext 类转换为 Mat BGR24。
转换 AVFrame To Mat
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
|
Mat AVFrameToMat(AVFrame* frame, int dstWidth, int dstHeight, bool isfree) { Mat image(dstHeight, dstWidth, CV_8UC3);
int srcWidth = frame->width; int srcHeight = frame->height;
int cvLinesizes[1]{ image.step1() }; auto srcFormat = ConvertDeprecatedFormat((AVPixelFormat)frame->format); SwsContext* conversion = sws_getContext(srcWidth, srcHeight, srcFormat, dstWidth, dstHeight, AV_PIX_FMT_BGR24, SWS_FAST_BILINEAR, NULL, NULL, NULL); sws_scale(conversion, frame->data, frame->linesize, 0, srcHeight, &image.data, cvLinesizes); sws_freeContext(conversion); if (isfree) { av_frame_free(&frame); } return image; }
Mat AVFrameToMat(AVFrame* frame, bool isfree) { return AVFrameToMat(frame, frame->width, frame->height, isfree); }
|
转换 Mat To AVFrame
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
|
AVFrame* MatToAVFrame(Mat* image, AVFrame* frame, int dstWidth, int dstHeight) { if (frame == NULL) { frame = av_frame_alloc(); frame->format = AV_PIX_FMT_YUV420P; frame->width = dstWidth; frame->height = dstHeight; av_frame_get_buffer(frame, 0); av_frame_make_writable(frame); }
int srcWidth = image->cols; int srcHeight = image->rows;
int cvLinesizes[1]{ image->step1() }; SwsContext* conversion = sws_getContext(srcWidth, srcHeight, AV_PIX_FMT_BGR24, dstWidth, dstHeight, (AVPixelFormat)frame->format, SWS_FAST_BILINEAR, NULL, NULL, NULL); sws_scale(conversion, &image->data, cvLinesizes, 0, srcHeight, frame->data, frame->linesize); sws_freeContext(conversion); return frame; }
AVFrame* MatToAVFrame(Mat* image, AVFrame* frame) { return MatToAVFrame(image, frame, image->cols, image->rows); }
|