为什么不能直接用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里面。代码如下,此处重点是把一帧数据放入队列之后要使用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;
}
- 转换、显示放在线程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;
}
- 图像处理放在线程3,从队列2里面读取图像并进行处理。图像处理线程使用图像队列的代码参考上面这个线程。此处重点仍是队列要pop()。