利用Opencv将视频转换为字符视频

摘要:在Python课上学习了将图片转换为字符画的代码,想看看可不可以把视频转换为字符画视频,便有了此次为期一周陆陆续续写的视频画代码。

整体思路

  1. 分离出构成视频的图片
  2. 对图片进行ASCII码的转换
  3. 将转换好的图片进行合成为视频
  4. 本次为了方便测试,未添加删除生成文件的代码,需要手动删除或者自己添加代码。

将像素转换为ASCII码

将像素转换为ASCII码是为了在后期上色以及转换为字符时加以区别。

1
2
3
4
5
6
7
8
9
def get_char(r,g,b,alpha = 256):
ascii_char = list("#RMNHQODBWGPZ*@$C&98?32I1>!:-;. ")
#ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:oa+>!:+. ")
if alpha == 0:
return ''
length = len(ascii_char)
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
unit = (256.0+1)/len(ascii_char)
return ascii_char[int(gray/unit)]

​ 此函数的输入的图片的三基色加上透明度,分别为R,G,B,ALPHA。此处会在后面用pixel = im.getpixel((j, i))来进行传递值。

​ 其中ascii_char就是字符列表,用来将不同灰度的像素进行不同字符体替换的参照,大家可以参试用不同的字符来观察出最好的组合。ps:听说个数必须为32的倍数,我也没有进行尝试,有兴趣的小伙伴们可以探究下其中的原因。

​ alpha在为0的时候便是完全透明的图片,所以返回空,当他不为零时则将图片转换为灰度图像,此处运用

​ gray = 0.2126 * r + 0.7152 * g + 0.0722 * b

来将图片转换为灰度图像进行下面的ASCII输出。

将视频分隔成图片

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#将视频转换为图片 并进行计数,返回总共生成了多少张图片!
def video_to_pic(vp):
#vp = cv2.VideoCapture(video_path)
number = 0
if vp.isOpened():
r,frame = vp.read()
if not os.path.exists('cache_pic'):
os.mkdir('cache_pic')
os.chdir('cache_pic')
else:
r = False
while r:
number += 1
cv2.imwrite(str(number)+'.jpg',frame)
r,frame = vp.read()
print('\n由视频一共生成了{}张图片!'.format(number))
os.chdir("..")
return number

​ 可以看出来函数的传入值是vp = cv2.VideoCapture(video_path) 我将其放在主函数来进行输入,怕某些小伙伴没看见所以下其下写了备注。

1.isOpened()的用来检测资源是否在 VideoCapture()函数中打开。

2.os.chdir在PYTHON中的方法是用于改变当前工作目录到指定的路径。此处直接进入新创建的文件夹将生成的图片直接存入文件夹中。

3.返回number方便于后面的使用。

将图片转换为字符图片

此处分为两个函数进行(明明放在一起可以省事,课当初太懒了,懒得改了555)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def star_to_char(number,save_pic_path):
if not os.path.exists('cache_char'):
os.mkdir('cache_char')
img_path_list = [save_pic_path + r'/{}.jpg'.format(i) for i in range(1,number+1)] #生成目标图片文件的路径列表
task = 0
for image_path in img_path_list:
img_width , img_height = Image.open(image_path).size #获取图片的分辨率
task += 1
img_to_char(image_path, img_width , img_height, task)
print('{}/{} is finished.'.format(task,number))
print('=======================')
print('All image was finished!')
print('=======================')
return 0

​ 第一个函数相当于开始准备函数,其中传入的是上一个传出的总共生成多少个图片的统计number,还有字符图片存储的路径。

​ img_path_list 是用来存储图片相对路径的列表,采用内置for函数进行存储。

​ 利用Image.open().size来获取图片大小,传入下面的转换函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def img_to_char(image_path,raw_width,raw_height,task):
width = int(raw_width/ 6)
height = int(raw_height / 15)
im = Image.open(image_path).convert('RGB')#必须以RGB模式打开
im = im.resize((width,height),Image.NEAREST)

