[opencv] 동영상 frame count, fps, duration

동영상 파일을 cv::VideoCapture 로 읽으면 헤더정보를 읽어올 수 가 있는데,

이 헤더정보가 실제 데이터와 다른 경우가 종종 존재한다. -_-;

동영상을 열기 전까지 헤더가 올바른지 아닌지 판별하기 어려우니 아래의 방법을 추천한다.

1. Total frame count

int64_t GetVideoFrameCount(std::string video_file) {
	cv::VideoCapture vc(video_file);
	cv::Mat frame;
	int64_t pos = 0;
	while (true) {
		vc.grab();
		if ((int64_t)vc.get(CV_CAP_PROP_POS_FRAMES) - pos == 0) {
			break;
		}
		++pos;
	}
	return pos;
}

cv::VideoCapture::grab 함수는 다음 프레임으로 이동하는것이다.

같이 사용하는 함수로 cv::VideoCapture::retrieve(cv::OutputArray image,int flag=0) 함수가 있다.

retrieve 함수는 해당 프레임을 cv::Mat 으로 가져온다.

즉!, cv::VideoCapture::readcv::VideoCapture::operator>>는 저 두 함수를 합친것이다.

실제 읽을수있는 프레임을 측정하는데 있어서, 데이터를 읽을 필요는 없다.

실제로 grab 만 사용한경우가 몇십배는 빠르다.

근데! 이것도 ffmpeg로 구할 수 있다.

심지어 몇백배는 빠르다.

아래에서 설명한다.

2. Duration

영상의 길이는 OPENCV 에서는 제공하고 있지 않다.(내가 모르는걸수도)

다시 말하지만 헤더정보를 이용해 duration을 구할수 있는데, 헤더가 잘못된걸 수도 있다.

그래서 ffmpeg 를 사용한다.

int64_t GetVideoDuration(std::string video_file) { // -> msec
	av_register_all();
	AVFormatContext* pFormatCtx = nullptr;
	avformat_open_input(&pFormatCtx, video_file.c_str(), NULL, NULL);
	int64_t duration = pFormatCtx->duration*1000 / AV_TIME_BASE;
	return duration;
}

근데 어떤 영상에서는 duration 정보가 헤더에 없는 경우가 있다.

이럴때는 avformat_find_stream_info로 모든 정보를 다 찾을 수 있다.

3. frame count, duration, fps

이제 모든 정보를 한번에 빠르게 구해보자.

#include<iostream>
extern "C" {
#include <libavformat/avformat.h>
#include<libavcodec/avcodec.h>
}

void GetVideoInfo(std::string video_file, int64_t* pframe_count = nullptr, int64_t* pduration_msec = nullptr, double* pfps = nullptr) {
	bool ret = true;
	av_register_all();
	AVFormatContext* pFormatCtx = nullptr;
	avformat_open_input(&pFormatCtx, video_file.c_str(), NULL, NULL);
	avformat_find_stream_info(pFormatCtx, NULL);
	int videoStream = -1;	//Find video Stream
	for (int i = 0; i < pFormatCtx->nb_streams; i++) {
		if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
			videoStream = i; 
			break;
		}
	}
	int64_t frame_count = pFormatCtx->streams[videoStream]->nb_frames;

	if (frame_count<=0 || frame_count>INT_MAX) {
		//frame_count is incorrect
		frame_count = 0;
		AVPacket packet;
		while (av_read_frame(pFormatCtx, &packet) >= 0) {
			if (packet.stream_index == videoStream) {
				frame_count++;
			}
			av_packet_unref(&packet);
		}
	}
	int64_t duration_msec = pFormatCtx->duration*1000.0 / AV_TIME_BASE;
	double fps = frame_count * 1000.0 / duration_msec;
	if (frame_count) {
		*pframe_count = frame_count;
	}
	if (pduration_msec) {
		*pduration_msec = duration_msec;
	}
	if (pfps) {
		*pfps = fps;
	}
	avformat_close_input(&pFormatCtx);
	avformat_free_context(pFormatCtx);
}
int main() {

	double start = clock();
	std::string video_file = "OBGB_20170809_13293(2).avi";
	int64_t duration, frame_count;
	double fps;
	
	GetVideoInfo(video_file, &frame_count, &duration, &fps);
	

	std::cout << frame_count << std::endl;
	std::cout << duration << std::endl;
	std::cout << fps << std::endl;

	std::cout << (clock() - start) / CLOCKS_PER_SEC << std::endl;
	
}