Fundamentals 13 min read

Creating an ASCII‑Art Video from a Celebrity Clip Using Python and OpenCV

This tutorial demonstrates how to convert a video of a celebrity into an ASCII art video using Python 3.7, OpenCV, PIL, and NumPy, covering tool setup, frame extraction, grayscale conversion, K‑means clustering for brightness mapping, character rendering, and final video assembly.

Python Programming Learning Circle
Python Programming Learning Circle
Python Programming Learning Circle
Creating an ASCII‑Art Video from a Celebrity Clip Using Python and OpenCV

This article explains how to turn a short video (e.g., a clip of singer Cyndi Wang) into an ASCII‑art video by leveraging Python libraries such as OpenCV, Pillow (PIL), and NumPy. It starts with a brief nostalgic introduction and presents the overall workflow.

Tools required

Development IDE: PyCharm

Runtime: Python 3.7 on Windows 10

Libraries: Pillow (PIL), OpenCV (cv2), NumPy

Project effect

Project idea

The process consists of three main stages: (1) split the source video into individual frames, (2) convert each frame into an ASCII‑style image, and (3) re‑assemble the processed frames into a new video.

Step 1 – Video to frames

<code># 将视频转换为图片 并进行计数,返回总共生成了多少张图片!
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</code>

Step 2 – Convert frames to ASCII images

<code>def img2strimg(frame, K=3):
    # 读取矩阵的长度 有时返回两个值,有时三个值
    height, width, *_ = frame.shape
    # 颜色空间转化 图片对象, 灰度处理
    frame_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY)
    # 转换数据类型,将数据降维
    frame_array = np.float32(frame_gray.reshape(-1))
    # K‑means 聚类
    compactness, labels, centroids = cv2.kmeans(frame_array, K, None,
        (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0),
        10, cv2.KMEANS_RANDOM_CENTERS)
    centroids = np.uint8(centroids).flatten()
    centroids_sorted = sorted(centroids)
    centroids_index = np.array([centroids_sorted.index(v) for v in centroids])
    # 亮度设置
    bright = [abs((3 * i - 2 * K) / (3 * K)) for i in range(1, K+1)]
    bright_bound = bright.index(np.min(bright))
    # 背景阴影设置
    shadow = [abs((3 * i - K) / (3 * K)) for i in range(1, K+1)]
    shadow_bound = shadow.index(np.min(shadow))
    labels = labels.flatten()
    labels = centroids_index[labels]
    # 每2*2像素挑选一个
    labels_picked = [labels[rows * width:(rows+1)*width:2] for rows in range(0, height, 2)]
    canvas = np.zeros((3 * height, 3 * width, 3), np.uint8)
    canvas.fill(255)
    y = 0
    for rows in labels_picked:
        x = 0
        for cols in rows:
            if cols <= shadow_bound:
                cv2.putText(canvas, str(random.randint(2,9)), (x, y),
                    cv2.FONT_HERSHEY_PLAIN, 0.45, 0.1)
            elif cols <= bright_bound:
                cv2.putText(canvas, '-', (x, y), cv2.FONT_HERSHEY_PLAIN, 0.4, 0, 1)
            x += 6
        y += 6
    return canvas</code>

Step 3 – Assemble ASCII frames into a video

<code>def jpg_to_video(char_image_path, FPS):
    video_fourcc = cv2.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 = cv2.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)
        count += 1
        process_bar(count / sum, start_str='', end_str='100%', total_length=15)
    video_writter.release()
    print('\n=======================')
    print('The video is finished!')
    print('=======================')</code>

Simple source sharing

<code># from platypus import
import os
from PIL import Image, ImageFont, ImageDraw
import cv2
import random
import numpy as np
import threading

# Main workflow
if __name__ == '__main__':
    video_path = '王心凌.mp4'
    vp = cv2.VideoCapture(video_path)
    number = video_to_pic(vp)
    for i in range(1, number):
        fp = r"cache_pic/{}.jpg".format(i)
        img = cv2.imread(fp)
        str_img = img2strimg(img)
        cv2.imwrite("cache_char/{}.jpg".format(i), str_img)
    FPS = vp.get(cv2.CAP_PROP_FPS)
    jpg_to_video('cache_char', FPS)</code>

The article concludes with a reminder that the generated video can become large if a high frame rate is used, and provides links to related Python tutorials.

pythonimage processingvideo processingopencvascii artk-means clustering
Python Programming Learning Circle
Written by

Python Programming Learning Circle

A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.