新年新气象,100行 Python 代码制作动态鞭炮

news2024/12/24 2:22:54

放鞭炮贺新春,在我国有两千多年历史。关于鞭炮的起源,有个有趣的传说。

西方山中有焉,长尺余,一足,性不畏人。犯之令人寒热,名曰年惊惮,后人遂象其形,以火药为之。——《神异经》

当初人们燃竹而爆,是为了驱吓危害人们的山魈。据说山魈最怕火光和响声,所以每到除夕,人们便“燃竹而爆”,把山魈吓跑。这样年复一年,便形成了过年放鞭炮、点红烛、敲锣打鼓欢庆新春的年俗。

新年新气象,今天就用代码来制作一个 动态鞭炮 ,效果如下所示。

动态鞭炮的基本原理是:将一个录制好的鞭炮视频以字符画的形式复现,基本步骤是帧采样 → 逐帧转换为字符画 → 字符画合成视频。下面开始吧!

1、视频帧采样

函数如下所示,主要功能是将视频的图像流逐帧保存到特定的缓存文件夹中(若该文件夹不存在会自动创建)。函数输入vp是openCV视频句柄,输出number是转换的图片数。

def video2Pic(vp):
    number = 0
    if vp.isOpened():
        r,frame = vp.read()
        if not os.path.exists('cachePic'):
            os.mkdir('cachePic')
        os.chdir('cachePic')
    else:
        r = False
    while r:
        number += 1
        cv2.imwrite(str(number)+'.jpg',frame)
        r,frame = vp.read()
    os.chdir("..")
    return number

2、将图片转为字符画

2.1 创建像素-字符索引

函数输入像素RGBA值,输出对应的字符码。其原理是将字符均匀地分布在整个灰度范围内,像素灰度值落在哪个区间就对应哪个字符码。字符码可以参考 ASCII码

