系列文章目录
opencv-python学习笔记(一)—— opencv介绍
opencv-python学习笔记(二)—— 图片视频读写、绘制几何形状、鼠标事件等
opencv-python学习笔记(三)—— 像素操作、几何变换、性能优化
opencv-python学习笔记(四)—— 图像处理之色彩空间、图像几何变换
文章目录
前言
在本节中,将介绍一些图像的基本操作,如像素获取、更改,几何变换,性能优化,一些数学工具等。
一、 图像基本操作
1. 获取、修改像素值
import numpy as np
import cv2 as cv
img = cv.imread('messi5.jpg')
# 获取像素值
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)
注意:如果是灰度图像,则返回的元组只包含行数和列数,因这是一种检查加载的图像是灰度的还是彩色的好方法。
使用image.size获取像素总数:
>>> print( img.size ) # 342*548*3
562248
图像数据类型由’ img.dtype '获取:
>>> print( img.dtype )
uint8
注意:img.dtype在调试时非常重要,因为OpenCV-Python代码中的大量错误是由无效的数据类型引起的。
3. 图片ROI
有时,我们可能要处理图像的某些区域。对于图像中的眼睛检测,首先对整个图像进行人脸检测。当得到一张人脸时,我们只选择人脸区域并搜索其中的眼睛,而不是搜索整个图像。它提高了准确性(因为眼睛总是盯着面孔:D)和表现(因为我们在小范围内搜索)
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. 为图片设置边框(Padding)
如果想要为图像创建边框,比如相框,可以使用**cv.copyMakeBorder()**。但它在卷积运算、零填充等方面有更多的应用。这个函数接受以下参数:
- src - input image
- top, bottom, left, right - border width in number of pixels in corresponding directions
- borderType - Flag defining what kind of border to be added. It can be following types:
- cv.BORDER_CONSTANT - Adds a constant colored border. The value should be given as next argument.
- cv.BORDER_REFLECT - Border will be mirror reflection of the border elements, like this : fedcba|abcdefgh|hgfedcb
- cv.BORDER_REFLECT_101 or cv.BORDER_DEFAULT - Same as above, but with a slight change, like this : gfedcb|abcdefgh|gfedcba
- cv.BORDER_REPLICATE - Last element is replicated throughout, like this: aaaaaa|abcdefgh|hhhhhhh
- cv.BORDER_WRAP - Can’t explain, it will look like this : cdefgh|abcdefgh|abcdefg
- value - Color of border if border type is cv.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显示。所以红色和蓝色通道将互换):
二、图像的算术运算
1. 图像相加
可以使用OpenCV函数cv.add()让两个图像相加,或者简单地通过numpy操作res = img1 + img2。这两个图像应该具有相同的深度和类型,或者第二个图像可以是一个标量值。
>>> 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]
当两个图像相加时,结果会更加直观。
import cv2 as cv
import numpy as np
x = cv.imread('../data/pic1.png')
y = cv.imread('../data/pic2.png')
dst = cv.add(x, y)
cv.imshow('xImg', x)
cv.imshow('yImg', y)
cv.imshow('addImgs', dst)
cv.waitKey(0)
2. 图像混合
这也是图像加法,但给图像不同的权重,产生混合或透明的感觉。图像按照下面的公式添加:
通过改变α从0→1,你可以实现一个图片到另一个图片的完美过渡。
在这里,将两张图片混合在一起。第一幅图像的权重为0.7,第二幅图像的权重为0.3。cv.addWeighted()对图像应用以下等式:
这里取γ为零。
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()
结果:
3. 位操作
包括位与、或、非和异或运算。它们在提取图像的指定区域、定义和处理非矩形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()
左图是我们创建的掩膜。右边的图像显示了最终的结果。
三、性能测试和改进
找到解决方案很重要。但以最快的方式得到它更重要。学会检查你的代码的速度,优化代码等。
目标:
在图像处理中,因为每秒要处理大量的操作,所以代码不仅要提供正确的解决方案,速度也要说的过去。所以在本章中,将介绍怎么度量代码的性能,提高代码性能。
常用函数: cv.getTickCount, cv.getTickFrequency, 等
除了OpenCV之外,Python还提供了一个时间模块,这对测量执行时间很有帮助。另一个模块概要文件有助于获得关于代码的详细报告,比如代码中的每个函数花费了多少时间,调用了多少次函数,等等。但是,如果您正在使用IPython,那么所有这些特性都将以用户友好的方式集成。
1. 使用OpenCV测试性能
**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 second
注意:
也可以使用python的tim模块,使用time.time()函数。然后取两者之差。
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优化的。所以你可以使用它来在你的代码的顶部启用优化(记住它是默认启用的)。
2. 在IPython中进行性能测试
有时你可能需要比较两个类似操作的性能。IPython为您提供了一个神奇的命令时间来执行此操作。它多次运行代码以获得更准确的结果。同样,它适用于度量单行代码。
例如,你知道下面哪个加法运算更好,x = 5;Y = x**2, x = 5;Y = xx, x = np.uint8([5]);Y = xx,还是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 loop
In [36]: %timeit z = np.count_nonzero(img)
1000 loops, best of 3: 370 us per loop
OpenCV函数几乎比Numpy函数快25倍。
注意:通常,OpenCV函数比Numpy函数快。因此,对于相同的操作,OpenCV函数是首选。但是,也有例外。
4. 更多IPython魔法命令
还有其他一些魔法命令来测试性能、内存等等。
有兴趣的可以去参考一下文档:Timing and Profiling in IPython
5. 性能优化技术
有几种技术和编码方法可以最大限度地利用Python和Numpy的性能。这里只列出相关资料和链接。这里需要注意的主要事情是,==首先尝试以一种简单的方式实现算法。==一旦它开始工作,分析它,找到瓶颈,并优化它们。
在Python中尽量避免使用循环,特别是双/三重循环等。他们天生就很慢。
最大限度地向量化算法/代码,因为Numpy和OpenCV针对向量操作进行了优化。
利用缓存一致性.
除非必要,否则不要复制数组。尝试使用视图代替。数组复制是一个代价很高的操作。
如果你的代码在完成所有这些操作后仍然很慢,或者如果使用大型循环是不可避免的,那么使用额外的库(如Cython)来提高速度。
6. 扩展资料
-
Scipy Lecture Notes - Advanced Numpy