txt = ''
color = []
for i in range(height):
for j in range(width):
pixel = im.getpixel((j, i))
color.append((pixel[0],pixel[1],pixel[2])) #将颜色加入进行索引
if len(pixel)==4 :
txt +=get_char(pixel[0],pixel[1],pixel[2],pixel[3])
else:
txt +=get_char(pixel[0],pixel[1],pixel[2])
txt += '\n'
color.append((255,255,255))

im_txt = Image.new("RGB",(raw_width,raw_height),(255,255,255))
dr = ImageDraw.Draw(im_txt)
#font = ImageFont.truetype('consola.ttf', 10, encoding='unic') #改为这个字体会让图片比例改变
font = ImageFont.load_default().font
x,y = 0,0
font_w,font_h=font.getsize(txt[1])
font_h *= 1.37 #调整字体大小
for i in range(len(txt)):
if(txt[i]=='\n'):
x += font_h
y = -font_w
dr.text((y,x),txt[i] ,fill = color[i])
y+=font_w
os.chdir('cache_char')
im_txt.save(str(task)+'.jpg')
os.chdir("..")
return 0

​ 要提醒的是,看下面代码:

1
2
width = int(raw_width/ 6)
height = int(raw_height / 15)

​ 如果要将图片进行转换必须把图片大小进行转换,否则转换出的代码必定会出现乱码。

​ dr = ImageDraw.Draw(im_txt)是opencv中创建画板的函数。

​ font_h是将字体进行调整,可以让生成的字符画视频更加的好康。

​ 下面 for i in range(len(txt)):就是遍历txt按行按列进行写入图片,当遇到\n时推出并进行上色,最后将图片进行输出。

将图片合成为视频

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def jpg_to_video(char_image_path,FPS):
video_fourcc=VideoWriter_fourcc(*"MP42") # 设置视频编码器,这里使用使用MP42编码器,可以生成更小的视频文件
char_img_path_list = [char_image_path + r'/{}.jpg'.format(i) for i in range(1,number+1)] #生成目标字符图片文件的路径列表
char_img_test = Image.open(char_img_path_list[1]).size #获取图片的分辨率
if not os.path.exists('video'):
os.mkdir('video')
video_writter= VideoWriter('video/new_char_video.avi' , video_fourcc , FPS , char_img_test)
sum = len(char_img_path_list)
count = 0
for image_path in char_img_path_list:
img = cv2.imread(image_path)
video_writter.write(img)
end_str = '100%'
count= count + 1
process_bar(count/sum, start_str='', end_str=end_str, total_length=15)

video_writter.release()
print('\n')
print('=======================')
print('The video is finished!')
print('=======================')

​ 此处最需要注意的是第二行的视频编码器,我进行了大量的测试,发现MP42是可以生成较小并且较小的视频文件的编码方式,具体详细参数可以参考

[]: https://blog.csdn.net/Archger/article/details/102868923

完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
import cv2 
from PIL import Image,ImageFont,ImageDraw
import os
from cv2 import VideoWriter, VideoWriter_fourcc, imread, resize
#=========================
#coding:UTF-8
# 视频转字符画含音频version-1
#参考1:https://blog.csdn.net/mp624183768/article/details/81161260
#参考2:https://blog.csdn.net/qq_42820064/article/details/90958577
#参考3:https://blog.csdn.net/zj360202/article/details/79026891
#=========================
def get_char(r,g,b,alpha = 256):
ascii_char = list("#RMNHQODBWGPZ*@$C&98?32I1>!:-;. ")
#ascii_char = list("$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:oa+>!:+. ")
if alpha == 0:
return ''
length = len(ascii_char)
gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b)
unit = (256.0+1)/len(ascii_char)
return ascii_char[int(gray/unit)]