ASCII 码使用指定的7 位或8 位二进制数组合来表示128 或256 种可能的字符。标准ASCII 码也叫基础ASCII码,使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符。其中:0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(响铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10 和13 分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序,而对文本显示有不同的影响。

RGBA是代表Red(红色)、Green(绿色)、Blue(蓝色)和Alpha的色彩空间,Alpha通道一般用作不透明度参数。如果一个像素的alpha通道数值为0%,那它就是完全透明的,而数值为100%则意味着一个完全不透明的像素(传统的数字图像)。gray=0.2126 * r + 0.7152 * g + 0.0722 * b是RGB转为灰度值的经验公式,人眼对绿色更敏感。

def color2Char(r,g,b,alpha = 256):
    imgChar= list("#RMNHQODBWGPZ*@$C&98?32I1>!:-;. ")
    if alpha:
      gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
      unit = 256 / len(imgChar)
      return imgChar[int(gray / unit)]
    else:
      return ''

2.2 将图片逐像素转换为字符

核心代码如下,遍历图片的每个像素

 img = Image.open(imagePath).convert('RGB').resize((imgWidth, imgHeight),Image.NEAREST)
    for i in range(imgHeight):
        for j in range(imgWidth):
            pixel = img.getpixel((j, i))
            color.append((pixel[0],pixel[1],pixel[2]))
            txt = txt + color2Char(pixel[0], pixel[1], pixel[2], pixel[3]) if len(pixel) == 4 else \
                  txt + color2Char(pixel[0], pixel[1], pixel[2]) 
        txt += '\n'
        color.append((255,255,255))

3、将字符图像合成视频

输入参数vp是openCV视频句柄,number是帧数,savePath是视频保存路径,函数中 MP42 是可以生成较小并且较小的视频文件的编码方式,其他类似的还有isom、mp41、avc1、qt等,表示“最好”基于哪种格式来解析当前的文件。

    img = Image.open(imagePath).convert('RGB').resize((imgWidth, imgHeight),Image.NEAREST)
    for i in range(imgHeight):
        for j in range(imgWidth):
            pixel = img.getpixel((j, i))
            color.append((pixel[0],pixel[1],pixel[2]))
            txt = txt + color2Char(pixel[0], pixel[1], pixel[2], pixel[3]) if len(pixel) == 4 else \
                  txt + color2Char(pixel[0], pixel[1], pixel[2]) 
        txt += '\n'
        color.append((255,255,255))

4、完整代码

import cv2 
from PIL import Image,ImageFont,ImageDraw
import os
from cv2 import VideoWriter, VideoWriter_fourcc

'''
* @breif: 将像素颜色转换为ASCII字符
* @param[in]: 像素RGBA值
* @retval: 字符
'''
def color2Char(r,g,b,alpha = 256):
    imgChar = list("#RMNHQODBWGPZ*@$C&98?32I1>!:-;. ")
    if alpha:
      gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
      unit = 256 / len(imgChar)
      return imgChar[int(gray / unit)]
    else:
      return ''
 
'''
* @breif: 将视频逐帧转换为图片
* @param[in]: vp -> openCV视频句柄
* @retval: number -> 转换的图片数
'''
def video2Pic(vp):
    number = 0
    if vp.isOpened():
        r,frame = vp.read()
        if not os.path.exists('cachePic'):
            os.mkdir('cachePic')
        os.chdir('cachePic')
    else:
        r = False
    while r:
        number += 1
        cv2.imwrite(str(number)+'.jpg',frame)
        r,frame = vp.read()
    os.chdir("..")
    return number
 
'''
* @breif: 将图片逐像素转换为ASCII字符
* @param[in]: imagePath -> 图片路径
* @param[in]: index -> 图片索引
* @retval: None
'''
def img2Char(imagePath, index):
    # 初始化
    txt, color, font = '', [], ImageFont.load_default().font
    imgWidth, imgHeight = Image.open(imagePath).size
    asciiImg = Image.new("RGB",(imgWidth, imgHeight), (255,255,255))
    drawPtr = ImageDraw.Draw(asciiImg)
    imgWidth, imgHeight = int(imgWidth / 6), int(imgHeight / 15)

    # 对图像帧逐像素转化为ASCII字符并记录RGB值
    img = Image.open(imagePath).convert('RGB').resize((imgWidth, imgHeight),Image.NEAREST)
    for i in range(imgHeight):
        for j in range(imgWidth):
            pixel = img.getpixel((j, i))
            color.append((pixel[0],pixel[1],pixel[2]))
            txt = txt + color2Char(pixel[0], pixel[1], pixel[2], pixel[3]) if len(pixel) == 4 else \
                  txt + color2Char(pixel[0], pixel[1], pixel[2]) 
        txt += '\n'
        color.append((255,255,255))
    
    # 绘制ASCII字符画并保存
    x, y = 0,0
    fontW, fontH = font.getsize(txt[1])
    fontH *= 1.37
    for i in range(len(txt)):
        if(txt[i]=='\n'):
            x += fontH
            y = -fontW
        drawPtr.text((y,x), txt[i], fill=color[i])
        y += fontW
    os.chdir('cacheChar')
    asciiImg.save(str(index)+'.jpg')
    os.chdir("..")

'''
* @breif: 将视频转换为ASCII图像集
* @param[in]: number -> 帧数
* @retval: None
''' 
def video2Char(number):
    if not os.path.exists('cacheChar'):
        os.mkdir('cacheChar')
    img_path_list = ['cachePic' + r'/{}.jpg'.format(i) for i in range(1, number + 1)] 
    task = 0
    for imagePath in img_path_list:
        task += 1
        img2Char(imagePath, task)

'''
* @breif: 将图像合成视频
* @param[in]: vp -> openCV视频句柄
* @param[in]: number -> 帧数
* @param[in]: savePath -> 视频保存路径
* @retval: None
'''  
def img2Video(vp, number, savePath):
    videoFourcc = VideoWriter_fourcc(*"MP42")  # 设置视频编码器
    asciiImgPathList = ['cacheChar' + r'/{}.jpg'.format(i) for i in range(1, number + 1)]
    asciiImgTemp = Image.open(asciiImgPathList[1]).size
    videoWritter= VideoWriter(savePath, videoFourcc, vp.get(cv2.CAP_PROP_FPS), asciiImgTemp)
    for imagePath in asciiImgPathList:
        videoWritter.write(cv2.imread(imagePath))
    videoWritter.release()
    
if __name__ == '__main__': 
  videoPath = 'test.mp4'
  savePath = 'new.avi'
  vp = cv2.VideoCapture(videoPath)
  number = video2Pic(vp)
  video2Char(number)
  img2Video(vp, number, savePath)
  vp.release()

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/124315.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

IU8689 单声道145W/75W立体声D类音频功放IC产品介绍

概要 IU8689E是-款单声道可输出145W,立体声2*75W D类音频功率放大器,这款器件在顶层设计了散热焊盘,焊盘上连接散热器后在供电电压24V的情况下,最大可以输出2x75W的连续功率;通过主从模式的设置可以让IU8689E实现无限级联&#x…

C# 线程的基本使用

一 多线程的概念 1 进程Process 2 线程Thread ① 线程中的指令:一个方法(委托) ② 线程中的数据:相关的对象; 3 System.Threading.Thread属性 4 System.Threading.Thread方法 5 线程的创建 1) Thread类…

初识Spring

目录 一: 为什么要学习? 二: 概述 三:Spring发展史 一: 为什么要学习? 可以最大程度地简化项目的开发大量公司在使用顶级的源码设计:spring框架源码设计非常优秀,在java开源项目中可以说是顶…

1.8T数据离奇消失之谜

编者按 数字化浪潮蓬勃兴起,企业面临的安全挑战亦日益严峻。 腾讯安全近期将复盘2022年典型的攻击事件,帮助企业深入了解攻击手法和应对措施,完善自身安全防御体系。 本篇是第六期,讲述了某企业NAS系统数据被删除,始…

SpringBoot 配合126邮箱实现邮件发送功能

126邮箱邮件发送授权码申请pom依赖邮件配置代码编写Bean与配置自动装载原始手工方式发送邮件发送纯文本的邮件发送包含HTML标签的邮件发送包含附件的邮件发送包含静态资源的文件测试SpringBoot 提供了系统级别邮箱服务,只需要导入一个邮箱启动器然后进行配置就可以使…

