如果您想在没有 x-server、ssh 连接的机器上观看网络摄像头,或者只是自己为 Instagram 拍摄了一张 ascii 的照片,这可能会有所帮助。
要求
我正在使用 Ubuntu,所以准备好使用 apt install 命令……我为什么要使用 Konsole? 因为它更快,并且显示所有内容都没有故障 VS ubuntu 终端、xterm 或 tmux。
sudo apt install konsole python3 \
&& python3 -m pip install numpy \
&& python3 -m pip install opencv-python
import
# hah, looks like foot :)
import os
import sys
import time
import signal
import cv2 as cv
import numpy as np
from typing import List
from enum import Enum, unique
from os import get_terminal_size
网络摄像头流
创建此代码也是为了将录制的视频转换为 ascii 格式,但在以后的文章中会对此进行更多介绍……
播放模式
现在我将只使用 REALTIME 模式,只是跳过帧以获得更低的 fps,这是降低标准输出负载所必需的。 因此,我们可以以较低的 fps 观看网络摄像头,但分辨率更高而不会出现故障。
@unique
class PlayMode(Enum): # fps-dependent behaviour:
VIDEO = 'RESTART_CMD' # sleep until next frame time
REALTIME = 'SHUTDOWN_CMD' # skip until nex frame time
清算控制台
我将在 ctrl+c 键入(SIGINT)之后、在打印每一帧之前以及在 ok 程序退出之前清除控制台。
clearConsole = lambda: os.system('cls' if os.name in ('nt', 'dos') else 'clear')
def on_sigint_clear_console(sig, frame):
if sig == signal.SIGINT:
clearConsole()
sys.exit(0)
读取视频流
在这里,我们做所有顶级的事情:
- 使用 opencv 读取网络摄像头流
- 转换为灰度帧
- 调整到终端大小
- 转换为 ascii 帧
- 在终端打印它。
同样在这里,我以 2 种不同的方式将 fps 限制设置为 max_fps arg,如上所述……
black2white_chars — 包含将用于替换帧像素的字符。 像素范围是 [0, 255] => black...white,所以如果你通过 ['.', '+', '@'], - 黑色像素将替换为 '.',灰色替换为 '+' 和带有“@”的白色。 您可以传递更多不同的字符,使图像更时髦。
black2white_ansi_colors — 包含 ansi 转义码,将用于打印特定像素,与其值相关。 它类似于 black2white_chars,但我使用颜色而不是字符,这是 ansi 转义码。
def play_in_terminal(
video_path_or_cam_idx : [str, int],
black2white_chars : List[str],
black2white_ansi_colors : List[int],
height : int,
width : int,
mode : PlayMode,
max_fps = None):
cap = cv.VideoCapture(video_path_or_cam_idx)
if not cap.isOpened():
print("Cannot cap")
exit()
signal.signal(signal.SIGINT, on_sigint_clear_console)
max_fps = max_fps if max_fps else cap.get(cv.CAP_PROP_FPS)
frame_period_ns = 1e9 / max_fps
prev_frame_time_ns = time.time_ns()
while True:
ret, frame = cap.read()
if not ret:
print("Can't receive frame, exiting...")
break
elapsed_time_ns = (time.time_ns() - prev_frame_time_ns)
if (elapsed_time_ns < frame_period_ns):
if mode == PlayMode.REALTIME:
continue
elif mode == PlayMode.VIDEO:
time.sleep((frame_period_ns-elapsed_time_ns)/1e9)
else:
assert 0, "Unhandled play mode"
prev_frame_time_ns = time.time_ns()
frame = cv.cvtColor(frame, cv.COLOR_BGR2GRAY)
frame = cv.resize(frame, (width, height), cv.INTER_NEAREST)
ascii_frame = frame2ascii(frame, black2white_chars, black2white_ansi_colors)
print_video_frame_ascii(ascii_frame)
cap.release()
clearConsole()
帧转ASCII
在这里我做两件事:
- 用 ascii 字符替换像素灰度值,分别对应于 black2white_chars
- 使用 ansi 转义码对 ascii 字符进行着色,分别对应于 black2white_ansi_colors
- 使用 np.vectorize() 向量化这个过程,以获得更快的代码。
def frame2ascii(frame: np.ndarray, black2white_chars: List[str], black2white_ansi_colors: List[int]):
dims = len(frame.shape)
assert dims == 2 \
f"Frame shape should be 2d, got: <{dims}d>"
if dims == 2:
return process_gray_frame(frame, black2white_chars, black2white_ansi_colors)
return res
def process_gray_frame(frame: np.ndarray, black2white_chars: List[str], black2white_ansi_colors: List[int]):
return np.vectorize(lambda pix : colorize_text_gray(pix2ascii_gray(pix, black2white_chars), pix, black2white_ansi_colors))(frame)
def colorize_text_gray(text: str, gray: int, black2white_ansi_colors: List[int]):
black2white = black2white_ansi_colors
step = 255 / len(black2white)
color_idx = min(int(gray / step), len(black2white)-1)
color = black2white[color_idx]
return f"\033[{color};1m{text}\033[0m"
def pix2ascii_gray(pix: int, black2white_chars: List[str]):
step = 255 / len(black2white_chars)
char_idx = min(int(pix/step), len(black2white_chars)-1)
return black2white_chars[char_idx]
打印ASCII帧
所以......我只是连接所有 ascii 字符,插入 \n 以形成行,然后清除控制台并使用刷新选项打印它以立即将所有内容放入终端(不允许缓冲它)。
def print_video_frame_ascii(frame: np.ndarray):
dims = len(frame.shape)
assert dims == 2, f"Frame shape should be 2d, got: <{dims}d>"
frame_str = ''
for row in frame:
for pix in row:
frame_str += pix
frame_str += "\n"
clearConsole()
print(f"{frame_str}", flush=True)
主要的
有趣的东西从这里开始 :) 我选择了几个调色板用于框架着色和一些漂亮的 ascii 字符集。 让我们都试试!!!
if __name__ == "__main__":
term_size = get_terminal_size()
h, w = term_size.lines, term_size.columns
print(f"Terminal size (h, w): ({h}, {w})")
black2white_gray = [90, 37, 97]
black2white_green = [90, 32, 92]
black2white_yellow = [33, 93]
black2white_blue = [34, 36, 94, 96]
black2white_rainbow_dark = list(range(30, 38))
black2white_rainbow_light = list(range(90, 98))
while True:
play_in_terminal(
video_path_or_cam_idx = 0,
black2white_chars = [' ', '`', '.', '~', '+', '*', 'o', 'O', '0', '#', '@'],
black2white_ansi_colors = black2white_green,
height = h,
width = w,
mode = PlayMode.REALTIME,
max_fps = 30
)
本文暂时没有评论,来添加一个吧(●'◡'●)