opencv准确定位帧

原文地址:http://www.cnblogs.com/dwdxdy/archive/2012/06/04/2534733.html

问题描述:关键帧提取后,将会得一序列关键帧的帧号,然后需要把这些帧保存起来,以便于浏览和管理.

通过opencv里的VideoCapture的函数set(CV_CAP_PROP_POS_FRAMES,nextFrameNumber),定位到具体的帧号,但最终读取的并不是对应帧的图像.

问题出现的原因:

Opencv底层是通过ffmpeg读取视频.其中定位主要用av_seek_frame()来定位frame的位置.

int av_seek_frame(AVFormatContext *s,int stream_index,int64_t timestamp,int flags)其中最后一个参数有

AVSEEK_FLAG_BACKWARD = 1 // seek backward

AVSEEK_FLAG_BYTE = 2 // seeking based on position in bytes

AVSEEK_FLAG_ANY = 4 // seek to any frame,even non key-frames.

ffmpeg默认的是选取关键帧,opencv里面这个函数的参数flag是0.

因而,进行定位时,若下一帧不是关键帧,进行读取时会出跳跃现象.

将参数改为AVSEEK_FLAG_ANY,虽然可以解决跳跃现象,读取任何帧图像.

但是将会出现花屏现象,因为帧图像解码是需要利用关键帧的图像进行帧间的解码,

若读取帧图像时,其对应关键帧没有被读取解码,将只会对该帧进行帧内解码得到花屏图像.

如何才能解决跳跃现象,但不产生花屏图像?

解决思路:读取下一帧号最相近且前面的关键帧,然后一帧帧的读取视频,直到读到下一帧的帧号为止.

将Opencv2.3.1里面的cap_ffmpeg_impl.cpp里面bool CvCapture_FFMPEG::setProperty( int property_id, double value )函数改成如下实现方式,

即可达到准确定位的效果.

 

下面是我的修改:

bool CvCapture_FFMPEG::setProperty( int property_id, double value )
{
    if( !video_st ) return false;
/*
    switch( property_id )
    {
    case CV_FFMPEG_CAP_PROP_POS_MSEC:
    case CV_FFMPEG_CAP_PROP_POS_FRAMES:
    case CV_FFMPEG_CAP_PROP_POS_AVI_RATIO:
        {
            int64_t    timestamp  = ic->streams[video_stream]->first_dts;
            AVRational time_base  = ic->streams[video_stream]->time_base;
            AVRational frame_base = ic->streams[video_stream]->r_frame_rate;
            double     timeScale  = (time_base.den / (double)time_base.num) / (frame_base.num / (double)frame_base.den);
            switch( property_id )
            {
            case CV_FFMPEG_CAP_PROP_POS_FRAMES:
                timestamp += (int64_t)(value * timeScale);
                if(ic->start_time != AV_NOPTS_VALUE_)
                    timestamp += ic->start_time;
                break;

            case CV_FFMPEG_CAP_PROP_POS_MSEC:
                timestamp +=(int64_t)(value*(float(time_base.den)/float(time_base.num))/1000);
                if(ic->start_time != AV_NOPTS_VALUE_)
                    timestamp += ic->start_time;
                break;

            case CV_FFMPEG_CAP_PROP_POS_AVI_RATIO:
                timestamp += (int64_t)(value*ic->duration);
                if(ic->start_time != AV_NOPTS_VALUE_ && ic->duration != AV_NOPTS_VALUE_)
                    timestamp += ic->start_time;
                break;
            }

            if ( filename )
            {
                // ffmpeg's seek doesn't work...
                if (!slowSeek((int)timestamp))
                {
                    fprintf(stderr, "HIGHGUI ERROR: AVI: could not (slow) seek to position %0.3f\n",
                        (double)timestamp / AV_TIME_BASE);
                    return false;
                }
            }
            else
            {
                int flags = AVSEEK_FLAG_ANY;//AVSEEK_FLAG_FRAME;
                if (timestamp < ic->streams[video_stream]->cur_dts)
                  flags |= AVSEEK_FLAG_BACKWARD;
                int ret = av_seek_frame(ic, video_stream, timestamp, flags);
                if (ret < 0)
                {
                    fprintf(stderr, "HIGHGUI ERROR: AVI: could not seek to position %0.3f\n",
                            (double)timestamp / AV_TIME_BASE);
                    return false;
                }
            }
            picture_pts=(int64_t)value;
        }
        break;
    default:
        return false;
    }
*/
	int ret,framenumber;
	switch(property_id)
	{
		case CV_FFMPEG_CAP_PROP_POS_FRAMES:
			framenumber = (int)value;
			break;
		case CV_FFMPEG_CAP_PROP_POS_MSEC:
			framenumber = value/(1000.0f*av_q2d(video_st->time_base));
			break;
		default:
			return false;
	}
	if(framenumber == 0)
	{
		ret = av_seek_frame(ic, video_stream, framenumber,AVSEEK_FLAG_BACKWARD );
		assert(ret >=0);
		if(ret <0 ) return false;
	}
	else
	{
		ret = av_seek_frame(ic, video_stream, framenumber - 1,AVSEEK_FLAG_BACKWARD );
		if(ret < 0) return false;
		if(!grabFrame()) return false;
		while(video_st->cur_dts != framenumber)
		{
			if(!grabFrame()) return false;
		}
	}
    return true;
}
  
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值