以前就在抖音上看到过字符视频,直到昨天才突然想自己动手做一个,然后就利用各种博客,自己总结 兼 借鉴,终于完成了字符视频的制作
一、思路:
众所周知,视频是一帧一帧的图片组成的,所以我想的就是将抖音的视频先解析成好多帧图片,然后对图片进行操作,最后再把图片合成视频
二、遇到的问题:
其中我遇到的最大问题就是 pycharm 的 BUG,我从来没有想过 pycharm 会有 BUG,所以在我安装 opencv-python 库的时候,我一直以为是我的安装错误,可最后翻阅了超多博客后,才下定结论是 pycharm 的 BUG,并且代码验证确实可以运行,所以石锤了。
错误显示:
Cannot find reference 'waitKey' in '__init__.py' less... (Ctrl+F1)
Inspection info: This inspection detects names that should resolve but don't. Due to dynamic dispatch and duck typing, this is possible in a limited but useful number of cases. Top-level and class-level items are supported better than instance items.
这个错误可以忽略,因为这是 pycharm 本身的 BUG,不影响代码运行
视频格式错误
从抖音上下载的视频格式显示是 .Mp4 ,可是并不能用,所以需要转换编码,网上有很多免费的在线转格式的网站。
三、具体操作步骤
while ret:
# 进行单张图片的读取,ret的值为True或者Flase,frame表示读入的图片
ret, frame = vc.read()
if ret:
# 存储为图像
cv2.imwrite(folder_path + str(c) + '.jpg', frame)
# 输出图像名称
#print(folder_path + str(c) + '.jpg')
c = c + 1
# 在一个给定的时间内(单位ms)等待用户按键触发,1ms
cv2.waitKey(1)
else:
break
for j in range(len(txts)):
for i in range(len(txts[j])):
if isgray:
draw_handle.text((i*block_x, j*block_y), txts[j][i], (50, 50, 50))
else:
draw_handle.text((i*block_x, j*block_y), txts[j][i], colors[j][i])
整体思路:读出旧图片,新建一个Image对象,基于旧图片,构造新图片for i in range(1, 1000):
filename = folder_path_char + str(i) + '.jpg'
# 判断图片是否存在
if os.path.exists(filename):
img = cv2.imread(filename=filename)
# 在一个给定的时间内(单位ms)等待用户按键触发,100ms
cv2.waitKey(100)
# 将图片写入视频中
videoWriter.write(img)
# print(str(i) + '.jpg' + ' done!')
四、详细代码
from PIL import Image, ImageDraw, ImageFont
import cv2
import os
import threading
ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. ")
#在当前目录下新建文件夹
folder_path = r"D:\Python_workspace\char_video_sources\pictures\\"#每帧
folder_path_char =r"D:\Python_workspace\char_video_sources\char_pitcures\\"#彩色字符帧
width=None
height=None
fps=None
count=None
def getVideo(url):
# 进行视频的载入
vc = cv2.VideoCapture(url)
c = 0
# 判断载入的视频是否可以打开
ret = vc.isOpened()
"""获取视频参数"""
global fps
fps=vc.get(cv2.CAP_PROP_FPS)#获取视频帧速
global count
count=vc.get(cv2.CAP_PROP_FRAME_COUNT)#获取视频帧数
global width
global height
width =int(vc.get(cv2.CAP_PROP_FRAME_WIDTH)) # 获取长宽
height =int(vc.get(cv2.CAP_PROP_FRAME_HEIGHT))
"""end"""
# 循环读取视频帧
while ret:
# 进行单张图片的读取,ret的值为True或者Flase,frame表示读入的图片
ret, frame = vc.read()
if ret:
# 存储为图像
cv2.imwrite(folder_path + str(c) + '.jpg', frame)
# 输出图像名称
#print(folder_path + str(c) + '.jpg')
c = c + 1
# 在一个给定的时间内(单位ms)等待用户按键触发,1ms
cv2.waitKey(1)
else:
break
vc.release()
def get_char(r,g,b,alpha = 256):#获取对应的字符
if alpha == 0:
return ' '
length = len(ascii_char)
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
unit = (256.0 + 1)/length
return ascii_char[int(gray/unit)]
def severalThreadings(isgray=False):#多线程处理灰度图像
lines=5
eachPice=int(count/lines)#每个线程所需处理帧数
threads=[]
for line in range(lines):#对于一片进行处理,然后计算扩展到
begin=line*eachPice
end=(line+1)*eachPice
if line==lines-1:#如果是最后一个线程,则其工作为收尾工作,将图片进行到最后
end=int(count)
thread = threading.Thread(target=getCharPitcure, args=(begin,end,isgray))
threads.append(thread)
thread.setDaemon(True)
thread.start()
for item in threads:
item.join()
def getCharPitcure(begin=0,end=count,isgray=False):#传入是否生成灰色,默认为否
for op in range(begin, end):
#print("这是第{}个".format(op))
img = folder_path + str(op) + '.jpg'
# print(img)
if os.path.exists(img):
im = Image.open(img).convert('RGB') # 注意,此处需要先将图片转换为RGB模式
# 设定处理后的字符画大小,需要为整型
raw_width = int(im.width)
raw_height = int(im.height)
# 获取设定的字体的尺寸,ImageFont默认的尺寸大小为6x11,其他字体会有所不同
# 此处使用的字体为truetype字体,大小为10px
font = ImageFont.truetype('consola.ttf', 10, encoding='unic')
font_x, font_y = font.getsize(' ')
# 确定单元的大小
block_x = int(font_x)
block_y = int(font_y)
# 确定长宽各有几个单元
w = int(raw_width/block_x)
h = int(raw_height/block_y)
# 将每个单元缩小为一个像素
im = im.resize((w, h), Image.NEAREST)
# txts和colors分别存储对应块的ASCII字符和RGB值
txts = []
colors = []
for i in range(h):#遍历行
line = ''
lineColor = []
for j in range(w):#遍历列
pixel = im.getpixel((j, i))
lineColor.append((pixel[0], pixel[1], pixel[2]))
line += get_char(pixel[0], pixel[1], pixel[2])
txts.append(line)
colors.append(lineColor)
# 创建新画布
im_txt = Image.new("RGB", (raw_width, raw_height), (255, 255, 255))
# 创建ImageDraw对象以写入ASCII
draw_handle = ImageDraw.Draw(im_txt)
for j in range(len(txts)):
for i in range(len(txts[j])):
if isgray:
draw_handle.text((i*block_x, j*block_y), txts[j][i], (50, 50, 50))
else:
draw_handle.text((i*block_x, j*block_y), txts[j][i], colors[j][i])
name = folder_path_char + str(op) + '.jpg'
# print(name)
im_txt.save(name, 'JPEG')
def createVideo(url):
# 设置视频编码器,这里使用使用MJPG编码器
fourcc = cv2.VideoWriter_fourcc(*'MJPG')
# 输出视频参数设置,包含视频文件名、编码器、帧率、视频宽高(此处参数需和字符图片大小一致)
videoWriter = cv2.VideoWriter(url, fourcc, fps, (width,height))
for i in range(1, 1000):
filename = folder_path_char + str(i) + '.jpg'
# 判断图片是否存在
if os.path.exists(filename):
img = cv2.imread(filename=filename)
# 在一个给定的时间内(单位ms)等待用户按键触发,100ms
cv2.waitKey(100)
# 将图片写入视频中
videoWriter.write(img)
# print(str(i) + '.jpg' + ' done!')
# 视频释放
videoWriter.release()
if __name__ == '__main__':
"""使用教程:
将抖音视频转换编码,抖音的编码不能直接使用,转成 mp4 格式 -》https://app.xunjiepdf.com/video
传入视频地址
解析出无声音版的视频
提取视频里的 mp3 -》http://audio-extractor.net/cn/
用视频编辑器将视频压缩并配乐
完成
"""
print("BEGIN")
"""检查文件夹路径在不在"""
if not os.path.exists(folder_path):
os.mkdir(folder_path)
if not os.path.exists(folder_path_char):
os.mkdir(folder_path_char)
"""end"""
url=r'D:\Python_workspace\char_video_sources\dance.mp4'
getVideo(url)
severalThreadings()#多线程处理图片
# getCharPitcure()#单线程处理,已设置默认值为(0,count,False)
createVideo(url.replace(".mp4",".avi"))
"""删除对应文件夹"""
os.system("rd /s /q "+folder_path)
os.system("rd /s /q "+folder_path_char)
"""END"""
print("END")
五、参考博客
https://blog.csdn.net/XZQ121963/article/details/90045996
https://blog.csdn.net/qq_41841569/article/details/84940294
https://blog.csdn.net/weixin_41010198/article/details/88535234
综上:三篇博客中,综合了第一二篇的优点,我自己又加入了多线程,并且视频长宽完全是基于原视频的,所以在性能和功能上都优于前者,第三篇主要是参考了 cv2 的 get() 函数的属性表
六、运行结果(直接上视频截图,哈,因为需要看出效果,所以抖音上找的一个跳舞的小姐姐,人长得挺漂亮,大家感兴趣可以关注一下,还有用人家图片来写博客也挺不好意思的。)