当前位置: 首页 > news >正文

使用OpenCV+C++将Gif文件分解并且转换为视频文件

原文链接

目标:

  • 认识 Gif
  • 利用 FreeImage 将Gif解析为 Mat;
  • 利用 FreeImage 获取多帧Gif图像;
  • 将获取的多帧图像保存,并利用OpenCv生成为视频文件。

实际操作:

认识 Gif:

在浏览器中输入 Gif,查看维基百科的解释:
关于科学上网的方法可以查看

img_436da75b30e464f3a649f938b6301814.gif
搜索Gif

在这里,我将其下载为了PDF,供大家下载: 点击下载

img_b99e8d9144c30524ed89ba4c8a50f3f7.png
内容

我们注意它的特性就可以了:

优秀的压缩算法使其在一定程度上保证图像质量的同时将体积变得很小。
可插入多帧,从而实现动画效果。
可设置透明色以产生对象浮现于背景之上的效果。
由于采用了8位压缩,最多只能处理256种颜色,故不宜应用于真彩色图片。

算法解析:

环境:

  • OpenCv3.1
  • window10
  • vs2013
  • FreeImage

OpenCv 3.1官方教程中文翻译链接
FreeImage 下载地址
FreeImage是没有文档的,搜索之后,在网上发现一个写得还不错的文档点击下载

环境搭建:

新建一个OpenCv的工程, 下载FreeImage,将相应的文件复制到工程文件夹下:
img_87a129869a170a75384e6ddb35eb384c.gif
复制相应的文件
将.lib 文件添加到工程:
img_a23f68143d8fbd8e8ca9a50732adc8d1.gif
配置.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;
}
解释:
  • 这段代码有详细的注释,不再赘言。

运行结果以及源代码:

img_2fd1f1df5a94007181eb17c3a7e4e682.gif
运行结果

GutHub:

GitHub

相关文章:

  • webTest-----webUI自动化框架
  • 高通无人机新技术,深度学习把控飞行安全
  • 比特币价格再创新高,当年的0.3美分已经变为7290万美元
  • 使用Tinker来调试Laravel应用程序的数据以及使用Tinker一些总结
  • 2018 Web 开发者最佳学习路线之less
  • Logistic 回归算法及Python实现
  • 「镁客·请讲」Vincross徐凯强:从运动控制技术出发,为机器人开发者提供便捷的开发平台...
  • centos6中了挖矿***的解决办法
  • 数据库乐观锁的配置
  • 方案
  • 安装oracle出现环境不满足最低要求
  • C、C++ static 的作用
  • Java集合--整体框架
  • 从伪并行的 Python 多线程说起
  • 练习一 第三题
  • 【Under-the-hood-ReactJS-Part0】React源码解读
  • Cumulo 的 ClojureScript 模块已经成型
  • es6--symbol
  • HTML5新特性总结
  • HTTP 简介
  • Java深入 - 深入理解Java集合
  • js学习笔记
  • k个最大的数及变种小结
  • Linux各目录及每个目录的详细介绍
  • MD5加密原理解析及OC版原理实现
  • react-native 安卓真机环境搭建
  • RedisSerializer之JdkSerializationRedisSerializer分析
  • Redux 中间件分析
  • tensorflow学习笔记3——MNIST应用篇
  • Webpack 4x 之路 ( 四 )
  • 基于OpenResty的Lua Web框架lor0.0.2预览版发布
  • 深入浏览器事件循环的本质
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 新手搭建网站的主要流程
  • 用Python写一份独特的元宵节祝福
  • 正则表达式
  • Linux权限管理(week1_day5)--技术流ken
  • UI设计初学者应该如何入门?
  • 资深实践篇 | 基于Kubernetes 1.61的Kubernetes Scheduler 调度详解 ...
  • ​iOS实时查看App运行日志
  • ​LeetCode解法汇总2304. 网格中的最小路径代价
  • #Spring-boot高级
  • $jQuery 重写Alert样式方法
  • (02)Hive SQL编译成MapReduce任务的过程
  • (13):Silverlight 2 数据与通信之WebRequest
  • (6)添加vue-cookie
  • (附源码)spring boot公选课在线选课系统 毕业设计 142011
  • (学习日记)2024.03.12:UCOSIII第十四节:时基列表
  • (学习日记)2024.04.04:UCOSIII第三十二节:计数信号量实验
  • (一)搭建springboot+vue前后端分离项目--前端vue搭建
  • (原創) 如何動態建立二維陣列(多維陣列)? (.NET) (C#)
  • (转)Android中使用ormlite实现持久化(一)--HelloOrmLite
  • .L0CK3D来袭:如何保护您的数据免受致命攻击
  • .NET 的静态构造函数是否线程安全?答案是肯定的!
  • .net(C#)中String.Format如何使用