[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;

}