摘要
本文为利用python代码实现从视频到点阵的快速生成,方便为单片机的动画提供点阵资源。从视频获取连续帧图使用的是ffmpeg。相应的资源文件在附件提供。
版本信息:python-3.10,selenium-4.9.0,PIL-Image-9.4.0.
样例
根据封面生成的128x64的点阵, light_ratio=200
代码
# -*- coding: utf-8 -*-
# @Time : 2024/4/14 15:01
import os
import time
import subprocess
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service
class GenerateFontBitmapFromVideo:
def __init__(self, video_path, frames_dir_path, drop_ratio, row_width, column_height, reverse):
self.video_path = video_path # 视频文件路径
self.frames_dir_path = frames_dir_path # 输出帧图片的目录和文件名格式
self.drop_ratio = drop_ratio
self.row_width = row_width
self.column_height = column_height
self.reverse = reverse
def generateFramesFromVideo(self, start_time="00:00:00", duration="10"):
output_pattern = os.path.join(self.frames_dir_path, 'frame_%04d.png')
# 构建FFmpeg命令
ffmpeg_command = [
'ffmpeg',
'-i', self.video_path,
'-ss', start_time,
'-t', duration,
output_pattern
]
# 执行FFmpeg命令
subprocess.run(ffmpeg_command)
def generateBitmapFromZheTao(self, driver):
driver.get("https://www.zhetao.com/fontarray.html")
time.sleep(1)
rows = driver.find_element(By.ID, "rows")
coltimes = driver.find_element(By.ID, "coltimes")
inputs = driver.find_element(By.ID, "selfile")
text = driver.find_element(By.ID, "chars")
buttons = driver.find_elements(By.CLASS_NAME, "genbtn")
store = open("store.txt", "w")
store.write("u8 frames[][{}*{}]".format(self.row_width // 8, self.column_height) + " = {\n")
def extract_and_save(content, index):
# 寻找起始行的索引
start_marker = "static const unsigned char bitmap_bit_bytes[] = "
end_marker = "};"
start_index = content.find(start_marker)
if start_index == -1:
print("Start marker not found.")
return
start_index += len(start_marker)
# 寻找结束行的索引
end_index = content.find(end_marker, start_index)
if end_index == -1:
print("End marker not found.")
return
# 加上结束标记长度,确保包括结束行
end_index += len(end_marker)
# 提取字符串
extracted_content = content[start_index:end_index]
# 写入到文件
store.write(extracted_content.replace(end_marker, "},\n"))
if self.reverse: driver.find_element(By.ID, "getanti").click()
for index, frame in enumerate(os.listdir(self.frames_dir_path)):
if index % self.drop_ratio == 1: continue
inputs.clear()
inputs.send_keys(r"{}\{}".format(self.frames_dir_path, frame))
time.sleep(0.1)
coltimes.clear()
coltimes.send_keys(self.row_width // 8)
rows.clear()
rows.send_keys(self.column_height)
buttons[-1].click()
time.sleep(0.2)
print(index)
# 将文本的以static const unsigned char bitmap_byte开头,};结尾的部分写入store.txt
extract_and_save(text.text, index)
store.write("};")
store.close()
def generateBitmapFromLocal(self, ligth_ratio=128):
from PIL import Image
store = open("store.txt", "w")
store.write("u8 frames[][{}*{}]".format(self.row_width // 8, self.column_height) + " = {\n")
os.chdir(self.frames_dir_path)
for index, frame in enumerate(os.listdir(self.frames_dir_path)):
if index % self.drop_ratio == 1: continue
image = Image.open(frame).convert('L')
# 调整图片大小
image = image.resize([self.row_width, self.column_height])
# 获取图像的像素数据
pixels = image.load()
# 存储点阵数据
dot_matrix = []
for y in range(self.column_height):
byte = 0
byte_list = []
for x in range(self.row_width):
# 获取灰度值并转换为二值
if pixels[x, y] < ligth_ratio: # 阈值可以根据需要调整
byte = (byte << 1) | (self.reverse)
else:
byte = (byte << 1) | (not self.reverse)
# 每8个像素存储一个字节
if (x + 1) % 8 == 0:
byte_list.append(byte)
byte = 0
# 处理剩余的不足8个像素
if self.row_width % 8 != 0:
byte = byte << (8 - (self.row_width % 8))
byte_list.append(byte)
dot_matrix.extend(byte_list)
formatted_output = "{\n "
for i, byte in enumerate(dot_matrix):
formatted_output += f"0x{byte:08b}, "
if (i + 1) % (self.row_width // 8) == 0:
formatted_output += "\n "
formatted_output = formatted_output.rstrip(", \n") + "\n},"
print(formatted_output)
store.write(formatted_output)
store.write("};")
store.close()
class Property:
google_path = r"C:\Program Files\Google\Chrome\Application\chrome.exe"
video_path = r"C:\Users\GroLin\Downloads\Video\灼眼的夏娜 緋色の空.mp4"
frames_dir_path = r"C:\Users\GroLin\Downloads\Video\2024上\frames"
drop_ratio = 2 # 丢帧比例
reverse = True # 是否取反
video_start_time = "00:00:14" # 视频开始时间
duration = "10" # 视频持续时间
row_width = 128 # 每行的像素数
column_height = 64 # 每列的像素数
ligth_ratio = 200 # 阈值
def __init__(self):
os.makedirs(self.frames_dir_path, exist_ok=True)
def getDriver(self):
try:
service = Service("chromedriver.exe")
from selenium.webdriver.chrome.options import Options
chrome_options = Options()
chrome_options.binary_location = self.google_path
return webdriver.Chrome(service=service, options=chrome_options)
except Exception as e:
print(e)
import updateDriver as ud
ud.update_driver()
return self.getDriver()
if __name__ == '__main__':
property = Property()
GFBFV = GenerateFontBitmapFromVideo(
property.video_path,
property.frames_dir_path,
property.drop_ratio,
property.row_width,
property.column_height,
property.reverse
) # 实例化类
GFBFV.generateFramesFromVideo(property.video_start_time, property.duration) # 从视频中提取帧图片
#GFBFV.generateBitmapFromZheTao(property.getDriver()) # 从网站生成点阵
GFBFV.generateBitmapFromLocal(property.ligth_ratio) # 从本地图片生成点阵
简要说明
- Properly类为配置类,可以自定义生成点阵的规格,是否取反等。
- GenerateFontBitmapFromVideo类提供两个方法来从图片生成点阵,本地生成的可控性更高并且速度更快,这里提供网页生成主要是为了分享这个网站,更适合不了解代码的同学。
- 将必要文件准备好后放在一个文件夹,如下图(两个图片文件不用在意)
附件
- FFmpeg下载Download FFmpeg
- 点阵生成网站https://www.zhetao.com/fontarray.html
- updateDriver.py
# -*- coding: utf-8 -*- # @Time : 2023/8/29 13:48 import requests import zipfile import os import json root = "." def removeDirs(path, dir): if os.path.exists(os.path.join(path, dir)): path = os.path.join(path, dir) if os.path.isdir(path): os.chdir(path) absPath = os.getcwd() for inner in os.listdir('.'): if os.path.isdir(inner): removeDirs(os.getcwd(), inner) continue os.remove(inner) os.chdir(absPath[:absPath.rfind("\\")]) os.removedirs(dir) else: os.remove(path) def update_driver(): url = 'https://googlechromelabs.github.io/chrome-for-testing/last-known-good-versions-with-downloads.json' resp = json.loads(requests.get(url).text) for platforms in resp['channels']['Stable']['downloads']['chromedriver']: if platforms['platform'] == "win64": zips = platforms['url'] file = requests.get(zips).content with open('./chromedriver-win64.zip', 'wb') as f: f.write(file) zip_file = zipfile.ZipFile('chromedriver-win64.zip') name_ = filter(lambda x: x.endswith(".exe"), zip_file.namelist()).__next__() zip_file.extract(name_, root) if os.path.exists(f'{root}\chromedriver.exe'): os.remove(f'{root}\chromedriver.exe') zip_file.close() os.rename(os.path.join(root, name_), f'{root}\chromedriver.exe') removeDirs(root, 'chromedriver-win64') removeDirs(root, 'chromedriver-win64.zip') if __name__ == '__main__': update_driver()
- 6x8的英文字符点阵文件
u8 font6x8[][6] = { { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // ¿Õ¸ñ { 0x00, 0x00, 0x00, 0x2f, 0x00, 0x00 }, // ! { 0x00, 0x00, 0x07, 0x00, 0x07, 0x00 }, // " { 0x00, 0x14, 0x7f, 0x14, 0x7f, 0x14 }, // # { 0x00, 0x24, 0x2a, 0x7f, 0x2a, 0x12 }, // $ { 0x00, 0x62, 0x64, 0x08, 0x13, 0x23 }, // % { 0x00, 0x36, 0x49, 0x55, 0x22, 0x50 }, // & { 0x00, 0x00, 0x05, 0x03, 0x00, 0x00 }, // ' { 0x00, 0x00, 0x1c, 0x22, 0x41, 0x00 }, // ( { 0x00, 0x00, 0x41, 0x22, 0x1c, 0x00 }, // ) { 0x00, 0x14, 0x08, 0x3E, 0x08, 0x14 }, // * { 0x00, 0x08, 0x08, 0x3E, 0x08, 0x08 }, // + { 0x00, 0x00, 0x00, 0xA0, 0x60, 0x00 }, // , { 0x00, 0x08, 0x08, 0x08, 0x08, 0x08 }, // - { 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 }, // . { 0x00, 0x20, 0x10, 0x08, 0x04, 0x02 }, // / { 0x00, 0x3E, 0x51, 0x49, 0x45, 0x3E }, // 0 { 0x00, 0x00, 0x42, 0x7F, 0x40, 0x00 }, // 1 { 0x00, 0x42, 0x61, 0x51, 0x49, 0x46 }, // 2 { 0x00, 0x21, 0x41, 0x45, 0x4B, 0x31 }, // 3 { 0x00, 0x18, 0x14, 0x12, 0x7F, 0x10 }, // 4 { 0x00, 0x27, 0x45, 0x45, 0x45, 0x39 }, // 5 { 0x00, 0x3C, 0x4A, 0x49, 0x49, 0x30 }, // 6 { 0x00, 0x01, 0x71, 0x09, 0x05, 0x03 }, // 7 { 0x00, 0x36, 0x49, 0x49, 0x49, 0x36 }, // 8 { 0x00, 0x06, 0x49, 0x49, 0x29, 0x1E }, // 9 { 0x00, 0x00, 0x36, 0x36, 0x00, 0x00 }, // : { 0x00, 0x00, 0x56, 0x36, 0x00, 0x00 }, // ; { 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, // < { 0x00, 0x14, 0x14, 0x14, 0x14, 0x14 }, // = { 0x00, 0x00, 0x41, 0x22, 0x14, 0x08 }, // > { 0x00, 0x02, 0x01, 0x51, 0x09, 0x06 }, // ? { 0x00, 0x32, 0x49, 0x59, 0x51, 0x3E }, // @ { 0x00, 0x7C, 0x12, 0x11, 0x12, 0x7C }, // A { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x36 }, // B { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x22 }, // C { 0x00, 0x7F, 0x41, 0x41, 0x22, 0x1C }, // D { 0x00, 0x7F, 0x49, 0x49, 0x49, 0x41 }, // E { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x01 }, // F { 0x00, 0x3E, 0x41, 0x49, 0x49, 0x7A }, // G { 0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F }, // H { 0x00, 0x00, 0x41, 0x7F, 0x41, 0x00 }, // I { 0x00, 0x20, 0x40, 0x41, 0x3F, 0x01 }, // J { 0x00, 0x7F, 0x08, 0x14, 0x22, 0x41 }, // K { 0x00, 0x7F, 0x40, 0x40, 0x40, 0x40 }, // L { 0x00, 0x7F, 0x02, 0x0C, 0x02, 0x7F }, // M { 0x00, 0x7F, 0x04, 0x08, 0x10, 0x7F }, // N { 0x00, 0x3E, 0x41, 0x41, 0x41, 0x3E }, // O { 0x00, 0x7F, 0x09, 0x09, 0x09, 0x06 }, // P { 0x00, 0x3E, 0x41, 0x51, 0x21, 0x5E }, // Q { 0x00, 0x7F, 0x09, 0x19, 0x29, 0x46 }, // R { 0x00, 0x46, 0x49, 0x49, 0x49, 0x31 }, // S { 0x00, 0x01, 0x01, 0x7F, 0x01, 0x01 }, // T { 0x00, 0x3F, 0x40, 0x40, 0x40, 0x3F }, // U { 0x00, 0x1F, 0x20, 0x40, 0x20, 0x1F }, // V { 0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F }, // W { 0x00, 0x63, 0x14, 0x08, 0x14, 0x63 }, // X { 0x00, 0x07, 0x08, 0x70, 0x08, 0x07 }, // Y { 0x00, 0x61, 0x51, 0x49, 0x45, 0x43 }, // Z { 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 }, // [ { 0x00, 0x55, 0x2A, 0x55, 0x2A, 0x55 }, // 55 { 0x00, 0x00, 0x41, 0x41, 0x7F, 0x00 }, // ] { 0x00, 0x04, 0x02, 0x01, 0x02, 0x04 }, // ^ { 0x00, 0x40, 0x40, 0x40, 0x40, 0x40 }, // _ { 0x00, 0x00, 0x01, 0x02, 0x04, 0x00 }, // ' { 0x00, 0x20, 0x54, 0x54, 0x54, 0x78 }, // a { 0x00, 0x7F, 0x48, 0x44, 0x44, 0x38 }, // b { 0x00, 0x38, 0x44, 0x44, 0x44, 0x20 }, // c { 0x00, 0x38, 0x44, 0x44, 0x48, 0x7F }, // d { 0x00, 0x38, 0x54, 0x54, 0x54, 0x18 }, // e { 0x00, 0x08, 0x7E, 0x09, 0x01, 0x02 }, // f { 0x00, 0x18, 0xA4, 0xA4, 0xA4, 0x7C }, // g { 0x00, 0x7F, 0x08, 0x04, 0x04, 0x78 }, // h { 0x00, 0x00, 0x44, 0x7D, 0x40, 0x00 }, // i { 0x00, 0x40, 0x80, 0x84, 0x7D, 0x00 }, // j { 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 }, // k { 0x00, 0x00, 0x41, 0x7F, 0x40, 0x00 }, // l { 0x00, 0x7C, 0x04, 0x18, 0x04, 0x78 }, // m { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x78 }, // n { 0x00, 0x38, 0x44, 0x44, 0x44, 0x38 }, // o { 0x00, 0xFC, 0x24, 0x24, 0x24, 0x18 }, // p { 0x00, 0x18, 0x24, 0x24, 0x18, 0xFC }, // q { 0x00, 0x7C, 0x08, 0x04, 0x04, 0x08 }, // r { 0x00, 0x48, 0x54, 0x54, 0x54, 0x20 }, // s { 0x00, 0x04, 0x3F, 0x44, 0x40, 0x20 }, // t { 0x00, 0x3C, 0x40, 0x40, 0x20, 0x7C }, // u { 0x00, 0x1C, 0x20, 0x40, 0x20, 0x1C }, // v { 0x00, 0x3C, 0x40, 0x30, 0x40, 0x3C }, // w { 0x00, 0x44, 0x28, 0x10, 0x28, 0x44 }, // x { 0x00, 0x1C, 0xA0, 0xA0, 0xA0, 0x7C }, // y { 0x00, 0x44, 0x64, 0x54, 0x4C, 0x44 }, // z { 0x08, 0x08, 0x49, 0x2A, 0x14, 0x08 } // cursor };