本节几乎所有操作都主要与 Numpy 有关,而不是 OpenCV。需要对 Numpy 有很好的了解才能使用 OpenCV 编写更好的优化代码。
对于单个像素访问,Numpy 数组方法 array.item() 和 array.itemset() 被认为更好。但是,它们总是返回一个标量,因此如果您想访问所有 B、G、R 值,则需要为每个值分别调用 array.item()。
更好的像素访问和编辑方法:
# accessing RED value
>>> img.item(10,10,2)
59
# modifying RED value
>>> img.itemset((10,10,2),100)
>>> img.item(10,10,2)
100
图像属性包括行数、列数和通道数;图像数据类型;像素数;等等
图像的形状由 img.shape 访问。它返回行数、列数和通道数的元组(如果图像是彩色的):
有时,您将不得不使用图像的某些区域。对于图像中的眼睛检测,首先对整个图像进行人脸检测。当获得人脸时,我们单独选择人脸区域并在其中搜索眼睛而不是搜索整个图像。它提高了准确性(因为眼睛在脸上 )和性能(因为我们在一个小区域进行搜索)。
再次使用 Numpy 索引获得 ROI。在这里,我选择球并将其复制到图像中的另一个区域:
ball = img[280:340, 330:390]
img[273:333, 100:160] = ball
有时您需要分别处理图像的 B、G、R 通道。在这种情况下,您需要将 BGR 图像拆分为单个通道。在其他情况下,您可能需要加入这些单独的通道来创建 BGR 图像。您可以通过以下方式简单地做到这一点:
>>> b,g,r = cv.split(img)
>>> img = cv.merge((b,g,r))
#或者
>>> b = img[:,:,0]
#如果想要修改
>>> img[:,:,2] = 0
cv.split() 是一项代价高昂的操作(就时间而言)。所以只在必要时使用它。否则用Numpy索引。
如果你想在图像周围创建一个边框,比如相框,你可以使用 cv.copyMakeBorder()。但它在卷积运算、零填充等方面有更多应用。此函数采用以下参数:
cv.BORDER_CONSTANT:添加常量彩色边框。该值应作为下一个参数给出。 |
cv.BORDER_REFLECT:边框将是边框元素的镜像反射,如下所示:fedcba|abcdefgh|hgfedcb |
cv.BORDER_REFLECT_101 或 cv.BORDER_DEFAULT :与上面相同,但略有变化,如下所示:gfedcb|abcdefgh|gfedcba |
cv.BORDER_WRAP:无法解释,它看起来像这样:cdefgh|abcdefgh|abcdefg |
#demo
import cv2 as cv
import numpy as np
from matplotlib import pyplot as plt
BLUE = [255,0,0]
img1 = cv.imread('opencv-logo.png')
replicate = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REPLICATE)
reflect = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT)
reflect101 = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_REFLECT_101)
wrap = cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_WRAP)
constant= cv.copyMakeBorder(img1,10,10,10,10,cv.BORDER_CONSTANT,value=BLUE)
plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')
plt.show()
您可以使用 OpenCV 函数 cv.add() 或简单地通过 numpy 操作 res = img1 + img2 添加两个图像。两个图像应该具有相同的深度和类型,或者第二个图像可以只是一个标量值。
OpenCV 加法和 Numpy 加法是有区别的。 OpenCV 加法是饱和运算,而 Numpy 加法是模运算。
饱和运算在这里指的是:最后计算的范围必须是在[0,255]之间,小于或者大于的数,对应的值分别为0或1。
这也是图像加法,只是给图像赋予了不同的权重,以便给人一种混合或透明的感觉。根据以下等式添加图像:
通过从 0 → 1 改变 α,你可以在一幅图像到另一幅图像之间执行炫酷的过渡。
在这里,我拍了两张图片混合在一起。第一张图像的权重为 0.7,第二张图像的权重为 0.3。 cv.addWeighted() 将以下等式应用于图像:
这里 γ 取为零。
import cv2 as cv
def nothing(x):
pass
cv.namedWindow('image')
img1 = cv.imread('D:/LEARNING/OpenCV/core/img1.jpg')
img2 = cv.imread('D:/LEARNING/OpenCV/core/img2.jpg')
img_1 = cv.resize(img1, dsize=(500, 500), dst=None, fx=2, fy=2, interpolation=cv.INTER_NEAREST)
img_2 = cv.resize(img2, dsize=(500, 500), dst=None, fx=2, fy=2, interpolation=cv.INTER_NEAREST)
cv.createTrackbar('alpha','image',0,100,nothing)
cv.createTrackbar('beta','image',0,100,nothing)
cv.createTrackbar('gamma','image',0,100,nothing)
while(1):
img1 = cv.imread('D:/LEARNING/OpenCV/core/img1.jpg')
img2 = cv.imread('D:/LEARNING/OpenCV/core/img2.jpg')
alpha = cv.getTrackbarPos('alpha', 'image')
beta = cv.getTrackbarPos('beta', 'image')
gamma = cv.getTrackbarPos('gamma', 'image')
dst = cv.addWeighted(img_1, alpha/100, img_2, beta/100, gamma/100)
cv.imshow('image', dst)
k = cv.waitKey(1) & 0xFF
if k == 27:
break
#加了一个功能就是trackbar来调解参数值
这包括按位与、或、非和异或运算。它们在提取图像的任何部分(我们将在接下来的章节中看到)、定义和使用非矩形 ROI 等时非常有用。下面我们将看到一个如何更改图像特定区域的示例。
我想将 OpenCV 徽标放在图像上方。如果我添加两个图像,它会改变颜色。如果我混合它们,我会得到透明的效果。但我希望它是不透明的。如果它是一个矩形区域,我可以像上一章那样使用 ROI。但是 OpenCV 的标志不是矩形。因此,您可以使用按位运算来完成,如下所示:
# I want to put logo on top-left corner, So I create a ROI
rows,cols,channels = img_2.shape
roi = img_1[0:rows, 0:cols]
# Now create a mask of logo and create its inverse mask also
img2gray = cv.cvtColor(img_2,cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 60, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)
# Now black-out the area of logo in ROI
img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv)
# Take only region of logo from logo image.
img2_fg = cv.bitwise_and(img_2,img_2,mask = mask)
# Put logo in ROI and modify the main image
dst = cv.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst
cv.imshow('res',img1)
cv.waitKey(0)
cv.destroyAllWindows()
请参阅下面的结果。左图显示了我们创建的蒙版。右图显示了最终结果。为了更好地理解,显示上面代码中的所有中间图像,尤其是 img1_bg 和 img2_fg。
在图像处理中,由于您每秒处理大量操作,因此您的代码必须不仅提供正确的解决方案,而且还以最快的方式提供解决方案。因此,在本章中,将学习:
除了 OpenCV,Python 还提供了一个模块时间,有助于测量执行时间。另一个模块配置文件有助于获得有关代码的详细报告,例如代码中的每个函数花费了多少时间,函数被调用了多少次等。但是,如果您使用的是 IPython,则所有这些功能都集成在一个用户中-友好的方式。我们将看到一些重要的内容,有关更多详细信息,请查看其他资源部分中的链接。
cv.getTickCount 函数返回参考事件(比如机器打开的时刻)到调用该函数的时刻的时钟周期数。因此,如果您在函数执行之前和之后调用它,您将获得用于执行函数的时钟周期数。
cv.getTickFrequency 函数返回时钟周期的频率,或每秒的时钟周期数。因此,要以秒为单位查找执行时间,您可以执行以下操作:
e1 = cv.getTickCount()
# your code execution
e2 = cv.getTickCount()
time = (e2 - e1)/ cv.getTickFrequency()
我们将通过以下示例进行演示。下面的示例应用中值过滤,内核大小从 5 到 49 不等。(不要担心结果会是什么样子——这不是我们的目标):
img1 = cv.imread('messi5.jpg')
e1 = cv.getTickCount()
for i in range(5,49,2):
img1 = cv.medianBlur(img1,i)
e2 = cv.getTickCount()
t = (e2 - e1)/cv.getTickFrequency()
print( t )
# Result I got is 0.521107655 seconds
你可以用时间模块做同样的事情。使用 time.time() 函数代替 cv.getTickCount。然后取两次的差值。
许多 OpenCV 函数都使用 SSE2、AVX 等进行了优化。它还包含未优化的代码。因此,如果我们的系统支持这些功能,我们应该利用它们(几乎所有现代处理器都支持它们)。编译时默认启用。因此,如果启用,OpenCV 将运行优化代码,否则将运行未优化代码。您可以使用 cv.useOptimized() 来检查它是否启用/禁用,并使用 cv.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
如您所见,优化的中值过滤比未优化的版本快 2 倍。如果查看其来源,您会发现中值滤波是 SIMD 优化的。因此,您可以使用它在代码的顶部启用优化(请记住它是默认启用的)。
有多种技术和编码方法可以发挥 Python 和 Numpy 的最大性能。此处仅注明相关内容,并提供重要来源的链接。这里主要要注意的是,首先尝试以简单的方式实现算法。一旦它开始工作,分析它,找到瓶颈,并优化它们。
如果在执行所有这些操作后您的代码仍然很慢,或者如果使用大循环是不可避免的,请使用其他库(如 Cython)来使其更快。