首页 > temp > 简明python教程 >
-
【Python | opencv+PIL】常见操作(创建、添加帧、绘图、读取等)的效率对比及其
一、背景
本人准备用python做图像和视频编辑的操作,却发现opencv和PIL的效率并不是很理想,并且同样的需求有多种不同的写法并有着不同的效率。见全网并无较完整的效率对比文档,遂决定自己丰衣足食。
二、目的
本篇文章将对Python下的opencv接口函数及PIL(Pillow)函数的常用部分进行逐个运行并计时(多次测算取平均时间和最短时间,次数一般在100次以上),并简单使用numba、ctypes、cython等方法优化代码。
三、测试方法及环境
1.硬件
CPU:Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz 3.30 GHz
内存:4.00 GB
硬盘:ATA WDC WD5000AAKX-7 SCSI Disk Device
2.软件:
操作系统:Windows 7 Service Pack 1 Ultimate 64bit zh-cn
Python解释器:3.7.5 64bit (provided by Anaconda)
各模块:皆为最新
(事情有所变化,暂时使用下面机房电脑的配置进行测试)
1.硬件
CPU:Intel(R) Xeon(R) Silver 4116 CPU @ 2.10GHz 2.10 GHz
内存:3.00 GB
硬盘:VMware Virtual disk SCSI Disk Service
2.软件:
操作系统:Windows 7 Service Pack 1 Ultimate 64bit zh-cn (powered by VMware Horizon View Client)
Python解释器:3.7.3 64bit (provided by Anaconda)
各模块:皆为最新
四、具体实现
1.待测试函数
以下定义新建的视频规定为MP4格式、mp4v编码、1920*1080尺寸、60帧速率;定义新建的图片为JPG格式、1920*1080尺寸、RGB通道。
根据实际需要(其实是我自己的需要),先暂定测试以下函数[1][2]:
1)创建视频
vw = cv2.VideoWriter('out.mp4', cv2.VideoWriter_fourcc(*'mp4v'), 60, (1920, 1080)) # Return MP4 video object
2)视频帧读取(视频不好做测试数据,故使用了手头上现成的。in.mp4参数:时长27秒,尺寸1920x1080,数据速率17073kbps,总比特率17331kbps,帧速率29fps,大小55.7MB)
cap = cv2.VideoCapture('in.mp4')
while cap.isOpened():
ret, frame = cap.read() # frame return a numpy.ndarray object (WRITEABLE) with RGB of pixels
if not ret: # Return True when read operation is successful
break # Read operation fails and break
cap.release()
3)视频帧写入[3] (PS:为什么Opencv官方教程中没有这个函数...)
vw.write(frame)
4)写入视频(后来发现这个应该类似于file.close(),只是一个释放文件对象的过程,并不是真的在这个时候写入所有的数据。之前看见在release之前文件是空的应该是数据还没有从内存写入磁盘导致的)
vw.release()
5)创建图片 ( matrix & pillow object )
# Matrix
arr = np.zeros((1080, 1920, 3), dtype=np.uint8) # numpy中xy貌似是颠倒的,于是长1920宽1080的图像输出的shape应该是1080x1920,第三维度3表示图片通道为RGB
# Return a numpy.ndarray object (WRITEABLE)
# Pillow
img = Image.new('RGB', (1920, 1080)) # 这里的xy没有颠倒
6)图片读取(opencv & pillow)(使用新建的图片,满足上面的定义,大小33kb)
# OpenCV
arr = cv2.imread('in.jpg') # Notice that OpenCV don't support ALPHA channel
# Pillow
img = Image.open('in.jpg') # Return a PIL.Image.Image object
7)图片数据结构转换
arr1 = list(img.im) # Return a list
arr2 = np.asarray(img) # Return a np.ndarray object (NOT WRITEABLE) (Shallow copy)
arr3 = np.array(img) # Return a np.ndarray object (WRITEABLE) (Deep copy)
8)图片点操作(matrix & pillow object )
# Matrix
arr3[0][0] = (255, 255, 255)
# Pillow
img.putpixel((0, 0), (255, 255, 255)) # Putpixel
draw = ImageDraw.Draw(img) # ImageDraw.Point
draw.point((0, 0), (255, 255, 255))
# PS: OpenCV don't has a function that draw a pixel directly so we don't show the code here
9)图片其他绘图操作(matrix & pillow object & opencv )
这里我们测试画直线、画矩形、画圆(不包括matrix)、画椭圆操作(不包括matrix)、绘制文字(不包括matrix)。
注:pillow中默认绘制的图形都是实心的[4],而opencv要设置线宽为负值才是实心的[5]。
### Line
# Matrix
for x in range(100, 500):
arr3[100][x] = (255, 255, 255) # 注意到numpy的颠倒
# Pillow
draw.line((100, 100, 500, 100), (255, 255, 255))
# OpenCV
cv2.line(arr, (100, 100), (500, 100), (255, 255, 255), 1) # 最后的1表示线宽
### Rectangle
# Matrix
for x in range(100, 500):
for y in range(100, 500):
arr3[y][x] = (255, 255, 255)
# Pillow
draw.rectangle((100, 100, 500, 500), (255, 255, 255))
# OpenCV
cv2.rectangle(arr, (100, 100), (500, 500), (255, 255, 255), -1)
### Circle
# Pillow
draw.arc((100, 100, 500, 500), 0, 360, (255, 255, 255)) # PIL.ImageDraw.Draw.arc
# arc方法前一个四元元组表示圆弧的左上点右下点,这里表示半径200、中心(300, 300);后面两个整数表示度数(0-360表示整个圆)
draw.ellipse((100, 100, 500, 500), (255, 255, 255)) # PIL.ImageDraw.Draw.ellipse
# ellipse方法同样表示两点
# OpenCV
cv2.circle(arr, (300, 300), 200, (255, 255, 255), -1) # cv2.circle
# 与Pillow不同的是,这里读取的是中心点和半径,更符合正常的习惯;1表示线宽,如果是-1则是实心圆
cv2.ellipse(arr, (300, 300), (200, 200), 0, 0, 360, (255, 255, 255), -1) # cv2.ellipse
# 这里第一个二元组是椭圆中心,第二个二元组分别表示半长轴长和半短轴长(注:中文文档漏掉了“半”字),后面三个参数分别表示椭圆本身逆时针旋转角(相当于坐标轴旋转)、起始角度和终止角度(0-360表示整个圆)
### Ellipse
# Pillow
draw.ellipse((100, 100, 700, 500), (255, 255, 255)) # 表示椭圆中心(400, 300),半长轴300,半短轴200
# OpenCV
cv2.ellipse(arr, (400, 300), (300, 200), 0, 0, 360, (255, 255, 255), -1)
### Text
# Pillow
font = ImageFont.truetype('simkai.ttf', 32) # 楷体,字号32
draw.text((100, 100), 'Hello, world!', (255, 255, 255), font) # 这里的坐标是左上角
# OpenCV
font = cv2.FONT_HERSHEY_SIMPLEX
cv2.putText(arr, 'Hello, world!', (100, 200), font, 2, (255, 255, 255), 1, cv2.LINE_AA) # 这里的坐标是左下角,1表示线宽(cv2不支持中文输出,故不测试中文)
其中opencv的字体参数参考:[6]
10)图片其他操作
11)写入图片( Pillow & OpenCV)
# Pillow
img.save('out.jpg')
# OpenCV
cv2.imwrite('out.jpg', arr) # Read from cv2.imread
cv2.imwrite('out.jpg', arr2) # np.asarray
cv2.imwrite('out.jpg', arr3) # np.array
2.时间计算工具
这里的时间计算工具用一个类实现给定次数的循环和智能循环(自动控制循环次数)的功能,并能给出每次循环的函数返回值、循环次数、平均时间、最短时间、最长时间、总共用时。
对于自动判断循环次数的算法参考了Python的timeit模块源码(autorange函数)[7]:
1 # -*- coding: utf-8 -*-
2
3 import time
4 import cv2
5 from PIL import Image, ImageDraw, ImageFont
6 import numpy as np
7
8 # Class
9 class FunctionTimer(object):
10 MAX_WAIT_SEC = 0.5
11 INF = 2147483647
12 SMART_LOOP = -1
13
14 def __init__(self, timer=None, count=None):
15 self._timer = timer if timer != None else time.perf_counter
16 self._count = count if count != None else 100
17
18 def _get_single_time(self, func, *args, **kwargs):
19 s = self._timer()
20 ret = func(*args, **kwargs)
21 f = self._timer()
22 return ret, f - s
23
24 def _get_repeat_time(self, number, func, *args, **kwargs):
25 time_min, time_max, time_sum = self.INF, 0, 0
26 for i in range(number):
27 ret, delta = self._get_single_time(func, *args, **kwargs)
28 time_min = min(time_min, delta)
29 time_max = max(time_max, delta)
30 time_sum += delta
31 return func, ret, number, time_sum / number, time_min, time_max, time_sum
32
33 def gettime(self, func, *args, **kwargs):
34 if self._count != self.SMART_LOOP:
35 return self._get_repeat_time(self._count, func, *args, **kwargs)
36 else:
37 # Arrange loop count automatically
38 # Refer to Lib/timeit.py
39 i = 1
40 while True:
41 for j in 1, 2, 5:
42 number = i * j
43 func, ret, number, time_ave, time_min, time_max, time_sum = self._get_repeat_time(number, func, *args, **kwargs)
44 if time_sum >= self.MAX_WAIT_SEC:
45 return func, ret, number, time_ave, time_min, time_max, time_sum
46 i *= 10
47
48 def better_print(self, params):
49 func, ret, count, ave, minn, maxn, sumn = params
50 print('========================================')
51 print(' Function name:')
52 print(' ' + func.__repr__())
53 print('========================================')
54 print(' Function has the return content below:')
55 print(' ' + ret.__name__)
56 print('========================================')
57 print(' Summary of Function Timer:')
58 print(' Count of loops: {}'.format(count))
59 print(' Average time of loops: {} (sec)'.format(ave))
60 print(' Minimum of every loop time: {} (sec)'.format(minn))
61 print(' Maximum of every loop time: {} (sec)'.format(maxn))
62 print(' Total time of loops: {} (sec)'.format(sumn))
63 print('========================================')
64
65 # Function
66 def testfunc(x=10000000):
67 for i in range(x):
68 pass
69 return i
70
71 # Main Function
72 timer = FunctionTimer()
测试结果(将整个文件作为模块以op为名字调用):
In [168]: op.timer.better_print(op.timer.gettime(op.testfunc, 10000))
========================================
Function name:
testfunc
========================================
Function has the return content below:
9999
========================================
Summary of Function Timer:
Count of loops: 100
Average time of loops: 0.00039519199983260476 (sec)
Minimum of every loop time: 0.0002532999988034135 (sec)
Maximum of every loop time: 0.0010392999993200647 (sec)
Total time of loops: 0.03951919998326048 (sec)
========================================
In [169]: op.timer.better_print(op.timer.gettime(op.testfunc, 100000))
========================================
Function name:
testfunc
========================================
Function has the return content below:
99999
========================================
Summary of Function Timer:
Count of loops: 100
Average time of loops: 0.0029596240000137187 (sec)
Minimum of every loop time: 0.002567899999121437 (sec)
Maximum of every loop time: 0.006201700000019628 (sec)
Total time of loops: 0.29596240000137186 (sec)
========================================
In [170]: op.timer.better_print(op.timer.gettime(op.testfunc, 10))
========================================
Function name:
testfunc
========================================
Function has the return content below:
9
========================================
Summary of Function Timer:
Count of loops: 100
Average time of loops: 9.039999349624849e-07 (sec)
Minimum of every loop time: 7.999988156370819e-07 (sec)
Maximum of every loop time: 2.6999987312592566e-06 (sec)
Total time of loops: 9.03999934962485e-05 (sec)
========================================
3.完整代码
1 # opencv_pil_time.py
2
3 # -*- coding: utf-8 -*-
4
5 import time
6 import cv2
7