news 2026/5/10 9:46:03

基于Python 图形学实验(生成中间帧)

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于Python 图形学实验(生成中间帧)

图形学实验: 生成中间帧

给定初始图片和结束图片,生成中间的N帧,使得首尾自然过渡

开发环境

  • 开发环境:macOS Mojave 10.14.6
  • 开发软件:PyCharm 2019.1.3
  • 开发语言:python

如何运行

  • 将项目文件夹拷贝到本地环境
  • 运行src/GenerateIntermediateFrames.py
  • Resources/Result/目录下可查看到生成的中间帧图片
    • framexx.png: 为中间帧图片,其中xx为该帧编号
    • result.gif: 为该文件夹中所有帧图片生成的gif图片(方便观察处理结果)
  • 可使用自定义图片替换Resources/Origin/目录下的原始图片;并且请修改src/GenerateIntermediateFrames.py->main()函数中path_catpath_tiger的路径

实验结果

测试用例一

原图

生成的中间帧(转化为gif)

测试用例二

考虑到机器性能和生成动图大小,图片有压缩

原图

生成的中间帧(转化为gif)

具体实现

读取图片

使用cv2.imread()将图片读取成矩阵

'''读取原始图片''' def readOriginImg(path_cat, path_tiger): I_cat = cv2.imread(path_cat) I_tiger = cv2.imread(path_tiger) return I_cat, I_tiger
# 读取的图片数据结构 [[[255 255 255] [255 255 255] [255 255 255] ... ... [ 84 103 112] [103 122 128] [167 178 181]]]

生成中间帧(线性插值算法)

  • 取两图片最大的长宽作为目标图片的长宽
# 取两图片最大的长宽作为目标图片的长宽 width, height = max(I_cat.shape[0], I_tiger.shape[0]), max(I_cat.shape[1], I_tiger.shape[1])
直接使用三维数组
  • 构建 (N+2)widthheight 三纬结果矩阵
result_frames = [[[0 for _ in range(width)] for _ in range(height)] for _ in range(N+2)]
  • 使用三重循环进行中间帧生成
for k in range(0, N+2): # 帧循环 t = k/(1+N) # 对二维图片矩阵遍历 for x in range(width): for y in range(height): result_frames[k][x][y] = (1-t)*I_cat[x][y] + t*I_tiger[x][y] # 线性插值公式
使用numpy数组

将矩阵写出成图片

'''将矩阵写出成图片''' def writeResultFrames(result_frames, dirname, multi_thread=False): for index, frame in enumerate(result_frames): filename = 'frame' + str(index) + '.png'
直接写出
else: cv2.imwrite(dirname + filename, np.float32(frame))
使用多线程处理复杂任务
'''将矩阵保存成图片 - 线程类''' class FrameHandler(Thread): def __init__(self, dirname, filename, frame): super().__init__() self._dirname = dirname self._filename = filename self._frame = frame def run(self): imwrite(self._dirname+self._filename, np.float32(self._frame))
if multi_thread: frame_thread = FrameHandler(dirname, filename, frame) frame_thread.start() frame_thread.join()

将一组帧图片保存为Gif

  • 对结果目录中的所有文件进行过滤(只读入生成的中间帧)
  • 对读入的中间帧按照先后进行排序
  • 调用image.mimsave()方法将一组帧图片保存为Gif
''' Resources/Result/目录下有多张图片 图片格式: framexx.png ''' import os import imageio def myImage2Gif(dirname): frames = list(filter(lambda x: x[0:5] == 'frame' and x[-4:] == '.png', os.listdir(dirname))) # 将不符合命名要求的图片过滤掉(MacOS)会默认创建一些文件 frames.sort(key=lambda x: int(x[5:-4])) # 按照图片编号进行排序 imgs = [] for frame in frames: img = Image.open(dirname + frame) imgs.append(img) imgs[0].save(dirname + 'result.gif', save_all=True, append_images=imgs, duration=300)

实验分析

使用普通三维数组和numpy数组的效率差异

start = time() # 生成指定数量的中间帧 result_frames = generateFrame(I_cat, I_tiger, N) end = time() print(end-start)

该测试使用的是测试用例一,生成的中间帧数量N = 100

仅测试generateFrame()函数的时间,用于反映两种算法对于处理图片矩阵,生成中间帧的效率差异:

  • 使用普通三维数组
    • 结果数据结构的定义:
result_frames = [[[0 for _ in range(width)] for _ in range(height)] for _ in range(N + 2)]
    • 线性插值公式的使用:
result_frames[k][x][y] = (1 - t) * I_cat[x][y] + t * I_tiger[x][y]
    • 耗时:16.906198024749756 s
  • 使用numpy数组
    • 结果数据结构的定义:
