FFmpeg转OpenCV Mat显示 梦里梦外; 2022-05-29 06:15 254阅读 0赞 FFmpeg一般采用SDL进行显示,如果不追求复杂的界面、交互和多线程功能,当然也可以使用OpenCV的imshow()方法进行显示了,而且实现起来比SDL更简单。方法也很简单,只需要把视频帧的BGR格式的数据(如果是RGB格式,需要转换)转存到OpenCV的Mat矩阵里。OpenCV的Mat是一个类,由两个数据部分组成: 矩阵头(包含信息有矩阵的大小,用于存储的方法,矩阵存储的地址等信息) 和一个指向存储所有像素值矩阵的指针。OpenCV的data属性是一个uchar类型的指针,它指向Mat数据矩阵的首地址;利用该属性,只需要把Mat的data指向FFmpeg的帧数据里即可,就可以用OpenCV的imshow()显示了。 下面给出两个方法,第一个是网上参考别人,并修改好的方法;第二个是鄙人再次精简,更好理解的方法 ### 方法一: ### 先定义个out\_buffer指针指向AVFrame帧数据,注意存储格式一定要选择与OpenCV格式类似的AV\_PIX\_FMT\_BGR24 int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); uint8_t *out_buffer = (uint8_t *)av_malloc(size); avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); 经过FFmpeg的sws\_scale()后,只需要在OpenCV初试化时,传入out\_buffer即可,如下: //Mat mRGB(pCodecCtx->height, pCodecCtx->width, CV_8UC3, out_buffer);//等效于下面 Mat mRGB(Size(pCodecCtx->width,pCodecCtx->height), CV_8UC3); mRGB.data = out_buffer; ### 方法二: ### 前面已经说明,OpenCV的Mat是一个类,由两个数据部分组成: 矩阵头(包含信息有矩阵的大小,用于存储的方法,矩阵存储的地址等信息) 和一个指向存储所有像素值矩阵的指针。OpenCV的data属性是一个uchar类型的指针,它指向Mat数据矩阵的首地址;利用该属性,只需要把Mat的data指向FFmpeg的帧数据里即可,就可以用OpenCV的imshow()显示了。 因此,我们只需要在sws\_scale后,把Mat地址直接指向AVFrame帧数据的首地址即可,不需要out\_buffer,将上面的关键代码改为: Mat mRGB(Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3); mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data 下面是完整的代码: #define __STDC_CONSTANT_MACROS #include <stdio.h> // Opencv #include <opencv/cv.h> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> extern "C" { #include "libavutil/avutil.h" #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" //新版里的图像转换结构需要引入的头文件 #include "libswscale/swscale.h" }; using namespace cv; char* filename = "F:/FFmpeg/testvideo/屌丝男士.mov";; int main() { AVCodec *pCodec; //解码器指针 AVCodecContext* pCodecCtx; //ffmpeg解码类的类成员 AVFrame* pAvFrame; //多媒体帧,保存解码后的数据帧 AVFormatContext* pFormatCtx; //保存视频流的信息 av_register_all(); //注册库中所有可用的文件格式和编码器 pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //检查文件头部 printf("Can't find the stream!\n"); } if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息 printf("Can't find the stream information !\n"); } int videoindex = -1; for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍历各个流,找到第一个视频流,并记录该流的编码信息 { if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoindex = i; break; } } if (videoindex == -1) { printf("Don't find a video stream !\n"); return -1; } pCodecCtx = pFormatCtx->streams[videoindex]->codec; //得到一个指向视频流的上下文指针 pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到该格式的解码器 if (pCodec == NULL) { printf("Cant't find the decoder !\n"); //寻找解码器 return -1; } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打开解码器 printf("Can't open the decoder !\n"); return -1; } pAvFrame = av_frame_alloc(); //分配帧存储空间 AVFrame* pFrameBGR = av_frame_alloc(); //存储解码后转换的RGB数据 // 保存BGR,opencv中是按BGR来保存的 int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); uint8_t *out_buffer = (uint8_t *)av_malloc(size); avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket)); printf("-----------输出文件信息---------\n"); av_dump_format(pFormatCtx, 0, filename, 0); printf("------------------------------"); struct SwsContext *img_convert_ctx; img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, //设置sws_scale转换格式为BGR24,这样转换后可以直接用OpenCV显示图像了 SWS_BICUBIC, NULL, NULL, NULL); int ret; int got_picture; cvNamedWindow("RGB", 1); for (;;) { if (av_read_frame(pFormatCtx, packet) >= 0) { if (packet->stream_index == videoindex) { ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet); if (ret < 0) { printf("Decode Error.(解码错误)\n"); return -1; } if (got_picture) { //YUV to RGB sws_scale(img_convert_ctx, (const uint8_t* const*)pAvFrame->data, pAvFrame->linesize, 0, pCodecCtx->height, pFrameBGR->data, pFrameBGR->linesize); //Mat mRGB(pCodecCtx->height, pCodecCtx->width, CV_8UC3, out_buffer);//(1)等效于下面 //Mat mRGB(Size(pCodecCtx->width,pCodecCtx->height), CV_8UC3);//(2) //mRGB.data = out_buffer;//memcpy(pCvMat.data, out_buffer, size); Mat mRGB(Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3); mRGB.data =(uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data imshow("RGB", mRGB); waitKey(40); } } av_free_packet(packet); } else { break; } } av_free(out_buffer); av_free(pFrameBGR); av_free(pAvFrame); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); sws_freeContext(img_convert_ctx); cvDestroyWindow("RGB"); system("pause"); return 0; } ### 方法三:为了方便使用,这里提供一个函数可以实现AVFrame到OpenCV Mat的转换 ### cv::Mat avFrame2Mat(AVFrame* pAvFrame, AVCodecContext*pCodecCtx) { AVFrame* pFrameBGR = av_frame_alloc(); //存储解码后转换的RGB数据 // 保存BGR,opencv中是按BGR来保存的 int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); uint8_t *out_buffer = (uint8_t *)av_malloc(size); avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); struct SwsContext *img_convert_ctx; img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, //设置sws_scale转换格式为BGR24,这样转换后可以直接用OpenCV显示图像了 SWS_BICUBIC, NULL, NULL, NULL); sws_scale(img_convert_ctx, (const uint8_t* const*)pAvFrame->data, pAvFrame->linesize, 0, pCodecCtx->height, pFrameBGR->data, pFrameBGR->linesize); //Mat mRGB(pCodecCtx->height, pCodecCtx->width, CV_8UC3, out_buffer);//(1)等效于下面 //Mat mRGB(Size(pCodecCtx->width,pCodecCtx->height), CV_8UC3);//(2) //mRGB.data = out_buffer;//memcpy(pCvMat.data, out_buffer, size); Mat mRGB(Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3); mRGB.data = (uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data //av_free(out_buffer); av_free(pFrameBGR); //av_free(pAvFrame); sws_freeContext(img_convert_ctx); return mRGB; } 完整的程序如下: #define __STDC_CONSTANT_MACROS #include <stdio.h> // Opencv #include <opencv/cv.h> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> extern "C" { #include "libavutil/avutil.h" #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libswscale/swscale.h" }; using namespace cv; cv::Mat avFrame2Mat(AVFrame* pAvFrame, AVCodecContext*pCodecCtx); char* filename = "F:/FFmpeg/testvideo/屌丝男士.mov";; int main() { AVCodec *pCodec; //解码器指针 AVCodecContext* pCodecCtx; //ffmpeg解码类的类成员 AVFrame* pAvFrame; //多媒体帧,保存解码后的数据帧 AVFormatContext* pFormatCtx; //保存视频流的信息 av_register_all(); //注册库中所有可用的文件格式和编码器 pFormatCtx = avformat_alloc_context(); if (avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { //检查文件头部 printf("Can't find the stream!\n"); } if (avformat_find_stream_info(pFormatCtx, NULL)<0) { //查找流信息 printf("Can't find the stream information !\n"); } int videoindex = -1; for (int i = 0; i < pFormatCtx->nb_streams; ++i) //遍历各个流,找到第一个视频流,并记录该流的编码信息 { if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) { videoindex = i; break; } } if (videoindex == -1) { printf("Don't find a video stream !\n"); return -1; } pCodecCtx = pFormatCtx->streams[videoindex]->codec; //得到一个指向视频流的上下文指针 pCodec = avcodec_find_decoder(pCodecCtx->codec_id); //到该格式的解码器 if (pCodec == NULL) { printf("Cant't find the decoder !\n"); //寻找解码器 return -1; } if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { //打开解码器 printf("Can't open the decoder !\n"); return -1; } pAvFrame = av_frame_alloc(); //分配帧存储空间 // 保存BGR,opencv中是按BGR来保存的 AVPacket* packet = (AVPacket*)malloc(sizeof(AVPacket)); printf("-----------输出文件信息---------\n"); av_dump_format(pFormatCtx, 0, filename, 0); printf("------------------------------"); int ret; int got_picture; cvNamedWindow("RGB", 1); for (;;) { if (av_read_frame(pFormatCtx, packet) >= 0) { if (packet->stream_index == videoindex) { ret = avcodec_decode_video2(pCodecCtx, pAvFrame, &got_picture, packet); if (ret < 0) { printf("Decode Error.(解码错误)\n"); return -1; } if (got_picture) { cv::Mat des = avFrame2Mat(pAvFrame, pCodecCtx); imshow("RGB", des); waitKey(40); } } av_free_packet(packet); } else { break; } } //av_free(out_buffer); //av_free(pFrameBGR); av_free(pAvFrame); avcodec_close(pCodecCtx); avformat_close_input(&pFormatCtx); //sws_freeContext(img_convert_ctx); cvDestroyWindow("RGB"); system("pause"); return 0; } cv::Mat avFrame2Mat(AVFrame* pAvFrame, AVCodecContext*pCodecCtx) { AVFrame* pFrameBGR = av_frame_alloc(); //存储解码后转换的RGB数据 // 保存BGR,opencv中是按BGR来保存的 int size = avpicture_get_size(AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); uint8_t *out_buffer = (uint8_t *)av_malloc(size); avpicture_fill((AVPicture *)pFrameBGR, out_buffer, AV_PIX_FMT_BGR24, pCodecCtx->width, pCodecCtx->height); struct SwsContext *img_convert_ctx; img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_BGR24, //设置sws_scale转换格式为BGR24,这样转换后可以直接用OpenCV显示图像了 SWS_BICUBIC, NULL, NULL, NULL); sws_scale(img_convert_ctx, (const uint8_t* const*)pAvFrame->data, pAvFrame->linesize, 0, pCodecCtx->height, pFrameBGR->data, pFrameBGR->linesize); //Mat mRGB(pCodecCtx->height, pCodecCtx->width, CV_8UC3, out_buffer);//(1)等效于下面 //Mat mRGB(Size(pCodecCtx->width,pCodecCtx->height), CV_8UC3);//(2) //mRGB.data = out_buffer;//memcpy(pCvMat.data, out_buffer, size); Mat mRGB(Size(pCodecCtx->width, pCodecCtx->height), CV_8UC3); mRGB.data = (uchar*)pFrameBGR->data[0];//注意不能写为:(uchar*)pFrameBGR->data //av_free(out_buffer); av_free(pFrameBGR); //av_free(pAvFrame); sws_freeContext(img_convert_ctx); return mRGB; } 参考资料: 【1】《[利用ffmpeg和opencv进行视频的解码播放][ffmpeg_opencv]》https://www.jianshu.com/p/6ef3c18d61b0 【2】《[ffmpeg中avframe的YUV格式数据到OpenCV中Mat的BGR格式转换][ffmpeg_avframe_YUV_OpenCV_Mat_BGR]》http://www.cnblogs.com/riddick/p/7719190.html [ffmpeg_opencv]: https://www.jianshu.com/p/6ef3c18d61b0 [ffmpeg_avframe_YUV_OpenCV_Mat_BGR]: http://www.cnblogs.com/riddick/p/7719190.html
还没有评论,来说两句吧...