OpenCV-Python (官方)中文教程(部分一)_Day8

12.程序性能检测及优化


在图像处理中你每秒钟都要做大量的运算,所以你的程序不仅要能给出正 确的结果,同时还必须要快。所以这节我们将要学习:

•  检测程序的效率.

•  一些能够提高程序效率的技巧.

•  你要学到的函数有:cv2.getTickCount,cv2.getTickFrequency 等。

除了 OpenCV,Python 也提供了一个叫 time 的的模块,你可以用它来计算程序的运行时间。另外一个叫做 profile 的模块会帮你得到一份关于你的程序的详细报告,其中包含了代码中每个函数运行需要的时间,以及每个函数被调用的次数。如果你正在使用 IPython 的话,所有这些特点都被以一种用户友好的方式整合在一起了。我们会学习几个重要的,要想学到更加详细的知识就打 开更多资源中的链接吧。

12.1使用OpenCV检测程序效率


cv2.getTickCount 函数返回从参考点到这个函数被执行的时钟数。所以当你在一个函数执行前后都调用它的话,你就会得到这个函数的执行时间(时钟数)。

cv2.getTickFrequency 返回时钟频率,或者说每秒钟的时钟数。所以 你可以按照下面的方式得到一个函数运行了多少秒:

import cv2
 
e1 = cv2.getTickCount()
e2 = cv2.getTickCount()
time = (e2 - e1)/ cv2.getTickFrequency()


我们将会用下面的例子演示。下面的例子是用窗口大小不同(5,7,9)的 核函数来做中值滤波:

import cv2
 
