0. Write in front
Setting off firecrackers to celebrate the Spring Festival has a history of more than 2000 years in China. There is an interesting legend about the origin of firecrackers.
In the west, there are mountains, more than feet long, one foot, and the nature is not afraid of people. The crime was so cold and hot that it was called Nian Jing'an. Later generations took its shape and used gunpowder—— "Supernatural Sutra"
At first, people burned bamboo and exploded in order to scare away mandrills that harmed people. It is said that mandrills are most afraid of fire and sound, so every new year's Eve, people "burn bamboo and explode" to scare mandrills away. In this way, year after year, the custom of setting off firecrackers, lighting red candles, beating gongs and drums to celebrate the new year has formed.
New year's new weather, today we use the code to make a dynamic firecracker. The effect is as follows.

The basic principle of dynamic firecracker is to reproduce a recorded firecracker video in the form of character painting. The basic steps are frame sampling → frame by frame conversion to character painting → character painting synthesis video. Let's start now!
1. Video frame sampling
The function is as follows. The main function is to save the video image stream frame by frame to a specific cache folder (it will be created automatically if the folder does not exist). The function input vp is the openCV video handle, and the output number is the number of converted pictures.
def video2Pic(vp): number = 0 if vp.isOpened(): r,frame = vp.read() if not os.path.exists('cachePic'): os.mkdir('cachePic') os.chdir('cachePic') else: r = False while r: number += 1 cv2.imwrite(str(number)+'.jpg',frame) r,frame = vp.read() os.chdir("..") return number
2. Convert picture to character picture
2.1 create pixel character index
Function inputs the RGBA value of the pixel and outputs the corresponding character code. The principle is that the characters are evenly distributed in the whole gray range, and the pixel gray value corresponds to which character code in which interval. The character code can refer to ASCII code
ASCII code uses the specified 7-bit or 8-bit binary array combination to represent 128 or 256 possible characters. Standard ascii code, also known as basic ASCII code, uses 7-bit binary numbers (the remaining 1-bit binary is 0) to represent all uppercase and lowercase letters, numbers 0 to 9, punctuation, and special control characters used in American English. Among them, 0 ~ 31 and 127 (33 in total) are control characters or special characters for communication (the rest are displayable characters), such as control characters: LF (line feed), CR (enter), FF (page feed), DEL (delete), BS (backspace), BEL (ring), etc; Special characters for communication: SOH (text beginning), EOT (text end), ACK (confirmation), etc; ASCII values of 8, 9, 10, and 13 are converted to backspace, tab, line feed, and carriage return characters, respectively. They have no specific graphic display, but they will have different effects on text display according to different applications.
RGBA is the color space representing red, green, blue and alpha. Alpha channel is generally used as opacity parameter. If the alpha channel value of a pixel is 0%, it is completely transparent, while a value of 100% means a completely opaque pixel (traditional digital image). gray=0.2126 * r + 0.7152 * g + 0.0722 * b is the empirical formula for converting RGB to gray value, and human eyes are more sensitive to green.
def color2Char(r,g,b,alpha = 256): imgChar= list("#RMNHQODBWGPZ*@$C&98?32I1>!:-;. ") if alpha: gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b) unit = 256 / len(imgChar) return imgChar[int(gray / unit)] else: return ''
2.2 convert pictures to characters pixel by pixel
The core code is as follows, traversing each pixel of the picture
img = Image.open(imagePath).convert('RGB').resize((imgWidth, imgHeight),Image.NEAREST) for i in range(imgHeight): for j in range(imgWidth): pixel = img.getpixel((j, i)) color.append((pixel[0],pixel[1],pixel[2])) txt = txt + color2Char(pixel[0], pixel[1], pixel[2], pixel[3]) if len(pixel) == 4 else \ txt + color2Char(pixel[0], pixel[1], pixel[2]) txt += '\n' color.append((255,255,255))
3. Synthesize character images into video
The input parameter vp is the openCV video handle, number is the number of frames, savePath is the video save path, MP42 in the function is the encoding method that can generate smaller and smaller video files, and other similar methods include isom, mp41, avc1, qt, etc., indicating the "best" format to parse the current file.
def img2Video(vp, number, savePath): videoFourcc = VideoWriter_fourcc(*"MP42") # Set up video encoder asciiImgPathList = ['cacheChar' + r'/{}.jpg'.format(i) for i in range(1, number + 1)] asciiImgTemp = Image.open(asciiImgPathList[1]).size videoWritter= VideoWriter(savePath, videoFourcc, vp.get(cv2.CAP_PROP_FPS), asciiImgTemp) for imagePath in asciiImgPathList: videoWritter.write(cv2.imread(imagePath)) videoWritter.release()
4. Complete code
import cv2 from PIL import Image,ImageFont,ImageDraw import os from cv2 import VideoWriter, VideoWriter_fourcc ''' * @breif: Convert pixel colors to ASCII character * @param[in]: pixel RGBA value * @retval: character ''' def color2Char(r,g,b,alpha = 256): imgChar = list("#RMNHQODBWGPZ*@$C&98?32I1>!:-;. ") if alpha: gray = int(0.2126 * r + 0.7152 * g + 0.0722 * b) unit = 256 / len(imgChar) return imgChar[int(gray / unit)] else: return '' ''' * @breif: Convert video frame by frame to picture * @param[in]: vp -> openCV Video handle * @retval: number -> Number of pictures converted ''' def video2Pic(vp): number = 0 if vp.isOpened(): r,frame = vp.read() if not os.path.exists('cachePic'): os.mkdir('cachePic') os.chdir('cachePic') else: r = False while r: number += 1 cv2.imwrite(str(number)+'.jpg',frame) r,frame = vp.read() os.chdir("..") return number ''' * @breif: Convert pictures pixel by pixel to ASCII character * @param[in]: imagePath -> Picture path * @param[in]: index -> Picture index * @retval: None ''' def img2Char(imagePath, index): # initialization txt, color, font = '', [], ImageFont.load_default().font imgWidth, imgHeight = Image.open(imagePath).size asciiImg = Image.new("RGB",(imgWidth, imgHeight), (255,255,255)) drawPtr = ImageDraw.Draw(asciiImg) imgWidth, imgHeight = int(imgWidth / 6), int(imgHeight / 15) # Convert the image frame pixel by pixel into ASCII characters and record the RGB value img = Image.open(imagePath).convert('RGB').resize((imgWidth, imgHeight),Image.NEAREST) for i in range(imgHeight): for j in range(imgWidth): pixel = img.getpixel((j, i)) color.append((pixel[0],pixel[1],pixel[2])) txt = txt + color2Char(pixel[0], pixel[1], pixel[2], pixel[3]) if len(pixel) == 4 else \ txt + color2Char(pixel[0], pixel[1], pixel[2]) txt += '\n' color.append((255,255,255)) # Draw ASCII characters and save x, y = 0,0 fontW, fontH = font.getsize(txt[1]) fontH *= 1.37 for i in range(len(txt)): if(txt[i]=='\n'): x += fontH y = -fontW drawPtr.text((y,x), txt[i], fill=color[i]) y += fontW os.chdir('cacheChar') asciiImg.save(str(index)+'.jpg') os.chdir("..") ''' * @breif: Convert video to ASCII Image set * @param[in]: number -> Number of frames * @retval: None ''' def video2Char(number): if not os.path.exists('cacheChar'): os.mkdir('cacheChar') img_path_list = ['cachePic' + r'/{}.jpg'.format(i) for i in range(1, number + 1)] task = 0 for imagePath in img_path_list: task += 1 img2Char(imagePath, task) ''' * @breif: Synthesize images into video * @param[in]: vp -> openCV Video handle * @param[in]: number -> Number of frames * @param[in]: savePath -> Video save path * @retval: None ''' def img2Video(vp, number, savePath): videoFourcc = VideoWriter_fourcc(*"MP42") # Set up video encoder asciiImgPathList = ['cacheChar' + r'/{}.jpg'.format(i) for i in range(1, number + 1)] asciiImgTemp = Image.open(asciiImgPathList[1]).size videoWritter= VideoWriter(savePath, videoFourcc, vp.get(cv2.CAP_PROP_FPS), asciiImgTemp) for imagePath in asciiImgPathList: videoWritter.write(cv2.imread(imagePath)) videoWritter.release() if __name__ == '__main__': videoPath = 'test.mp4' savePath = 'new.avi' vp = cv2.VideoCapture(videoPath) number = video2Pic(vp) video2Char(number) img2Video(vp, number, savePath) vp.release()