zl程序教程

您现在的位置是:首页 >  后端

当前栏目

FFmpeg转OpenCV Mat显示

Opencv 显示 FFMPEG MAT
2023-09-11 14:17:47 时间

      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进行视频的解码播放》https://www.jianshu.com/p/6ef3c18d61b0

【2】《ffmpeg中avframe的YUV格式数据到OpenCV中Mat的BGR格式转换》http://www.cnblogs.com/riddick/p/7719190.html