OpenCV-Python 教程 (5): OpenCV 核心操作

Igor OpenCV评论1,446字数 8508阅读28分21秒阅读模式

部分 III 核心操作 OpenCV-Python 中文教程(搬运)

部分 III
核心操作

OpenCV-Python 中文教程(搬运)目录

 

9 图像的基础操作

目标

• 获取像素值并修改
• 获取图像的属性(信息)
• 图像的 ROI()
• 图像通道的拆分及合并
几乎所有这些操作与 Numpy 的关系都比与 OpenCV 的关系更加紧密,因此熟练 Numpy 可以帮助我们写出性能更好的代码。
(示例将会在 Python 终端中展示,因为他们大部分都只有一行代码)

9.1 获取并修改像素值

首先我们需要读入一幅图像:

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('messi5.jpg')

你可以根据像素的行和列的坐标获取他的像素值。对 BGR 图像而言,返回值为 B,G,R 的值。对灰度图像而言,会返回他的灰度值(亮度?intensity)

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('messi5.jpg')
  4. px=img[100,100]
  5. print(px)
  6. blue=img[100,100,0]
  7. print(blue)

你可以以类似的方式修改像素值。

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('messi5.jpg')
  4. img[100,100]=[255,255,255]
  5. print(img[100,100])
  6. ## [255 255 255]

警告:Numpy 是经过优化了的进行快速矩阵运算的软件包。所以我们不推荐逐个获取像素值并修改,这样会很慢,能有矩阵运算就不要用循环。
注意:上面提到的方法被用来选取矩阵的一个区域,比如说前 5 行的后 3列。对于获取每一个像素值,也许使用 Numpy 的 array.item() 和 array.itemset() 会更好。但是返回值是标量。如果你想获得所有 B,G,R 的值,你需要使用 array.item() 分割他们。

获取像素值及修改的更好方法。

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('messi5.jpg')
  4. print(img.item(10,10,2))
  5. img.itemset((10,10,2),100)
  6. print(img.item(10,10,2))
  7.  
  8. # 59
  9.  
  10. # 100

 

9.2 获取图像属性

图像的属性包括:行,列,通道,图像数据类型,像素数目等img.shape 可以获取图像的形状。他的返回值是一个包含行数,列数,通道数的元组。

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('messi5.jpg')
  4. print(img.shape)
  5.  
  6. ##(342, 548, 3)

注意:如果图像是灰度图,返回值仅有行数和列数。所以通过检查这个返回值就可以知道加载的是灰度图还是彩色图。
img.size 可以返回图像的像素数目:

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('messi5.jpg')
  4. print(img.size, img.dtype) # 返回的是图像的数据类型.
  5.  
  6. # 562248 uint8
  7.  
  8. # uint8

注意:在debug时 img.dtype 非常重要。因为在 OpenCV Python 代码中经常出现数据类型的不一致。

 

9.3 图像 ROI

有时你需要对一幅图像的特定区域进行操作。例如我们要检测一副图像中眼睛的位置,我们首先应该在图像中找到脸,再在脸的区域中找眼睛,而不是直接在一幅图像中搜索。这样会提高程序的准确性和性能。
ROI 也是使用 Numpy 索引来获得的。现在我们选择球的部分并把他拷贝到图像的其他区域。

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('messi5.jpg')
  4. ball=img[280:340,330:390]
  5. img[273:333,100:160]=ball
  6. img=cv2.imshow('test', img)
  7. cv2.waitKey(0)

看看结果吧:

OpenCV-Python 教程 (5): OpenCV 核心操作-图片1

 

9.4 拆分及合并图像通道

有时我们需要对 BGR 三个通道分别进行操作。这是你就需要把 BGR 拆分成单个通道。有时你需要把独立通道的图片合并成一个 BGR 图像。你可以这样做:

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('/home/duan/workspace/opencv/images/roi.jpg')
  4. b,g,r=cv2.split(img)
  5. img=cv2.merge(b,g,r)

或者:

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('/home/duan/workspace/opencv/images/roi.jpg')
  4. b=img[:,:,0]

 

假如你想使所有像素的红色通道值都为 0,你不必先拆分再赋值。你可以直接使用 Numpy 索引,这会更快。

  1. import cv2
  2. import numpy as np
  3. img=cv2.imread('/home/duan/workspace/opencv/images/roi.jpg')
  4. 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

