【OpenCV学习笔记11】- 性能测量和改进技术

这是对于 OpenCV 官方文档的 核心操作的学习笔记。学习笔记中会记录官方给出的例子,也会给出自己根据官方的例子完成的更改代码,同样彩蛋的实现也会结合多个知识点一起实现一些小功能,来帮助我们对学会的知识点进行结合应用。
如果有喜欢我笔记的请麻烦帮我关注、点赞、评论。谢谢诸位。

学习笔记:
学习笔记目录里面会收录我关于OpenCV系列学习笔记博文,大家如果有什么不懂的可以通过阅读我的学习笔记进行学习。
【OpenCV学习笔记】- 学习笔记目录

内容

在图像处理中,由于我们每秒需要处理大量操作,因此我们的代码不仅要提供正确的解决方案,还要以最快的方式提供。所以,在本章中,我们将学习 衡量代码的性能 一些提高代码性能的技巧, 我们将看到以下函数:cv.getTickCount, cv.getTickFrequency等。

除了 OpenCV 之外,Python 还提供了一个时间模块,有助于记录代码执行时间。另一个模块配置文件有助于获取有关代码的详细报告,例如代码中每个函数花费的时间,调用函数的次数等。但是,如果您使用的是 IPython,所有这些功能都集成在一个用户友好的方式中。我们将看到一些重要的内容,有关更多详细信息,请查看其他资源部分中的链接。

使用 OpenCV 测量性能

cv.getTickCount函数返回参考事件(如机器开启时刻)到调用此函数的时钟周期数。因此,如果在函数执行之前和之后调用它,则会获得用于执行函数的时钟周期数。

cv.getTickFrequency函数返回时钟周期的频率,或每秒钟的时钟周期数。因此,要在几秒钟内找到执行时间,您可以执行以下操作。
示例代码:

# 性能测量和改进技术
# 使用 OpenCV 测量性能
import cv2

e1 = cv2.getTickCount()
# 执行你的代码
e2 = cv2.getTickCount()
time = (e2 - e1) / cv2.getTickFrequency()

我们将通过以下例子进行演示。下面的例子使用奇数大小从 5 到 49 的内核进行中值过滤。(不用关心结果会是什么样的,我们的目标是监测时间):
示例代码:

# 性能测量和改进技术
# 使用 OpenCV 测量性能
import cv2

img1 = cv2.imread('../image/2.2.jpg')
e1 = cv2.getTickCount()
for i in range(5, 49, 2):
    img1 = cv2.medianBlur(img1, i)
e2 = cv2.getTickCount()
t = (e2 - e1) / cv2.getTickFrequency()
print(t)
# 得到的结果是 0.419330167 秒
# 每一次的时间都是不一样的
# 同样你选择处理的图片所包含的像素点数、电脑的配置等等因素决定了处理时间。

注意 你可以用时间模块做同样的事情。使用 time.time()函数来替代cv.getTickCount,然后取两次的差异,比如start=time.time(), end = time.time(), print(end-start)。

OpenCV 中的默认优化

许多 OpenCV 功能都使用 SSE2,AVX 等进行了优化。它还包含未经优化的代码。因此,如果我们的系统支持这些功能,我们应该利用它们(几乎所有现代处理器都支持它们)。优化功能在编译时是默认启用的,因此,OpenCV 在启用时运行优化代码,否则运行未优化代码。您可以使用 cv.useOptimized() 来检查它是否已启用/禁用,并使用 cv.setUseOptimized() 来启用/禁用它。让我们看一个简单的例子。
示例代码:

# 性能测量和改进技术
# OpenCV 中的默认优化
import cv2
import timeit


# 定义一个函数用于执行你需要计时的操作
def median_blur(img, ksize):
    return cv2.medianBlur(img, ksize)


def get_time(setup_code_str, stmt_str):
    # 使用timeit模块进行计时
    number = 10  # 运行次数(可以根据实际情况调整)
    repeat = 3  # 重复测试次数以得到更准确的平均值
    timer = timeit.Timer(stmt=stmt_str, setup=setup_code_str)
    result = timer.repeat(repeat=repeat, number=number)
    average_time = max(result)
    average_time_ms = average_time * 1000
    print(f"{number} loops, best of {repeat} : {average_time_ms:.1f} ms per loop")
    # # 如果需要实际运行中值滤波并获取结果
    # res = median_blur(img, 49)


# 检查是否使用了优化
# 禁用优化
# cv2.setUseOptimized(False)
print(cv2.useOptimized())
img = cv2.imread('../image/2.2.jpg')
# 在Jupyter Notebook或IPython环境中可以直接使用 %timeit
# %timeit res = cv2.medianBlur(img1,49)
# 在 python 代码中可以使用如下代码统计运行时常
setup_code_str = """
from __main__ import img, median_blur
"""
stmt_str = "median_blur(img, 49)"
get_time(setup_code_str, stmt_str)

启用优化:
控制台/命令行输出:

True
10 loops, best of 3 : 192.5 ms per loop

禁用优化:
控制台/命令行输出:

False
10 loops, best of 3 : 195.5 ms per loop

可能耗时过长的任务优化没有那么明显或者可优化空间较小。优化的前提也是和电脑配置本身有很大关系。我用的m2的集显输出的,可能独显的效果会更加明显。

请参阅,优化中值过滤比未优化版本快。如果检查其来源,您可以看到中值过滤是 SIMD 优化的。因此,您可以使用它来在代码顶部启用优化(请记住它默认启用)。

测量 IPython 中的性能

有时您可能需要比较两个类似操作的性能。IPython 为您提供了一个神奇的命令时间来执行此操作。它运行代码几次以获得更准确的结果。注意,它们适用于测量单行代码。