LabVIEW如何减少下一代测试系统中的硬件过时6

LabVIEW如何减少下一代测试系统中的硬件过时6 HAL Benefits When addressing obsolescence, HALs yield the benefits of lowermigration costs, faster migration time, higher code reuse, and easiermaintainability. Lower Migration Costs The act of designing a use…

如何pdf合并成一个?推荐3种方法

在企业处理多份合同文件或者财务报销时,经常会处理大量的PDF文件。因此为了更高效率办公,我们经常需要将多个pdf合并成一个。如何pdf合并成一个呢?给大家推荐3个方法。 1、Smallpdf工具 如何pdf合并成一个?推荐大家可以使用Small…

一篇canvas带你画出整个特效世界

目录 一,canvas是啥? 1.初识canvas 2.路径绘制 3.拆分画法 4.清除画布 5.绘制圆形笑脸 6.贝塞尔曲线 ①二次贝塞尔曲线 ②三次贝塞尔曲线 一,canvas是啥? Canvas是HTML5中新出的一个元素,我们可以在上面绘制…

为什么redis中提供hash数据类型?

目录 1.什么是哈希表?缺点是什么? 2.Redis的数据类型(type、encoding) 3.比较常用命令、使用场景、实现方式 1.什么是哈希表?缺点是什么? 把关键字key映射到表中记录的地址。映射关系是散列函数&#x…

图表控件LightningChart.NET 入门教程(六):许可证管理介绍(上)

LightningChart.NET SDK 是一款高性能数据可视化插件工具,由数据可视化软件组件和工具类组成,可支持基于 Windows 的用户界面框架(Windows Presentation Foundation)、Windows 通用应用平台(Universal Windows Platfor…

初学Java web(十)Filter 和 Listener

Filter和Listener 一.Filter 概念:Filter表示过滤器,是JavaWeb三大组件(Servlet、Filter、Listener)之一。 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能。 过滤器一般完成一些通用的操作,比如:权限控…

Nuttx学习入门

Nuttx学习 NuttX 是一个实时操作系统 (RTOS),强调标准合规性和占用空间小。可从 8 位扩展到 64 位微控制器环境,NuttX 中的主要管理标准是 POSIX 和 ANSI 标准。 NuttX 的主要环境依赖性是 (1) GNU make,(2) bash 脚本,和 (3) L…

第三十四章 数论——高斯消元解线性方程组

第三十四章 数论——高斯消元解线性方程组一、高斯消元1、线性方程组2、高斯消元步骤(1)数学知识铺垫增广矩阵和阶梯矩阵初等变换(2)高斯消元步骤二、代码模板1、问题:2、代码一、高斯消元 1、线性方程组 我们在小学…

P3884 [JLOI2009]二叉树问题

题目 如下图所示的一棵二叉树的深度、宽度及结点间距离分别为: 深度:44宽度:44结点 8 和 6 之间的距离:88结点 7 和 6 之间的距离:33 其中宽度表示二叉树上同一层最多的结点个数,节点 u, vu,v 之间的距离…

算法工程师需要学习的基础

文章目录应该早点系统地了解算法工程师需要学习的东西的,B站上的up主:梁唐讲的很好,大家可以去看一下,只截了一部分图做一个记录

MySQL5.7 多主一从(多源复制)同步配置

主从复制有如下一些优势: 分担负载:对业务进行读写分离,减轻主库I/O负载,将部分压力分担到从库上,缩短客户查询响应时间。 增加健壮性:在主库出现问题时,可通过多种方案将从库设置为主库&#…

100%全国产龙芯2K1000设计方案

国产工业处理器,龙芯2K1000主板,100%全国产化方案 可实现100%国产元器件方案,国产处理器 信迈2k1000开发板采用龙芯 2k1000处理器,处理器集成 2 个 GS264 处理器核,主频 1GHz,64 位 DDR3 控制器&#xff…

Vue与VueComponent的内置关系

上一节讲到了 Vue.extend 与 VueComponent 的区别,这一节讲一讲 Vue 与 VueComponent的内置关系。 原型与原型链 这里需要用到原型与原型链中的知识点,具体文章链接在这里。js中的原型与原型链 这里只需要理解一个点,那就是构造函数的protot…

【idea2022.3】安装教程2022-12

教程目录教程简介所需环境和版本软件下载执行过程安装激活开始使用前的配置结束语教程简介 换电脑了,又经历了一遍软件和环境的安装,已经安装好了jdk和对应版本的maven,所以接下来该安装idea等软件了 所需环境和版本 系统:win1…

软件测试人员30K的月薪,是个什么段位?

大家可以参照BAT等一线大厂的职级,一般是高级测试工程师和资深测试开发工程师的职位,下面是在字节跳动年薪40W的测试工程师 掌握的技能树主要包含哪个方面? 现在的行情,大家想要拿到30k这个薪资,不妨审视自己&#xf…