利用Python和C#实现字符视频

简介

大家可能或多或少的看过那么几个字符视频,像Bad Apple控制台版,效果十分不错。其实,字符视频的原理十分简单,将视频的每一帧都转换为字符画,再连续播放就可以了。本文就来实现一个字符视频的制作工具和控制台播放器。

准备

我们使用Python(opencv、pillow两个库)和C#
先确保安装了opencvpillow

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
pip install opencv-python
pip install pillow
pip install opencv-python pip install pillow
pip install opencv-python
pip install pillow

处理视频

步骤

  1. 利用OpenCV,将视频逐帧保存为图片
  2. 调整图片的分辨率以适合控制台播放,这里调整为64×48
  3. 将小图片通过灰度化转换为灰度图
  4. 取一个合适的临界值,将灰度图二值化,也就是0或1,得到字符画
  5. 将所有的字符画帧保存供播放器播放
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
import cv2
import numpy as np
from PIL import Image
#高度48 宽度64
H = 48
W = 64
#总帧数
cnt = 0
print("Char video maker by Azure99\nPress Enter to continue")
input()
print("Reading video")
video = cv2.VideoCapture("video.flv")
while video.isOpened() :
print("img%d" % cnt)
r, img = video.read();
try :
gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
except :
break
cv2.imwrite("png\\%d.png" % cnt, img)
cnt += 1
videoData = open("video.txt", "w")
#逐帧转换为字符图
for i in range(cnt):
print("frame%d" % i)
#读入图片并修改大小, 再转换为灰度图
img = Image.open("png\\%d.png" % i, "r").resize((W, H)).convert("L")
for Y in range(H):
for X in range(W):
pixel = img.getpixel((X, Y))
#二值化
if pixel < 200:
videoData.write(" #")
else :
videoData.write(" ")
videoData.write("\n")
videoData.close()
print("All done!Now you can move video.txt to C# Player folder")
import cv2 import numpy as np from PIL import Image #高度48 宽度64 H = 48 W = 64 #总帧数 cnt = 0 print("Char video maker by Azure99\nPress Enter to continue") input() print("Reading video") video = cv2.VideoCapture("video.flv") while video.isOpened() : print("img%d" % cnt) r, img = video.read(); try : gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY) except : break cv2.imwrite("png\\%d.png" % cnt, img) cnt += 1 videoData = open("video.txt", "w") #逐帧转换为字符图 for i in range(cnt): print("frame%d" % i) #读入图片并修改大小, 再转换为灰度图 img = Image.open("png\\%d.png" % i, "r").resize((W, H)).convert("L") for Y in range(H): for X in range(W): pixel = img.getpixel((X, Y)) #二值化 if pixel < 200: videoData.write(" #") else : videoData.write(" ") videoData.write("\n") videoData.close() print("All done!Now you can move video.txt to C# Player folder")
import cv2
import numpy as np
from PIL import Image

#高度48 宽度64
H = 48
W = 64

#总帧数
cnt = 0

print("Char video maker by Azure99\nPress Enter to continue")
input()

print("Reading video")
video = cv2.VideoCapture("video.flv")
while video.isOpened() :
    print("img%d" % cnt)
    r, img = video.read();
    try :
        gary = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    except :
        break
    cv2.imwrite("png\\%d.png" % cnt, img)
    cnt += 1

videoData = open("video.txt", "w")

#逐帧转换为字符图
for i in range(cnt):
    print("frame%d" % i)
    #读入图片并修改大小, 再转换为灰度图
    img = Image.open("png\\%d.png" % i, "r").resize((W, H)).convert("L")
    for Y in range(H):
        for X in range(W):
            pixel = img.getpixel((X, Y))
            #二值化
            if pixel < 200:
                videoData.write(" #")
            else :
                videoData.write("  ")
        videoData.write("\n")

videoData.close()
print("All done!Now you can move video.txt to C# Player folder")

编写播放器