为了更好的理解这几种类型请看下面的演示程序。

  1. import cv2
  2. import numpy as np
  3. from matplotlib import pyplot as plt
  4. BLUE=[255,0,0]
  5. img1=cv2.imread('opencv_logo.png')
  6. replicate = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REPLICATE)
  7. reflect = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT)
  8. reflect101 = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_REFLECT_101)
  9. wrap = cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_WRAP)
  10. constant= cv2.copyMakeBorder(img1,10,10,10,10,cv2.BORDER_CONSTANT,value=BLUE)
  11. plt.subplot(231),plt.imshow(img1,'gray'),plt.title('ORIGINAL')
  12. plt.subplot(232),plt.imshow(replicate,'gray'),plt.title('REPLICATE')
  13. plt.subplot(233),plt.imshow(reflect,'gray'),plt.title('REFLECT')
  14. plt.subplot(234),plt.imshow(reflect101,'gray'),plt.title('REFLECT_101')
  15. plt.subplot(235),plt.imshow(wrap,'gray'),plt.title('WRAP')
  16. plt.subplot(236),plt.imshow(constant,'gray'),plt.title('CONSTANT')
  17. plt.show()

结果如下(由于是使用 matplotlib 绘制,所以交换 R 和 B 的位置,OpenCV 中是按 BGR,matplotlib 中是按 RGB 排列):

OpenCV-Python 教程 (5): OpenCV 核心操作-图片2

 

10 图像上的算术运算

目标

• 学习图像上的算术运算,加法,减法,位运算等。
• 我们将要学习的函数与有:cv2.add(),cv2.addWeighted() 等。

10.1 图像加法

你可以使用函数 cv2.add() 将两幅图像进行加法运算,当然也可以直接使用 numpy,res=img1+img。两幅图像的大小,类型必须一致,或者第二个图像可以使一个简单的标量值。
注意:OpenCV 中的加法与 Numpy 的加法是有所不同的。OpenCV 的加法是一种饱和操作,而 Numpy 的加法是一种模操作。
例如下面的两个例子:

  1. x = np.uint8([250])
  2. y = np.uint8([10])
  3. print cv2.add(x,y) # 250+10 = 260 => 255
  4. [[255]]
  5. print x+y # 250+10 = 260 % 256 = 4
  6. [4]

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

10.2 图像混合

这其实也是加法,但是不同的是两幅图像的权重不同,这就会给人一种混合或者透明的感觉。图像混合的计算公式如下:
g (x) = (1 − α) f0 (x) + α f1 (x)
通过修改 α 的值(0 → 1),可以实现非常酷的混合。
现在我们把两幅图混合在一起。第一幅图的权重是 0.7,第二幅图的权重是 0.3。函数 cv2.addWeighted() 可以按下面的公式对图片进行混合操作。
dst = α · img1 + β · img2 + γ
这里 γ 的取值为 0。

  1. img1 = cv2.imread('ml.png')
  2. img2 = cv2.imread('opencv_logo.jpg')
  3.  
  4. dst = cv2.addWeighted(img1, 0.7, img2, 0.3, 0)
  5.  
  6. cv2.imshow('dst',dst)
  7. cv2.waitKey(0)
  8. cv2.destroyAllWindows()
  1. dst = cv2.addWeighted(img1,0.7,img2,0.3,0)
  2. error: C:\projects\opencv-python\opencv\modules\core\src\arithm.cpp:659:
  3. error: (-209) The operation is neither 'array op array' (where arrays have
  4. the same size and the same number of channels), nor 'array op scalar',
  5. nor 'scalar op array' in function cv::arithm_op
  6.  
  7. #  这个运行有问题

 

10.3 按位运算

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

  1. import cv2
  2. import numpy as np
  3. # Load two images
  4. img1 = cv2.imread('messi5.jpg')
  5. img2 = cv2.imread('opencv-logo-white.png')
  6.  
  7. # I want to put logo on top-left corner, So I create a ROI
  8. rows,cols,channels = img2.shape
  9. roi = img1[0:rows, 0:cols ]
  10.  
  11. # Now create a mask of logo and create its inverse mask also
  12. img2gray = cv2.cvtColor(img2,cv2.COLOR_BGR2GRAY)
  13. ret, mask = cv2.threshold(img2gray, 10, 255, cv2.THRESH_BINARY)
  14. mask_inv = cv2.bitwise_not(mask)
  15.  
  16. # Now black-out the area of logo in ROI
  17. img1_bg = cv2.bitwise_and(roi,roi,mask = mask_inv)
  18.  
  19. # Take only region of logo from logo image.
  20. img2_fg = cv2.bitwise_and(img2,img2,mask = mask)
  21.  
  22. # Put logo in ROI and modify the main image
  23. dst = cv2.add(img1_bg,img2_fg)
  24. img1[0:rows, 0:cols ] = dst
  25.  
  26. cv2.imshow('res',img1)
  27. cv2.waitKey(0)
  28. cv2.destroyAllWindows()