result_frames = np.zeros((N + 2, width, height, 3)) # 对于图片矩阵中的每一个像素进行数值运算
    • 线性插值公式的使用:
result_frames[k] = (1 - t) * I_cat + t * I_tiger # 直接对于图片矩阵进行矩阵运算
    • 耗时:0.0948951244354248 s

从耗时中可以明显的看到使用numpy数组对于性能的提升,提升倍数达到了惊人的178倍;这提示我将来凡事观察到数值元算可以转换为直接对矩阵进行运算时,要果断采用numpy库相关函数进行科学计算,效率会得到很大的提升

使用多线程与直接写出的效率差异

start = time() # 将结果矩阵写出成图片 writeResultFrames(result_frames, path_result, multi_thread=True) end = time() print(end - start)

该测试使用的是测试用例一,生成的中间帧数量N = 1000

仅测试writeResultFrames()函数的时间,通过传入参数multi_thread不同的值,用于反映两种算法对于将矩阵写出成图片的效率差异:

  • 直接写出
    • 耗时:2.9145750999450684 s
  • 使用多线程
    • 耗时:3.5594019889831543 s

按道理,采用多线程应该会提高效率,但是将数据量从100上升到1000,仍是直接写出效率更高,猜想可能是使用的原始图片像素太小,且生成的中间帧数量还是不够巨大,无法发挥出多线程的效率,反而多线程的算法中要花很多时间在函数调用和类的创建上;此外还可能跟我把线程封装成类,并且独立写在一个.py文件中让主文件调用有关。在系统调用中浪费太多时间,反而在真正能发挥多线程性能时数据量又不是很大,导致效果并不好。

遇到的问题 & 解决方案

python构建三维数组

  • 初始时结果数组的定义如下
result_frames = [[0 for _ in range(width)] for _ in range(height)] * (N+2)
  • 测试该数组的结构如下,觉得没有任何不妥
[[[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]], [[0, 0], [0, 0], [0, 0]]]
  • 但是下一步进行中间帧生成后,写出的结果矩阵却怎么也不对,生成的图片非常奇怪;仔细找了很久也没发现有任何不妥,于是读了很多篇关于python多维数组的文章,最终通过如下的测试终于找到了问题所在
  • 测试代码如下,遍历三位数组的每一个元素,从小到大递增的赋值,理论上最终数组中应该是从0~23的数字
index = 0 for k in range(0,N+2): for x in range(height): for y in range(width): print(k,x,y) result_frames[k][x][y] = index index += 1 print(result_frames)
  • 测试结果如下
[[[18, 19], [20, 21], [22, 23]], [[18, 19], [20, 21], [22, 23]], [[18, 19], [20, 21], [22, 23]], [[18, 19], [20, 21], [22, 23]]]
  • 从测试中可以看到数组的第二三纬度的值每次都会同时修改,导致[[18, 19], [20, 21], [22, 23]]这组值反复出现,终于找到问题的所在
  • 找到准确问题后在网上进一步了解,发现时python深拷贝浅拷贝的问题,在创建数组中的*(N+2)造成了对上一层两维数组的浅拷贝
  • 修改方法:
result_frames = [[[0 for _ in range(width)] for _ in range(height)] for _ in range(N + 2)] # 或直接使用numpy数组 result_frames = np.zeros((N + 2, width, height, 3))

原始图片尺寸不匹配

  • 在最开始的程序中,如果开始的两张图片尺寸不同,则会报错
ValueError: operands could not be broadcast together with shapes (826,661,3) (640,640,3)
  • 是由于python中广播机制的存在导致无法对尺寸不匹配的矩阵进行运算
  • 所以必须在程序中对矩阵进行尺寸的重定义,是的不同尺寸的矩阵可以进行运算
  • 修改方法:
    • 取两图片最大的长宽作为目标图片的长宽
width, height = max(I_cat.shape[0], I_tiger.shape[0]), max(I_cat.shape[1], I_tiger.shape[1])
    • 分别将两张图片进行尺寸的重新调整
I_cat = np.resize(I_cat, (width, height, 3)) I_tiger = np.resize(I_tiger, (width, height, 3))

将一组帧图片保存为Gif

  • 生成的N+2张图片十分不便于观察自然过渡的效果,因此打算使用python将所有图片转换成一个gif
  • 查阅了python库函数,发现大致有三种方法可以实现一组图片转gif
    • imageio.mimsave()
    • images2gif库中的writeGif()
    • PIL.Image库中的save()
  • 分别使用三种方法试验之后发现
    • 第一种方法:效率有点低下,在合并10几张图片时无任何问题,但合并很多张图片容易卡死,造成无法生成gif图片
    • 第二种方法:该库是python2中的库,不再支持python3
      /Library/Frameworks/Python.framework/Versions/3.7/lib/images2gif中的