步骤

  1. 读入所有的字符画数据
  2. 以48行(也就是图片高度)为帧临界点
  3. 逐帧输出

可以优化的地方

  • 在播放前全部读入数据,并以每48行为单位保存在数组中
  • 输出一帧时不要清屏,而是将光标移到开头重新输出,这样可以避免清屏到下一帧之间的空隙带来的闪烁
Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;
namespace Player
{
class Program
{
private const int HEIGHT = 48;//每帧高度
private static string[] frames;
static void Main(string[] args)
{
Console.WriteLine("\t\tReading video...\n\n");
ReadVideo();
Console.WriteLine("\t\tChar video player by Azure99\n\t\tPress any key to play");
Console.ReadKey();
PlayVideo();
}
private static void ReadVideo()
{
StreamReader sr = new StreamReader("video.txt", Encoding.Default);
List<string> framesList = new List<string>();
while (!sr.EndOfStream)
{
StringBuilder sb = new StringBuilder();
for(int i=0; i<HEIGHT; i++)//48行为一帧
{
sb.Append(sr.ReadLine() + "\n");
}
framesList.Add(sb.ToString());
}
frames = framesList.ToArray();
}
private static void PlayVideo()
{
Console.Clear();
for (int i = 0; i < frames.Length; i++)
{
// 不要清屏, 将光标移到0, 0
Console.CursorLeft = 0;
Console.CursorTop = 0;
Console.Write(frames[i]);
// 根据机器配置, 调整暂停时间
Thread.Sleep(30);
}
}
}
}
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.IO; using System.Threading; namespace Player { class Program { private const int HEIGHT = 48;//每帧高度 private static string[] frames; static void Main(string[] args) { Console.WriteLine("\t\tReading video...\n\n"); ReadVideo(); Console.WriteLine("\t\tChar video player by Azure99\n\t\tPress any key to play"); Console.ReadKey(); PlayVideo(); } private static void ReadVideo() { StreamReader sr = new StreamReader("video.txt", Encoding.Default); List<string> framesList = new List<string>(); while (!sr.EndOfStream) { StringBuilder sb = new StringBuilder(); for(int i=0; i<HEIGHT; i++)//48行为一帧 { sb.Append(sr.ReadLine() + "\n"); } framesList.Add(sb.ToString()); } frames = framesList.ToArray(); } private static void PlayVideo() { Console.Clear(); for (int i = 0; i < frames.Length; i++) { // 不要清屏, 将光标移到0, 0 Console.CursorLeft = 0; Console.CursorTop = 0; Console.Write(frames[i]); // 根据机器配置, 调整暂停时间 Thread.Sleep(30); } } } }
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Threading;

namespace Player
{
    class Program
    {
        private const int HEIGHT = 48;//每帧高度

        private static string[] frames;
        static void Main(string[] args)
        {
            Console.WriteLine("\t\tReading video...\n\n");

            ReadVideo();

            Console.WriteLine("\t\tChar video player by Azure99\n\t\tPress any key to play");
            Console.ReadKey();

            PlayVideo();
        }

        private static void ReadVideo()
        {
            StreamReader sr = new StreamReader("video.txt", Encoding.Default);
            List<string> framesList = new List<string>();

            while (!sr.EndOfStream)
            {
                StringBuilder sb = new StringBuilder();
                for(int i=0; i<HEIGHT; i++)//48行为一帧
                {
                    sb.Append(sr.ReadLine() + "\n");
                }
                framesList.Add(sb.ToString());
            }

            frames = framesList.ToArray();
        }

        private static void PlayVideo()
        {
            Console.Clear();
            for (int i = 0; i < frames.Length; i++)
            {
                // 不要清屏, 将光标移到0, 0
                Console.CursorLeft = 0;
                Console.CursorTop = 0;
                Console.Write(frames[i]);
                // 根据机器配置, 调整暂停时间
                Thread.Sleep(30);
            }
        }
    }
}

Azure99

底层码农,休闲音游玩家,偶尔写写代码

看看这些?

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注