通过一个图像合成视频的脚本案例测试了一下Python3在单线程、多线程和多进程下的运行速度。发现多线程可以一定程度上提高运行速度(将1个线程拆为4个线程,提速大约83%),但是不能达到完全的并行运算,据说是因为threading模块并不能真正调用多个处理器。在多进程下运行,可以接近完全平行的运算速度。

例程概述

在相对路径./image_storage/里有四个文件夹,分别是./image_storage/1/, ./image_storage/2/, ./image_storage/3/, ./image_storage/4/。每个文件夹中有300张.bmp图片文件,单个图片的文件大小为14.3MB,尺寸为2448×2048。需要将这四个文件夹中的图片分别合成成四个.mp4视频。此外,由于图片文件中记录了其拍摄时刻,所以还会据此来计算视频的帧率。

合成视频的函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def generate_mp4(path, image_list):
    print('Generating video... '+path)
    image_list.sort() # 按时间排序(即按文件名排序)
    t_steps = [] # 计算帧率,具体命名格式不重要
    for i in range(len(image_list)-1):
        im0_stamp = image_list[i][:-4]
        t0 = int(im0_stamp.split('_')[-1])/1e6 + int(im0_stamp.split('_')[-2]) + int(im0_stamp.split('_')[-3])*60 + int(im0_stamp.split('_')[-4])*60*60
        im1_stamp = image_list[i+1][:-4]
        t1 = int(im1_stamp.split('_')[-1])/1e6 + int(im1_stamp.split('_')[-2]) + int(im1_stamp.split('_')[-3])*60 + int(im1_stamp.split('_')[-4])*60*60
        t_steps.append(t1-t0)
    frame_rate = 1/np.mean(t_steps) # 根据相邻帧平均时差得帧率
    
    size = (2448, 2048) #(width, height)
    four_cc = cv2.VideoWriter_fourcc(*'mp4v')

    cam_id = ...
    video_name = cam_id+image_list[0][-31:-4]+'-'+image_list[-1][-19:-4]
    videowriter = cv2.VideoWriter('./saved_video/'+video_name+'.mp4', four_cc, frame_rate, size)

    for im in image_list:
        img = cv2.imread(path+im)
        videowriter.write(img)
    videowriter.release()
    cv2.destroyAllWindows()

测试与结果

线性程序

先测一下单线情况下的运行时长,依次将四个文件夹里的图片合成为视频。

1
2
3
4
5
6
7
8
paths = ['./image_storage/4/', './image_storage/2/', './image_storage/3/', './image_storage/4/']
start = time.time()
for p in paths:
    raw_list = os.listdir(p)
    im_list = [i for i in raw_list if i[-4:]=='.bmp']
    generate_mp4(p, im_list)
end = time.time()
print(str(end-start))

Output:

1
2
3
4
5
Generating video... ./image_storage/1/
Generating video... ./image_storage/2/
Generating video... ./image_storage/3/
Generating video... ./image_storage/4/
74.97052454948425

分别单独运行的结果:

1
2
Generating video... ./image_storage/1/
19.02564239501953
1
2
Generating video... ./image_storage/2/
19.450006008148193
1
2
Generating video... ./image_storage/3/
16.513428211212158
1
2
Generating video... ./image_storage/4/
19.51286506652832

一共用时大约75秒,四个任务的耗时基本上是相似的。

多线程

threading模块实现多线程处理,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
threads = []
paths = ['./image_storage/4/', './image_storage/2/', './image_storage/3/', './image_storage/4/']
start = time.time()
for p in paths:
    raw_list = os.listdir(p)
    im_list = [i for i in raw_list if i[-4:]=='.bmp']
    thread = threading.Thread(name=p, target=generate_mp4, args=(p, im_list), daemon=False) # 创建线程
    threads.append(thread)

for th in threads:
    th.start() # 开始线程

while(True): # 监测线程数量,≤1表示除主线程外都已完成
    c = threading.active_count()
    if c<=1:
        break
end = time.time()
print(str(end-start))

Output:

1
2
3
4
5
Generating video... ./image_storage/1/
Generating video... ./image_storage/2/
Generating video... ./image_storage/3/
Generating video... ./image_storage/4/
40.90414476394653

一共用时大约41秒,比线性的耗时少了45%,速度明显提高,但是并没有达到完全并行的效果。

多进程

multiprocessing模块实现多进程处理,

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
if __name__=='__main__':
    processes = []
    paths = ['./image_storage/4/', './image_storage/2/', './image_storage/3/', './image_storage/4/']
    start = time.time()
    for p in paths:
        raw_list = os.listdir(p)
        im_list = [i for i in raw_list if i[-4:]=='.bmp']
        process = multiprocessing.Process(name=p, target=generate_mp4, args=(p, im_list), daemon=False) # 创建进程
        processes.append(process)

    for pr in processes:
        pr.start() # 开始进程

    while(True): # 监测子进程数量
        c = len(multiprocessing.active_children())
        if c<=0:
            break
    end = time.time()
    print(str(end-start))

Output:

1
2
3
4
5
Generating video... ./image_storage/4/
Generating video... ./image_storage/2/
Generating video... ./image_storage/3/
Generating video... ./image_storage/4/
22.97856903076172

用时大约23秒,比较接近线性运行用时的四分之一了。

注意:1

  • 在Windows中,由于没有fork(Linux中创建进程的机制),在创建进程的时候会import启动该文件,而在import文件的时候又会再次运行整个文件,如果把Process()放在 if __name__ == '__main__'判断之外,则Process()在被import的时候也会被运行,导致无限递归创建子进程导致报错,所以在Windows系统下,必须把Process()放在 if __name__ == '__main__' 的判断保护之下。
  • 在子进程中不能使用input,因为输入台只显示在主进程中,故如果在子进程中使用input,会导致报错。