二、核心操作

2.1 图像基本操作

学习目标:

  • 学会调整图像的某个像素
  • 学会调整图像的基本属性
  • 设置图像的ROI(Region Of Interest)
  • 切分/合并图像

这一节的操作与NumPy的关联性很强,所以实操前请先准备好NumPy的相关知识。

实例在Python终端执行

调整图像的某个像素

先在终端导入一幅图:

>>> import numpy as np
>>> import cv2 as cv

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

我们可以根据某个像素点的坐标来修改它的值,对于一个BGR格式的图像而言,点坐标会返回它的BGR通道值,而对于一个灰度图像而言,返回的是灰度值。

>>> px = img[100,100]
>>> print( px )
[157 166 200]

# 只获取某像素的Blue通道的值
>>> blue = img[100,100,0]
>>> print( blue )
157

也可以通过类似的方法修改某个像素的值:

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

注意:

NumPy是一个及其高效的科学计算库,所以依次遍历某一个或每一个像素值会很慢,所以不提倡这么做。

上述方法只适用于查询/修改某一个像素值,在NumPy中有更高效的方法用于更高端的操作,所以好好学NumPy吧!

下面是一个使用NumPy访问/修改像素值的简单例子:

# 读取Red通道的值
>>> img.item(10,10,2)
59

# 修改Red通道的值
>>> img.itemset((10,10,2),100)
>>> img.item(10,10,2)
100

调整图像的基本属性

“基本属性”包括行像素数、列像素数、颜色通道数、总像素数等等。

图像的形状由一个元组img.shape()储存,包括行数、列数、颜色通道数(如果有的话):

>>> print( img.shape )
(342, 548, 3)

灰度图像没有第三个参数,所以这也是判断一个图像是不是灰度图像的方法。

像素数由img.size()储存:

>>> print( img.size )
562248

图像的存储数据类型用img.dtype()表示:

>>> print( img.dtype )
uint8

设置图像的ROI(Region Of Interest)

有时候,我们只需要操作图像的某一部分,于是便要设置图像的ROI。比方说,为了实现某一个美颜算法,操作前就要先确定“脸”的大致位置,于是便把表示脸部的像素设置为ROI。这样做能加快计算机读取和操作的速度。

设置ROI这一步也要用到NumPy,以下的操作就是复制图像的每一部分到另一部分:

>>> ball = img[280:340, 330:390]
>>> img[273:333, 100:160] = ball

切分/缩放图像

对图像进行操作时,有时候要用到切分/合并图像的某一个颜色通道,下面的例子就是切分并图像的BGR通道:

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

或者这样:

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

如果想删除某一个通道,可以这样做:

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

注意:cv.split()是一个费时的操作,能不用尽量别用。

2.2 图像的相关运算

学习目标:

  • 学会对图像进行数学运算操作,如加法、减法、位运算等
  • 学会以下函数:cv.add()cv.addWeighted()

加法

OpenCV提供了cv.add()函数用于将两个图像相加,也可以简单地使用img1 + img2 以达到相同的效果。注意,两幅图像必须尺寸相同。

tips: OpenCV的cv.add()与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提供了另一种图像的加法,即加权加法,它遵循以下公式:
g(x) = (1-\alpha)f_0(x)+\alpha f_1(x)
在上式中改变\alpha的值(0 → 1)可以改变两幅图像的混合情况。

在下面这个实例中,我使用cv.addWeighted()将两幅图像以0.3:0.7的比例混合,并且加上一个修订值\gamma,如下公式表示:
dst=\alpha \cdot img1 + \beta \cdot img2 + \gamma
下面这个实例中,\gamma取0:

import cv2 as cv
import numpy as np

img1 = cv.imread('image01.png')
img2 = cv.imread('image02.png')

dst = cv.addWeighted(img1,0.7,img2,0.3,0)

cv.imshow('dst',dst)
cv.waitKey(0)
cv.destroyAllWindows()

位运算操作

位运算包括按位与、按位或、按位非、按位异或,这些运算相当重要,接下去的章节将展示它们的重要性,注意位运算最好在ROI中进行。

如果我现在想把一张黑底的图置于白底的图的上方,如果简单地相加或混合结果必然会不尽人意,这时候位运算就显得尤为重要,下面的实例将展示位运算操作有些操作现在看不懂不要紧