例如,你知道以下哪个加法操作更好, x = 5; y = x2, x = 5; y = xx, x = np.uint8([5]); y = xx 或者 y = np.square(x) ?我们可以使用IPython中的timeit魔术指令方便的进行测试。
我在本地电脑没有安装IPython,所以使用了Python代码直接测试。
示例代码:

# 性能测量和改进技术
# 测量 IPython 中的性能
import timeit
import numpy as np


def get_time(setup_code_str, stmt_str, number=100, repeat=3):

    # 使用timeit模块进行计时
    # number = 10  # 运行次数(可以根据实际情况调整)
    # repeat = 3  # 重复测试次数以得到更准确的平均值
    timer = timeit.Timer(stmt=stmt_str, setup=setup_code_str)
    result = timer.repeat(repeat=repeat, number=number)
    average_time = max(result)
    average_time_ms = average_time * 1000
    print(f"{number} loops, best of {repeat} : {average_time_ms:.1f} ms per loop")
    # # 如果需要实际运行中值滤波并获取结果
    # res = median_blur(img, 49)


x = 5
# 在Jupyter Notebook或IPython环境中可以直接使用 %timeit
setup_code_str = """
from __main__ import x
"""
stmt_str = "y=x**2"
get_time(setup_code_str, stmt_str, number=1000000)
stmt_str = "y=x*x"
get_time(setup_code_str, stmt_str, number=1000000)
z = np.uint8([5])
setup_code_str = """
from __main__ import x, z, np
"""
stmt_str = "y=z*z"
get_time(setup_code_str, stmt_str, number=1000000)
stmt_str = "y=np.square(z)"
get_time(setup_code_str, stmt_str, number=1000000)

控制台/命令行输出:

1000000 loops, best of 3 : 221.8 ms per loop
1000000 loops, best of 3 : 10.9 ms per loop
1000000 loops, best of 3 : 208.9 ms per loop
1000000 loops, best of 3 : 191.6 ms per loop

你可以看到,x = 5; y = x * x 是最快的,与 Numpy 相比快了约 20 倍。如果您也考虑创建矩阵,它可能会快达 100 倍。很酷,对吗?(Numpy 开发者正在研究这个问题)

注意 Python 标量操作比 Numpy 标量操作更快。因此对于包含一个或两个元素的操作,Python 标量比 Numpy 数组更好。当阵列的大小稍大时,Numpy 会占据优势。

我们将再尝试一个例子。这次,我们将比较同一图像的cv.countNonZero()和 np.count_nonzero()的性能。
示例代码:

# 性能测量和改进技术
# 测量 IPython 中的性能
import timeit
import cv2
import numpy as np


def get_time(setup_code_str, stmt_str, number=100, repeat=3):

    # 使用timeit模块进行计时
    # number = 10  # 运行次数(可以根据实际情况调整)
    # repeat = 3  # 重复测试次数以得到更准确的平均值
    timer = timeit.Timer(stmt=stmt_str, setup=setup_code_str)
    result = timer.repeat(repeat=repeat, number=number)
    average_time = max(result)
    average_time_ms = average_time * 1000
    print(f"{number} loops, best of {repeat} : {average_time_ms:.1f} ms per loop")
    # # 如果需要实际运行中值滤波并获取结果
    # res = median_blur(img, 49)


img = cv2.imread('../image/2.2.jpg')
# 将彩色图像转换为灰度图像
gray_img = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
setup_code_str = """
from __main__ import gray_img, cv2
"""
# z = cv2.countNonZero(gray_img)
stmt_str = "z = cv2.countNonZero(gray_img)"
get_time(setup_code_str, stmt_str, number=100000)
setup_code_str = """
from __main__ import gray_img, np
"""
stmt_str = "z = np.count_nonzero(gray_img)"
get_time(setup_code_str, stmt_str, number=100000)

控制台/命令行输出:

100000 loops, best of 3 : 1632.4 ms per loop
100000 loops, best of 3 : 1238.2 ms per loop

官网文档说 -> OpenCV 功能比 Numpy 功能快近 25 倍。
大家可以在IPython环境中试试

注意 通常,OpenCV 函数比 Numpy 函数更快。因此,对于相同的操作,OpenCV 功能是首选。但是,可能有例外,尤其是当 Numpy 使用视图而不是副本时。

更多 IPython 魔术命令

还有其他一些魔术命令可以测量性能,分析,线性分析,内存测量等。它们都有很好的文档记录。因此,此处仅提供这些文档的链接。建议有兴趣的读者试用。

性能优化技术

有几种技术和编码方法可以利用 Python 和 Numpy 的最大性能。这里仅注明相关的内容,并提供重要来源的链接。
这里要注意的主要是,首先尝试以简单的方式实现算法。一旦它正常工作,对其进行分析,找到瓶颈并对其进行优化。

  1. 尽量避免在 Python 中使用循环,尤其是双循环/三循环等。它们本质上很慢。
  2. 将算法/代码矢量化到最大可能范围,因为 Numpy 和 OpenCV 针对向量运算进行了优化。
  3. 利用缓存一致性。
  4. 除非需要,否则永远不要复制数组。尝试使用视图。复制多维数组是一项耗费时间和空间都很多的操作。(我的理解是除非必要,否则尽量不要使用深拷贝)

即使在完成所有这些操作之后,如果您的代码仍然很慢,或者使用大型循环是不可避免的,请使用其他库(如 Cython)来加快速度。

其他资源链接

  1. Python 优化技术
  2. Scipy 讲义-**高级 Numpy **
  3. Ipython 中的时序和分析

你可能感兴趣的:(OpenCV,Python,opencv,学习,笔记,python,计算机视觉,人工智能)