结果如下。左面的图像是我们创建的掩码。右边的是最终结果。为了帮助大家理解我把上面程序的中间结果也显示了出来,特别是 img1_bg 和 img2_fg。

OpenCV-Python 教程 (5): OpenCV 核心操作-图片3

练习
1. 创建一个幻灯片用来演示一幅图如何平滑的转换成另一幅图(使用函数cv2.addWeighted)

 

11 程序性能检测及优化

目标

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

  • 检测程序的效率
  • 一些能够提高程序效率的技巧
  • 你要学到的函数有:cv2.getTickCount,cv2.getTickFrequency等

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

11.1 使用 OpenCV 检测程序效率

cv2.getTickCount 函数返回从参考点到这个函数被执行的时钟数。所以当你在一个函数执行前后都调用它的话,你就会得到这个函数的执行时间(时钟数)。
cv2.getTickFrequency 返回时钟频率,或者说每秒钟的时钟数。所以你可以按照下面的方式得到一个函数运行了多少秒:

  1. e1 = cv2.getTickCount()
  2. # your code execution
  3. e2 = cv2.getTickCount()
  4. time = (e2 - e1)/ cv2.getTickFrequency()

 

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

  1. img1 = cv2.imread('messi5.jpg')
  2.  
  3. e1 = cv2.getTickCount()
  4. for i in xrange(5,49,2):
  5. img1 = cv2.medianBlur(img1,i)
  6. e2 = cv2.getTickCount()
  7. t = (e2 - e1)/cv2.getTickFrequency()
  8. print t
  9.  
  10. # Result I got is 0.521107655 seconds

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

11.2 OpenCV 中的默认优化

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

  1. # check if optimization is enabled
  2. In [5]: cv2.useOptimized()
  3. Out[5]: True
  4.  
  5. In [6]: %timeit res = cv2.medianBlur(img,49)
  6. 10 loops, best of 3: 34.9 ms per loop
  7.  
  8. # Disable it
  9. In [7]: cv2.setUseOptimized(False)
  10.  
  11. In [8]: cv2.useOptimized()
  12. Out[8]: False
  13.  
  14. In [9]: %timeit res = cv2.medianBlur(img,49)
  15. 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 中使用魔法命令找到答案。

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

竟然是第一种写法,它居然比 Nump 快了 20 倍。如果考虑到数组构建的话,能达到 100 倍的差。
注意:Python 的标量计算比 Nump 的标量计算要快。对于仅包含一两个元素的操作 Python 标量比 Numpy 的数组要快。但是当数组稍微大一点时Numpy 就会胜出了。
我们来再看几个例子。我们来比较一下 cv2.countNonZero() 和 np.count_nonzero()。

  1. In [35]: %timeit z = cv2.countNonZero(img)
  2. 100000 loops, best of 3: 15.8 us per loop
  3.  
  4. In [36]: %timeit z = np.count_nonzero(img)
  5. 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 Techniques
2. Scipy Lecture Notes - Advanced Numpy
3. Timing and Profiling in IPython

文章末尾固定信息

weinxin
我的微信
我的微信
一个码农、工程狮、集能量和智慧于一身的、DIY高手、小伙伴er很多的、80后奶爸。
 
Igor
  • 本文由 Igor 发表于 2020-06-1221:40:55
opencv和pillow对图片的读写耗时对比 OpenCV

opencv和pillow对图片的读写耗时对比

?生成数组 不同的包对于读写图片有不同的优化方式,导致他们的读写时间有差异,这个差异一般情况下可能无所谓,但是在大量图片数据的读写时,却可以节约大量的时间。 生成二维数组: import cv2 im...
匿名

发表评论

匿名网友
:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:
确定

拖动滑块以完成验证
加载中...