快乐学习
前程无忧、中华英才非你莫属!

5-OpenCV-核心操作


本章的学习内容


* 获取并修改图像的像素值

* 获取图像的属性

* 设置感兴趣区域

* 图像的拆分和合并

* 图像的算数运算

* 程序的评估和优化


1.获取并修改图像的像素值

首先我们先加载一章彩色图像:

>>> import numpy as np

>>> import cv2 as cv

>>> img = cv.imread('messi5.jpg')

之后你可以通过行和列的坐标值获取该像素点的像素值。对于BGR图像,它返回一个蓝色,绿色,红色值的数组。对于灰度图像,仅返回相应的强度值

>>> px = img[100,100]

>>> print( px )

[157 166 200]

# accessing only blue pixel

>>> blue = img[100,100,0]

>>> print( blue )

157

你可以用同样的方法修改像素点的像素值:

>>> img[100,100] = [255,255,255]

>>> print( img[100,100] )

[255 255 255]


注意:Numpy是一个用于快速阵列计算的优化库。因此,简单地访问每个像素值并对其进行修改将非常缓慢,并且不推荐这样做。上述方法通常用于选择数组的区域,比如前5行和后3列。对于单个像素访问,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

2.获取图像的属性

图像属性包括行数,列数和通道数,图像数据类型,像素数等。

使用img.shape可以获取图像的形状。它返回一组行,列和通道的元组(如果图像是彩色的):

>>> print( img.shape )

(342, 548, 3)

注意:如果图像是灰度图像,则返回的元组仅包含行数和列数,因此检查加载的图像是灰度还是颜色是一种很好的方法。

使用img.size获取的像素总数:

>>> print( img.size )

562248

使用img.dtype获取图像数据类型:

>>> print( img.dtype )

uint8

注意:img.dtype在调试时非常重要,因为OpenCV-Python代码中的大量错误是由无效的数据类型引起的。


3.图像ROI

有时你需要对一幅图像的特定区域进行操作。例如我们要检测一副图像中眼睛的位置,我们首先应该在图像中找到脸,再在脸的区域中找眼睛,而不是直接在一整幅图像中搜索。这样会提高程序的准确性(因为眼睛总在脸上)和性能(因为我们在很小的区域内搜索)。

ROI 也是使用 Numpy 索引来获得的。现在我们选择球的部分并把他拷贝到图像的其他区域。

>>> ball = img[280:340, 330:390]

>>> img[273:333, 100:160] = ball

窗口将如下图所示:


4.图像通道的查分和合并

有时需要在B,G,R通道图像上单独工作。在这种情况下,需要将BGR图像分割为单个通道。或者在其他情况下,可能需要将这些单独的通道合并到BGR图像。你可以通过以下方式完成:

>>> b,g,r = cv.split(img)

>>> img = cv.merge((b,g,r))

或者:

>>> b = img[:,:,0]

假设你要将所有红色像素设置为零,则无需先拆分通道。使用Numpy索引更快:

>>> img[:,:,2] = 0

注意:cv.split()是一项代价高昂的操作(就消耗时间而言)。所以只有在你需要时才这样做。否则使用Numpy索引。




5.制作图像边框

如果要在图像周围创建边框,比如相框,可以使用cv.copyMakeBorder()。但它有更多卷积运算,零填充等应用。该函数需要以下参数:

  • src 输入图像

  • top, bottom, left, right 对应边界的像素数目。

  • borderType 要添加那种类型的边界,类型如下: – cv2.BORDER_CONSTANT 添加有颜色的常数值边界,还需要下一个参数(value)。 – cv2.BORDER_REFLECT 边界元素的镜像。比如: fedcba|abcde-fgh|hgfedcb – cv2.BORDER_REFLECT_101 or cv2.BORDER_DEFAULT跟上面一样,但稍作改动。例如: gfedcb|abcdefgh|gfedcba – cv2.BORDER_REPLICATE 重复最后一个元素。例如: aaaaaa|abcdefgh|hhhhhhh – cv2.BORDER_WRAP 不知道怎么说了, 就像这样: cdefgh|abcdefgh|abcdefg

  • value 边界颜色,如果边界的类型是 cv2.BORDER_CONSTANT

下面是一个示例代码,演示了所有这些边框类型,以便更好地理解:

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()

窗口将如下图所示(图像与matplotlib一起显示。因此RED和BLUE通道将互换):


6.图像的加法

你可以使用OpenCV的cv.add()函数把两幅图像相加,或者可以简单地通过numpy操作添加两个图像,如res = img1 + img2。两个图像应该具有相同的大小和类型,或者第二个图像可以是标量值。

注意:OpenCV加法和Numpy加法之间存在差异。OpenCV的加法是饱和操作,而Numpy添加是模运算。

参考以下代码:

>>> x = np.uint8([250])
>>> y = np.uint8([10])
>>> print( cv.add(x,y) ) # 250+10 = 260 => 255
[[255]]
>>> print( x+y ) # 250+10 = 260 % 256 = 4
[4]

