|
| 1 | +""" |
| 2 | +@author: Viet Nguyen <[email protected]> |
| 3 | +""" |
| 4 | +import argparse |
| 5 | + |
| 6 | +import cv2 |
| 7 | +import numpy as np |
| 8 | +from PIL import Image, ImageFont, ImageDraw, ImageOps |
| 9 | + |
| 10 | + |
| 11 | +def get_args(): |
| 12 | + parser = argparse.ArgumentParser("Image to ASCII") |
| 13 | + parser.add_argument("--input", type=str, default="data/input.mp4", help="Path to input video") |
| 14 | + parser.add_argument("--output", type=str, default="data/output.mp4", help="Path to output video") |
| 15 | + parser.add_argument("--mode", type=str, default="complex", choices=["simple", "complex"], |
| 16 | + help="10 or 70 different characters") |
| 17 | + parser.add_argument("--background", type=str, default="black", choices=["black", "white"], |
| 18 | + help="background's color") |
| 19 | + parser.add_argument("--num_cols", type=int, default=100, help="number of character for output's width") |
| 20 | + parser.add_argument("--scale", type=int, default=1, help="upsize output") |
| 21 | + parser.add_argument("--fps", type=int, default=0, help="frame per second") |
| 22 | + parser.add_argument("--overlay_ratio", type=float, default=0.2, help="Overlay width ratio") |
| 23 | + args = parser.parse_args() |
| 24 | + return args |
| 25 | + |
| 26 | + |
| 27 | +def main(opt): |
| 28 | + if opt.mode == "simple": |
| 29 | + CHAR_LIST = '@%#*+=-:. ' |
| 30 | + else: |
| 31 | + CHAR_LIST = "$@B%8&WM#*oahkbdpqwmZO0QLCJUYXzcvunxrjft/\|()1{}[]?-_+~<>i!lI;:,\"^`'. " |
| 32 | + if opt.background == "white": |
| 33 | + bg_code = (255, 255, 255) |
| 34 | + else: |
| 35 | + bg_code = (0, 0, 0) |
| 36 | + font = ImageFont.truetype("fonts/DejaVuSansMono-Bold.ttf", size=int(10 * opt.scale)) |
| 37 | + cap = cv2.VideoCapture(opt.input) |
| 38 | + if opt.fps == 0: |
| 39 | + fps = int(cap.get(cv2.CAP_PROP_FPS)) |
| 40 | + else: |
| 41 | + fps = opt.fps |
| 42 | + num_chars = len(CHAR_LIST) |
| 43 | + num_cols = opt.num_cols |
| 44 | + while cap.isOpened(): |
| 45 | + flag, frame = cap.read() |
| 46 | + if flag: |
| 47 | + image = frame |
| 48 | + else: |
| 49 | + break |
| 50 | + height, width, _ = image.shape |
| 51 | + cell_width = width / opt.num_cols |
| 52 | + cell_height = 2 * cell_width |
| 53 | + num_rows = int(height / cell_height) |
| 54 | + if num_cols > width or num_rows > height: |
| 55 | + print("Too many columns or rows. Use default setting") |
| 56 | + cell_width = 6 |
| 57 | + cell_height = 12 |
| 58 | + num_cols = int(width / cell_width) |
| 59 | + num_rows = int(height / cell_height) |
| 60 | + char_width, char_height = font.getsize("A") |
| 61 | + out_width = char_width * num_cols |
| 62 | + out_height = 2 * char_height * num_rows |
| 63 | + out_image = Image.new("RGB", (out_width, out_height), bg_code) |
| 64 | + draw = ImageDraw.Draw(out_image) |
| 65 | + for i in range(num_rows): |
| 66 | + for j in range(num_cols): |
| 67 | + partial_image = image[int(i * cell_height):min(int((i + 1) * cell_height), height), |
| 68 | + int(j * cell_width):min(int((j + 1) * cell_width), width), :] |
| 69 | + partial_avg_color = np.sum(np.sum(partial_image, axis=0), axis=0) / (cell_height * cell_width) |
| 70 | + partial_avg_color = tuple(partial_avg_color.astype(np.int32).tolist()) |
| 71 | + char = CHAR_LIST[min(int(np.mean(partial_image) * num_chars / 255), num_chars - 1)] |
| 72 | + draw.text((j * char_width, i * char_height), char, fill=partial_avg_color, font=font) |
| 73 | + |
| 74 | + if opt.background == "white": |
| 75 | + cropped_image = ImageOps.invert(out_image).getbbox() |
| 76 | + else: |
| 77 | + cropped_image = out_image.getbbox() |
| 78 | + out_image = out_image.crop(cropped_image) |
| 79 | + out_image = np.array(out_image) |
| 80 | + try: |
| 81 | + out |
| 82 | + except: |
| 83 | + out = cv2.VideoWriter(opt.output, cv2.VideoWriter_fourcc(*"XVID"), fps, |
| 84 | + ((out_image.shape[1], out_image.shape[0]))) |
| 85 | + |
| 86 | + if opt.overlay_ratio: |
| 87 | + height, width, _ = out_image.shape |
| 88 | + overlay = cv2.resize(frame, (int(width * opt.overlay_ratio), int(height * opt.overlay_ratio))) |
| 89 | + out_image[height - int(height * opt.overlay_ratio):, width - int(width * opt.overlay_ratio):, :] = overlay |
| 90 | + out.write(out_image) |
| 91 | + cap.release() |
| 92 | + out.release() |
| 93 | + |
| 94 | + |
| 95 | +if __name__ == '__main__': |
| 96 | + opt = get_args() |
| 97 | + main(opt) |
0 commit comments