img1 = cv2.imread('roi.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)
 
# Result I got is 0.521107655 seconds

代码解析:

cv2.getTickCount():返回 CPU 的时钟周期数(高精度计时器)

  • range(5, 49, 2):生成一个从 5 到 48 的奇数序列(步长为 2),即 [5, 7, 9, ..., 47, 49]

    • 为什么用奇数?:中值滤波的核大小必须是奇数。

  • cv2.medianBlur(img1, i):对图像 img1 应用中值滤波,核大小为 i×i

    • 中值滤波的作用:去除噪声(如椒盐噪声),同时保留边缘。

    • 注意:每次滤波的结果会覆盖 img1,因此滤波是 累积叠加 的。

  • cv2.getTickFrequency():返回 CPU 每秒的时钟周期数(用于将周期数转换为秒)。

  • 公式

  • 结果t 是代码执行的总时间(单位:秒)。

关键点总结

  1. 中值滤波的累积效果

    • 每次循环都会在前一次滤波结果的基础上再次滤波,核大小逐步增大(5→7→...→49)。

    • 最终图像会变得非常模糊(因为大核滤波会抹去更多细节)。

  2. 性能测试目的

    这段代码主要用于 测试中值滤波在不同核大小下的耗时,而非实际图像处理需求。
  3. 时间计算的原理

    getTickCount() 提供高精度计时,适合测量短时间操作的性能。

t = (e2 - e1) / cv2.getTickFrequency() 

这行代码的作用是 计算代码段的执行时间(单位:秒),具体分解如下:


1. 核心组件

  • cv2.getTickCount()

    • 返回当前CPU的时钟周期计数(高精度计时器,单位是" ticks ")。

    • 类似秒表,记录某一时刻的计数器值。

  • cv2.getTickFrequency()

    • 返回CPU每秒的时钟周期数(即计时器的频率,单位是" ticks/second ")。

    • 例如,若频率为 1000000,表示每秒有100万次时钟周期。


2. 计算步骤

  1. e2 - e1

    获取两个时间点之间的时钟周期差值(即代码执行消耗的周期数)。
  2. (e2 - e1) / cv2.getTickFrequency()

    将周期数转换为秒:

 

    • 设你的心脏每分钟跳60次(频率=60次/分钟)。

    • 某件事发生时心跳了30次 → 耗时 = 30 / 60 = 0.5分钟。

  • 代码中的逻辑

    • e1 和 e2 是开始和结束时的“心跳计数”。

    • getTickFrequency() 是“心脏频率”(每秒多少次)。

    • 最终时间 = (结束计数 - 开始计数) / 频率。


4. 为什么用这种方式计时?

  • 高精度:CPU时钟周期是纳秒级精度,适合测量短时间操作(如OpenCV图像处理)。

  • 跨平台:OpenCV封装了底层硬件计时器,保证不同系统下的准确性。

5. 示例

假设:

  • e1 = 1000(开始时的周期数)

  • e2 = 2500(结束时的周期数)

  • cv2.getTickFrequency() = 1000000(每秒100万周期)

计算:

6. 对比Python标准库

如果用Python的 time 模块,等效写法是:

import time
st行art = time.time()
# 执代码...
end = time.time()
t = end - start  # 直接得到秒数

但 cv2.getTickCount() 精度通常更高(尤其是Windows系统)

注 意: 你 也 可 以 中   time   模 块 实 现 上 面 的 功 能。 但 是 要 用 的 函 数 是 time.time()  而不是  cv2.getTickCount。 比较一下这两个结果的差别 吧。

 中值滤波(Median Filter) 是一种常用的非线性图像处理技术,主要用于去除噪声(尤其是椒盐噪声)同时保留边缘细节。它的核心思想是用像素邻域的中值替代当前像素值,而非像均值滤波那样取平均值。

1. 基本原理

  • 操作步骤

    1. 定义一个滑动窗口(通常为奇数的正方形,如 3×3、5×5)。

    2. 将窗口内的所有像素值排序。

    3. 取排序后的中间值(中位数)作为当前中心像素的新值。

  • 数学表达

其中 k 是窗口半径(如 3×3 窗口的 k=1)。

2. 关键特点

3. 与均值滤波的对比

 

4. 代码示例(OpenCV)

import cv2
import numpy as np

# 读取图像(添加椒盐噪声示例)
img = cv2.imread('image.jpg', 0)  # 灰度图
noisy_img = np.copy(img)
# 随机添加椒盐噪声
salt_pepper_prob = 0.05  # 噪声比例
noise = np.random.rand(*img.shape)
noisy_img[noise < salt_pepper_prob/2] = 0    # 椒噪声(黑点)
noisy_img[noise > 1 - salt_pepper_prob/2] = 255  # 盐噪声(白点)

# 中值滤波去噪
kernel_size = 5  # 5x5窗口
denoised_img = cv2.medianBlur(noisy_img, kernel_size)

# 显示结果
cv2.imshow('Noisy Image', noisy_img)
cv2.imshow('Denoised Image', denoised_img)
cv2.waitKey(0)
cv2.destroyAllWindows()

5. 直观效果

  • 输入图像:(黑白点状噪声)

  • 中值滤波后:(噪声消失,边缘清晰)


6. 参数选择

  • 窗口大小(kernel_size

    • 较小(如 3×3):去噪较弱,但细节保留更好。

    • 较大(如 7×7):去噪更强,但可能过度平滑。

  • 经验法则

    • 椒盐噪声:窗口大小通常为 3 或 5。

    • 大颗粒噪声:可能需要更大的窗口(但需权衡边缘损失)。


7. 应用场景

  1. 图像去噪:修复扫描文档的黑白噪点。

  2. 医学影像:去除CT/MRI中的孤立噪声点。

  3. 实时视频处理:抑制摄像头采集的随机噪声。


8. 注意事项

  • 不适用于高斯噪声:中值滤波对随机分布的灰度噪声(高斯噪声)效果较差,此时均值滤波或高斯滤波更合适。

  • 避免过大窗口:可能导致图像纹理丢失。


总结

中值滤波通过排序取中值的方式,在去除椒盐噪声的同时最大程度保留边缘,是图像去噪的经典工具。其非线性特性使其在特定场景下比线性滤波(如均值滤波)更具优势。

椒盐噪声(Salt-and-Pepper Noise) 是数字图像中常见的一种噪声类型,其特点是图像中随机出现的纯白(盐)和纯黑(椒)像素点,类似于撒在图像上的胡椒和盐粒,因此得名。


1. 噪声特征

  • 表现形式

    • 白点(盐噪声):像素值为最大值(如255,8位图像中为纯白)。

    • 黑点(椒噪声):像素值为最小值(如0,纯黑)。

  • 分布:随机出现在图像的任何位置,通常稀疏但明显。


2. 产生原因

 3. 与其他噪声的对比

4. 模拟椒盐噪声的代码(Python) 

import numpy as np
import cv2

def add_salt_pepper_noise(image, prob=0.05):
    """
    添加椒盐噪声
    :param image: 输入图像(灰度或彩色)
    :param prob: 噪声比例(默认5%)
    :return: 带噪声的图像
    """
    output = np.copy(image)
    # 生成随机噪声矩阵
    rng = np.random.rand(*image.shape)
    # 椒噪声(黑点)
    output[rng < prob/2] = 0
    # 盐噪声(白点)
    output[rng > 1 - prob/2] = 255
    return output

# 示例使用
img = cv2.imread('lena.jpg', 0)  # 读取灰度图
noisy_img = add_salt_pepper_noise(img, prob=0.1)  # 添加10%噪声
cv2.imshow('Noisy Image', noisy_img)
cv2.waitKey(0)

5. 去噪方法

(1) 中值滤波(最佳选择)
denoised = cv2.medianBlur(noisy_img, 3)  # 3x3窗口
  • 原理:用邻域中值替代噪声点(黑白点是极端值,排序后会被剔除)。

(2) 均值滤波(效果较差)
denoised = cv2.blur(noisy_img, (3,3))  # 3x3均值
  • 缺点:会模糊边缘,且无法完全去除噪声。

(3) 自适应滤波
  • 结合局部统计特性,更适合非均匀噪声。

7. 实际应用场景

  1. 老旧照片修复:去除扫描照片的黑白噪点。

  2. 监控视频处理:抑制摄像头传输中的突发噪声。

  3. 医学影像:清理X光片或显微镜图像的孤立噪声。


8. 注意事项

  • 噪声密度:若噪声比例过高(如>20%),中值滤波可能失效。

  • 彩色图像:需分别处理每个通道(BGR),或转换到HSV空间处理亮度通道。


总结

椒盐噪声是图像中随机出现的黑白像素点,通常由硬件故障或传输错误引起。中值滤波是其经典解决方案,能在去噪的同时保留边缘信息。理解这种噪声有助于选择正确的图像复原方法。

高斯噪声(Gaussian Noise) 是数字图像和信号处理中最常见的一种噪声类型,其特点是噪声值服从正态分布(高斯分布),表现为图像像素值的随机微小波动。以下是详细解析: 

1. 核心特性 

2. 与椒盐噪声的对比

3. 数学建模

在图像中,高斯噪声的数学模型为:

4. 代码模拟高斯噪声(Python/OpenCV)

import cv2
import numpy as np

def add_gaussian_noise(image, mean=0, sigma=25):
    """
    添加高斯噪声
    :param image: 输入图像(灰度或彩色)
    :param mean: 噪声均值(默认0)
    :param sigma: 噪声标准差(默认25)
    :return: 带噪声的图像
    """
    row, col, ch = image.shape
    gauss = np.random.normal(mean, sigma, (row, col, ch))
    noisy = image + gauss
    return np.clip(noisy, 0, 255).astype(np.uint8)  # 确保值在0-255范围内

# 示例
img = cv2.imread('lena.jpg')  # 读取彩色图像
noisy_img = add_gaussian_noise(img, sigma=30)  # 添加标准差为30的噪声
cv2.imshow('Noisy Image', noisy_img)
cv2.waitKey(0)

5. 去噪方法

(1) 高斯滤波(线性滤波)
blurred = cv2.GaussianBlur(noisy_img, (5,5), 0)  # 5x5高斯核
  • 原理:用高斯加权平均值替代中心像素,抑制噪声。

  • 缺点:会轻微模糊边缘。

(2) 非局部均值去噪(Non-Local Means)
denoised = cv2.fastNlMeansDenoisingColored(noisy_img, None, 10, 10, 7, 21)
  • 优势:保留细节更好,但计算较慢。

(3) 小波变换
  • 适用于高频噪声分离,需专用库(如PyWavelets)。


6. 实际应用场景

  1. 低光照摄影:手机夜间模式拍摄的图像常含高斯噪声。

  2. 医学影像:MRI/CT图像中的热噪声。

  3. 无线传输:受信道干扰的监控视频。


7. 参数影响

  • 标准差(σ)

    • σ越小 → 噪声越微弱(如σ=10)。

    • σ越大 → 噪声越明显(如σ=50)。

  • 均值(μ)
    通常设为0,非零值会导致整体亮度偏移。

8. 注意事项

  • 彩色图像:需在RGB各通道分别添加噪声,或转换到YUV空间仅处理亮度通道(Y)。

  • 噪声叠加:实际图像可能同时存在高斯噪声和椒盐噪声,需组合滤波方法。


总结

高斯噪声是图像中符合正态分布的随机波动,广泛存在于电子成像系统中。通过高斯滤波非局部均值算法可有效抑制,但需权衡去噪强度与细节保留。理解其特性有助于优化图像处理流程。

12.2 OpenCV中的默认优化


OpenCV 中的很多函数都被优化过(使用 SSE2,AVX 等)。也包含一些 没有被优化的代码。如果我们的系统支持优化的话要尽量利用只一点。在编译时 优化是被默认开启的。因此 OpenCV 运行的就是优化后的代码,如果你把优化 关闭的话就只能执行低效的代码了。你可以使用函数 cv2.useOptimized() 来查看优化是否被开启了,使用函数 cv2.setUseOptimized() 来开启优化。

# check if optimization is enabled
In [5]: cv.useOptimized()
Out[5]: True
In [6]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 34.9 ms per loop
# Disable it
In [7]: cv.setUseOptimized(False)
In [8]: cv.useOptimized()
Out[8]: False
In [9]: %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 64.1 ms per loop


看见了吗,优化后中值滤波的速度是原来的两倍。如果你查看源代码的话, 你会发现中值滤波是被 SIMD优化的。所以你可以在代码的开始处开启优化(优化是默认开启的)。

12.3在IPython中检测程序效率


有时你需要比较两个相似操作的效率,这时你可以使用 IPython 为你提供 的魔法命令%time。他会让代码运行好几次从而得到一个准确的(运行)时 间。它也可以被用来测试单行代码的。

例如,你知道下面这同一个数学运算用哪种行式的代码会执行的更快吗?

x = 5; y = x ∗ ∗2
x = 5; y = x ∗ x
x = np.uint([5]); y = x ∗ x y = np.squre(x)

关键对比总结 

扩展说明

1. 数据类型的重要性(NumPy)
  • np.uint8([5]) 定义了 8位无符号整数数组,取值范围 0-255。

    • 若计算结果超出范围,会截断(模运算):

      x = np.uint8([200])
      y = x * x  # 200*200=40000 → 40000 % 256 = 144 → y=array([144], dtype=uint8)
2. 性能考虑
  • 对于大规模数组,np.square(x) 和 x * x 性能几乎相同,均优化为底层C运算。

  • Python原生的 ** 和 * 在小规模计算中无差别。

3. 实际应用建议
  • 标量计算:直接用 x * x 或 x ** 2(简洁)。

  • NumPy数组:优先用 np.square(x)(意图明确,避免溢出问题)。


代码验证示例

import numpy as np

# 标量计算
x = 5
print("Python原生:")
print("x ** 2 =", x ** 2)      # 输出 25
print("x * x =", x * x)        # 输出 25

# NumPy数组计算
x_np = np.uint8([5])
print("\nNumPy数组:")
print("x * x =", x_np * x_np)          # 输出 [25]
print("np.square(x) =", np.square(x_np))  # 输出 [25]

# 溢出测试
x_overflow = np.uint8([200])
print("\n溢出测试:")
print("200 * 200 (uint8) =", x_overflow * x_overflow)  # 输出 [144]

我们可以在 IPython 的 Shell 中使用魔法命令找到答案。

In [10]: x = 5
In [11]: %timeit y=x**2
10000000 loops, best of 3: 73 ns per loop
In [12]: %timeit y=x*x
10000000 loops, best of 3: 58.3 ns per loop
In [15]: z = np.uint8([5])
In [17]: %timeit y=z*z
1000000 loops, best of 3: 1.25 us per loop
In [19]: %timeit y=np.square(z)
1000000 loops, best of 3: 1.16 us per loop


竟然是第一种写法,它居然比 Nump 快了 20 倍。如果考虑到数组构建的 话,能达到 100 倍的差。

注意:Python  的标量计算比  Nump  的标量计算要快。对于仅包含一两个 元素的操作 Python  标量比 Numpy  的数组要快。但是当数组稍微大一点时 Numpy 就会胜出了。

我 们 来 比 较 一 下  cv2.countNonZero()  和np.count_nonzero()

In [35]: %timeit z = cv.countNonZero(img)
100000 loops, best of 3: 15.8 us per loop
In [36]: %timeit z = np.count_nonzero(img)
1000 loops, best of 3: 370 us per loop


看见了吧,OpenCV 的函数是 Numpy 函数的 25 倍。

注意:一般情况下 OpenCV  的函数要比 Numpy  函数快。所以对于相同的操 作最好使用 OpenCV  的函数。当然也有例外,尤其是当使用 Numpy  对视图(而非复制)进行操作时。

12.4效率优化技术


有些技术和编程方法可以让我们最大的发挥 Python 和 Numpy 的威力。 我们这里仅仅提一下相关的,你可以通过超链接查找更多详细信息。我们要说 的最重要的一点是:首先用简单的方式实现你的算法(结果正确最重要),当结 果正确后,再使用上面的提到的方法找到程序的瓶颈来优化它。

1. 尽量避免使用循环,尤其双层三层循环,它们天生就是非常慢的。

2. 算法中尽量使用向量操作,因为 Numpy 和 OpenCV 都对向量操作进行了优化。

3. 利用高速缓存一致性。

4. 没有必要的话就不要复制数组。使用视图来代替复制。数组复制非常浪费资源。

就算进行了上述优化,如果你的程序还是很慢,或者说大的训话不可避免的话, 你你应该尝试使用其他的包,比如说   Cython,来加速你的程序。

还有几个魔法命令可以用来检测程序的效率,profiling,line profiling, 内存使用等。他们都有完善的文档。所以这里只提供了超链接。感兴趣的可以 自己学习一下。

更多资源
  1. Python Optimization Techniques
  2. Timing and Profiling in IPython
  3. Scipy Lecture Notes - Advanced Numpy

cv2.countNonZero() 和 np.count_nonzero() 都是用于计算数组中非零元素数量的函数,但它们在 输入类型、功能细节 和 使用场景 上有一些区别。以下是详细对比:


1. cv2.countNonZero()(OpenCV函数)

功能
  • 计算单通道数组(通常是图像或掩码)中非零像素的数量

  • 仅适用于单通道(如灰度图、二值掩码)。

语法
count = cv2.countNonZero(src)
  • src:输入数组(必须是单通道,如 cv2.Mat 或 numpy.ndarray)。

特点
  • 只支持单通道:多通道图像需先拆分或转换为灰度。

  • 针对图像优化:OpenCV内部优化,适合处理图像数据。

  • 输入类型:通常用于 uint8 类型的二值掩码(0或255)。

示例
import cv2
import numpy as np

# 创建一个二值掩码(单通道)
mask = np.array([[0, 255, 0],
                [255, 0, 255]], dtype=np.uint8)

count = cv2.countNonZero(mask)
print(count)  # 输出:3(非零像素数)

2. np.count_nonzero()(NumPy函数)

功能
  • 计算任意维度的NumPy数组中非零元素的总数

  • 支持多通道和多维数组。

语法
count = np.count_nonzero(arr)
  • arr:输入数组(可以是任意维度和数据类型)。

特点
  • 通用性强:支持任何维度的数组(如RGB图像、多维矩阵)。

  • 灵活性高:可结合条件统计(如 np.count_nonzero(arr > 5))。

  • 输入类型:不限制数据类型(intfloatbool等均可)。

示例
import numpy as np

# 单通道示例
mask = np.array([[0, 255, 0],
                 [255, 0, 255]], dtype=np.uint8)
count = np.count_nonzero(mask)
print(count)  # 输出:3

# 多通道示例(如RGB图像)
rgb_img = np.array([[[255, 0, 0], [0, 255, 0]],
                   [[0, 0, 255], [0, 0, 0]])
count = np.count_nonzero(rgb_img)
print(count)  # 输出:3(统计所有通道的非零值)

3. 核心区别对比

4. 如何选择?

  • 处理OpenCV图像/掩码 → 优先用 cv2.countNonZero()(效率更高)。

  • 处理多维数组或需要条件统计 → 用 np.count_nonzero()

  • 多通道图像的非零统计 → 必须用 np.count_nonzero()


5. 进阶用法

(1) 统计多通道图像的非零像素
# 方法1:合并所有通道
rgb_img = cv2.imread('image.jpg')  # 3通道BGR
count = np.count_nonzero(rgb_img)  # 统计所有通道

# 方法2:分通道统计
count_per_channel = [np.count_nonzero(rgb_img[:, :, i]) for i in range(3)]
(2) 条件统计(NumPy)
arr = np.array([[1, 0, 3], [4, 5, 0]])
count = np.count_nonzero(arr > 2)  # 统计大于2的元素数量
print(count)  # 输出:2(3和4满足条件)

总结

  • cv2.countNonZero():专为OpenCV单通道图像设计,高效但功能有限。

  • np.count_nonzero():NumPy通用函数,灵活支持复杂场景。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值