原文链接
目标:
- 认识 Gif
- 利用 FreeImage 将Gif解析为 Mat;
- 利用 FreeImage 获取多帧Gif图像;
- 将获取的多帧图像保存,并利用OpenCv生成为视频文件。
实际操作:
认识 Gif:
在浏览器中输入 Gif,查看维基百科的解释:
关于科学上网的方法可以查看
在这里,我将其下载为了PDF,供大家下载: 点击下载
我们注意它的特性就可以了:
优秀的压缩算法使其在一定程度上保证图像质量的同时将体积变得很小。
可插入多帧,从而实现动画效果。
可设置透明色以产生对象浮现于背景之上的效果。
由于采用了8位压缩,最多只能处理256种颜色,故不宜应用于真彩色图片。
算法解析:
环境:
- OpenCv3.1
- window10
- vs2013
- FreeImage
OpenCv 3.1官方教程中文翻译链接
FreeImage 下载地址
FreeImage是没有文档的,搜索之后,在网上发现一个写得还不错的文档点击下载
环境搭建:
新建一个OpenCv的工程, 下载FreeImage,将相应的文件复制到工程文件夹下:
将.lib 文件添加到工程:
如此便搭建好了环境。
算法实现:
Gif文件的加载:
代码:
//------------- GIF文件的载入
//
bool Gif_Load(const string &filename)
{
FIBITMAP *dib = 0;
FIMULTIBITMAP *bitmap = 0;
FIBITMAP * pFrame;
fif = FreeImage_GetFileType(filename.c_str(), 0);
if (fif == FIF_UNKNOWN) fif = FreeImage_GetFIFFromFilename(filename.c_str());
if (fif == FIF_UNKNOWN) return false;
if (FreeImage_FIFSupportsReading(fif)) dib = FreeImage_Load(fif, filename.c_str());
if (!dib) return false;//dib Load failed
//bpp = FreeImage_GetBPP(dib);
bits = (BYTE*)FreeImage_GetBits(dib);
width = FreeImage_GetWidth(dib);
height = FreeImage_GetHeight(dib);
cout << "Load The File: " << filename.c_str() << endl;
cout << "The File's width: " << width << endl;
cout << "The File's height: " << height << endl;
if ((bits == 0) || (width == 0) || (height == 0)) return false;
bitmap = FreeImage_OpenMultiBitmap(fif, filename.c_str(), 0, 0, 1, GIF_DEFAULT);
if (bitmap == NULL)
{
cout << "BitMap == Null" << endl;
return FALSE;
}
int count = FreeImage_GetPageCount(bitmap);//获取帧数;
for (int i = 0; i <=count; i++)
{
pFrame = FreeImage_LockPage(bitmap, i);
//cout << "pFrame:" << pFrame << endl;
Src_Gif = Gif_To_Mat(pFrame, fif); //转换为Mat;
string Src_Gif_Name = to_string(i);
imwrite(Src_Gif_Name + ".jpg", Src_Gif);
FreeImage_UnlockPage(bitmap, pFrame, 1);
}
FreeImage_Unload(dib);
FreeImage_DeInitialise();
Load_flag = TRUE;
return Load_flag;
}
解释:
- 首先是利用
FreeImage_GetFileType()
函数,通过文件名,获取文件的类型; - 然后判断
FreeImage_FIFSupportsReading()
,是否是FreeImage支持的文件类型; - 之后获取文件的相关信息,位数,大小。
- 再利用
FreeImage_OpenMultiBitmap
,以GIF_DEFAULT
的方式加载Gif文件,关于这个函数参数的意义,可查看我上面给出的文档。 - 利用
FreeImage_GetPageCount
函数获取帧数。 - 之后就是在一个for循环里面,使用
Gif_To_Mat
,将每一帧图片转换为Mat。 - 再使用
imwrite
将图像储存到本地。
由此Gif加载完毕,并且转换为了Mat。下面来解释一下Gif转换为Mat的方法:
Gif转换为Mat:
代码:
Mat Gif_To_Mat(FIBITMAP* fiBmp, const FREE_IMAGE_FORMAT fif)
{
if (fiBmp == NULL || fif != FIF_GIF)
{
return Mat();
}
BYTE intensity;
BYTE* PIintensity = &intensity;
if (FreeImage_GetBPP(fiBmp) != 8)
fiBmp = FreeImage_ConvertTo8Bits(fiBmp);
RGBQUAD* pixels = new RGBQUAD;
pixels = FreeImage_GetPalette(fiBmp);
Mat img = Mat::zeros(height, width, CV_8UC3);
uchar *p;
for (int i = 0; i < height; i++)
{
p = img.ptr<uchar>(i);
for (int j = 0; j < width; j++)
{
FreeImage_GetPixelIndex(fiBmp, j, height - i, PIintensity);
p[3 * j] = pixels[intensity].rgbBlue;
p[3 * j + 1] = pixels[intensity].rgbGreen;
p[3 * j + 2] = pixels[intensity].rgbRed;
}
}
return img;
}
解释:
- for 循环之前图像的加载不需要多说了;
- for 循环,遍历整个图像,利用
FreeImage_GetPixelIndex
获取相应(x, y)的像素值,注意这里的坐标 表示的是(j, height-1),因为坐标的x, y 和 height, width是不一样的;大可将 height-i 改为i运行查看区别。
关于对图像的遍历,可参考【OpenCvTutorials3.1中文翻译】- 如何使用OpenCV扫描图像,查找表和时间测量,这里有详细的解释。
将文件夹中的jpg文件合成为视频:
代码:
//将当前文件夹中的 “.jpg” 生成为视频文件;
bool Jpg_To_Video()
{
VideoWriter video("output.avi", CV_FOURCC('M', 'P', '4', '2'), 25.0, Size(150, 131));
String File_Name = "*.jpg";
vector <String> fn;
glob(File_Name, fn, false);//遍历文件夹的图片/文件
size_t size = fn.size();
cout << "Jpg_To_Video size:" << size << endl;
cout << "开始将图片文件写入视频" << endl;
for (size_t i = 0; i < size; i++)
{
Mat image = imread(fn[i]);
//imshow(to_string(i), image);
//resize(image, image, Size(640, 480)); //这里 必须将image的大小 转换为 VideoWriter video(...)一样的大小。
video.write(image);
}
cout << "写入 成功!" << endl;
return TRUE;
}
解释:
- 这里大多是OpenCv的基础知识,唯一需要解释的是我们使用了
glob
函数。
该函数的使用可参考 链接
至此,已经获取Gif文件的多帧图像,并且保存为了视频文件,接下来我们将它显示出来。
显示生成的视频文件:
代码:
bool Show_Video()
{
cout << "Show The Video.." << endl;
VideoCapture video_capture("output.avi");
if (!video_capture.isOpened()) return FALSE;
double totalFrameNumber = video_capture.get(CV_CAP_PROP_FRAME_COUNT);
cout << "整个视频共" << totalFrameNumber << "帧" << endl;
//设置开始帧()
long frameToStart = 1;
video_capture.set(CV_CAP_PROP_POS_FRAMES, frameToStart);
cout << "从第" << frameToStart << "帧开始读" << endl;
//设置结束帧
int frameToStop = totalFrameNumber;
if (frameToStop < frameToStart)
{
cout << "结束帧小于开始帧,程序错误,即将退出!" << endl;
return FALSE;
}
else
{
cout << "结束帧为:第" << frameToStop << "帧" << endl;
}
//获取帧率
double rate = video_capture.get(CV_CAP_PROP_FPS);
cout << "帧率为:" << rate << endl;
//定义一个用来控制读取视频循环结束的变量
bool stop = false;
//承载每一帧的图像
Mat frame;
//显示每一帧的窗口
namedWindow("Extracted frame");
//两帧间的间隔时间:
double delay = rate;
//利用while循环读取帧
//currentFrame是在循环体中控制读取到指定的帧后循环结束的变量
long currentFrame = frameToStart;
while (!stop)
{
//读取下一帧
if (!video_capture.read(frame))
{
cout << "读取视频结束" << endl;
return FALSE;
}
imshow("Extracted frame", frame);
cout << "正在读取第" << currentFrame << "帧" << endl;
//waitKey(int delay=0)当delay ≤ 0时会永远等待;当delay>0时会等待delay毫秒
//当时间结束前没有按键按下时,返回值为-1;否则返回按键
int c = waitKey(delay);
//按下ESC键退出视频的帧流显示
if ((char)c == 27 || currentFrame > frameToStop)
{
stop = true;
}
//按下按键后会停留在当前帧,等待下一次按键
if (c >= 0)
{
waitKey(0);
}
currentFrame++;
}
//关闭视频文件
video_capture.release();
waitKey(0);
return TRUE;
}
解释:
- 这段代码有详细的注释,不再赘言。