编程开源技术交流,分享技术与知识

网站首页 > 开源技术 正文

在终端中观看网络摄像头流,使用 ascii 字符和 Python

wxchong 2024-08-19 01:53:16 开源技术 25 ℃ 0 评论

如果您想在没有 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
        )

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表