目标
• 获取像素值并修改
• 获取图像的属性(信息)
• 图像的 ROI()
• 图像通道的拆分及合并
几乎全部这些操做与 Numpy 的关系都比与 OpenCV 的关系更加紧密,所以熟练 Numpy 能够帮助咱们写出性能更好的代码。
(示例将会在 Python 终端中展现,由于他们大部分都只有一行代码)python
9.1 获取并修改像素值
首先咱们须要读入一幅图像:算法
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')编程
你能够根据像素的行和列的坐标获取他的像素值。对 BGR 图像而言,返回值为 B,G,R 的值。对灰度图像而言,会返回他的灰度值(亮度?intensity)数组
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
px=img[100,100]
print(px)
blue=img[100,100,0]
print(blue)缓存
你能够以相似的方式修改像素值。app
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
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() 分割他们。oop
获取像素值及修改的更好方法。post
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
print(img.item(10,10,2))
img.itemset((10,10,2),100)
print(img.item(10,10,2))
# 59
# 100
9.2 获取图像属性
图像的属性包括:行,列,通道,图像数据类型,像素数目等img.shape 能够获取图像的形状。他的返回值是一个包含行数,列数,通道数的元组。
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
print(img.shape)
##(342, 548, 3)
注意:若是图像是灰度图,返回值仅有行数和列数。因此经过检查这个返回值就能够知道加载的是灰度图仍是彩色图。
img.size 能够返回图像的像素数目:
注意:在debug时 img.dtype 很是重要。由于在 OpenCV Python 代码中常常出现数据类型的不一致。
9.3 图像 ROI
有时你须要对一幅图像的特定区域进行操做。例如咱们要检测一副图像中眼睛的位置,咱们首先应该在图像中找到脸,再在脸的区域中找眼睛,而不是直接在一幅图像中搜索。这样会提升程序的准确性和性能。
ROI 也是使用 Numpy 索引来得到的。如今咱们选择球的部分并把他拷贝到图像的其余区域。
import cv2
import numpy as np
img=cv2.imread('messi5.jpg')
ball=img[280:340,330:390]
img[273:333,100:160]=ball
img=cv2.imshow('test', img)
cv2.waitKey(0)
看看结果吧:
9.4 拆分及合并图像通道
有时咱们须要对 BGR 三个通道分别进行操做。这是你就须要把 BGR 拆分红单个通道。有时你须要把独立通道的图片合并成一个 BGR 图像。你能够这样作:
import cv2 import numpy as np img=cv2.imread('/home/duan/workspace/opencv/images/roi.jpg') b,g,r=cv2.split(img) img=cv2.merge(b,g,r)
或者:
import cv2 import numpy as np img=cv2.imread('/home/duan/workspace/opencv/images/roi.jpg') b=img[:,:,0]
假如你想使全部像素的红色通道值都为 0,你没必要先拆分再赋值。你能够直接使用 Numpy 索引,这会更快。
import cv2 import numpy as np img=cv2.imread('/home/duan/workspace/opencv/images/roi.jpg') img[:,:,2]=0
警告:cv2.split() 是一个比较耗时的操做。只有真正须要时才用它,能用Numpy 索引就尽可能用。
9.5 为图像扩边(填充)
若是你想在图像周围建立一个边,就像相框同样,你可使用 cv2.copyMakeBorder()函数。这常常在卷积运算或 0 填充时被用到。这个函数包括以下参数:
• 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 import numpy as np from matplotlib import pyplot as plt BLUE=[255,0,0] img1=cv2.imread('opencv_logo.png') replicate = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REPLICATE) reflect = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT) reflect101 = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT_101) wrap = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_WRAP) constant= cv2.copyMakeBorder(img1,10,10,10,10,cv2.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 绘制,因此交换 R 和 B 的位置,OpenCV 中是按 BGR,matplotlib 中是按 RGB 排列):
目标
• 学习图像上的算术运算,加法,减法,位运算等。
• 咱们将要学习的函数与有:cv2.add(),cv2.addWeighted() 等。
10.1 图像加法
你可使用函数 cv2.add() 将两幅图像进行加法运算,固然也能够直接使用 numpy,res=img1+img。两幅图像的大小,类型必须一致,或者第二个图像可使一个简单的标量值。
注意:OpenCV 中的加法与 Numpy 的加法是有所不一样的。OpenCV 的加法是一种饱和操做,而 Numpy 的加法是一种模操做。
例以下面的两个例子:
x = np.uint8([250])
y = np.uint8([10]) print cv2.add(x,y) # 250+10 = 260 => 255 [[255]] print x+y # 250+10 = 260 % 256 = 4 [4]
这种差异在你对两幅图像进行加法时会更加明显。OpenCV 的结果会更好一点。因此咱们尽可能使用 OpenCV 中的函数。
10.2 图像混合
这其实也是加法,可是不一样的是两幅图像的权重不一样,这就会给人一种混合或者透明的感受。图像混合的计算公式以下:
g (x) = (1 − α)f 0 (x) + αf 1 (x)
经过修改 α 的值(0 → 1),能够实现很是酷的混合。
如今咱们把两幅图混合在一块儿。第一幅图的权重是 0.7,第二幅图的权重是 0.3。函数 cv2.addWeighted() 能够按下面的公式对图片进行混合操做。
dst = α · img1 + β · img2 + γ
这里 γ 的取值为 0。
img1 = cv2.imread('ml.png')
img2 = cv2.imread('opencv_logo.jpg') dst = cv2.addWeighted(img1,0.7,img2,0.3,0) cv2.imshow('dst',dst) cv2.waitKey(0) cv2.destroyAllWindows()
dst = cv2.addWeighted(img1,0.7,img2,0.3,0)
error: C:\projects\opencv-python\opencv\modules\core\src\arithm.cpp:659:
error: (-209) The operation is neither 'array op array' (where arrays have
the same size and the same number of channels), nor 'array op scalar',
nor 'scalar op array' in function cv::arithm_op
下面就是结果:# 这个运行有问题
10.3 按位运算
这里包括的按位操做有:AND,OR,NOT,XOR 等。当咱们提取图像的一部分,选择非矩形 ROI 时这些操做会颇有用(下一章你就会明白)。下面的例子就是教给咱们如何改变一幅图的特定区域。我想把 OpenCV 的标志放到另外一幅图像上。若是我使用加法,颜色会改变,若是使用混合,会获得透明效果,可是我不想要透明。若是他是矩形我能够象上一章那样使用 ROI。可是他不是矩形。可是咱们能够经过下面的按位运算实现:
import cv2
import numpy as np
# Load two images
img1 = cv2.imread('messi5.jpg')
img2 = cv2.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 = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
mask_inv = cv2.bitwise_not(mask)
# Now black-out the area of logo in ROI
img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)
# Take only region of logo from logo image.
img2_fg = cv2.bitwise_and(img2,img2,mask = mask)
# Put logo in ROI and modify the main image
dst = cv2.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst
cv2.imshow('res',img1)
cv2.waitKey(0)
cv2.destroyAllWindows()
结果以下。左面的图像是咱们建立的掩码。右边的是最终结果。为了帮助你们理解我把上面程序的中间结果也显示了出来,特别是 img1_bg 和 img2_fg。
练习
1. 建立一个幻灯片用来演示一幅图如何平滑的转换成另外一幅图(使用函数cv2.addWeighted)
目标
在图像处理中你每秒钟都要作大量的运算,因此你的程序不只要能给出正确的结果,同时还必需要快。因此这节咱们将要学习:
• 检测程序的效率
• 一些可以提升程序效率的技巧
• 你要学到的函数有:cv2.getTickCount,cv2.getTickFrequency等
除了 OpenCV,Python 也提供了一个叫 time 的的模块,你能够用它来测量程序的运行时间。另一个叫作 profile 的模块会帮你获得一份关于你的程序的详细报告,其中包含了代码中每一个函数运行须要的时间,以及每一个函数被调用的次数。若是你正在使用 IPython 的话,全部这些特色都被以一种用户友好的方式整合在一块儿了。咱们会学习几个重要的,要想学到更加详细的知识就打开更多资源中的连接吧。
11.1 使用 OpenCV 检测程序效率
cv2.getTickCount 函数返回从参考点到这个函数被执行的时钟数。因此当你在一个函数执行先后都调用它的话,你就会获得这个函数的执行时间(时钟数)。
cv2.getTickFrequency 返回时钟频率,或者说每秒钟的时钟数。因此你能够按照下面的方式获得一个函数运行了多少秒:
e1 = cv2.getTickCount()
# your code execution
e2 = cv2.getTickCount() time = (e2 - e1)/ cv2.getTickFrequency()
咱们将会用下面的例子演示。下面的例子是用窗口大小不一样(5,7,9)的核函数来作中值滤波:
img1 = cv2.imread('messi5.jpg')
e1 = cv2.getTickCount() for i in xrange(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
注 意: 你 也 可 以 中 time 模 块 实 现 上 面 的 功 能。 但 是 要 用 的 函 数 是time.time() 而不是 cv2.getTickCount。比较一下这两个结果的差异吧。
11.2 OpenCV 中的默认优化
OpenCV 中的不少函数都被优化过(使用 SSE2,AVX 等)。也包含一些没有被优化的代码。若是咱们的系统支持优化的话要尽可能利用只一点。在编译时优化是被默认开启的。所以 OpenCV 运行的就是优化后的代码,若是你把优化关闭的话就只能执行低效的代码了。你可使用函数 cv2.useOptimized()来查看优化是否被开启了,使用函数 cv2.setUseOptimized() 来开启优化。
让咱们来看一个简单的例子吧。
# check if optimization is enabled
In [5]: cv2.useOptimized()
Out[5]: True In [6]: %timeit res = cv2.medianBlur(img,49) 10 loops, best of 3: 34.9 ms per loop # Disable it In [7]: cv2.setUseOptimized(False) In [8]: cv2.useOptimized() Out[8]: False In [9]: %timeit res = cv2.medianBlur(img,49) 10 loops, best of 3: 64.1 ms per loop
看见了吗,优化后中值滤波的速度是原来的两倍。若是你查看源代码的话,你会发现中值滤波是被 SIMD 优化的。因此你能够在代码的开始处开启优化(你要记住优化是默认开启的)。
11.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)
咱们能够在 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 = cv2.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 对视图(而非复制)进行操做时。
11.4 更多 IPython 的魔法命令
还有几个魔法命令能够用来检测程序的效率,profiling,line profiling,内存使用等。他们都有完善的文档。因此这里只提供了超连接。感兴趣的能够本身学习一下。
11.5 效率优化技术
有些技术和编程方法可让咱们最大的发挥 Python 和 Numpy 的威力。
咱们这里仅仅提一下相关的,你能够经过超连接查找更多详细信息。咱们要说的最重要的一点是:首先用简单的方式实现你的算法(结果正确最重要),当结果正确后,再使用上面的提到的方法找到程序的瓶颈来优化它。
1. 尽可能避免使用循环,尤为双层三层循环,它们天生就是很是慢的。
2. 算法中尽可能使用向量操做,由于 Numpy 和 OpenCV 都对向量操做进行了优化。
3. 利用高速缓存一致性。
4. 没有必要的话就不要复制数组。使用视图来代替复制。数组复制是很是浪费资源的。
就算进行了上述优化,若是你的程序仍是很慢,或者说大的训话不可避免的话,你你应该尝试使用其余的包,好比说 Cython,来加速你的程序。
更多资源1. Python Optimization Techniques2. Scipy Lecture Notes - Advanced Numpy3. Timing and Profiling in IPython