为了提取关键帧,这里使用帧差法,语言为C语言,调用opencv库实现。
我们将视频第一帧设定为关键帧,后面的每一个帧与前一个关键帧进行像素帧差(灰度图),并设定阈值判断发生变化的像素点比例,通过此比例判断帧是否有发生突变,有发生突变的帧设定为关键帧并保存在特定文件夹中。以此循环将所有帧遍历完成即可。
在测试程序前,为了方便测试,我们将电影截取为两分钟的片段,不然太长了不方便测试,如果有备好的视频片段可以跳过这一步。在终端terminal打开窗口,输入以下命令,将(流浪地球)liulang_earth.mkv在一小时16分到一小时18分截取两分钟的视频片段并不保存为cutout1.mp4。之后对此两分钟视频提取关键帧。
ffmpeg -i ./liulang_earth.mkv -vcodec copy -acodec copy -ss 01:16:00 -to 01:18:00 ./cutout1.mp4 -y
程序我是放在文本编辑器中的,然后用g++编译。将pixels_cha.cpp编译生成pixels_cha可执行程序。g++编译命令如下
g++ `pkg-config opencv --cflags` pixels_cha.cpp -o pixels_cha `pkg-config opencv --libs
成功后再终端继续输入 ./pixels_cha 即可运行程序。如果使用其他编译器也是没有问题的。
程序如下
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/ml/ml.hpp>
#include <string.h>
using namespace std;
using namespace cv;
int main()
{
system("color 2F");
long currentFrame = 1;
float p;
VideoCapture cap;
//这里放置需要提取关键字的视频
cap.open("./cutout1.mp4");
if(!cap.isOpened())//如果视频不能正常打开则返回
{ cout<< "cannot open video!"<<endl;
return 0;}
Mat frame_key;
cap>>frame_key;
if(frame_key.empty())
cout<< "frame_key is empty!" << endl;
imshow("fram_1",frame_key);
waitKey(20);
stringstream str;
str<<"./keyframes_pixels_cha/" <<currentFrame << ".jpg";
cout << str.str() << endl;
imwrite(str.str() , frame_key);
Mat frame;
Mat previousImage,currentImage,resultImage;
while(1)
{
//printf(">>>>>>>>>>>>>00");
currentFrame++;
Mat frame;
cap>>frame;
//printf(">>>>>>>>>>>>>1");
if(frame.empty())
{
cout<< "frame is empty!" << endl;
break;}
imshow("fram_1",frame);
waitKey(20);
Mat srcImage_base, hsvImage_base;
Mat srcImage_test1, hsvImage_test1;
srcImage_base = frame_key;
srcImage_test1 = frame;
//将图像从BGR色彩空间转换到 HSV色彩空间
cvtColor(srcImage_base, previousImage, CV_BGR2GRAY);
cvtColor(srcImage_test1, currentImage, CV_BGR2GRAY);
//printf(">>>>>>>>>>>>>2");
absdiff(currentImage,previousImage,resultImage); //帧差法,相减
//printf(">>>>>>>>>>>>>3");
threshold(resultImage, resultImage, 10, 255.0, CV_THRESH_BINARY); //二值化,像素值相差大于20则置为255,其余为0
printf(">>>>>>>>>>>>>1\n");
float counter = 0;
float num = 0;
// 统计两帧相减后图像素
for (int i=0; i<resultImage.rows; i++)
{
uchar *data = resultImage.ptr<uchar>(i); //获取每一行的指针
for (int j=0;j<resultImage.cols; j++)
{
num = num+1;
if (data[j] == 255) //访问到像素值
{
counter = counter+1;
}
}
}
p = counter/num;
// counter num p 分别为 有变化的像素点数 总像素点数目 比例
printf(">>>>>>counter>>>>num>>>>p: %f %f %f \n",counter,num,p);
if (p > 0.55) //达到阈值的像素数达到一定的数量则保存该图像
{
//printf(">>>>>>>>>>>>>6");
cout<< ">>>>>>>>>>>>>>>>>>>>.>>>>>>>.this frame is keyframe!"<<endl;
frame_key = frame;
cout << "正在写第" << currentFrame << "帧" << endl;
stringstream str;
//写视频保存目录,我的是 ./keyframes_pixels_cha/xx.jpg xx为当前帧的序号
str<<"./keyframes_pixels_cha/" <<currentFrame << ".jpg";
cout << str.str() << endl;
imwrite(str.str() , frame_key);
}
else
{
cout<< ">>>>>>>>>>>>.this frame is not keyframe!"<<endl;
}
}
}
程序运行结果如下。第一帧不知道怎么不显示图,不过双击后仍然可以打开的。关键帧名字代表该帧属于视频中的第几帧。结果很理想,没有太多冗余帧。程序中可调参数为threshold(resultImage, resultImage, 10, 255.0, CV_THRESH_BINARY)中的 10,10为二值化阈值,还有p,p为比例。通过调节这两个参数可以优化关键帧提取结果。