#将视频转换为图片 并进行计数,返回总共生成了多少张图片!
def video_to_pic(vp):
#vp = cv2.VideoCapture(video_path)
number = 0
if vp.isOpened():
r,frame = vp.read()
if not os.path.exists('cache_pic'):
os.mkdir('cache_pic')
os.chdir('cache_pic')
else:
r = False
while r:
number += 1
cv2.imwrite(str(number)+'.jpg',frame)
r,frame = vp.read()
print('\n由视频一共生成了{}张图片!'.format(number))
os.chdir("..")
return number


def img_to_char(image_path,raw_width,raw_height,task):
width = int(raw_width/ 6)
height = int(raw_height / 15)
im = Image.open(image_path).convert('RGB')#必须以RGB模式打开
im = im.resize((width,height),Image.NEAREST)

txt = ''
color = []
for i in range(height):
for j in range(width):
pixel = im.getpixel((j, i))
color.append((pixel[0],pixel[1],pixel[2])) #将颜色加入进行索引
if len(pixel)==4 :
txt +=get_char(pixel[0],pixel[1],pixel[2],pixel[3])
else:
txt +=get_char(pixel[0],pixel[1],pixel[2])
txt += '\n'
color.append((255,255,255))

im_txt = Image.new("RGB",(raw_width,raw_height),(255,255,255))
dr = ImageDraw.Draw(im_txt)
#font = ImageFont.truetype('consola.ttf', 10, encoding='unic') #改为这个字体会让图片比例改变
font = ImageFont.load_default().font
x,y = 0,0
font_w,font_h=font.getsize(txt[1])
font_h *= 1.37 #调整字体大小
for i in range(len(txt)):
if(txt[i]=='\n'):
x += font_h
y = -font_w
dr.text((y,x),txt[i] ,fill = color[i])
y+=font_w
os.chdir('cache_char')
im_txt.save(str(task)+'.jpg')
os.chdir("..")
return 0


def star_to_char(number,save_pic_path):
if not os.path.exists('cache_char'):
os.mkdir('cache_char')
img_path_list = [save_pic_path + r'/{}.jpg'.format(i) for i in range(1,number+1)] #生成目标图片文件的路径列表
task = 0
for image_path in img_path_list:
img_width , img_height = Image.open(image_path).size #获取图片的分辨率
task += 1
img_to_char(image_path, img_width , img_height, task)
print('{}/{} is finished.'.format(task,number))
print('=======================')
print('All image was finished!')
print('=======================')
return 0

def process_bar(percent, start_str='', end_str='', total_length=0):
#进度条
bar = ''.join("■ " * int(percent * total_length)) + ''
bar = '\r' + start_str + bar.ljust(total_length) + ' {:0>4.1f}%|'.format(percent*100) + end_str
print(bar, end='', flush=True)

def jpg_to_video(char_image_path,FPS):
video_fourcc=VideoWriter_fourcc(*"MP42") # 设置视频编码器,这里使用使用MP42编码器,可以生成更小的视频文件
char_img_path_list = [char_image_path + r'/{}.jpg'.format(i) for i in range(1,number+1)] #生成目标字符图片文件的路径列表
char_img_test = Image.open(char_img_path_list[1]).size #获取图片的分辨率
if not os.path.exists('video'):
os.mkdir('video')
video_writter= VideoWriter('video/new_char_video.avi' , video_fourcc , FPS , char_img_test)
sum = len(char_img_path_list)
count = 0
for image_path in char_img_path_list:
img = cv2.imread(image_path)
video_writter.write(img)
end_str = '100%'
count= count + 1
process_bar(count/sum, start_str='', end_str=end_str, total_length=15)

video_writter.release()
print('\n')
print('=======================')
print('The video is finished!')
print('=======================')

if __name__ == '__main__':

video_path = 'test.mp4'
save_pic_path = 'cache_pic'
save_charpic_path = 'cache_char'

vp = cv2.VideoCapture(video_path)
number = video_to_pic(vp)
FPS = vp.get(cv2.CAP_PROP_FPS)
star_to_char(number , save_pic_path)
vp.release()
jpg_to_video(save_charpic_path,FPS)

注:转载请注明出处

------- 本文结束  感谢您的阅读 -------