这种差别在你对两幅图像进行加法时会更加明显。OpenCV 的结果会更好一点。所以我们尽量使用 OpenCV 中的函数。

7.图像的混合

这其实也是加法,但是不同的是两幅图像的权重不同,这就会给人一种混合或者透明的感觉。图像混合的计算公式如下:

g(x) = (1−α)f0(x) + αf1(x)

通过修改 α 的值(0 → 1),可以实现非常炫酷的混合。

现在我们把两幅图混合在一起。第一幅图的权重是0.7,第二幅图的权重是0.3。函数cv2.addWeighted()可以按下面的公式对图片进行混合操作。

dst = α⋅img1 + β⋅img2 + γ

这里γ取为零。

参考以下代码:

img1 = cv.imread('ml.png')
img2 = cv.imread('opencv-logo.png')
dst = cv.addWeighted(img1,0.7,img2,0.3,0)
cv.imshow('dst',dst)
cv.waitKey(0)
cv.destroyAllWindows()

窗口将如下图显示:

8.图像按位操作

这里包括的按位操作有:AND,OR,NOT,XOR 等。当我们提取图像的一部分,选择非矩形ROI时这些操作会很有用(你会在后续章节中看到)。下面的例子就是教给我们如何改变一幅图的特定区域。我想把OpenCV的标志放到另一幅图像上。如果我使用图像的加法,图像的颜色会改变,如果使用图像的混合,会得到一个透明的效果,但是我不希望它透明。如果它是矩形我可以像上一章那样使用ROI。但是OpenCV标志不是矩形。所以我们可以通过下面的按位运算实现:

# Load two images
img1 = cv.imread('messi5.jpg')
img2 = cv.imread('opencv-logo-white.png')
# I want to put logo on top-left corner, So I create a ROI
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols ]
# Now create a mask of logo and create its inverse mask also
img2gray = cv.cvtColor(img2,cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 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(img2,img2,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。


9、程序性能评估及优化


目标:

在图像处理中,由于每秒需要处理大量操作,因此处理图像的代码必须不仅要能给出正确的结果,同时还必须要快。所以在本小节中,你将学习:

* 衡量代码的性能。

* 一些优化代码性能的技巧。

* 你将学习以下几个函数:cv.getTickCount, cv.getTickFrequency

除了OpenCV库之外,Python还提供了一个time模块,有助于测量执行时间。另一个profile模块可以获得有关代码的详细报告,例如代码中每个函数所花费的时间,调用函数的次数等。如果你使用的是IPython,所有这些功能都以一个有好的方式整合到一起。我们将看到一些重要的内容,有关更多详细信息,请查看“其他资源”部分中的链接。

9.1 使用 OpenCV 衡量程序效率

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

cv.getTickFrequency函数返回时钟周期的频率,或每秒钟的时钟周期数。所以,要想获得函数的执行时间,你可以执行以下操作:

e1 = cv.getTickCount()
# your code executione2 = cv.getTickCount()
time = (e2 - e1)/ cv.getTickFrequency()

我们将展示以下示例示例,下面的例子使用从5到49几个不同大小的核进行中值滤波。(不要考虑结果会是什么样的,这不是我们的目的):

img1 = cv.imread('messi5.jpg')
e1 = cv.getTickCount()
for i in xrange(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模块的函数执行相同操作来替代cv.getTickCount,使用time.time()函数,然后取两次结果的时间差。

9.2 OpenCV中的默认优化

许多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优化的。因此,你可以使用它来在代码顶部启用优化(请记住它默认启用)。

9.3.检测IPython中的性能

有时你可能需要比较两个类似操作的性能。IPython提供了一个魔术命令timeit来执行此操作。 它可以让代码运行几次以获得更准确的结果。它们也适用于测量单行代码。

例如,你想知道以下哪个加法操作更快吗?

x = 5; y = x ** 2 x = 5; y = x * x x = np.uint8([5]); y = x * x y = np.square(x) 我们将在IPython shell中使用timeit得到答案。

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

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

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

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

In [35]: %timeit z = cv.countNonZero(img)

100000 loops, best of 3: 15.8 us per loopIn [36]: %timeit z = np.count_nonzero(img)

1000 loops, best of 3: 370 us per loop

你可以看到,OpenCV的执行性能比Numpy快将近25倍。

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

9.4.更多的IPython命令

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

9.5.性能优化技术

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

  1. 尽量避免在Python中使用循环,尤其是双层/三层嵌套循环等。它们本身就很慢。

  2. 将算法/代码尽量使用向量化操作,因为Numpy和OpenCV针对向量运算进行了优化。

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

  4. 除非需要,否则不要复制数组。尝试使用视图去替代复制数组。数组复制是一项非常浪费资源的操作。

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



打赏
赞(0) 打赏
未经允许不得转载:同乐学堂 » 5-OpenCV-核心操作

特别的技术,给特别的你!

联系QQ:1071235258QQ群:710045715

觉得文章有用就打赏一下文章作者

非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

支付宝扫一扫打赏

微信扫一扫打赏

error: Sorry,暂时内容不可复制!