import cv2 as cv
import numpy as np

# 载入图像
img1 = cv.imread('image01.jpg')
img2 = cv.imread('image02.png')

# 创建ROI
rows,cols,channels = img2.shape
roi = img1[0:rows, 0:cols]

# 创建图像的背景遮罩
img2gray = cv.cvtColor(img2,cv.COLOR_BGR2GRAY)
ret, mask = cv.threshold(img2gray, 10, 255, cv.THRESH_BINARY)
mask_inv = cv.bitwise_not(mask)

# 将黑色部分去除
img1_bg = cv.bitwise_and(roi,roi,mask = mask_inv)

# 将剩余部分取出
img2_fg = cv.bitwise_and(img2,img2,mask = mask)

# 添加到新图层上
dst = cv.add(img1_bg,img2_fg)
img1[0:rows, 0:cols ] = dst

cv.imshow('res',img1)
cv.waitKey(0)
cv.destroyAllWindows()

OpenCV: Arithmetic Operations on Images上有这个实例的展示图

2.3 计时/改进代码

目标

在图像处理的过程中机器可能要进行许多次运算操作,有时候会很耗时,因而优化代码/改进算法刻不容缓,在这一节我们将会学习:

  • 记录程序运行时间
  • 一些优化程序的小技巧
  • 了解并使用以下函数:cv.getTickCount, cv.getTickFrequency

除OpenCV之外,Python也提供了一个好用的计时模块Time,此外,profile模块能让用户获取程序报告,比如每个函数的执行时间什么的。然而,如果你使用的是IPython, 你会感到更友好的交互式体验,这些将会在本节“其他相关资源”中提及。

用OpenCV内置的方法计时

cv.getTickCount这个函数的返回值是一个某一个时钟刻(可能是机器开始运行时开始计时什么的,单位不是秒),所以调用两次这个函数并且将两次返回值相减就得到了这段时间的时钟刻数量。

cv.getTickFrequency这个函数的返回值表示时钟频,即每一秒的时钟刻的数量,所以你可以像如下实例这样计算某一段代码的运行时间:

e1 = cv.getTickCount()
# 运行的代码
e2 = cv.getTickCount()
time = (e2 - e1)/ cv.getTickFrequency()

接下来我们将用下面的实例进行展示,这个实例的作用目前不必了解,只是用来使用上述两个函数的:

img1 = cv.imread('image01.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 )

我自己的电脑上运行用时0.0213916s

OpenCV内置的默认优化

OpenCV内置的许多函数使用了SSE2, AVX等优化方式,但它也有些没使用优化的函数。对于那些有优化的功能,如果系统支持使用,那就最好把它们打开。其内置的cv.useOptimized()函数用于检查优化是否被开启,cv.setUseOptimized()函数用于开关某种优化,具体如下:

>>> cv.useOptimized()
True
>>> %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 34.9 ms per loop
    
>>> cv.setUseOptimized(False)
>>> cv.useOptimized()
False
>>> %timeit res = cv.medianBlur(img,49)
10 loops, best of 3: 64.1 ms per loop

如你所见,优化后的运行速度大约是优化前的两倍,因为这里用到了一种叫SIMD的优化算法(默认开启)。

一些优化的建议

在Python或NumPy中有许多优化运行速度的代码技巧,其中一些主要的罗列在下面,请读者尝试在一些简单的程序中测试以下算法,一旦测试成功,再应用到已有的程序中:

  1. 尽量少用python中的循环,尤其是多重循环,这玩意很慢
  2. 尽量多地处理矢量,因为OpenCV和NumPy对矢量运算有优化
  3. 利用缓存的一致性
  4. 绝不要复制列表(arrays)除非真的必要,列表的复制很费时

如果你在采用上述步骤后依然不能得到满意的运行效率,或者你不可避免地要使用多重循环,那就试试Cython(译者注:Python的一个库)吧。

(译者注:Python在没有第三方库(如NumPy)的情况下,是一种开发高效但是运行低效的语言,若是要追求运行效率,建议使用OpenCV for C++。此外,为了开发效率,必要的基础算法希望在读的各位能掌握。)

2022.4.14

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容