for im in images: palettes.append( getheader(im)[1] )

替换成

for im in images: palettes.append( im.palette.getdata()[1])

仍然会出现问题

    • 第三种方法:有时候会只在首位两张图片之间闪烁
  • 最终使用第三种方法,将duration设置为300,效果较好
  • 同时由于我的架构是生成所有中间帧图片,再转换为gif,所以要读取整个结果文件夹中的文件
  • 如果直接读取,顺序错乱,这样制作的动图无法观察到变化
['frame52.png', 'frame46.png', 'frame91.png', 'frame85.png', 'frame84.png', 'frame90.png', 'frame47.png', 'frame53.png', 'frame79.png', ..., 'frame77.png']
  • 对文件列表进行排序,但是有一些macOS系统自带的文件,并且排序是按照字符串的字典序排序,这样也同样是无法使用的
[.DS_Store, 'frame0.png', 'frame1.png', 'frame10.png', 'frame100.png',... ]
    • 首先对数据进行清洗,将无关的文件过滤掉
frames = list(filter(lambda x: x[0:5] == 'frame' and x[-4:] == '.png', os.listdir(dirname)))
    • 按照图片标号进行排序:自己创建排序函数的key
frames.sort(key=lambda x: int(x[5:-4]))

项目结构

. ├── README.md ├── Resources │ ├── Origin │ │ ├── cat.png │ │ ├── her1.jpeg │ │ ├── her2.jpeg │ │ └── tiger.png │ └── Result │ ├── catResult.gif │ └── herResult.gif ├── doc │ └── 生成中间帧实验文档.pdf └── src ├── FrameHandler.py ├── GenerateIntermediateFrames.py └── myImage2Gif.py

♻️ 资源

大小:13.6MB

➡️资源下载:https://download.csdn.net/download/s1t16/87404308

注:更多内容可关注微信公众号【神仙别闹】,如当前文章或代码侵犯了您的权益,请私信作者删除!

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/10 9:45:15

从理论到实践:Node-RED性能优化的完整案例解析

在物联网和自动化领域,Node-RED以其直观的可视化编程界面赢得了众多开发者的青睐。然而,许多用户在实际应用中都会遇到一个共同的问题:为什么我的Node-RED流程看起来逻辑清晰,运行起来却异常缓慢? 为什么你的Node-RED…

作者头像 李华
网站建设 2026/5/10 9:46:02

亲测好用10个降AIGC工具 千笔AI帮你高效降AI率

AI降重工具的崛起与实用价值 在当前学术写作日益依赖AI生成内容的背景下,越来越多的学生和研究者开始关注如何有效降低AIGC率、去除AI痕迹,同时保持文章的逻辑性和语义通顺。这不仅关乎论文通过查重系统的标准,更直接影响到学术诚信和论文质…

作者头像 李华
网站建设 2026/5/2 19:23:45

基于深度学习YOLOv10的辣椒叶片病害检测系统(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)

一、项目介绍 项目摘要 本项目基于YOLOv10目标检测算法,开发了一个针对辣椒叶片病害的智能检测系统。系统能够自动识别并分类5种常见的辣椒叶片状态,包括健康叶片和4种病害类型(黄单胞菌病、花叶病、尾孢菌病和卷叶病)。通过深度…

作者头像 李华
网站建设 2026/4/27 18:49:32

基于深度学习的水果品种分类检测系统(YOLOv10+YOLO数据集+UI界面+Python项目源码+模型)

一、项目介绍 摘要 水果品种的精准识别在农产品分级、智能零售和自动化分拣等领域具有重要应用价值。本研究开发了一种基于YOLOv10的高精度水果品种实时检测系统,可实现对6类常见水果品种(金冠苹果、澳洲青苹果、梨子、红富士苹果、红油桃、黄桃&#…

作者头像 李华
网站建设 2026/5/9 14:24:05

计算机毕业设计之基于.NET技术的吉他音乐社区系统的设计与实现

快速发展的社会中,人们的生活水平都在提高,生活节奏也在逐渐加快。为了节省时间和提高工作效率,越来越多的人选择利用互联网进行线上打理各种事务,然后线上管理系统也就相继涌现。与此同时,人们开始接受方便的生活方式…

作者头像 李华
网站建设 2026/5/9 16:00:45

语言数据类型的简明教程的20个例子

文章目录 关键概念总结: 1. 整数类型 2. 浮点类型 3. 字符类型 4. 特殊类型 5. 重要头文件 6. 字面量后缀 7. 格式说明符 以下是一个C语言数据类型的简明教程,包含20个代码示例: #include <stdio.h> #include <stdbool.h> #include <limits.h> #include &…

作者头像 李华