C++使用ffmpeg+OPENCV从摄像头读取数据并显示图像,解决高分辨率问题,FPS达不到预期及内存占满的问题

为什么不能直接用OPENCV

读取摄像头数据并显示通常的做法是直接用OPENCV的capture类。
即:

VideoCapture capture(cameraIndex);
Mat frame;
while(true)
{
	capture.read(frame);
}

这种方式简单、效率高,FPS能够达到摄像头所支持的水平。

但这种方式读上来的图像的分辨率被限制在1080P以内,现在的摄像头很多都是超过了1080P的。
因此需要使用其他方式读取图像数据并用OPENCV进行图像处理和显示。比如可以使用ffmpeg。

使用ffmpeg读取图像数据

ffmpeg读取上来的数据需要进行转换才能够被OPENCV处理,当分辨率高时,转换需要较长的时间,如果读取、转换、处理、显示在同一个线程里面,会导致FPS下降。此时需要使用多个线程。
定义一个类CameraDisplay,在类里面定义好2个队列
即:
std::queuecv::Mat pictureQueue;
std::queue<AVFrame*> avFrameQueue;

  1. 把读取放在线程1,读取上来的数据放进队列1里面。代码如下,此处重点是把一帧数据放入队列之后要使用av_free_packet(packet);把当前packet里面数据释放掉,不然内存很快会满导致程序崩溃。
DWORD WINAPI CameraDisplay::CaptureThread(LPVOID pParam)  //线程函数实现
{
	CameraDisplay* pObj = (CameraDisplay*)pParam;   //传入的参数转化为类对象指针

	AVFormatContext* pFormatCtx;
	int				i, videoindex;
	AVCodecContext* pCodecCtx;
	AVCodec* pCodec;

	AVDictionary* optionsall = NULL;

	av_register_all();
	avformat_network_init();
	avdevice_register_all();

	show_dshow_device();
	pFormatCtx = avformat_alloc_context();

	av_dict_set(&optionsall, "video_size", "3264x2448", 0);

	AVInputFormat* ifmt = av_find_input_format("dshow");
	if (avformat_open_input(&pFormatCtx, "video=USB Camera", ifmt, &optionsall) != 0)
	{
		printf("Couldn't open input stream.\n");
		return 1;
	}

	if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
	{
		printf("Couldn't find stream information.\n");
		return 2;
	}

	videoindex = -1;

	for (i = 0; i < pFormatCtx->nb_streams; i++)
	{
		if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
		{
			videoindex = i;
		}
	}

	if (videoindex == -1)
	{
		printf("Couldn't find a video stream.\n");
		return 3;
	}

	pCodecCtx = pFormatCtx->streams[videoindex]->codec;
	pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

	if (pCodec == NULL)
	{
		printf("Codec not found.\n");
		return 4;
	}

	if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
	{
		printf("Could not open codec.\n");
		return 5;
	}

	AVFrame* pFrame;
	pFrame = av_frame_alloc();
	int ret, got_picture;

	AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));

	fprintf(stderr, "w= %d h= %d\n", pCodecCtx->width, pCodecCtx->height);

	int thread_exit = 1;
	int count = 0;
	
	while (thread_exit)
	{
		while (true)
		{
			if (av_read_frame(pFormatCtx, packet) < 0)
				thread_exit = 0;
			if (packet->stream_index == videoindex)
				break;
		}
		
	
		ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);
		if (got_picture) {
			pObj->avFrameQueue.push(pFrame);
			
		}
		av_free_packet(packet);

		waitKey(5);
		
		
	}
	av_frame_free(&pFrame);
	return 0;
}
  1. 转换、显示放在线程2,从队列1里面把数据转换成可供OPENCV直接处理的Mat格式,并显示,如果需要做其他处理的话,又可以把Mat格式的数据放进队列2里面,供图像处理线程使用。代码如下,此处重点是读取完一帧数据后,记得队列要pop(),不然内存很快会撑不住导致程序崩溃。
DWORD WINAPI CameraDisplay::ShowThread(LPVOID pParam)
{
	CameraDisplay* pObj = (CameraDisplay*)pParam;   //传入的参数转化为类对象指针
	clock_t firstPictureTime = 0;
	clock_t pictureTime;
	Mat test;
	namedWindow("Display", CV_WINDOW_NORMAL);
	
	while (true)
	{
		//printf("start show\n");
		if (!pObj->avFrameQueue.empty())
		{
			test = AVFrameToMat(pObj->avFrameQueue.front());
			pObj->avFrameQueue.pop();
			imshow("Display", test);

			pObj->pictureQueue.push(test);
						
		}
		else
		{
			//printf("empty\n");
		}
		waitKey(1);
	}
	
	return 1;
}
  1. 图像处理放在线程3,从队列2里面读取图像并进行处理。图像处理线程使用图像队列的代码参考上面这个线程。此处重点仍是队列要pop()。
  • 9
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要实现这个功能,首先需要安装FFmpegOpenCV库。然后可以按照以下步骤进行操作: 1. 导入头文件 ```c++ #include <iostream> #include <opencv2/core/core.hpp> #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <opencv2/videoio.hpp> #include "ffmpeg.h" ``` 2. 定义回调函数 ```c++ void display_frame(cv::Mat frame) { cv::imshow("Video", frame); cv::waitKey(1); } ``` 3. 定义获取视频流的函数 ```c++ void get_stream(const std::string& url) { AVFormatContext* format_ctx = avformat_alloc_context(); AVDictionary* options_dict = NULL; av_dict_set(&options_dict, "rtsp_transport", "tcp", 0); av_dict_set(&options_dict, "stimeout", "5000000", 0); av_dict_set(&options_dict, "max_delay", "500000", 0); av_dict_set(&options_dict, "buffer_size", "1024000", 0); av_dict_set(&options_dict, "rtbufsize", "1024000", 0); av_dict_set(&options_dict, "max_delay", "500000", 0); avformat_open_input(&format_ctx, url.c_str(), NULL, &options_dict); avformat_find_stream_info(format_ctx, NULL); AVCodec* codec = nullptr; int stream_index = av_find_best_stream(format_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &codec, 0); AVCodecContext* codec_ctx = avcodec_alloc_context3(codec); avcodec_parameters_to_context(codec_ctx, format_ctx->streams[stream_index]->codecpar); avcodec_open2(codec_ctx, codec, NULL); AVPacket* packet = av_packet_alloc(); AVFrame* frame = av_frame_alloc(); while (av_read_frame(format_ctx, packet) >= 0) { if (packet->stream_index == stream_index) { avcodec_send_packet(codec_ctx, packet); while (avcodec_receive_frame(codec_ctx, frame) == 0) { cv::Mat mat(frame->height, frame->width, CV_8UC3, frame->data[0]); cv::cvtColor(mat, mat, cv::COLOR_BGR2RGB); display_frame(mat); av_frame_unref(frame); } } av_packet_unref(packet); } av_packet_free(&packet); av_frame_free(&frame); avcodec_close(codec_ctx); avcodec_free_context(&codec_ctx); avformat_close_input(&format_ctx); } ``` 4. 调用函数播放视频流 ```c++ int main() { std::string url = "rtsp://xxx.xxx.xxx.xxx:xxxx/xxx"; get_stream(url); return 0; } ``` 这样就可以获取RTSP视频流并使用OpenCV显示图像了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值